refactor: migrate shadcn-components to Svelte 5 and TW4 (#551)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
Kyle Mendell
2025-05-21 12:15:27 -05:00
committed by Elias Schneider
parent 05b443d984
commit 28c85990ba
197 changed files with 8142 additions and 7471 deletions

View File

@@ -1,3 +1,4 @@
{
"go.buildTags": "e2etest"
"go.buildTags": "e2etest",
"prettier.documentSelectors": ["**/*.svelte"],
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"$schema": "https://next.shadcn-svelte.com/schema.json",
"style": "default",
"tailwind": {
"config": "tailwind.config.ts",
@@ -8,7 +8,11 @@
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils/style"
"utils": "$lib/utils/style",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true
"typescript": true,
"registry": "https://next.shadcn-svelte.com/registry"
}

10830
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,61 @@
{
"name": "pocket-id-frontend",
"version": "0.53.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev --port 3000",
"build": "vite build",
"preview": "vite preview --port 3000",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"dependencies": {
"@simplewebauthn/browser": "^13.1.0",
"@tailwindcss/vite": "^4.0.0",
"axios": "^1.8.2",
"clsx": "^2.1.1",
"crypto": "^1.0.1",
"formsnap": "^1.0.1",
"jose": "^5.9.6",
"lucide-svelte": "^0.487.0",
"mode-watcher": "^0.5.1",
"qrcode": "^1.5.4",
"svelte-sonner": "^0.3.28",
"sveltekit-superforms": "^2.23.1",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@inlang/paraglide-js": "^2.0.0",
"@inlang/plugin-m-function-matcher": "^2.0.7",
"@inlang/plugin-message-format": "^4.0.0",
"@internationalized/date": "^3.7.0",
"@playwright/test": "^1.50.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.20.7",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/eslint": "^9.6.1",
"@types/node": "^22.10.10",
"@types/qrcode": "^1.5.5",
"bits-ui": "^0.22.0",
"cmdk-sv": "^0.0.19",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.19.3",
"svelte-check": "^4.1.4",
"tailwindcss": "^4.0.0",
"tslib": "^2.8.1",
"typescript": "^5.7.3",
"typescript-eslint": "^8.21.0",
"vite": "^6.3.4"
}
"name": "pocket-id-frontend",
"version": "0.53.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev --port 3000",
"build": "vite build",
"preview": "vite preview --port 3000",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"dependencies": {
"@lucide/svelte": "^0.511.0",
"@simplewebauthn/browser": "^13.1.0",
"@tailwindcss/vite": "^4.1.7",
"axios": "^1.8.2",
"clsx": "^2.1.1",
"crypto": "^1.0.1",
"jose": "^5.9.6",
"qrcode": "^1.5.4",
"sveltekit-superforms": "^2.23.1",
"tailwind-merge": "^3.3.0",
"zod": "^3.24.1"
},
"devDependencies": {
"@inlang/paraglide-js": "^2.0.0",
"@inlang/plugin-m-function-matcher": "^2.0.7",
"@inlang/plugin-message-format": "^4.0.0",
"@internationalized/date": "^3.7.0",
"@playwright/test": "^1.50.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.20.7",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/eslint": "^9.6.1",
"@types/node": "^22.10.10",
"@types/qrcode": "^1.5.5",
"bits-ui": "^1.5.3",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1",
"formsnap": "^2.0.1",
"globals": "^15.14.0",
"mode-watcher": "^1.0.7",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.31.1",
"svelte-check": "^4.1.4",
"svelte-sonner": "^1.0.1",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.7",
"tslib": "^2.8.1",
"tw-animate-css": "^1.3.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.21.0",
"vite": "^6.3.4"
}
}

View File

@@ -1,70 +1,199 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@config '../tailwind.config.ts';
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(240 10% 3.9%);
--muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%);
--border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%);
--primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%);
--accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 72.2% 50.6%);
--destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 10% 3.9%);
--radius: 0.5rem;
}
.dark {
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%);
--primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%);
--accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 4.9% 83.9%);
}
@theme inline {
/* Radius (for rounded-*) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-ring: var(--ring);
--color-radius: var(--radius);
--color-sidebar-background: var(--sidebar-background);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
/* Animations */
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-caret-blink: caret-blink 1.25s ease-out infinite;
/* Font */
--font-playfair: 'Playfair Display', serif;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 0 0% 98%;
--ring: 240 10% 3.9%;
--radius: 0.5rem;
* {
@apply border-border;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
body {
@apply bg-background text-foreground;
}
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
button {
@apply cursor-pointer;
}
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
@font-face {
font-family: 'Playfair Display';
font-weight: 400;
src: url('/fonts/PlayfairDisplay-Regular.woff') format('woff');
}
@font-face {
font-family: 'Playfair Display';
font-weight: 500;
src: url('/fonts/PlayfairDisplay-Medium.woff') format('woff');
}
@font-face {
font-family: 'Playfair Display';
font-weight: 600;
src: url('/fonts/PlayfairDisplay-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'Playfair Display';
font-weight: 700;
src: url('/fonts/PlayfairDisplay-Bold.woff') format('woff');
}
}
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
@keyframes accordion-down {
from {
height: 0;
}
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
to {
height: var(--bits-accordion-content-height);
}
}
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
@keyframes accordion-up {
from {
height: var(--bits-accordion-content-height);
}
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
to {
height: 0;
}
}
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 240 4.9% 83.9%;
20%,
50% {
opacity: 0;
}
}
@@ -102,7 +231,6 @@
animation: slide-bg-container 1.2s cubic-bezier(0.33, 1, 0.68, 1) forwards;
}
/* Fade in for content after the slide is mostly complete */
@keyframes delayed-fade {
0%,
40% {
@@ -116,38 +244,3 @@
.animate-delayed-fade {
animation: delayed-fade 1.5s ease-out forwards;
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
button {
@apply cursor-pointer;
}
@font-face {
font-family: 'Playfair Display';
font-weight: 400;
src: url('/fonts/PlayfairDisplay-Regular.woff') format('woff');
}
@font-face {
font-family: 'Playfair Display';
font-weight: 500;
src: url('/fonts/PlayfairDisplay-Medium.woff') format('woff');
}
@font-face {
font-family: 'Playfair Display';
font-weight: 600;
src: url('/fonts/PlayfairDisplay-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'Playfair Display';
font-weight: 700;
src: url('/fonts/PlayfairDisplay-Bold.woff') format('woff');
}
}

View File

@@ -8,7 +8,7 @@
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import { debounced } from '$lib/utils/debounce-util';
import { cn } from '$lib/utils/style';
import { ChevronDown } from 'lucide-svelte';
import { ChevronDown } from '@lucide/svelte';
import type { Snippet } from 'svelte';
import Button from './ui/button/button.svelte';
import { m } from '$lib/paraglide/messages';
@@ -96,7 +96,7 @@
)}
placeholder={m.search()}
type="text"
oninput={(e) => onSearch((e.target as HTMLInputElement).value)}
oninput={(e: Event) => onSearch((e.currentTarget as HTMLInputElement).value)}
/>
{/if}
@@ -114,7 +114,7 @@
<Checkbox
disabled={selectionDisabled}
checked={allChecked}
onCheckedChange={(c) => onAllCheck(c as boolean)}
onCheckedChange={(c: boolean) => onAllCheck(c as boolean)}
/>
</Table.Head>
{/if}
@@ -124,7 +124,7 @@
<Button
variant="ghost"
class="flex items-center"
on:click={() =>
onclick={() =>
onSort(
column.sortColumn,
requestOptions.sort?.direction === 'desc' ? 'asc' : 'desc'
@@ -134,7 +134,7 @@
{#if requestOptions.sort?.column === column.sortColumn}
<ChevronDown
class={cn(
'ml-2 h-4 w-4',
'ml-2 size-4',
requestOptions.sort?.direction === 'asc' ? 'rotate-180' : ''
)}
/>
@@ -155,7 +155,7 @@
<Checkbox
disabled={selectionDisabled}
checked={selectedIds.includes(item.id)}
onCheckedChange={(c) => onCheck(c as boolean, item.id)}
onCheckedChange={(c: boolean) => onCheck(c, item.id)}
/>
</Table.Cell>
{/if}
@@ -169,18 +169,16 @@
<div class="flex items-center space-x-2">
<p class="text-sm font-medium">{m.items_per_page()}</p>
<Select.Root
selected={{
label: items.pagination.itemsPerPage.toString(),
value: items.pagination.itemsPerPage
}}
onSelectedChange={(v) => onPageSizeChange(v?.value as number)}
type="single"
value={items.pagination.itemsPerPage.toString()}
onValueChange={(v) => onPageSizeChange(Number(v))}
>
<Select.Trigger class="h-9 w-[80px]">
<Select.Value>{items.pagination.itemsPerPage}</Select.Value>
{items.pagination.itemsPerPage}
</Select.Trigger>
<Select.Content>
{#each availablePageSizes as size}
<Select.Item value={size}>{size}</Select.Item>
<Select.Item value={size.toString()}>{size}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
@@ -191,25 +189,26 @@
perPage={items.pagination.itemsPerPage}
{onPageChange}
page={items.pagination.currentPage}
let:pages
>
<Pagination.Content class="flex justify-end">
<Pagination.Item>
<Pagination.PrevButton />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type !== 'ellipsis' && page.value != 0}
<Pagination.Item>
<Pagination.Link {page} isActive={items.pagination.currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.NextButton />
</Pagination.Item>
</Pagination.Content>
{#snippet children({ pages })}
<Pagination.Content class="flex justify-end">
<Pagination.Item>
<Pagination.PrevButton />
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type !== 'ellipsis' && page.value != 0}
<Pagination.Item>
<Pagination.Link {page} isActive={items.pagination.currentPage === page.value}>
{page.value}
</Pagination.Link>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<Pagination.NextButton />
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
</div>
{/if}

View File

@@ -58,7 +58,7 @@
</Table.Cell>
{/if}
<Table.Cell>
<Badge variant="outline">{toFriendlyEventString(item.event)}</Badge>
<Badge class="rounded-full" variant="outline">{toFriendlyEventString(item.event)}</Badge>
</Table.Cell>
<Table.Cell
>{item.city && item.country ? `${item.city}, ${item.country}` : m.unknown()}</Table.Cell

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { m } from '$lib/paraglide/messages';
import { cn } from '$lib/utils/style';
import { LucideChevronDown, type Icon as IconType } from 'lucide-svelte';
import { LucideChevronDown, type Icon as IconType } from '@lucide/svelte';
import { onMount, type Snippet } from 'svelte';
import { slide } from 'svelte/transition';
import { Button } from './ui/button';
@@ -50,12 +50,12 @@
</script>
<Card.Root>
<Card.Header class="cursor-pointer" onclick={toggleExpanded}>
<Card.Header class="bg-card cursor-pointer" onclick={toggleExpanded}>
<div class="flex items-center justify-between">
<div>
<Card.Title class="flex items-center gap-2 text-xl font-semibold">
{#if icon}{@const Icon = icon}
<Icon class="text-primary/80 h-5 w-5" />
<Icon class="text-primary/80 size-5" />
{/if}
{title}
</Card.Title>
@@ -65,17 +65,14 @@
</div>
<Button class="ml-10 h-8 p-3" variant="ghost" aria-label={m.expand_card()}>
<LucideChevronDown
class={cn(
'h-5 w-5 transition-transform duration-200',
expanded && 'rotate-180 transform'
)}
class={cn('size-5 transition-transform duration-200', expanded && 'rotate-180 transform')}
/>
</Button>
</div>
</Card.Header>
{#if expanded}
<div transition:slide={{ duration: 200 }}>
<Card.Content class="bg-muted/20 pt-5">
<Card.Content class="pt-5">
{@render children()}
</Card.Content>
</div>

View File

@@ -14,16 +14,18 @@
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<AlertDialog.Action asChild>
<Button
variant={$confirmDialogStore.confirm.destructive ? 'destructive' : 'default'}
on:click={() => {
$confirmDialogStore.confirm.action();
$confirmDialogStore.open = false;
}}
>
{$confirmDialogStore.confirm.label}
</Button>
<AlertDialog.Action>
{#snippet child()}
<Button
variant={$confirmDialogStore.confirm.destructive ? 'destructive' : 'default'}
onclick={() => {
$confirmDialogStore.confirm.action();
$confirmDialogStore.open = false;
}}
>
{$confirmDialogStore.confirm.label}
</Button>
{/snippet}
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import * as Tooltip from '$lib/components/ui/tooltip';
import { m } from '$lib/paraglide/messages';
import { LucideCheck } from 'lucide-svelte';
import { LucideCheck } from '@lucide/svelte';
import type { Snippet } from 'svelte';
let { value, children }: { value: string; children: Snippet } = $props();
@@ -17,7 +17,7 @@
function onOpenChange(state: boolean) {
open = state;
if (!state) {
copied = false;
setTimeout(() => (copied = false), 500);
}
}
@@ -28,15 +28,15 @@
}
</script>
<Tooltip.Root closeOnPointerDown={false} {onOpenChange} {open}>
<Tooltip.Trigger class="text-start" tabindex={-1} onclick={onClick}
>{@render children()}</Tooltip.Trigger
>
<Tooltip.Content onclick={copyToClipboard}>
{#if copied}
<span class="flex items-center"><LucideCheck class="mr-1 h-4 w-4" /> {m.copied()}</span>
{:else}
<span>{m.click_to_copy()}</span>
{/if}
</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Provider>
<Tooltip.Root {onOpenChange} {open} disableCloseOnTriggerClick>
<Tooltip.Trigger class="text-start" onclick={onClick}>{@render children()}</Tooltip.Trigger>
<Tooltip.Content onclick={copyToClipboard}>
{#if copied}
<span class="flex items-center"><LucideCheck class="mr-1 size-4" /> {m.copied()}</span>
{:else}
<span>{m.click_to_copy()}</span>
{/if}
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { m } from '$lib/paraglide/messages';
import { LucideXCircle } from 'lucide-svelte';
import { LucideXCircle } from '@lucide/svelte';
let { message, showButton = true }: { message: string; showButton?: boolean } = $props();
</script>
<div class="mt-[20%] flex flex-col items-center">
<LucideXCircle class="text-muted-foreground h-12 w-12" />
<LucideXCircle class="text-muted-foreground size-12" />
<h1 class="mt-3 text-2xl font-semibold">{m.something_went_wrong()}</h1>
<p class="text-muted-foreground">{message}</p>
{#if showButton}

View File

@@ -75,15 +75,16 @@
onfocus={() => (isInputFocused = true)}
onblur={() => (isInputFocused = false)}
/>
<Popover.Root
open={isOpen}
disableFocusTrap
openFocus={() => {}}
closeOnOutsideClick={false}
closeOnEscape={false}
>
<Popover.Root open={isOpen}>
<Popover.Trigger tabindex={-1} class="h-0 w-full" aria-hidden />
<Popover.Content class="p-0" sideOffset={5} sameWidth>
<Popover.Content
sameWidth
class="p-0"
sideOffset={5}
trapFocus={false}
interactOutsideBehavior="ignore"
onCloseAutoFocus={(e) => e.preventDefault()}
>
{#each filteredSuggestions as suggestion, index}
<div
role="button"

View File

@@ -4,7 +4,7 @@
import { Input } from '$lib/components/ui/input';
import CustomClaimService from '$lib/services/custom-claim-service';
import type { CustomClaim } from '$lib/types/custom-claim.type';
import { LucideMinus, LucidePlus } from 'lucide-svelte';
import { LucideMinus, LucidePlus } from '@lucide/svelte';
import { onMount, type Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import AutoCompleteInput from './auto-complete-input.svelte';
@@ -51,25 +51,25 @@
variant="outline"
size="sm"
aria-label={m.remove_custom_claim()}
on:click={() => (customClaims = customClaims.filter((_, index) => index !== i))}
onclick={() => (customClaims = customClaims.filter((_, index) => index !== i))}
>
<LucideMinus class="h-4 w-4" />
<LucideMinus class="size-4" />
</Button>
</div>
{/each}
</div>
</FormInput>
{#if error}
<p class="mt-1 text-sm text-red-500">{error}</p>
<p class="text-destructive mt-1 text-xs">{error}</p>
{/if}
{#if customClaims.length < limit}
<Button
class="mt-2"
variant="secondary"
size="sm"
on:click={() => (customClaims = [...customClaims, { key: '', value: '' }])}
onclick={() => (customClaims = [...customClaims, { key: '', value: '' }])}
>
<LucidePlus class="mr-1 h-4 w-4" />
<LucidePlus class="mr-1 size-4" />
{customClaims.length === 0 ? m.add_custom_claim() : m.add_another()}
</Button>
{/if}

View File

@@ -11,24 +11,53 @@
getLocalTimeZone,
type DateValue
} from '@internationalized/date';
import CalendarIcon from 'lucide-svelte/icons/calendar';
import CalendarIcon from '@lucide/svelte/icons/calendar';
import type { HTMLAttributes } from 'svelte/elements';
let { value = $bindable(), ...restProps }: HTMLAttributes<HTMLButtonElement> & { value: Date } =
$props();
type Props = {
value?: Date;
id?: string;
} & HTMLAttributes<HTMLDivElement>;
let { value = $bindable(undefined), id, ...restProps }: Props = $props();
let calendarDisplayDate: CalendarDate | undefined = $state(
value ? dateToCalendarDate(value) : undefined
);
let date: CalendarDate = $state(dateToCalendarDate(value));
let open = $state(false);
function dateToCalendarDate(date: Date) {
return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
function dateToCalendarDate(d: Date): CalendarDate {
return new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate());
}
function onValueChange(newDate?: DateValue) {
if (!newDate) return;
$effect(() => {
if (calendarDisplayDate) {
const newExternalDate = calendarDisplayDate.toDate(getLocalTimeZone());
if (!value || value.getTime() !== newExternalDate.getTime()) {
value = newExternalDate;
}
} else {
if (value !== undefined) {
value = undefined;
}
}
});
value = newDate.toDate(getLocalTimeZone());
date = newDate as CalendarDate;
$effect(() => {
if (value) {
const newInternalCalendarDate = dateToCalendarDate(value);
if (!calendarDisplayDate || calendarDisplayDate.compare(newInternalCalendarDate) !== 0) {
calendarDisplayDate = newInternalCalendarDate;
}
} else {
if (calendarDisplayDate !== undefined) {
calendarDisplayDate = undefined;
}
}
});
function handleCalendarInteraction(newDateValue?: DateValue) {
open = false;
}
@@ -37,19 +66,28 @@
});
</script>
<Popover.Root openFocus {open} onOpenChange={(o) => (open = o)}>
<Popover.Trigger asChild let:builder>
<Button
{...restProps}
variant="outline"
class={cn('w-full justify-start text-left font-normal', !value && 'text-muted-foreground')}
builders={[builder]}
>
<CalendarIcon class="mr-2 h-4 w-4" />
{date ? df.format(date.toDate(getLocalTimeZone())) : m.select_a_date()}
</Button>
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<Calendar bind:value={date} initialFocus {onValueChange} />
</Popover.Content>
</Popover.Root>
<div class="w-full" {...restProps}>
<Popover.Root bind:open>
<Popover.Trigger class="w-full">
<Button
{id}
variant="outline"
class={cn('w-full justify-start text-left font-normal', !value && 'text-muted-foreground')}
aria-label={m.select_a_date()}
>
<CalendarIcon class="mr-2 size-4" />
{calendarDisplayDate
? df.format(calendarDisplayDate.toDate(getLocalTimeZone()))
: m.select_a_date()}
</Button>
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<Calendar
type="single"
bind:value={calendarDisplayDate}
onValueChange={handleCalendarInteraction}
initialFocus
/>
</Popover.Content>
</Popover.Root>
</div>

View File

@@ -45,17 +45,18 @@
<DatePicker {id} bind:value={input.value as Date} />
{:else}
<Input
aria-invalid={!!input.error}
{id}
{placeholder}
{type}
bind:value={input.value}
{disabled}
on:input={(e) => onInput?.(e)}
oninput={(e) => onInput?.(e)}
/>
{/if}
{/if}
{#if input?.error}
<p class="mt-1 text-sm text-red-500">{input.error}</p>
<p class="text-destructive mt-1 text-xs">{input.error}</p>
{/if}
</div>
</div>

View File

@@ -4,7 +4,7 @@
import Button from '$lib/components/ui/button/button.svelte';
import { m } from '$lib/paraglide/messages';
import { getProfilePictureUrl } from '$lib/utils/profile-picture-util';
import { LucideLoader, LucideRefreshCw, LucideUpload } from 'lucide-svelte';
import { LucideLoader, LucideRefreshCw, LucideUpload } from '@lucide/svelte';
import { onMount } from 'svelte';
import { openConfirmDialog } from '../confirm-dialog';
@@ -65,7 +65,7 @@
<div class="flex flex-col items-center gap-6 sm:flex-row">
<div class="shrink-0">
{#if isLdapUser}
<Avatar.Root class="h-24 w-24">
<Avatar.Root class="size-24">
<Avatar.Image class="object-cover" src={imageDataURL} />
</Avatar.Root>
{:else}
@@ -75,7 +75,7 @@
accept="image/png, image/jpeg"
onchange={onImageChange}
>
<div class="group relative h-24 w-24 rounded-full">
<div class="group relative size-24 rounded-full">
<Avatar.Root class="h-full w-full transition-opacity duration-200">
<Avatar.Image
class="object-cover group-hover:opacity-30 {isLoading ? 'opacity-30' : ''}"
@@ -84,9 +84,9 @@
</Avatar.Root>
<div class="absolute inset-0 flex items-center justify-center">
{#if isLoading}
<LucideLoader class="h-5 w-5 animate-spin" />
<LucideLoader class="size-5 animate-spin" />
{:else}
<LucideUpload class="h-5 w-5 opacity-0 transition-opacity group-hover:opacity-100" />
<LucideUpload class="size-5 opacity-0 transition-opacity group-hover:opacity-100" />
{/if}
</div>
</div>
@@ -105,8 +105,8 @@
{m.click_profile_picture_to_upload_custom()}
</p>
<p class="text-muted-foreground mb-2 text-sm">{m.image_should_be_in_format()}</p>
<Button variant="outline" size="sm" on:click={onReset} disabled={isLoading || isLdapUser}>
<LucideRefreshCw class="mr-2 h-4 w-4" />
<Button variant="outline" size="sm" onclick={onReset} disabled={isLoading || isLdapUser}>
<LucideRefreshCw class="mr-2 size-4" />
{m.reset_to_default()}
</Button>
{/if}

View File

@@ -3,7 +3,7 @@
import * as Command from '$lib/components/ui/command';
import * as Popover from '$lib/components/ui/popover';
import { cn } from '$lib/utils/style';
import { LucideCheck, LucideChevronDown } from 'lucide-svelte';
import { LucideCheck, LucideChevronDown } from '@lucide/svelte';
import { tick } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
@@ -52,21 +52,19 @@
});
</script>
<Popover.Root bind:open let:ids>
<Popover.Trigger asChild let:builder>
<Popover.Root bind:open {...restProps}>
<Popover.Trigger class="w-full">
<Button
{...restProps}
builders={[builder]}
variant="outline"
role="combobox"
aria-expanded={open}
class={cn('justify-between', restProps.class)}
>
{items.find((item) => item.value === value)?.label || 'Select an option'}
<LucideChevronDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
<LucideChevronDown class="ml-2 size-4 shrink-0 opacity-50" />
</Button>
</Popover.Trigger>
<Popover.Content class="p-0" sameWidth>
<Popover.Content class="p-0">
<Command.Root shouldFilter={false}>
<Command.Input placeholder="Search..." oninput={(e: any) => filterItems(e.target.value)} />
<Command.Empty>No results found.</Command.Empty>
@@ -77,10 +75,11 @@
onSelect={() => {
value = item.value;
onSelect?.(item.value);
closeAndFocusTrigger(ids.trigger);
// If you need to focus the trigger, you may need to refactor to get the trigger id another way
closeAndFocusTrigger('popover-trigger');
}}
>
<LucideCheck class={cn('mr-2 h-4 w-4', value !== item.value && 'text-transparent')} />
<LucideCheck class={cn('mr-2 size-4', value !== item.value && 'text-transparent')} />
{item.label}
</Command.Item>
{/each}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '$lib/components/ui/tooltip';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { m } from '$lib/paraglide/messages';
import { LucideCalendar, LucidePencil, LucideTrash, type Icon as IconType } from 'lucide-svelte';
import { LucideCalendar, LucidePencil, LucideTrash, type Icon as IconType } from '@lucide/svelte';
let {
icon,
@@ -24,7 +24,7 @@
<div class="flex items-start gap-3">
<div class="bg-primary/10 text-primary mt-1 rounded-lg p-2">
{#if icon}{@const Icon = icon}
<Icon class="h-5 w-5" />
<Icon class="size-5" />
{/if}
</div>
<div>
@@ -33,7 +33,7 @@
</div>
{#if description}
<div class="text-muted-foreground mt-1 flex items-center text-xs">
<LucideCalendar class="mr-1 h-3 w-3" />
<LucideCalendar class="mr-1 size-3" />
{description}
</div>
{/if}
@@ -41,35 +41,39 @@
</div>
<div class="flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100">
<Tooltip>
<TooltipTrigger asChild>
<Button
on:click={onRename}
size="icon"
variant="ghost"
class="h-8 w-8"
aria-label={m.rename()}
>
<LucidePencil class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{m.rename()}</TooltipContent>
</Tooltip>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
onclick={onRename}
size="icon"
variant="ghost"
class="size-8"
aria-label={m.rename()}
>
<LucidePencil class="size-4" />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>{m.rename()}</Tooltip.Content>
</Tooltip.Root></Tooltip.Provider
>
<Tooltip>
<TooltipTrigger asChild>
<Button
on:click={onDelete}
size="icon"
variant="ghost"
class="hover:bg-destructive/10 hover:text-destructive h-8 w-8"
aria-label={m.delete()}
>
<LucideTrash class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{m.delete()}</TooltipContent>
</Tooltip>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
onclick={onDelete}
size="icon"
variant="ghost"
class="hover:bg-destructive/10 hover:text-destructive size-8"
aria-label={m.delete()}
>
<LucideTrash class="size-4" />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>{m.delete()}</Tooltip.Content>
</Tooltip.Root></Tooltip.Provider
>
</div>
</div>
</div>

View File

@@ -1,11 +1,12 @@
<script lang="ts">
import { goto } from '$app/navigation';
import * as Avatar from '$lib/components/ui/avatar';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { m } from '$lib/paraglide/messages';
import WebAuthnService from '$lib/services/webauthn-service';
import userStore from '$lib/stores/user-store';
import { getProfilePictureUrl } from '$lib/utils/profile-picture-util';
import { LucideLogOut, LucideUser } from 'lucide-svelte';
import { LucideLogOut, LucideUser } from '@lucide/svelte';
const webauthnService = new WebAuthnService();
@@ -17,11 +18,11 @@
<DropdownMenu.Root>
<DropdownMenu.Trigger
><Avatar.Root class="h-9 w-9">
><Avatar.Root class="size-9">
<Avatar.Image src={getProfilePictureUrl($userStore?.id)} />
</Avatar.Root></DropdownMenu.Trigger
>
<DropdownMenu.Content class="min-w-40" align="start">
<DropdownMenu.Content class="min-w-40" align="end">
<DropdownMenu.Label class="font-normal">
<div class="flex flex-col space-y-1">
<p class="text-sm leading-none font-medium">
@@ -33,11 +34,11 @@
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item href="/settings/account"
><LucideUser class="mr-2 h-4 w-4" /> {m.my_account()}</DropdownMenu.Item
<DropdownMenu.Item onclick={() => goto('/settings/account')}
><LucideUser class="mr-2 size-4" /> {m.my_account()}</DropdownMenu.Item
>
<DropdownMenu.Item on:click={logout}
><LucideLogOut class="mr-2 h-4 w-4" /> {m.logout()}</DropdownMenu.Item
<DropdownMenu.Item onclick={logout}
><LucideLogOut class="mr-2 size-4" /> {m.logout()}</DropdownMenu.Item
>
</DropdownMenu.Group>
</DropdownMenu.Content>

View File

@@ -24,7 +24,7 @@
href="/settings/account"
class="flex items-center gap-3 transition-opacity hover:opacity-80"
>
<Logo class="h-8 w-8" />
<Logo class="size-8" />
<h1 class="text-lg font-semibold tracking-tight" data-testid="application-name">
{$appConfigStore.appName}
</h1>

View File

@@ -5,7 +5,7 @@
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
const isDarkMode = $derived($mode === 'dark');
const isDarkMode = $derived(mode.current === 'dark');
</script>
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt={m.logo()} />

View File

@@ -77,15 +77,12 @@
<div>
<Label for="expiration">{m.expiration()}</Label>
<Select.Root
selected={{
label: Object.keys(availableExpirations)[0],
value: Object.keys(availableExpirations)[0]
}}
onSelectedChange={(v) =>
(selectedExpiration = v!.value as keyof typeof availableExpirations)}
type="single"
value={Object.keys(availableExpirations)[0]}
onValueChange={(v) => (selectedExpiration = v! as keyof typeof availableExpirations)}
>
<Select.Trigger class="h-9 w-full">
<Select.Value>{selectedExpiration}</Select.Value>
{selectedExpiration}
</Select.Trigger>
<Select.Content>
{#each Object.keys(availableExpirations) as key}
@@ -124,8 +121,8 @@
class="mb-2"
value={oneTimeLink}
size={180}
color={$mode === 'dark' ? '#FFFFFF' : '#000000'}
backgroundColor={$mode === 'dark' ? '#000000' : '#FFFFFF'}
color={mode.current === 'dark' ? '#FFFFFF' : '#000000'}
backgroundColor={mode.current === 'dark' ? '#000000' : '#FFFFFF'}
/>
<CopyToClipboard value={oneTimeLink!}>
<p data-testId="login-code-link">{oneTimeLink!}</p>

View File

@@ -1,11 +1,18 @@
<script lang="ts">
export let icon: ConstructorOfATypedSvelteComponent;
export let name: string;
export let description: string;
import type { Icon as IconType } from '@lucide/svelte';
interface Props {
icon: typeof IconType;
name: string;
description: string;
}
let { icon, name, description }: Props = $props();
const SvelteComponent = $derived(icon);
</script>
<div class="flex items-center">
<div class="bg-muted mr-5 rounded-lg p-2"><svelte:component this={icon} /></div>
<div class="bg-muted mr-5 rounded-lg p-2"><SvelteComponent /></div>
<div class="text-start">
<h3 class="font-semibold">{name}</h3>
<p class="text-muted-foreground text-sm">{description}</p>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { m } from '$lib/paraglide/messages';
import { LucideMail, LucideUser, LucideUsers } from 'lucide-svelte';
import { LucideMail, LucideUser, LucideUsers } from '@lucide/svelte';
import ScopeItem from './scope-item.svelte';
let { scope }: { scope: string } = $props();

View File

@@ -3,19 +3,16 @@
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.ActionProps;
type $$Events = AlertDialogPrimitive.ActionEvents;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.ActionProps = $props();
</script>
<AlertDialogPrimitive.Action
bind:ref
data-slot="alert-dialog-action"
class={cn(buttonVariants(), className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Action>
{...restProps}
/>

View File

@@ -3,19 +3,16 @@
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.CancelProps;
type $$Events = AlertDialogPrimitive.CancelEvents;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.CancelProps = $props();
</script>
<AlertDialogPrimitive.Cancel
class={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Cancel>
bind:ref
data-slot="alert-dialog-cancel"
class={cn(buttonVariants({ variant: 'outline' }), className)}
{...restProps}
/>

View File

@@ -1,28 +1,27 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import * as AlertDialog from './index.js';
import { cn, flyAndScale } from '$lib/utils/style.js';
import AlertDialogOverlay from './alert-dialog-overlay.svelte';
import { cn, type WithoutChild, type WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.ContentProps;
export let transition: $$Props['transition'] = flyAndScale;
export let transitionConfig: $$Props['transitionConfig'] = undefined;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
portalProps,
...restProps
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
} = $props();
</script>
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialogPrimitive.Portal {...portalProps}>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
{transition}
{transitionConfig}
bind:ref
data-slot="alert-dialog-content"
class={cn(
'bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full',
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className
)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Content>
</AlertDialog.Portal>
{...restProps}
/>
</AlertDialogPrimitive.Portal>

View File

@@ -2,15 +2,16 @@
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.DescriptionProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.DescriptionProps = $props();
</script>
<AlertDialogPrimitive.Description
bind:ref
data-slot="alert-dialog-description"
class={cn('text-muted-foreground text-sm', className)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Description>
{...restProps}
/>

View File

@@ -1,16 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...$$restProps}
bind:this={ref}
data-slot="alert-dialog-footer"
class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
{...restProps}
>
<slot />
{@render children?.()}
</div>

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div class={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...$$restProps}>
<slot />
<div
bind:this={ref}
data-slot="alert-dialog-header"
class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,21 +1,20 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { fade } from 'svelte/transition';
import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.OverlayProps;
let className: $$Props['class'] = undefined;
export let transition: $$Props['transition'] = fade;
export let transitionConfig: $$Props['transitionConfig'] = {
duration: 150
};
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.OverlayProps = $props();
</script>
<AlertDialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn('bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ', className)}
{...$$restProps}
bind:ref
data-slot="alert-dialog-overlay"
class={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className
)}
{...restProps}
/>

View File

@@ -1,9 +0,0 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
type $$Props = AlertDialogPrimitive.PortalProps;
</script>
<AlertDialogPrimitive.Portal {...$$restProps}>
<slot />
</AlertDialogPrimitive.Portal>

View File

@@ -2,13 +2,16 @@
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.TitleProps;
let className: $$Props['class'] = undefined;
export let level: $$Props['level'] = 'h3';
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.TitleProps = $props();
</script>
<AlertDialogPrimitive.Title class={cn('text-lg font-semibold', className)} {level} {...$$restProps}>
<slot />
</AlertDialogPrimitive.Title>
<AlertDialogPrimitive.Title
bind:ref
data-slot="alert-dialog-title"
class={cn('text-lg font-semibold', className)}
{...restProps}
/>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: AlertDialogPrimitive.TriggerProps = $props();
</script>
<AlertDialogPrimitive.Trigger bind:ref data-slot="alert-dialog-trigger" {...restProps} />

View File

@@ -1,9 +1,8 @@
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import Trigger from './alert-dialog-trigger.svelte';
import Title from './alert-dialog-title.svelte';
import Action from './alert-dialog-action.svelte';
import Cancel from './alert-dialog-cancel.svelte';
import Portal from './alert-dialog-portal.svelte';
import Footer from './alert-dialog-footer.svelte';
import Header from './alert-dialog-header.svelte';
import Overlay from './alert-dialog-overlay.svelte';
@@ -11,14 +10,12 @@ import Content from './alert-dialog-content.svelte';
import Description from './alert-dialog-description.svelte';
const Root = AlertDialogPrimitive.Root;
const Trigger = AlertDialogPrimitive.Trigger;
export {
Root,
Title,
Action,
Cancel,
Portal,
Footer,
Header,
Trigger,
@@ -30,7 +27,6 @@ export {
Title as AlertDialogTitle,
Action as AlertDialogAction,
Cancel as AlertDialogCancel,
Portal as AlertDialogPortal,
Footer as AlertDialogFooter,
Header as AlertDialogHeader,
Trigger as AlertDialogTrigger,

View File

@@ -1,13 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div class={cn('text-sm [&_p]:leading-relaxed', className)} {...$$restProps}>
<slot />
<div
bind:this={ref}
data-slot="alert-description"
class={cn(
'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
className
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,21 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import type { HeadingLevel } from './index.js';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
level?: HeadingLevel;
};
let className: $$Props['class'] = undefined;
export let level: $$Props['level'] = 'h5';
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<svelte:element
this={level}
class={cn('mb-1 leading-none font-medium tracking-tight', className)}
{...$$restProps}
<div
bind:this={ref}
data-slot="alert-title"
class={cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', className)}
{...restProps}
>
<slot />
</svelte:element>
{@render children?.()}
</div>

View File

@@ -1,21 +1,44 @@
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const alertVariants = tv({
base: 'relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
variants: {
variant: {
default: 'bg-card text-card-foreground',
destructive:
'text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current',
warning:
'bg-amber-100 text-amber-900 dark:bg-amber-900 dark:text-amber-100 [&>svg]:text-amber-900 dark:[&>svg]:text-amber-100'
}
},
defaultVariants: {
variant: 'default'
}
});
export type AlertVariant = VariantProps<typeof alertVariants>['variant'];
</script>
<script lang="ts">
import { cn } from '$lib/utils/style.js';
import { LucideX } from 'lucide-svelte';
import { onMount } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import { type Variant, alertVariants } from './index.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
import { onMount } from 'svelte';
import { LucideX } from '@lucide/svelte';
type $$Props = HTMLAttributes<HTMLDivElement> & {
variant?: Variant;
let {
ref = $bindable(null),
class: className,
variant = 'default',
children,
dismissibleId = undefined,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
variant?: AlertVariant;
dismissibleId?: string;
};
} = $props();
let className: $$Props['class'] = undefined;
export let variant: $$Props['variant'] = 'default';
export let dismissibleId: $$Props['dismissibleId'] = undefined;
export { className as class };
let isVisible = !dismissibleId;
let isVisible = $state(!dismissibleId);
onMount(() => {
if (dismissibleId) {
@@ -34,11 +57,17 @@
</script>
{#if isVisible}
<div class={cn(alertVariants({ variant }), className)} {...$$restProps} role="alert">
<slot />
<div
bind:this={ref}
data-slot="alert"
class={cn(alertVariants({ variant }), className)}
{...restProps}
role="alert"
>
{@render children?.()}
{#if dismissibleId}
<button on:click={dismiss} class="absolute top-0 right-0 m-3 text-black dark:text-white"
><LucideX class="w-4" /></button
<button onclick={dismiss} class="absolute top-0 right-0 m-3 text-black dark:text-white"
><LucideX class="size-4" /></button
>
{/if}
</div>

View File

@@ -1,35 +1,14 @@
import { type VariantProps, tv } from 'tailwind-variants';
import Root from './alert.svelte';
import Description from './alert-description.svelte';
import Title from './alert-title.svelte';
import Root from './alert.svelte';
export const alertVariants = tv({
base: '[&>svg]:text-foreground relative w-full rounded-lg border p-4 [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4',
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive text-destructive dark:border-destructive [&>svg]:text-destructive',
warning:
'bg-amber-100 text-amber-900 dark:bg-amber-900 dark:text-amber-100 [&>svg]:text-amber-900 dark:[&>svg]:text-amber-100'
}
},
defaultVariants: {
variant: 'default'
}
});
export type Variant = VariantProps<typeof alertVariants>['variant'];
export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export { alertVariants, type AlertVariant } from './alert.svelte';
export {
Root,
Description,
Title,
//
Root as Alert,
Description as AlertDescription,
Title as AlertTitle,
Description,
Root,
Title
Title as AlertTitle
};

View File

@@ -2,15 +2,16 @@
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = AvatarPrimitive.FallbackProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.FallbackProps = $props();
</script>
<AvatarPrimitive.Fallback
class={cn('bg-muted flex h-full w-full items-center justify-center rounded-full', className)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Fallback>
bind:ref
data-slot="avatar-fallback"
class={cn('bg-muted flex size-full items-center justify-center rounded-full', className)}
{...restProps}
/>

View File

@@ -2,17 +2,16 @@
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = AvatarPrimitive.ImageProps;
let className: $$Props['class'] = undefined;
export let src: $$Props['src'] = undefined;
export let alt: $$Props['alt'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.ImageProps = $props();
</script>
<AvatarPrimitive.Image
{src}
{alt}
class={cn('aspect-square h-full w-full', className)}
{...$$restProps}
bind:ref
data-slot="avatar-image"
class={cn('aspect-square size-full', className)}
{...restProps}
/>

View File

@@ -2,17 +2,16 @@
import { Avatar as AvatarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = AvatarPrimitive.Props;
let className: $$Props['class'] = undefined;
export let delayMs: $$Props['delayMs'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.RootProps = $props();
</script>
<AvatarPrimitive.Root
{delayMs}
class={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full border', className)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Root>
bind:ref
data-slot="avatar"
class={cn('relative flex size-10 shrink-0 overflow-hidden rounded-full border', className)}
{...restProps}
/>

View File

@@ -1,18 +1,49 @@
<script lang="ts">
import { type Variant, badgeVariants } from './index.js';
import { cn } from '$lib/utils/style.js';
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
let className: string | undefined | null = undefined;
export let href: string | undefined = undefined;
export let variant: Variant = 'default';
export { className as class };
export const badgeVariants = tv({
base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-md border px-2 py-0.5 text-xs font-medium transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3',
variants: {
variant: {
default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
secondary:
'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
destructive:
'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white',
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
}
},
defaultVariants: {
variant: 'default'
}
});
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
</script>
<script lang="ts">
import type { HTMLAnchorAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils/style.js';
let {
ref = $bindable(null),
href,
class: className,
variant = 'default',
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
} = $props();
</script>
<svelte:element
this={href ? 'a' : 'span'}
bind:this={ref}
data-slot="badge"
{href}
class={cn(badgeVariants({ variant, className }))}
{...$$restProps}
class={cn(badgeVariants({ variant }), className)}
{...restProps}
>
<slot />
{@render children?.()}
</svelte:element>

View File

@@ -1,20 +1,2 @@
import { type VariantProps, tv } from 'tailwind-variants';
export { default as Badge } from './badge.svelte';
export const badgeVariants = tv({
base: 'inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 break-keep whitespace-nowrap',
variants: {
variant: {
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground'
}
},
defaultVariants: {
variant: 'default'
}
});
export type Variant = VariantProps<typeof badgeVariants>['variant'];
export { badgeVariants, type BadgeVariant } from './badge.svelte';

View File

@@ -1,33 +1,90 @@
<script lang="ts">
import { cn } from '$lib/utils/style.js';
import { Button as ButtonPrimitive } from 'bits-ui';
import LoaderCircle from 'lucide-svelte/icons/loader-circle';
import type { ClassNameValue } from 'tailwind-merge';
import { type Events, type Props, buttonVariants } from './index.js';
<script lang="ts" module>
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';
type $$Props = Props;
type $$Events = Events;
export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
destructive:
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
outline:
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
let className: $$Props['class'] = undefined;
export let variant: $$Props['variant'] = 'default';
export let size: $$Props['size'] = 'default';
export let disabled: boolean | undefined | null = false;
export let isLoading: $$Props['isLoading'] = false;
export let builders: $$Props['builders'] = [];
export { className as class };
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
isLoading?: boolean;
};
</script>
<ButtonPrimitive.Root
{builders}
disabled={isLoading || disabled}
class={cn(buttonVariants({ variant, size, className: className as ClassNameValue }))}
type="button"
{...$$restProps}
on:click
on:keydown
>
{#if isLoading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
<slot />
</ButtonPrimitive.Root>
<script lang="ts">
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = 'button',
disabled,
isLoading = false,
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{#if isLoading}
<LoaderCircle class="mr-2 size-4 animate-spin" />
{/if}
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{#if isLoading}
<LoaderCircle class="mr-2 size-4 animate-spin" />
{/if}
{@render children?.()}
</button>
{/if}

View File

@@ -1,49 +1,17 @@
import { type VariantProps, tv } from 'tailwind-variants';
import type { Button as ButtonPrimitive } from 'bits-ui';
import Root from './button.svelte';
const buttonVariants = tv({
base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
type Variant = VariantProps<typeof buttonVariants>['variant'];
type Size = VariantProps<typeof buttonVariants>['size'];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
isLoading?: boolean;
};
type Events = ButtonPrimitive.Events;
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
} from './button.svelte';
export {
Root,
type Props,
type Events,
type ButtonProps as Props,
//
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant
};

View File

@@ -2,20 +2,18 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.CellProps;
export let date: $$Props['date'];
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.CellProps = $props();
</script>
<CalendarPrimitive.Cell
{date}
bind:ref
class={cn(
'[&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50 relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md',
'[&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50 relative size-8 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md',
className
)}
{...$$restProps}
>
<slot />
</CalendarPrimitive.Cell>
{...restProps}
/>

View File

@@ -1,42 +1,30 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/style.js';
import { Calendar as CalendarPrimitive } from 'bits-ui';
type $$Props = CalendarPrimitive.DayProps;
type $$Events = CalendarPrimitive.DayEvents;
export let date: $$Props['date'];
export let month: $$Props['month'];
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.DayProps = $props();
</script>
<CalendarPrimitive.Day
on:click
{date}
{month}
bind:ref
class={cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal ',
'size-8 p-0 font-normal select-none',
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
// Selected
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground data-[selected]:opacity-100',
'data-selected:bg-primary data-selected:text-primary-foreground data-selected:hover:bg-primary data-selected:hover:text-primary-foreground data-selected:focus:bg-primary data-selected:focus:text-primary-foreground dark:data-selected:hover:bg-primary dark:data-selected:focus:bg-primary data-selected:opacity-100',
// Disabled
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
'data-disabled:text-muted-foreground data-disabled:opacity-50',
// Unavailable
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
'data-unavailable:text-destructive-foreground data-unavailable:line-through',
// Outside months
'data-[outside-month]:text-muted-foreground [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground data-[outside-month]:pointer-events-none data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:opacity-30',
className
)}
{...$$restProps}
let:selected
let:disabled
let:unavailable
let:builder
>
<slot {selected} {disabled} {unavailable} {builder}>
{date.day}
</slot>
</CalendarPrimitive.Day>
{...restProps}
/>

View File

@@ -2,12 +2,11 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.GridBodyProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridBodyProps = $props();
</script>
<CalendarPrimitive.GridBody class={cn(className)} {...$$restProps}>
<slot />
</CalendarPrimitive.GridBody>
<CalendarPrimitive.GridBody bind:ref class={cn(className)} {...restProps} />

View File

@@ -2,12 +2,11 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.GridHeadProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridHeadProps = $props();
</script>
<CalendarPrimitive.GridHead class={cn(className)} {...$$restProps}>
<slot />
</CalendarPrimitive.GridHead>
<CalendarPrimitive.GridHead bind:ref class={cn(className)} {...restProps} />

View File

@@ -2,12 +2,11 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.GridRowProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridRowProps = $props();
</script>
<CalendarPrimitive.GridRow class={cn('flex', className)} {...$$restProps}>
<slot />
</CalendarPrimitive.GridRow>
<CalendarPrimitive.GridRow bind:ref class={cn('flex', className)} {...restProps} />

View File

@@ -2,12 +2,15 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.GridProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.GridProps = $props();
</script>
<CalendarPrimitive.Grid class={cn('w-full border-collapse space-y-1', className)} {...$$restProps}>
<slot />
</CalendarPrimitive.Grid>
<CalendarPrimitive.Grid
bind:ref
class={cn('w-full border-collapse space-y-1', className)}
{...restProps}
/>

View File

@@ -2,15 +2,15 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.HeadCellProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.HeadCellProps = $props();
</script>
<CalendarPrimitive.HeadCell
class={cn('text-muted-foreground w-9 rounded-md text-[0.8rem] font-normal', className)}
{...$$restProps}
>
<slot />
</CalendarPrimitive.HeadCell>
bind:ref
class={cn('text-muted-foreground w-8 rounded-md text-[0.8rem] font-normal', className)}
{...restProps}
/>

View File

@@ -2,15 +2,15 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.HeaderProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.HeaderProps = $props();
</script>
<CalendarPrimitive.Header
bind:ref
class={cn('relative flex w-full items-center justify-between pt-1', className)}
{...$$restProps}
>
<slot />
</CalendarPrimitive.Header>
{...restProps}
/>

View File

@@ -2,18 +2,11 @@
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.HeadingProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CalendarPrimitive.HeadingProps = $props();
</script>
<CalendarPrimitive.Heading
let:headingValue
class={cn('text-sm font-medium', className)}
{...$$restProps}
>
<slot {headingValue}>
{headingValue}
</slot>
</CalendarPrimitive.Heading>
<CalendarPrimitive.Heading bind:ref class={cn('text-sm font-medium', className)} {...restProps} />

View File

@@ -1,16 +1,19 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
class={cn('mt-4 flex flex-col space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4', className)}
{...$$restProps}
{...restProps}
>
<slot />
{@render children?.()}
</div>

View File

@@ -1,27 +1,28 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from 'bits-ui';
import ChevronRight from 'lucide-svelte/icons/chevron-right';
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.NextButtonProps;
type $$Events = CalendarPrimitive.NextButtonEvents;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: CalendarPrimitive.PrevButtonProps = $props();
</script>
{#snippet Fallback()}
<ChevronRightIcon class="size-4" />
{/snippet}
<CalendarPrimitive.NextButton
on:click
bind:ref
class={cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
className
)}
{...$$restProps}
let:builder
>
<slot {builder}>
<ChevronRight class="h-4 w-4" />
</slot>
</CalendarPrimitive.NextButton>
children={children || Fallback}
{...restProps}
/>

View File

@@ -1,27 +1,28 @@
<script lang="ts">
import { Calendar as CalendarPrimitive } from 'bits-ui';
import ChevronLeft from 'lucide-svelte/icons/chevron-left';
import ChevronLeftIcon from '@lucide/svelte/icons/chevron-left';
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.PrevButtonProps;
type $$Events = CalendarPrimitive.PrevButtonEvents;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: CalendarPrimitive.PrevButtonProps = $props();
</script>
{#snippet Fallback()}
<ChevronLeftIcon class="size-4" />
{/snippet}
<CalendarPrimitive.PrevButton
on:click
bind:ref
class={cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
className
)}
{...$$restProps}
let:builder
>
<slot {builder}>
<ChevronLeft class="h-4 w-4" />
</slot>
</CalendarPrimitive.PrevButton>
children={children || Fallback}
{...restProps}
/>

View File

@@ -1,137 +1,61 @@
<script lang="ts">
import * as Calendar from '$lib/components/ui/calendar/index.js';
import * as Select from '$lib/components/ui/select/index.js';
import { cn } from '$lib/utils/style';
import { DateFormatter, getLocalTimeZone, today } from '@internationalized/date';
import { Calendar as CalendarPrimitive } from 'bits-ui';
import * as Calendar from './index.js';
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.Props;
type $$Events = CalendarPrimitive.Events;
export let value: $$Props['value'] = undefined;
export let placeholder: $$Props['placeholder'] = today(getLocalTimeZone());
export let weekdayFormat: $$Props['weekdayFormat'] = 'short';
const monthOptions = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
].map((month, i) => ({ value: i + 1, label: month }));
const monthFmt = new DateFormatter('en-US', {
month: 'long'
});
const yearOptions = Array.from({ length: 100 }, (_, i) => ({
label: String(new Date().getFullYear() + i),
value: new Date().getFullYear() + i
}));
$: defaultYear = placeholder
? {
value: placeholder.year,
label: String(placeholder.year)
}
: undefined;
$: defaultMonth = placeholder
? {
value: placeholder.month,
label: monthFmt.format(placeholder.toDate(getLocalTimeZone()))
}
: undefined;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
value = $bindable(),
placeholder = $bindable(),
class: className,
weekdayFormat = 'short',
...restProps
}: WithoutChildrenOrChild<CalendarPrimitive.RootProps> = $props();
</script>
<!--
Discriminated Unions + Destructing (required for bindable) do not
get along, so we shut typescript up by casting `value` to `never`.
-->
<CalendarPrimitive.Root
{weekdayFormat}
class={cn('rounded-md border p-3', className)}
{...$$restProps}
on:keydown
let:months
let:weekdays
bind:value
bind:value={value as never}
bind:ref
bind:placeholder
{weekdayFormat}
class={cn('p-3', className)}
{...restProps}
>
<Calendar.Header>
<Calendar.Heading class="flex w-full items-center justify-between gap-2">
<Select.Root
selected={defaultMonth}
items={monthOptions}
onSelectedChange={(v) => {
if (!v || !placeholder) return;
if (v.value === placeholder?.month) return;
placeholder = placeholder.set({ month: v.value });
}}
>
<Select.Trigger aria-label="Select month" class="w-[60%]">
<Select.Value placeholder="Select month" />
</Select.Trigger>
<Select.Content class="max-h-[200px] overflow-y-auto">
{#each monthOptions as { value, label }}
<Select.Item {value} {label}>
{label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<Select.Root
selected={defaultYear}
items={yearOptions}
onSelectedChange={(v) => {
if (!v || !placeholder) return;
if (v.value === placeholder?.year) return;
placeholder = placeholder.set({ year: v.value });
}}
>
<Select.Trigger aria-label="Select year" class="w-[40%]">
<Select.Value placeholder="Select year" />
</Select.Trigger>
<Select.Content class="max-h-[200px] overflow-y-auto">
{#each yearOptions as { value, label }}
<Select.Item {value} {label}>
{label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</Calendar.Heading>
</Calendar.Header>
<Calendar.Months>
{#each months as month}
<Calendar.Grid>
<Calendar.GridHead>
<Calendar.GridRow class="flex">
{#each weekdays as weekday}
<Calendar.HeadCell>
{weekday.slice(0, 2)}
</Calendar.HeadCell>
{/each}
</Calendar.GridRow>
</Calendar.GridHead>
<Calendar.GridBody>
{#each month.weeks as weekDates}
<Calendar.GridRow class="mt-2 w-full">
{#each weekDates as date}
<Calendar.Cell {date}>
<Calendar.Day {date} month={month.value} />
</Calendar.Cell>
{#snippet children({ months, weekdays })}
<Calendar.Header>
<Calendar.PrevButton />
<Calendar.Heading />
<Calendar.NextButton />
</Calendar.Header>
<Calendar.Months>
{#each months as month (month)}
<Calendar.Grid>
<Calendar.GridHead>
<Calendar.GridRow class="flex">
{#each weekdays as weekday (weekday)}
<Calendar.HeadCell>
{weekday.slice(0, 2)}
</Calendar.HeadCell>
{/each}
</Calendar.GridRow>
{/each}
</Calendar.GridBody>
</Calendar.Grid>
{/each}
</Calendar.Months>
</Calendar.GridHead>
<Calendar.GridBody>
{#each month.weeks as weekDates (weekDates)}
<Calendar.GridRow class="mt-2 w-full">
{#each weekDates as date (date)}
<Calendar.Cell {date} month={month.value}>
<Calendar.Day />
</Calendar.Cell>
{/each}
</Calendar.GridRow>
{/each}
</Calendar.GridBody>
</Calendar.Grid>
{/each}
</Calendar.Months>
{/snippet}
</CalendarPrimitive.Root>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-action"
class={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,13 +1,15 @@
<script lang="ts">
import { cn } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div class={cn('bg-muted/20 p-6 pt-5 peer-[.card-header]:border-t', className)} {...$$restProps}>
<slot />
<div bind:this={ref} data-slot="card-content" class={cn('px-6', className)} {...restProps}>
{@render children?.()}
</div>

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLParagraphElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
</script>
<p class={cn('text-muted-foreground mt-1 text-sm', className)} {...$$restProps}>
<slot />
<p
bind:this={ref}
data-slot="card-description"
class={cn('text-muted-foreground text-sm', className)}
{...restProps}
>
{@render children?.()}
</p>

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div class={cn('flex items-center p-6 pt-0', className)} {...$$restProps}>
<slot />
<div
bind:this={ref}
data-slot="card-footer"
class={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,13 +1,23 @@
<script lang="ts">
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div class={cn('card-header peer flex flex-col space-y-1.5 p-6', className)} {...$$restProps}>
<slot />
<div
bind:this={ref}
data-slot="card-header"
class={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,21 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import type { HeadingLevel } from './index.js';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
tag?: HeadingLevel;
};
let className: $$Props['class'] = undefined;
export let tag: $$Props['tag'] = 'h3';
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<svelte:element
this={tag}
class={cn('flex items-center gap-2 text-xl leading-none font-semibold tracking-tight', className)}
{...$$restProps}
<div
bind:this={ref}
data-slot="card-title"
class={cn('flex flex-row items-center gap-2 text-xl leading-none font-semibold', className)}
{...restProps}
>
<slot />
</svelte:element>
{@render children?.()}
</div>

View File

@@ -1,16 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
class={cn('bg-card text-card-foreground overflow-hidden rounded-lg border shadow-sm', className)}
{...$$restProps}
bind:this={ref}
data-slot="card"
class={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
className
)}
{...restProps}
>
<slot />
{@render children?.()}
</div>

View File

@@ -4,6 +4,7 @@ import Description from './card-description.svelte';
import Footer from './card-footer.svelte';
import Header from './card-header.svelte';
import Title from './card-title.svelte';
import Action from './card-action.svelte';
export {
Root,
@@ -12,13 +13,13 @@ export {
Footer,
Header,
Title,
Action,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle
Title as CardTitle,
Action as CardAction
};
export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';

View File

@@ -1,35 +1,39 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
import Check from 'lucide-svelte/icons/check';
import Minus from 'lucide-svelte/icons/minus';
import { cn } from '$lib/utils/style.js';
import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus';
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = CheckboxPrimitive.Props;
type $$Events = CheckboxPrimitive.Events;
let className: $$Props['class'] = undefined;
export let checked: $$Props['checked'] = false;
export { className as class };
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>
<CheckboxPrimitive.Root
bind:ref
data-slot="checkbox"
class={cn(
'peer border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50',
'border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive peer size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className
)}
bind:checked
{...$$restProps}
on:click
bind:indeterminate
{...restProps}
>
<CheckboxPrimitive.Indicator
class={cn('flex h-4 w-4 items-center justify-center text-current')}
let:isChecked
let:isIndeterminate
>
{#if isChecked}
<Check class="h-3.5 w-3.5" />
{:else if isIndeterminate}
<Minus class="h-3.5 w-3.5" />
{/if}
</CheckboxPrimitive.Indicator>
{#snippet children({ checked, indeterminate })}
<div
data-slot="checkbox-indicator"
class="flex items-center justify-center text-current transition-none"
>
{#if checked}
<CheckIcon class="size-3.5" />
{:else if indeterminate}
<MinusIcon class="size-3.5" />
{/if}
</div>
{/snippet}
</CheckboxPrimitive.Root>

View File

@@ -1,23 +1,40 @@
<script lang="ts">
import type { Dialog as DialogPrimitive } from 'bits-ui';
import type { Command as CommandPrimitive } from 'cmdk-sv';
import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from 'bits-ui';
import type { Snippet } from 'svelte';
import Command from './command.svelte';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import type { WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps;
export let open: $$Props['open'] = false;
export let value: $$Props['value'] = undefined;
let {
open = $bindable(false),
ref = $bindable(null),
value = $bindable(''),
title = 'Command Palette',
description = 'Search for a command to run',
portalProps,
children,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.RootProps> &
WithoutChildrenOrChild<CommandPrimitive.RootProps> & {
portalProps?: DialogPrimitive.PortalProps;
children: Snippet;
title?: string;
description?: string;
} = $props();
</script>
<Dialog.Root bind:open {...$$restProps}>
<Dialog.Content class="overflow-hidden p-0 shadow-lg">
<Dialog.Root bind:open {...restProps}>
<Dialog.Header class="sr-only">
<Dialog.Title>{title}</Dialog.Title>
<Dialog.Description>{description}</Dialog.Description>
</Dialog.Header>
<Dialog.Content class="overflow-hidden p-0" {portalProps}>
<Command
class="[&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group]]:px-2 [&_[data-cmdk-group]:not([hidden])_~[data-cmdk-group]]:pt-0 [&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5 [&_[data-cmdk-input]]:h-12 [&_[data-cmdk-item]]:px-2 [&_[data-cmdk-item]]:py-3 [&_[data-cmdk-item]_svg]:h-5 [&_[data-cmdk-item]_svg]:w-5"
{...$$restProps}
class="**:data-[slot=command-input-wrapper]:h-12 [&_[data-command-group]]:px-2 [&_[data-command-group]:not([hidden])_~[data-command-group]]:pt-0 [&_[data-command-input-wrapper]_svg]:h-5 [&_[data-command-input-wrapper]_svg]:w-5 [&_[data-command-input]]:h-12 [&_[data-command-item]]:px-2 [&_[data-command-item]]:py-3 [&_[data-command-item]_svg]:h-5 [&_[data-command-item]_svg]:w-5"
{...restProps}
bind:value
>
<slot />
</Command>
bind:ref
{children}
/>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,13 +1,17 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.EmptyProps;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CommandPrimitive.EmptyProps = $props();
</script>
<CommandPrimitive.Empty class={cn('py-6 text-center text-sm', className)} {...$$restProps}>
<slot />
</CommandPrimitive.Empty>
<CommandPrimitive.Empty
bind:ref
data-slot="command-empty"
class={cn('py-6 text-center text-sm', className)}
{...restProps}
/>

View File

@@ -1,19 +1,30 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive, useId } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.GroupProps;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
heading,
value,
...restProps
}: CommandPrimitive.GroupProps & {
heading?: string;
} = $props();
</script>
<CommandPrimitive.Group
class={cn(
'text-foreground [&_[data-cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-medium',
className
)}
{...$$restProps}
bind:ref
data-slot="command-group"
class={cn('text-foreground overflow-hidden p-1', className)}
value={value ?? heading ?? `----${useId()}`}
{...restProps}
>
<slot />
{#if heading}
<CommandPrimitive.GroupHeading class="text-muted-foreground px-2 py-1.5 text-xs font-medium">
{heading}
</CommandPrimitive.GroupHeading>
{/if}
<CommandPrimitive.GroupItems {children} />
</CommandPrimitive.Group>

View File

@@ -1,24 +1,26 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui';
import SearchIcon from '@lucide/svelte/icons/search';
import { cn } from '$lib/utils/style.js';
import { Command as CommandPrimitive } from 'cmdk-sv';
import Search from 'lucide-svelte/icons/search';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.InputProps;
let className: ClassValue | undefined | null = undefined;
export { className as class };
export let value: string = '';
let {
ref = $bindable(null),
class: className,
value = $bindable(''),
...restProps
}: CommandPrimitive.InputProps = $props();
</script>
<div class="flex items-center border-b px-2" data-cmdk-input-wrapper="">
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<div class="flex h-9 items-center gap-2 border-b px-3" data-slot="command-input-wrapper">
<SearchIcon class="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
class={cn(
'placeholder:text-muted-foreground flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50',
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...$$restProps}
bind:ref
{...restProps}
bind:value
/>
</div>

View File

@@ -1,25 +1,20 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.ItemProps;
export let asChild = false;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CommandPrimitive.ItemProps = $props();
</script>
<CommandPrimitive.Item
{asChild}
bind:ref
data-slot="command-item"
class={cn(
'aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...$$restProps}
let:action
let:attrs
>
<slot {action} {attrs} />
</CommandPrimitive.Item>
{...restProps}
/>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: CommandPrimitive.LinkItemProps = $props();
</script>
<CommandPrimitive.LinkItem
bind:ref
data-slot="command-item"
class={cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...restProps}
/>

View File

@@ -1,16 +1,17 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.ListProps;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CommandPrimitive.ListProps = $props();
</script>
<CommandPrimitive.List
class={cn('max-h-[300px] overflow-x-hidden overflow-y-auto', className)}
{...$$restProps}
>
<slot />
</CommandPrimitive.List>
bind:ref
data-slot="command-list"
class={cn('max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto', className)}
{...restProps}
/>

View File

@@ -1,11 +1,17 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.SeparatorProps;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: CommandPrimitive.SeparatorProps = $props();
</script>
<CommandPrimitive.Separator class={cn('bg-border -mx-1 h-px', className)} {...$$restProps} />
<CommandPrimitive.Separator
bind:ref
data-slot="command-separator"
class={cn('bg-border -mx-1 h-px', className)}
{...restProps}
/>

View File

@@ -1,16 +1,20 @@
<script lang="ts">
import type { ClassValue, HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
data-slot="command-shortcut"
class={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...$$restProps}
{...restProps}
>
<slot />
{@render children?.()}
</span>

View File

@@ -1,23 +1,22 @@
<script lang="ts">
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.CommandProps;
export let value: $$Props['value'] = undefined;
let className: ClassValue | undefined | null = undefined;
export { className as class };
let {
ref = $bindable(null),
value = $bindable(''),
class: className,
...restProps
}: CommandPrimitive.RootProps = $props();
</script>
<CommandPrimitive.Root
bind:value
bind:ref
data-slot="command"
class={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
className
)}
bind:value
{...$$restProps}
>
<slot />
</CommandPrimitive.Root>
{...restProps}
/>

View File

@@ -1,4 +1,4 @@
import { Command as CommandPrimitive } from 'cmdk-sv';
import { Command as CommandPrimitive } from 'bits-ui';
import Root from './command.svelte';
import Dialog from './command-dialog.svelte';
@@ -9,6 +9,7 @@ import Input from './command-input.svelte';
import List from './command-list.svelte';
import Separator from './command-separator.svelte';
import Shortcut from './command-shortcut.svelte';
import LinkItem from './command-link-item.svelte';
const Loading = CommandPrimitive.Loading;
@@ -18,6 +19,7 @@ export {
Empty,
Group,
Item,
LinkItem,
Input,
List,
Separator,
@@ -29,6 +31,7 @@ export {
Empty as CommandEmpty,
Group as CommandGroup,
Item as CommandItem,
LinkItem as CommandLinkItem,
Input as CommandInput,
List as CommandList,
Separator as CommandSeparator,

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
</script>
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />

View File

@@ -1,41 +1,39 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import X from 'lucide-svelte/icons/x';
import XIcon from '@lucide/svelte/icons/x';
import type { Snippet } from 'svelte';
import * as Dialog from './index.js';
import { cn, flyAndScale } from '$lib/utils/style.js';
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = DialogPrimitive.ContentProps & {
closeButton?: boolean;
};
let className: $$Props['class'] = undefined;
export let transition: $$Props['transition'] = flyAndScale;
export let closeButton: $$Props['closeButton'] = true;
export let transitionConfig: $$Props['transitionConfig'] = {
duration: 200
};
export { className as class };
let {
ref = $bindable(null),
class: className,
portalProps,
children,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: DialogPrimitive.PortalProps;
children: Snippet;
} = $props();
</script>
<Dialog.Portal>
<Dialog.Portal {...portalProps}>
<Dialog.Overlay />
<DialogPrimitive.Content
{transition}
{transitionConfig}
bind:ref
data-slot="dialog-content"
class={cn(
'bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full',
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className
)}
{...$$restProps}
{...restProps}
>
<slot />
{#if closeButton}
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
{/if}
{@render children?.()}
<DialogPrimitive.Close
class="ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>

View File

@@ -2,15 +2,16 @@
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
</script>
<DialogPrimitive.Description
bind:ref
data-slot="dialog-description"
class={cn('text-muted-foreground text-sm', className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Description>
{...restProps}
/>

View File

@@ -1,16 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...$$restProps}
bind:this={ref}
data-slot="dialog-footer"
class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
{...restProps}
>
<slot />
{@render children?.()}
</div>

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...$$restProps}>
<slot />
<div
bind:this={ref}
data-slot="dialog-header"
class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -1,21 +1,20 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { fade } from 'svelte/transition';
import { cn } from '$lib/utils/style.js';
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props['class'] = undefined;
export let transition: $$Props['transition'] = fade;
export let transitionConfig: $$Props['transitionConfig'] = {
duration: 150
};
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn('bg-background/80 fixed inset-0 z-50 backdrop-blur-sm', className)}
{...$$restProps}
bind:ref
data-slot="dialog-overlay"
class={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className
)}
{...restProps}
/>

View File

@@ -1,8 +1,14 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
interface Props {
children?: import('svelte').Snippet;
[key: string]: any;
}
let { children, ...rest }: Props = $props();
type $$Props = DialogPrimitive.PortalProps;
</script>
<DialogPrimitive.Portal {...$$restProps}>
<slot />
<DialogPrimitive.Portal {...rest}>
{@render children?.()}
</DialogPrimitive.Portal>

View File

@@ -2,15 +2,16 @@
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
</script>
<DialogPrimitive.Title
class={cn('text-lg leading-none font-semibold tracking-tight', className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Title>
bind:ref
data-slot="dialog-title"
class={cn('text-lg leading-none font-semibold', className)}
{...restProps}
/>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
</script>
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />

View File

@@ -1,16 +1,16 @@
import { Dialog as DialogPrimitive } from 'bits-ui';
import Title from './dialog-title.svelte';
import Portal from './dialog-portal.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
import Trigger from './dialog-trigger.svelte';
import Close from './dialog-close.svelte';
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
const Close = DialogPrimitive.Close;
const Portal = DialogPrimitive.Portal;
export {
Root,

View File

@@ -1,35 +1,41 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import Check from 'lucide-svelte/icons/check';
import { cn } from '$lib/utils/style.js';
import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus';
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
import type { Snippet } from 'svelte';
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
let className: $$Props['class'] = undefined;
export let checked: $$Props['checked'] = undefined;
export { className as class };
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
children: childrenProp,
...restProps
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
children?: Snippet;
} = $props();
</script>
<DropdownMenuPrimitive.CheckboxItem
bind:ref
bind:checked
bind:indeterminate
data-slot="dropdown-menu-checkbox-item"
class={cn(
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
{...restProps}
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.CheckboxIndicator>
<Check class="h-4 w-4" />
</DropdownMenuPrimitive.CheckboxIndicator>
</span>
<slot />
{#snippet children({ checked, indeterminate })}
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<MinusIcon class="size-4" />
{:else}
<CheckIcon class={cn('size-4', !checked && 'text-transparent')} />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</DropdownMenuPrimitive.CheckboxItem>

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import { cn } from '$lib/utils/style.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn, flyAndScale } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.ContentProps;
type $$Events = DropdownMenuPrimitive.ContentEvents;
let className: $$Props['class'] = undefined;
export let sideOffset: $$Props['sideOffset'] = 4;
export let transition: $$Props['transition'] = flyAndScale;
export let transitionConfig: $$Props['transitionConfig'] = undefined;
export { className as class };
let {
ref = $bindable(null),
sideOffset = 4,
portalProps,
class: className,
...restProps
}: DropdownMenuPrimitive.ContentProps & {
portalProps?: DropdownMenuPrimitive.PortalProps;
} = $props();
</script>
<DropdownMenuPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
'bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-md focus:outline-none',
className
)}
{...$$restProps}
on:keydown
>
<slot />
</DropdownMenuPrimitive.Content>
<DropdownMenuPrimitive.Portal {...portalProps}>
<DropdownMenuPrimitive.Content
bind:ref
data-slot="dropdown-menu-content"
{sideOffset}
class={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className
)}
{...restProps}
/>
</DropdownMenuPrimitive.Portal>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.GroupHeading
bind:ref
data-slot="dropdown-menu-group-heading"
data-inset={inset}
class={cn('px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8', className)}
{...restProps}
/>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
</script>
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />

View File

@@ -1,31 +1,27 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
type $$Props = DropdownMenuPrimitive.ItemProps & {
let {
ref = $bindable(null),
class: className,
inset,
variant = 'default',
...restProps
}: DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
};
type $$Events = DropdownMenuPrimitive.ItemEvents;
let className: $$Props['class'] = undefined;
export let inset: $$Props['inset'] = undefined;
export { className as class };
variant?: 'default' | 'destructive';
} = $props();
</script>
<DropdownMenuPrimitive.Item
bind:ref
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
class={cn(
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<slot />
</DropdownMenuPrimitive.Item>
{...restProps}
/>

View File

@@ -1,19 +1,24 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements';
type $$Props = DropdownMenuPrimitive.LabelProps & {
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean;
};
let className: $$Props['class'] = undefined;
export let inset: $$Props['inset'] = undefined;
export { className as class };
} = $props();
</script>
<DropdownMenuPrimitive.Label
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...$$restProps}
<div
bind:this={ref}
data-slot="dropdown-menu-label"
data-inset={inset}
class={cn('px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8', className)}
{...restProps}
>
<slot />
</DropdownMenuPrimitive.Label>
{@render children?.()}
</div>

View File

@@ -1,11 +1,16 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
export let value: $$Props['value'] = undefined;
let {
ref = $bindable(null),
value = $bindable(),
...restProps
}: DropdownMenuPrimitive.RadioGroupProps = $props();
</script>
<DropdownMenuPrimitive.RadioGroup {...$$restProps} bind:value>
<slot />
</DropdownMenuPrimitive.RadioGroup>
<DropdownMenuPrimitive.RadioGroup
bind:ref
bind:value
data-slot="dropdown-menu-radio-group"
{...restProps}
/>

View File

@@ -1,35 +1,31 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import Circle from 'lucide-svelte/icons/circle';
import { cn } from '$lib/utils/style.js';
import CircleIcon from '@lucide/svelte/icons/circle';
import { cn, type WithoutChild } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
let className: $$Props['class'] = undefined;
export let value: $$Props['value'];
export { className as class };
let {
ref = $bindable(null),
class: className,
children: childrenProp,
...restProps
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
</script>
<DropdownMenuPrimitive.RadioItem
bind:ref
data-slot="dropdown-menu-radio-item"
class={cn(
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{value}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
{...restProps}
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.RadioIndicator>
<Circle class="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.RadioIndicator>
</span>
<slot />
{#snippet children({ checked })}
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
{#if checked}
<CircleIcon class="size-2 fill-current" />
{/if}
</span>
{@render childrenProp?.({ checked })}
{/snippet}
</DropdownMenuPrimitive.RadioItem>

View File

@@ -2,13 +2,16 @@
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.SeparatorProps;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
</script>
<DropdownMenuPrimitive.Separator
class={cn('bg-muted -mx-1 my-1 h-px', className)}
{...$$restProps}
bind:ref
data-slot="dropdown-menu-separator"
class={cn('bg-border -mx-1 my-1 h-px', className)}
{...restProps}
/>

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn } from '$lib/utils/style.js';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLSpanElement>;
let className: $$Props['class'] = undefined;
export { className as class };
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span class={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...$$restProps}>
<slot />
<span
bind:this={ref}
data-slot="dropdown-menu-shortcut"
class={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...restProps}
>
{@render children?.()}
</span>

View File

@@ -1,30 +1,20 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn, flyAndScale } from '$lib/utils/style.js';
import { cn } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.SubContentProps;
type $$Events = DropdownMenuPrimitive.SubContentEvents;
let className: $$Props['class'] = undefined;
export let transition: $$Props['transition'] = flyAndScale;
export let transitionConfig: $$Props['transitionConfig'] = {
x: -10,
y: 0
};
export { className as class };
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SubContentProps = $props();
</script>
<DropdownMenuPrimitive.SubContent
{transition}
{transitionConfig}
bind:ref
data-slot="dropdown-menu-sub-content"
class={cn(
'bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none',
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className
)}
{...$$restProps}
on:keydown
on:focusout
on:pointermove
>
<slot />
</DropdownMenuPrimitive.SubContent>
{...restProps}
/>

Some files were not shown because too many files have changed in this diff Show More