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

View File

@@ -1,70 +1,199 @@
@import 'tailwindcss'; @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 { @layer base {
:root { * {
--background: 0 0% 100%; @apply border-border;
--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;
} }
.dark { body {
--background: 240 10% 3.9%; @apply bg-background text-foreground;
--foreground: 0 0% 98%; }
--muted: 240 3.7% 15.9%; button {
--muted-foreground: 240 5% 64.9%; @apply cursor-pointer;
}
--popover: 240 10% 3.9%; @font-face {
--popover-foreground: 0 0% 98%; 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%; @keyframes accordion-down {
--card-foreground: 0 0% 98%; from {
height: 0;
}
--border: 240 3.7% 15.9%; to {
--input: 240 3.7% 15.9%; height: var(--bits-accordion-content-height);
}
}
--primary: 0 0% 98%; @keyframes accordion-up {
--primary-foreground: 240 5.9% 10%; from {
height: var(--bits-accordion-content-height);
}
--secondary: 240 3.7% 15.9%; to {
--secondary-foreground: 0 0% 98%; height: 0;
}
}
--accent: 240 3.7% 15.9%; @keyframes caret-blink {
--accent-foreground: 0 0% 98%; 0%,
70%,
100% {
opacity: 1;
}
--destructive: 0 62.8% 30.6%; 20%,
--destructive-foreground: 0 0% 98%; 50% {
opacity: 0;
--ring: 240 4.9% 83.9%;
} }
} }
@@ -102,7 +231,6 @@
animation: slide-bg-container 1.2s cubic-bezier(0.33, 1, 0.68, 1) forwards; 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 { @keyframes delayed-fade {
0%, 0%,
40% { 40% {
@@ -116,38 +244,3 @@
.animate-delayed-fade { .animate-delayed-fade {
animation: delayed-fade 1.5s ease-out forwards; 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 type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import { debounced } from '$lib/utils/debounce-util'; import { debounced } from '$lib/utils/debounce-util';
import { cn } from '$lib/utils/style'; import { cn } from '$lib/utils/style';
import { ChevronDown } from 'lucide-svelte'; import { ChevronDown } from '@lucide/svelte';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import Button from './ui/button/button.svelte'; import Button from './ui/button/button.svelte';
import { m } from '$lib/paraglide/messages'; import { m } from '$lib/paraglide/messages';
@@ -96,7 +96,7 @@
)} )}
placeholder={m.search()} placeholder={m.search()}
type="text" type="text"
oninput={(e) => onSearch((e.target as HTMLInputElement).value)} oninput={(e: Event) => onSearch((e.currentTarget as HTMLInputElement).value)}
/> />
{/if} {/if}
@@ -114,7 +114,7 @@
<Checkbox <Checkbox
disabled={selectionDisabled} disabled={selectionDisabled}
checked={allChecked} checked={allChecked}
onCheckedChange={(c) => onAllCheck(c as boolean)} onCheckedChange={(c: boolean) => onAllCheck(c as boolean)}
/> />
</Table.Head> </Table.Head>
{/if} {/if}
@@ -124,7 +124,7 @@
<Button <Button
variant="ghost" variant="ghost"
class="flex items-center" class="flex items-center"
on:click={() => onclick={() =>
onSort( onSort(
column.sortColumn, column.sortColumn,
requestOptions.sort?.direction === 'desc' ? 'asc' : 'desc' requestOptions.sort?.direction === 'desc' ? 'asc' : 'desc'
@@ -134,7 +134,7 @@
{#if requestOptions.sort?.column === column.sortColumn} {#if requestOptions.sort?.column === column.sortColumn}
<ChevronDown <ChevronDown
class={cn( class={cn(
'ml-2 h-4 w-4', 'ml-2 size-4',
requestOptions.sort?.direction === 'asc' ? 'rotate-180' : '' requestOptions.sort?.direction === 'asc' ? 'rotate-180' : ''
)} )}
/> />
@@ -155,7 +155,7 @@
<Checkbox <Checkbox
disabled={selectionDisabled} disabled={selectionDisabled}
checked={selectedIds.includes(item.id)} checked={selectedIds.includes(item.id)}
onCheckedChange={(c) => onCheck(c as boolean, item.id)} onCheckedChange={(c: boolean) => onCheck(c, item.id)}
/> />
</Table.Cell> </Table.Cell>
{/if} {/if}
@@ -169,18 +169,16 @@
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<p class="text-sm font-medium">{m.items_per_page()}</p> <p class="text-sm font-medium">{m.items_per_page()}</p>
<Select.Root <Select.Root
selected={{ type="single"
label: items.pagination.itemsPerPage.toString(), value={items.pagination.itemsPerPage.toString()}
value: items.pagination.itemsPerPage onValueChange={(v) => onPageSizeChange(Number(v))}
}}
onSelectedChange={(v) => onPageSizeChange(v?.value as number)}
> >
<Select.Trigger class="h-9 w-[80px]"> <Select.Trigger class="h-9 w-[80px]">
<Select.Value>{items.pagination.itemsPerPage}</Select.Value> {items.pagination.itemsPerPage}
</Select.Trigger> </Select.Trigger>
<Select.Content> <Select.Content>
{#each availablePageSizes as size} {#each availablePageSizes as size}
<Select.Item value={size}>{size}</Select.Item> <Select.Item value={size.toString()}>{size}</Select.Item>
{/each} {/each}
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
@@ -191,25 +189,26 @@
perPage={items.pagination.itemsPerPage} perPage={items.pagination.itemsPerPage}
{onPageChange} {onPageChange}
page={items.pagination.currentPage} page={items.pagination.currentPage}
let:pages
> >
<Pagination.Content class="flex justify-end"> {#snippet children({ pages })}
<Pagination.Item> <Pagination.Content class="flex justify-end">
<Pagination.PrevButton /> <Pagination.Item>
</Pagination.Item> <Pagination.PrevButton />
{#each pages as page (page.key)} </Pagination.Item>
{#if page.type !== 'ellipsis' && page.value != 0} {#each pages as page (page.key)}
<Pagination.Item> {#if page.type !== 'ellipsis' && page.value != 0}
<Pagination.Link {page} isActive={items.pagination.currentPage === page.value}> <Pagination.Item>
{page.value} <Pagination.Link {page} isActive={items.pagination.currentPage === page.value}>
</Pagination.Link> {page.value}
</Pagination.Item> </Pagination.Link>
{/if} </Pagination.Item>
{/each} {/if}
<Pagination.Item> {/each}
<Pagination.NextButton /> <Pagination.Item>
</Pagination.Item> <Pagination.NextButton />
</Pagination.Content> </Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root> </Pagination.Root>
</div> </div>
{/if} {/if}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -75,15 +75,16 @@
onfocus={() => (isInputFocused = true)} onfocus={() => (isInputFocused = true)}
onblur={() => (isInputFocused = false)} onblur={() => (isInputFocused = false)}
/> />
<Popover.Root <Popover.Root open={isOpen}>
open={isOpen}
disableFocusTrap
openFocus={() => {}}
closeOnOutsideClick={false}
closeOnEscape={false}
>
<Popover.Trigger tabindex={-1} class="h-0 w-full" aria-hidden /> <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} {#each filteredSuggestions as suggestion, index}
<div <div
role="button" role="button"

View File

@@ -4,7 +4,7 @@
import { Input } from '$lib/components/ui/input'; import { Input } from '$lib/components/ui/input';
import CustomClaimService from '$lib/services/custom-claim-service'; import CustomClaimService from '$lib/services/custom-claim-service';
import type { CustomClaim } from '$lib/types/custom-claim.type'; 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 { onMount, type Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
import AutoCompleteInput from './auto-complete-input.svelte'; import AutoCompleteInput from './auto-complete-input.svelte';
@@ -51,25 +51,25 @@
variant="outline" variant="outline"
size="sm" size="sm"
aria-label={m.remove_custom_claim()} 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> </Button>
</div> </div>
{/each} {/each}
</div> </div>
</FormInput> </FormInput>
{#if error} {#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}
{#if customClaims.length < limit} {#if customClaims.length < limit}
<Button <Button
class="mt-2" class="mt-2"
variant="secondary" variant="secondary"
size="sm" 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()} {customClaims.length === 0 ? m.add_custom_claim() : m.add_another()}
</Button> </Button>
{/if} {/if}

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
import * as Command from '$lib/components/ui/command'; import * as Command from '$lib/components/ui/command';
import * as Popover from '$lib/components/ui/popover'; import * as Popover from '$lib/components/ui/popover';
import { cn } from '$lib/utils/style'; import { cn } from '$lib/utils/style';
import { LucideCheck, LucideChevronDown } from 'lucide-svelte'; import { LucideCheck, LucideChevronDown } from '@lucide/svelte';
import { tick } from 'svelte'; import { tick } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
@@ -52,21 +52,19 @@
}); });
</script> </script>
<Popover.Root bind:open let:ids> <Popover.Root bind:open {...restProps}>
<Popover.Trigger asChild let:builder> <Popover.Trigger class="w-full">
<Button <Button
{...restProps}
builders={[builder]}
variant="outline" variant="outline"
role="combobox" role="combobox"
aria-expanded={open} aria-expanded={open}
class={cn('justify-between', restProps.class)} class={cn('justify-between', restProps.class)}
> >
{items.find((item) => item.value === value)?.label || 'Select an option'} {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> </Button>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content class="p-0" sameWidth> <Popover.Content class="p-0">
<Command.Root shouldFilter={false}> <Command.Root shouldFilter={false}>
<Command.Input placeholder="Search..." oninput={(e: any) => filterItems(e.target.value)} /> <Command.Input placeholder="Search..." oninput={(e: any) => filterItems(e.target.value)} />
<Command.Empty>No results found.</Command.Empty> <Command.Empty>No results found.</Command.Empty>
@@ -77,10 +75,11 @@
onSelect={() => { onSelect={() => {
value = item.value; value = item.value;
onSelect?.(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} {item.label}
</Command.Item> </Command.Item>
{/each} {/each}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,27 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import * as AlertDialog from './index.js'; import AlertDialogOverlay from './alert-dialog-overlay.svelte';
import { cn, flyAndScale } from '$lib/utils/style.js'; import { cn, type WithoutChild, type WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.ContentProps; let {
ref = $bindable(null),
export let transition: $$Props['transition'] = flyAndScale; class: className,
export let transitionConfig: $$Props['transitionConfig'] = undefined; portalProps,
...restProps
let className: $$Props['class'] = undefined; }: WithoutChild<AlertDialogPrimitive.ContentProps> & {
export { className as class }; portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
} = $props();
</script> </script>
<AlertDialog.Portal> <AlertDialogPrimitive.Portal {...portalProps}>
<AlertDialog.Overlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
{transition} bind:ref
{transitionConfig} data-slot="alert-dialog-content"
class={cn( 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 className
)} )}
{...$$restProps} {...restProps}
> />
<slot /> </AlertDialogPrimitive.Portal>
</AlertDialogPrimitive.Content>
</AlertDialog.Portal>

View File

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

View File

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

View File

@@ -1,13 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from 'svelte/elements'; 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 {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div class={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...$$restProps}> <div
<slot /> 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> </div>

View File

@@ -1,21 +1,20 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { fade } from 'svelte/transition';
import { cn } from '$lib/utils/style.js'; import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.OverlayProps; let {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export let transition: $$Props['transition'] = fade; ...restProps
export let transitionConfig: $$Props['transitionConfig'] = { }: AlertDialogPrimitive.OverlayProps = $props();
duration: 150
};
export { className as class };
</script> </script>
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
{transition} bind:ref
{transitionConfig} data-slot="alert-dialog-overlay"
class={cn('bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ', className)} class={cn(
{...$$restProps} '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 { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js'; import { cn } from '$lib/utils/style.js';
type $$Props = AlertDialogPrimitive.TitleProps; let {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export let level: $$Props['level'] = 'h3'; ...restProps
export { className as class }; }: AlertDialogPrimitive.TitleProps = $props();
</script> </script>
<AlertDialogPrimitive.Title class={cn('text-lg font-semibold', className)} {level} {...$$restProps}> <AlertDialogPrimitive.Title
<slot /> bind:ref
</AlertDialogPrimitive.Title> 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 { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import Trigger from './alert-dialog-trigger.svelte';
import Title from './alert-dialog-title.svelte'; import Title from './alert-dialog-title.svelte';
import Action from './alert-dialog-action.svelte'; import Action from './alert-dialog-action.svelte';
import Cancel from './alert-dialog-cancel.svelte'; import Cancel from './alert-dialog-cancel.svelte';
import Portal from './alert-dialog-portal.svelte';
import Footer from './alert-dialog-footer.svelte'; import Footer from './alert-dialog-footer.svelte';
import Header from './alert-dialog-header.svelte'; import Header from './alert-dialog-header.svelte';
import Overlay from './alert-dialog-overlay.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'; import Description from './alert-dialog-description.svelte';
const Root = AlertDialogPrimitive.Root; const Root = AlertDialogPrimitive.Root;
const Trigger = AlertDialogPrimitive.Trigger;
export { export {
Root, Root,
Title, Title,
Action, Action,
Cancel, Cancel,
Portal,
Footer, Footer,
Header, Header,
Trigger, Trigger,
@@ -30,7 +27,6 @@ export {
Title as AlertDialogTitle, Title as AlertDialogTitle,
Action as AlertDialogAction, Action as AlertDialogAction,
Cancel as AlertDialogCancel, Cancel as AlertDialogCancel,
Portal as AlertDialogPortal,
Footer as AlertDialogFooter, Footer as AlertDialogFooter,
Header as AlertDialogHeader, Header as AlertDialogHeader,
Trigger as AlertDialogTrigger, Trigger as AlertDialogTrigger,

View File

@@ -1,13 +1,23 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from 'svelte/elements'; 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 {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div class={cn('text-sm [&_p]:leading-relaxed', className)} {...$$restProps}> <div
<slot /> 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> </div>

View File

@@ -1,21 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
import type { HeadingLevel } from './index.js'; import { cn, type WithElementRef } from '$lib/utils/style.js';
import { cn } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLHeadingElement> & { let {
level?: HeadingLevel; ref = $bindable(null),
}; class: className,
children,
let className: $$Props['class'] = undefined; ...restProps
export let level: $$Props['level'] = 'h5'; }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
export { className as class };
</script> </script>
<svelte:element <div
this={level} bind:this={ref}
class={cn('mb-1 leading-none font-medium tracking-tight', className)} data-slot="alert-title"
{...$$restProps} class={cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', className)}
{...restProps}
> >
<slot /> {@render children?.()}
</svelte:element> </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"> <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 { 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> & { let {
variant?: Variant; ref = $bindable(null),
class: className,
variant = 'default',
children,
dismissibleId = undefined,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
variant?: AlertVariant;
dismissibleId?: string; dismissibleId?: string;
}; } = $props();
let className: $$Props['class'] = undefined; let isVisible = $state(!dismissibleId);
export let variant: $$Props['variant'] = 'default';
export let dismissibleId: $$Props['dismissibleId'] = undefined;
export { className as class };
let isVisible = !dismissibleId;
onMount(() => { onMount(() => {
if (dismissibleId) { if (dismissibleId) {
@@ -34,11 +57,17 @@
</script> </script>
{#if isVisible} {#if isVisible}
<div class={cn(alertVariants({ variant }), className)} {...$$restProps} role="alert"> <div
<slot /> bind:this={ref}
data-slot="alert"
class={cn(alertVariants({ variant }), className)}
{...restProps}
role="alert"
>
{@render children?.()}
{#if dismissibleId} {#if dismissibleId}
<button on:click={dismiss} class="absolute top-0 right-0 m-3 text-black dark:text-white" <button onclick={dismiss} class="absolute top-0 right-0 m-3 text-black dark:text-white"
><LucideX class="w-4" /></button ><LucideX class="size-4" /></button
> >
{/if} {/if}
</div> </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 Description from './alert-description.svelte';
import Title from './alert-title.svelte'; import Title from './alert-title.svelte';
import Root from './alert.svelte'; export { alertVariants, type AlertVariant } 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 { export {
Root,
Description,
Title,
// //
Root as Alert, Root as Alert,
Description as AlertDescription, Description as AlertDescription,
Title as AlertTitle, Title as AlertTitle
Description,
Root,
Title
}; };

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,49 @@
<script lang="ts"> <script lang="ts" module>
import { type Variant, badgeVariants } from './index.js'; import { type VariantProps, tv } from 'tailwind-variants';
import { cn } from '$lib/utils/style.js';
let className: string | undefined | null = undefined; export const badgeVariants = tv({
export let href: string | undefined = undefined; 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',
export let variant: Variant = 'default'; variants: {
export { className as class }; 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> </script>
<svelte:element <svelte:element
this={href ? 'a' : 'span'} this={href ? 'a' : 'span'}
bind:this={ref}
data-slot="badge"
{href} {href}
class={cn(badgeVariants({ variant, className }))} class={cn(badgeVariants({ variant }), className)}
{...$$restProps} {...restProps}
> >
<slot /> {@render children?.()}
</svelte:element> </svelte:element>

View File

@@ -1,20 +1,2 @@
import { type VariantProps, tv } from 'tailwind-variants';
export { default as Badge } from './badge.svelte'; export { default as Badge } from './badge.svelte';
export { badgeVariants, type BadgeVariant } 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'];

View File

@@ -1,33 +1,90 @@
<script lang="ts"> <script lang="ts" module>
import { cn } from '$lib/utils/style.js'; import { cn, type WithElementRef } from '$lib/utils/style.js';
import { Button as ButtonPrimitive } from 'bits-ui'; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import LoaderCircle from 'lucide-svelte/icons/loader-circle'; import { type VariantProps, tv } from 'tailwind-variants';
import type { ClassNameValue } from 'tailwind-merge';
import { type Events, type Props, buttonVariants } from './index.js';
type $$Props = Props; export const buttonVariants = tv({
type $$Events = Events; 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 type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export let variant: $$Props['variant'] = 'default'; export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export let size: $$Props['size'] = 'default';
export let disabled: boolean | undefined | null = false; export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
export let isLoading: $$Props['isLoading'] = false; WithElementRef<HTMLAnchorAttributes> & {
export let builders: $$Props['builders'] = []; variant?: ButtonVariant;
export { className as class }; size?: ButtonSize;
isLoading?: boolean;
};
</script> </script>
<ButtonPrimitive.Root <script lang="ts">
{builders} import LoaderCircle from '@lucide/svelte/icons/loader-circle';
disabled={isLoading || disabled}
class={cn(buttonVariants({ variant, size, className: className as ClassNameValue }))} let {
type="button" class: className,
{...$$restProps} variant = 'default',
on:click size = 'default',
on:keydown ref = $bindable(null),
> href = undefined,
{#if isLoading} type = 'button',
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" /> disabled,
{/if} isLoading = false,
<slot /> children,
</ButtonPrimitive.Root> ...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 Root, {
import type { Button as ButtonPrimitive } from 'bits-ui'; type ButtonProps,
import Root from './button.svelte'; type ButtonSize,
type ButtonVariant,
const buttonVariants = tv({ buttonVariants
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', } from './button.svelte';
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;
export { export {
Root, Root,
type Props, type ButtonProps as Props,
type Events,
// //
Root as Button, Root as Button,
type Props as ButtonProps, buttonVariants,
type Events as ButtonEvents, type ButtonProps,
buttonVariants type ButtonSize,
type ButtonVariant
}; };

View File

@@ -2,20 +2,18 @@
import { Calendar as CalendarPrimitive } from 'bits-ui'; import { Calendar as CalendarPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js'; import { cn } from '$lib/utils/style.js';
type $$Props = CalendarPrimitive.CellProps; let {
ref = $bindable(null),
export let date: $$Props['date']; class: className,
let className: $$Props['class'] = undefined; ...restProps
export { className as class }; }: CalendarPrimitive.CellProps = $props();
</script> </script>
<CalendarPrimitive.Cell <CalendarPrimitive.Cell
{date} bind:ref
class={cn( 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 className
)} )}
{...$$restProps} {...restProps}
> />
<slot />
</CalendarPrimitive.Cell>

View File

@@ -1,42 +1,30 @@
<script lang="ts"> <script lang="ts">
import { Calendar as CalendarPrimitive } from 'bits-ui';
import { buttonVariants } from '$lib/components/ui/button/index.js'; import { buttonVariants } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils/style.js'; import { cn } from '$lib/utils/style.js';
import { Calendar as CalendarPrimitive } from 'bits-ui';
type $$Props = CalendarPrimitive.DayProps; let {
type $$Events = CalendarPrimitive.DayEvents; ref = $bindable(null),
class: className,
export let date: $$Props['date']; ...restProps
export let month: $$Props['month']; }: CalendarPrimitive.DayProps = $props();
let className: $$Props['class'] = undefined;
export { className as class };
</script> </script>
<CalendarPrimitive.Day <CalendarPrimitive.Day
on:click bind:ref
{date}
{month}
class={cn( class={cn(
buttonVariants({ variant: 'ghost' }), 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', '[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
// Selected // 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 // Disabled
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50', 'data-disabled:text-muted-foreground data-disabled:opacity-50',
// Unavailable // Unavailable
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through', 'data-unavailable:text-destructive-foreground data-unavailable:line-through',
// Outside months // 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', '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 className
)} )}
{...$$restProps} {...restProps}
let:selected />
let:disabled
let:unavailable
let:builder
>
<slot {selected} {disabled} {unavailable} {builder}>
{date.day}
</slot>
</CalendarPrimitive.Day>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,19 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from 'svelte/elements'; 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 {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div <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)} 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> </div>

View File

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

View File

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

View File

@@ -1,137 +1,61 @@
<script lang="ts"> <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 { 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; let {
type $$Events = CalendarPrimitive.Events; ref = $bindable(null),
value = $bindable(),
export let value: $$Props['value'] = undefined; placeholder = $bindable(),
export let placeholder: $$Props['placeholder'] = today(getLocalTimeZone()); class: className,
export let weekdayFormat: $$Props['weekdayFormat'] = 'short'; weekdayFormat = 'short',
...restProps
const monthOptions = [ }: WithoutChildrenOrChild<CalendarPrimitive.RootProps> = $props();
'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 };
</script> </script>
<!--
Discriminated Unions + Destructing (required for bindable) do not
get along, so we shut typescript up by casting `value` to `never`.
-->
<CalendarPrimitive.Root <CalendarPrimitive.Root
{weekdayFormat} bind:value={value as never}
class={cn('rounded-md border p-3', className)} bind:ref
{...$$restProps}
on:keydown
let:months
let:weekdays
bind:value
bind:placeholder bind:placeholder
{weekdayFormat}
class={cn('p-3', className)}
{...restProps}
> >
<Calendar.Header> {#snippet children({ months, weekdays })}
<Calendar.Heading class="flex w-full items-center justify-between gap-2"> <Calendar.Header>
<Select.Root <Calendar.PrevButton />
selected={defaultMonth} <Calendar.Heading />
items={monthOptions} <Calendar.NextButton />
onSelectedChange={(v) => { </Calendar.Header>
if (!v || !placeholder) return; <Calendar.Months>
if (v.value === placeholder?.month) return; {#each months as month (month)}
placeholder = placeholder.set({ month: v.value }); <Calendar.Grid>
}} <Calendar.GridHead>
> <Calendar.GridRow class="flex">
<Select.Trigger aria-label="Select month" class="w-[60%]"> {#each weekdays as weekday (weekday)}
<Select.Value placeholder="Select month" /> <Calendar.HeadCell>
</Select.Trigger> {weekday.slice(0, 2)}
<Select.Content class="max-h-[200px] overflow-y-auto"> </Calendar.HeadCell>
{#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>
{/each} {/each}
</Calendar.GridRow> </Calendar.GridRow>
{/each} </Calendar.GridHead>
</Calendar.GridBody> <Calendar.GridBody>
</Calendar.Grid> {#each month.weeks as weekDates (weekDates)}
{/each} <Calendar.GridRow class="mt-2 w-full">
</Calendar.Months> {#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> </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"> <script lang="ts">
import { cn } from '$lib/utils/style.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$lib/utils/style.js';
type $$Props = HTMLAttributes<HTMLDivElement>; let {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div class={cn('bg-muted/20 p-6 pt-5 peer-[.card-header]:border-t', className)} {...$$restProps}> <div bind:this={ref} data-slot="card-content" class={cn('px-6', className)} {...restProps}>
<slot /> {@render children?.()}
</div> </div>

View File

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

View File

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

View File

@@ -1,13 +1,23 @@
<script lang="ts"> <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'; import type { HTMLAttributes } from 'svelte/elements';
type $$Props = HTMLAttributes<HTMLDivElement>; let {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div class={cn('card-header peer flex flex-col space-y-1.5 p-6', className)} {...$$restProps}> <div
<slot /> 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> </div>

View File

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

View File

@@ -1,16 +1,23 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from 'svelte/elements'; 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 {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div <div
class={cn('bg-card text-card-foreground overflow-hidden rounded-lg border shadow-sm', className)} bind:this={ref}
{...$$restProps} 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> </div>

View File

@@ -4,6 +4,7 @@ import Description from './card-description.svelte';
import Footer from './card-footer.svelte'; import Footer from './card-footer.svelte';
import Header from './card-header.svelte'; import Header from './card-header.svelte';
import Title from './card-title.svelte'; import Title from './card-title.svelte';
import Action from './card-action.svelte';
export { export {
Root, Root,
@@ -12,13 +13,13 @@ export {
Footer, Footer,
Header, Header,
Title, Title,
Action,
// //
Root as Card, Root as Card,
Content as CardContent, Content as CardContent,
Description as CardDescription, Description as CardDescription,
Footer as CardFooter, Footer as CardFooter,
Header as CardHeader, 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"> <script lang="ts">
import { Checkbox as CheckboxPrimitive } from 'bits-ui'; import { Checkbox as CheckboxPrimitive } from 'bits-ui';
import Check from 'lucide-svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import Minus from 'lucide-svelte/icons/minus'; import MinusIcon from '@lucide/svelte/icons/minus';
import { cn } from '$lib/utils/style.js'; import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = CheckboxPrimitive.Props; let {
type $$Events = CheckboxPrimitive.Events; ref = $bindable(null),
checked = $bindable(false),
let className: $$Props['class'] = undefined; indeterminate = $bindable(false),
export let checked: $$Props['checked'] = false; class: className,
export { className as class }; ...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script> </script>
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
bind:ref
data-slot="checkbox"
class={cn( 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 className
)} )}
bind:checked bind:checked
{...$$restProps} bind:indeterminate
on:click {...restProps}
> >
<CheckboxPrimitive.Indicator {#snippet children({ checked, indeterminate })}
class={cn('flex h-4 w-4 items-center justify-center text-current')} <div
let:isChecked data-slot="checkbox-indicator"
let:isIndeterminate class="flex items-center justify-center text-current transition-none"
> >
{#if isChecked} {#if checked}
<Check class="h-3.5 w-3.5" /> <CheckIcon class="size-3.5" />
{:else if isIndeterminate} {:else if indeterminate}
<Minus class="h-3.5 w-3.5" /> <MinusIcon class="size-3.5" />
{/if} {/if}
</CheckboxPrimitive.Indicator> </div>
{/snippet}
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>

View File

@@ -1,23 +1,40 @@
<script lang="ts"> <script lang="ts">
import type { Dialog as DialogPrimitive } from 'bits-ui'; import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from 'bits-ui';
import type { Command as CommandPrimitive } from 'cmdk-sv'; import type { Snippet } from 'svelte';
import Command from './command.svelte'; import Command from './command.svelte';
import * as Dialog from '$lib/components/ui/dialog/index.js'; import * as Dialog from '$lib/components/ui/dialog/index.js';
import type { WithoutChildrenOrChild } from '$lib/utils/style.js';
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps; let {
open = $bindable(false),
export let open: $$Props['open'] = false; ref = $bindable(null),
export let value: $$Props['value'] = undefined; 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> </script>
<Dialog.Root bind:open {...$$restProps}> <Dialog.Root bind:open {...restProps}>
<Dialog.Content class="overflow-hidden p-0 shadow-lg"> <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 <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" 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} {...restProps}
bind:value bind:value
> bind:ref
<slot /> {children}
</Command> />
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>

View File

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

View File

@@ -1,19 +1,30 @@
<script lang="ts"> <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 { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.GroupProps;
let className: ClassValue | undefined | null = undefined; let {
export { className as class }; ref = $bindable(null),
class: className,
children,
heading,
value,
...restProps
}: CommandPrimitive.GroupProps & {
heading?: string;
} = $props();
</script> </script>
<CommandPrimitive.Group <CommandPrimitive.Group
class={cn( bind:ref
'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', data-slot="command-group"
className class={cn('text-foreground overflow-hidden p-1', className)}
)} value={value ?? heading ?? `----${useId()}`}
{...$$restProps} {...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> </CommandPrimitive.Group>

View File

@@ -1,24 +1,26 @@
<script lang="ts"> <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 { 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 {
ref = $bindable(null),
let className: ClassValue | undefined | null = undefined; class: className,
export { className as class }; value = $bindable(''),
export let value: string = ''; ...restProps
}: CommandPrimitive.InputProps = $props();
</script> </script>
<div class="flex items-center border-b px-2" data-cmdk-input-wrapper=""> <div class="flex h-9 items-center gap-2 border-b px-3" data-slot="command-input-wrapper">
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" /> <SearchIcon class="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input <CommandPrimitive.Input
data-slot="command-input"
class={cn( 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 className
)} )}
{...$$restProps} bind:ref
{...restProps}
bind:value bind:value
/> />
</div> </div>

View File

@@ -1,25 +1,20 @@
<script lang="ts"> <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 { cn } from '$lib/utils/style.js';
import type { ClassValue } from 'svelte/elements';
type $$Props = CommandPrimitive.ItemProps; let {
ref = $bindable(null),
export let asChild = false; class: className,
...restProps
let className: ClassValue | undefined | null = undefined; }: CommandPrimitive.ItemProps = $props();
export { className as class };
</script> </script>
<CommandPrimitive.Item <CommandPrimitive.Item
{asChild} bind:ref
data-slot="command-item"
class={cn( 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 className
)} )}
{...$$restProps} {...restProps}
let:action />
let:attrs
>
<slot {action} {attrs} />
</CommandPrimitive.Item>

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

View File

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

View File

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

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 Root from './command.svelte';
import Dialog from './command-dialog.svelte'; import Dialog from './command-dialog.svelte';
@@ -9,6 +9,7 @@ import Input from './command-input.svelte';
import List from './command-list.svelte'; import List from './command-list.svelte';
import Separator from './command-separator.svelte'; import Separator from './command-separator.svelte';
import Shortcut from './command-shortcut.svelte'; import Shortcut from './command-shortcut.svelte';
import LinkItem from './command-link-item.svelte';
const Loading = CommandPrimitive.Loading; const Loading = CommandPrimitive.Loading;
@@ -18,6 +19,7 @@ export {
Empty, Empty,
Group, Group,
Item, Item,
LinkItem,
Input, Input,
List, List,
Separator, Separator,
@@ -29,6 +31,7 @@ export {
Empty as CommandEmpty, Empty as CommandEmpty,
Group as CommandGroup, Group as CommandGroup,
Item as CommandItem, Item as CommandItem,
LinkItem as CommandLinkItem,
Input as CommandInput, Input as CommandInput,
List as CommandList, List as CommandList,
Separator as CommandSeparator, 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"> <script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui'; 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 * 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 & { let {
closeButton?: boolean; ref = $bindable(null),
}; class: className,
portalProps,
let className: $$Props['class'] = undefined; children,
export let transition: $$Props['transition'] = flyAndScale; ...restProps
export let closeButton: $$Props['closeButton'] = true; }: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
export let transitionConfig: $$Props['transitionConfig'] = { portalProps?: DialogPrimitive.PortalProps;
duration: 200 children: Snippet;
}; } = $props();
export { className as class };
</script> </script>
<Dialog.Portal> <Dialog.Portal {...portalProps}>
<Dialog.Overlay /> <Dialog.Overlay />
<DialogPrimitive.Content <DialogPrimitive.Content
{transition} bind:ref
{transitionConfig} data-slot="dialog-content"
class={cn( 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 className
)} )}
{...$$restProps} {...restProps}
> >
<slot /> {@render children?.()}
{#if closeButton} <DialogPrimitive.Close
<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"
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" >
> <XIcon />
<X class="h-4 w-4" /> <span class="sr-only">Close</span>
<span class="sr-only">Close</span> </DialogPrimitive.Close>
</DialogPrimitive.Close>
{/if}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</Dialog.Portal> </Dialog.Portal>

View File

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

View File

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

View File

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

View File

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

View File

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

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 { Dialog as DialogPrimitive } from 'bits-ui';
import Title from './dialog-title.svelte'; import Title from './dialog-title.svelte';
import Portal from './dialog-portal.svelte';
import Footer from './dialog-footer.svelte'; import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte'; import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte'; import Overlay from './dialog-overlay.svelte';
import Content from './dialog-content.svelte'; import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte'; import Description from './dialog-description.svelte';
import Trigger from './dialog-trigger.svelte';
import Close from './dialog-close.svelte';
const Root = DialogPrimitive.Root; const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger; const Portal = DialogPrimitive.Portal;
const Close = DialogPrimitive.Close;
export { export {
Root, Root,

View File

@@ -1,35 +1,41 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import Check from 'lucide-svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import { cn } from '$lib/utils/style.js'; 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; let {
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents; ref = $bindable(null),
checked = $bindable(false),
let className: $$Props['class'] = undefined; indeterminate = $bindable(false),
export let checked: $$Props['checked'] = undefined; class: className,
export { className as class }; children: childrenProp,
...restProps
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
children?: Snippet;
} = $props();
</script> </script>
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
bind:ref
bind:checked bind:checked
bind:indeterminate
data-slot="dropdown-menu-checkbox-item"
class={cn( 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 className
)} )}
{...$$restProps} {...restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> {#snippet children({ checked, indeterminate })}
<DropdownMenuPrimitive.CheckboxIndicator> <span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<Check class="h-4 w-4" /> {#if indeterminate}
</DropdownMenuPrimitive.CheckboxIndicator> <MinusIcon class="size-4" />
</span> {:else}
<slot /> <CheckIcon class={cn('size-4', !checked && 'text-transparent')} />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>

View File

@@ -1,27 +1,27 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$lib/utils/style.js';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn, flyAndScale } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.ContentProps; let {
type $$Events = DropdownMenuPrimitive.ContentEvents; ref = $bindable(null),
sideOffset = 4,
let className: $$Props['class'] = undefined; portalProps,
export let sideOffset: $$Props['sideOffset'] = 4; class: className,
export let transition: $$Props['transition'] = flyAndScale; ...restProps
export let transitionConfig: $$Props['transitionConfig'] = undefined; }: DropdownMenuPrimitive.ContentProps & {
export { className as class }; portalProps?: DropdownMenuPrimitive.PortalProps;
} = $props();
</script> </script>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Portal {...portalProps}>
{transition} <DropdownMenuPrimitive.Content
{transitionConfig} bind:ref
{sideOffset} data-slot="dropdown-menu-content"
class={cn( {sideOffset}
'bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-md focus:outline-none', class={cn(
className '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} )}
on:keydown {...restProps}
> />
<slot /> </DropdownMenuPrimitive.Portal>
</DropdownMenuPrimitive.Content>

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"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js'; 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; inset?: boolean;
}; variant?: 'default' | 'destructive';
type $$Events = DropdownMenuPrimitive.ItemEvents; } = $props();
let className: $$Props['class'] = undefined;
export let inset: $$Props['inset'] = undefined;
export { className as class };
</script> </script>
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
bind:ref
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
class={cn( 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', "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",
inset && 'pl-8',
className className
)} )}
{...$$restProps} {...restProps}
on:click />
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
>
<slot />
</DropdownMenuPrimitive.Item>

View File

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

View File

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

View File

@@ -1,35 +1,31 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import Circle from 'lucide-svelte/icons/circle'; import CircleIcon from '@lucide/svelte/icons/circle';
import { cn } from '$lib/utils/style.js'; import { cn, type WithoutChild } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.RadioItemProps; let {
type $$Events = DropdownMenuPrimitive.RadioItemEvents; ref = $bindable(null),
class: className,
let className: $$Props['class'] = undefined; children: childrenProp,
export let value: $$Props['value']; ...restProps
export { className as class }; }: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
</script> </script>
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
bind:ref
data-slot="dropdown-menu-radio-item"
class={cn( 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 className
)} )}
{value} {...restProps}
{...$$restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerdown
on:pointerleave
on:pointermove
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> {#snippet children({ checked })}
<DropdownMenuPrimitive.RadioIndicator> <span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<Circle class="h-2 w-2 fill-current" /> {#if checked}
</DropdownMenuPrimitive.RadioIndicator> <CircleIcon class="size-2 fill-current" />
</span> {/if}
<slot /> </span>
{@render childrenProp?.({ checked })}
{/snippet}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>

View File

@@ -2,13 +2,16 @@
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils/style.js'; import { cn } from '$lib/utils/style.js';
type $$Props = DropdownMenuPrimitive.SeparatorProps; let {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; ...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
</script> </script>
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
class={cn('bg-muted -mx-1 my-1 h-px', className)} bind:ref
{...$$restProps} 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"> <script lang="ts">
import type { HTMLAttributes } from 'svelte/elements'; 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 {
ref = $bindable(null),
let className: $$Props['class'] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script> </script>
<span class={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...$$restProps}> <span
<slot /> bind:this={ref}
data-slot="dropdown-menu-shortcut"
class={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...restProps}
>
{@render children?.()}
</span> </span>

View File

@@ -1,30 +1,20 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; 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; let {
type $$Events = DropdownMenuPrimitive.SubContentEvents; ref = $bindable(null),
class: className,
let className: $$Props['class'] = undefined; ...restProps
export let transition: $$Props['transition'] = flyAndScale; }: DropdownMenuPrimitive.SubContentProps = $props();
export let transitionConfig: $$Props['transitionConfig'] = {
x: -10,
y: 0
};
export { className as class };
</script> </script>
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
{transition} bind:ref
{transitionConfig} data-slot="dropdown-menu-sub-content"
class={cn( 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 className
)} )}
{...$$restProps} {...restProps}
on:keydown />
on:focusout
on:pointermove
>
<slot />
</DropdownMenuPrimitive.SubContent>

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