mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-07-15 21:48:13 +03:00
feat: upgrade shadcn components
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"css": "src/app.css",
|
||||
"baseColor": "neutral"
|
||||
@@ -13,5 +12,9 @@
|
||||
"lib": "$lib"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://shadcn-svelte.com/registry"
|
||||
"registry": "https://shadcn-svelte.com/registry",
|
||||
"style": "luma",
|
||||
"iconLibrary": "lucide",
|
||||
"menuColor": "default-translucent",
|
||||
"menuAccent": "subtle"
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"prettier-plugin-svelte": "^3.5.2",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"rollup": "^4.60.4",
|
||||
"shadcn-svelte": "^1.3.0",
|
||||
"svelte": "^5.55.8",
|
||||
"svelte-check": "^4.4.8",
|
||||
"svelte-sonner": "^1.1.1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
@import 'shadcn-svelte/tailwind.css';
|
||||
|
||||
@variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@@ -19,10 +20,19 @@
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentcolor);
|
||||
}
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 1rem;
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
@@ -135,6 +145,15 @@
|
||||
|
||||
/* Font */
|
||||
--font-gloock: 'gloock', serif;
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--radius-2xl: calc(var(--radius) * 1.8);
|
||||
--radius-3xl: calc(var(--radius) * 2.2);
|
||||
--radius-4xl: calc(var(--radius) * 2.6);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
case 'Enter':
|
||||
if (selectedIndex >= 0) {
|
||||
handleSuggestionClick(filteredSuggestions[selectedIndex]);
|
||||
e.preventDefault();
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
@@ -80,8 +81,8 @@
|
||||
<Popover.Root open={isOpen}>
|
||||
<Popover.Trigger tabindex={-1} class="h-0 w-full" aria-hidden />
|
||||
<Popover.Content
|
||||
class="gap-0! px-1 py-2"
|
||||
sameWidth
|
||||
class="p-0"
|
||||
sideOffset={5}
|
||||
trapFocus={false}
|
||||
interactOutsideBehavior="ignore"
|
||||
@@ -98,7 +99,7 @@
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter') handleSuggestionClick(suggestion);
|
||||
}}
|
||||
class="hover:bg-accent hover:text-accent-foreground relative flex w-full 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 {selectedIndex ===
|
||||
class="hover:bg-accent hover:text-accent-foreground relative flex w-full cursor-default items-center rounded-full py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 {selectedIndex ===
|
||||
index
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: ''}"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Calendar } from '$lib/components/ui/calendar';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
@@ -68,11 +67,10 @@
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger {id} class="w-full">
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
<button
|
||||
{...props}
|
||||
variant="outline"
|
||||
class={cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
'w-full flex justify-start items-center font-normal h-9 bg-input/50 rounded-full p-3',
|
||||
!value && 'text-muted-foreground'
|
||||
)}
|
||||
aria-label={m.select_a_date()}
|
||||
@@ -81,7 +79,7 @@
|
||||
{calendarDisplayDate
|
||||
? df.format(calendarDisplayDate.toDate(getLocalTimeZone()))
|
||||
: m.select_a_date()}
|
||||
</Button>
|
||||
</button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0" align="start">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
data-slot="alert-dialog-content"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-4xl p-5 sm:p-8 sm:pb-6 ring-1 duration-100 data-[size=default]:max-w-sm data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 gap-6 rounded-4xl p-6 shadow-xl ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="alert-dialog-footer"
|
||||
class={cn(
|
||||
'-mx-4 -mb-4 rounded-b-xl p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
|
||||
'cn-alert-dialog-footer flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="alert-dialog-header"
|
||||
class={cn(
|
||||
'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
|
||||
'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="alert-dialog-media"
|
||||
class={cn(
|
||||
"bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
||||
"bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-full sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="alert-dialog-overlay"
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm fixed inset-0 z-50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="alert-dialog-title"
|
||||
class={cn(
|
||||
'text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
|
||||
'text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
export type DropdownButtonProps = WithElementRef<HTMLAttributes<HTMLDivElement>>;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
class: className,
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: DropdownButtonProps = $props();
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} data-slot="dropdown-button" class={cn('flex', className)} {...restProps}>
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert-action"
|
||||
class={cn('absolute top-2.5 right-3', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -13,7 +13,10 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert-description"
|
||||
class={cn('col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed', className)}
|
||||
class={cn(
|
||||
'text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert-title"
|
||||
class={cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', className)}
|
||||
class={cn(
|
||||
'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script lang="ts" module>
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
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-3xl 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',
|
||||
base: "grid gap-0.5 rounded-2xl border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/alert relative w-full",
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-card text-card-foreground',
|
||||
success:
|
||||
'bg-green-100 text-green-900 dark:bg-green-900 dark:text-green-100 [&>svg]:text-green-900 dark:[&>svg]:text-green-100',
|
||||
info: 'bg-blue-100 text-blue-900 dark:bg-blue-900 dark:text-blue-100 [&>svg]:text-blue-900 dark:[&>svg]:text-blue-100',
|
||||
destructive:
|
||||
'bg-red-100 text-red-900 dark:bg-red-900 dark:text-red-100 [&>svg]:text-red-900 dark:[&>svg]:text-red-100',
|
||||
warning:
|
||||
'bg-warning text-warning-foreground border-warning/40 [&>svg]:text-warning-foreground'
|
||||
'text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',
|
||||
success:
|
||||
'bg-green-100 text-green-900 dark:bg-green-900 dark:text-green-100 *:[svg]:text-current',
|
||||
info: 'bg-blue-100 text-blue-900 dark:bg-blue-900 dark:text-blue-100 *:[svg]:text-current',
|
||||
warning: 'bg-warning text-warning-foreground border-warning/40 *:[svg]:text-current'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -24,10 +23,10 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import { LucideX } from '@lucide/svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -66,15 +65,15 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="alert"
|
||||
role="alert"
|
||||
class={cn(alertVariants({ variant }), className)}
|
||||
{...restProps}
|
||||
role="alert"
|
||||
>
|
||||
{@render children?.()}
|
||||
{#if dismissibleId || onDismiss}
|
||||
<button onclick={dismiss} class="absolute right-0 top-0 m-3 text-black dark:text-white"
|
||||
><LucideX class="size-4" /></button
|
||||
>
|
||||
<button onclick={dismiss} class="absolute top-2.5 right-3 text-current">
|
||||
<LucideX class="size-4" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import Root from './alert.svelte';
|
||||
import Description from './alert-description.svelte';
|
||||
import Title from './alert-title.svelte';
|
||||
import Action from './alert-action.svelte';
|
||||
export { alertVariants, type AlertVariant } from './alert.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Description,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Alert,
|
||||
Description as AlertDescription,
|
||||
Title as AlertTitle
|
||||
Title as AlertTitle,
|
||||
Action as AlertAction
|
||||
};
|
||||
|
||||
26
frontend/src/lib/components/ui/avatar/avatar-badge.svelte
Normal file
26
frontend/src/lib/components/ui/avatar/avatar-badge.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="avatar-badge"
|
||||
class={cn(
|
||||
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
|
||||
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',
|
||||
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
|
||||
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
@@ -12,6 +12,9 @@
|
||||
<AvatarPrimitive.Fallback
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn('bg-muted flex size-full items-center justify-center rounded-full', className)}
|
||||
class={cn(
|
||||
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="avatar-group-count"
|
||||
class={cn(
|
||||
'bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
frontend/src/lib/components/ui/avatar/avatar-group.svelte
Normal file
23
frontend/src/lib/components/ui/avatar/avatar-group.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="avatar-group"
|
||||
class={cn(
|
||||
'cn-avatar-group *:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -12,6 +12,6 @@
|
||||
<AvatarPrimitive.Image
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn('aspect-square size-full', className)}
|
||||
class={cn('rounded-full aspect-square size-full object-cover', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -4,14 +4,23 @@
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
loadingStatus = $bindable('loading'),
|
||||
size = 'default',
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.RootProps = $props();
|
||||
}: AvatarPrimitive.RootProps & {
|
||||
size?: 'default' | 'sm' | 'lg';
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
bind:ref
|
||||
bind:loadingStatus
|
||||
data-slot="avatar"
|
||||
class={cn('relative flex size-10 shrink-0 overflow-hidden rounded-full border', className)}
|
||||
data-size={size}
|
||||
class={cn(
|
||||
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import Root from './avatar.svelte';
|
||||
import Image from './avatar-image.svelte';
|
||||
import Fallback from './avatar-fallback.svelte';
|
||||
import Badge from './avatar-badge.svelte';
|
||||
import Group from './avatar-group.svelte';
|
||||
import GroupCount from './avatar-group-count.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
Badge,
|
||||
Group,
|
||||
GroupCount,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback
|
||||
Fallback as AvatarFallback,
|
||||
Badge as AvatarBadge,
|
||||
Group as AvatarGroup,
|
||||
GroupCount as AvatarGroupCount
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const badgeVariants = tv({
|
||||
base: 'h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none',
|
||||
base: 'h-5 gap-1 rounded-3xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none',
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
orientation = 'vertical',
|
||||
...restProps
|
||||
}: ComponentProps<typeof Separator> = $props();
|
||||
</script>
|
||||
|
||||
<Separator
|
||||
bind:ref
|
||||
data-slot="button-group-separator"
|
||||
{orientation}
|
||||
class={cn(
|
||||
'bg-input relative self-stretch data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
child,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
child?: Snippet<[{ props: Record<string, unknown> }]>;
|
||||
} = $props();
|
||||
|
||||
const mergedProps = $derived({
|
||||
...restProps,
|
||||
class: cn(
|
||||
"bg-muted gap-2 rounded-4xl border px-2.5 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",
|
||||
className
|
||||
),
|
||||
'data-slot': 'button-group-text'
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ props: mergedProps })}
|
||||
{:else}
|
||||
<div bind:this={ref} {...mergedProps}>
|
||||
{@render mergedProps.children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" module>
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const buttonGroupVariants = tv({
|
||||
base: "has-[>[data-variant=outline]]:[&>input]:border-border has-[>[data-variant=outline]]:[&>input:focus-visible]:border-ring has-[>[data-variant=outline]]:*:data-[slot=input-group]:border-border has-[>[data-variant=outline]]:[&>[data-slot=input-group]:has(:focus-visible)]:border-ring has-[>[data-variant=outline]]:*:data-[slot=select-trigger]:border-border has-[>[data-variant=outline]]:[&>[data-slot=select-trigger]:focus-visible]:border-ring has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl flex w-fit items-stretch [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal:
|
||||
'[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl! [&>[data-slot]]:rounded-r-none [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0',
|
||||
vertical:
|
||||
'[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl! flex-col [&>[data-slot]]:rounded-b-none [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: 'horizontal'
|
||||
}
|
||||
});
|
||||
|
||||
export type ButtonGroupOrientation = VariantProps<typeof buttonGroupVariants>['orientation'];
|
||||
</script>
|
||||
|
||||
<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,
|
||||
orientation = 'horizontal',
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
orientation?: ButtonGroupOrientation;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
role="group"
|
||||
data-slot="button-group"
|
||||
data-orientation={orientation}
|
||||
class={cn(buttonGroupVariants({ orientation }), className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
15
frontend/src/lib/components/ui/button-group/index.ts
Normal file
15
frontend/src/lib/components/ui/button-group/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import Root, { buttonGroupVariants, type ButtonGroupOrientation } from './button-group.svelte';
|
||||
import Text from './button-group-text.svelte';
|
||||
import Separator from './button-group-separator.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Text,
|
||||
Separator,
|
||||
buttonGroupVariants,
|
||||
type ButtonGroupOrientation,
|
||||
//
|
||||
Root as ButtonGroup,
|
||||
Text as ButtonGroupText,
|
||||
Separator as ButtonGroupSeparator
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-full 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 [&_svg]:shrink-0 transition active:scale-95",
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:not-aria-[haspopup]:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
outline:
|
||||
'border border-border bg-background hover:bg-muted hover:text-foreground dark:hover:bg-input/30 aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-transparent',
|
||||
'border-border bg-background hover:bg-muted hover:text-foreground dark:hover:bg-input/30 aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-transparent',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
||||
ghost:
|
||||
@@ -19,10 +19,15 @@
|
||||
link: 'text-primary underline-offset-4 hover:underline'
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 px-3 py-2',
|
||||
lg: 'h-11 px-8',
|
||||
icon: 'h-10 w-10'
|
||||
default:
|
||||
'h-9.5 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
|
||||
xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: 'h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
||||
lg: 'h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3',
|
||||
icon: 'size-9.5',
|
||||
'icon-xs': "size-6 [&_svg:not([class*='size-'])]:size-3",
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -110,11 +115,11 @@
|
||||
onclick={handleOnClick}
|
||||
{...restProps}
|
||||
>
|
||||
<span class="flex items-center w-full justify-center">
|
||||
<span class="flex w-full items-center justify-center">
|
||||
<Spinner
|
||||
class={cn(
|
||||
'grid overflow-hidden transition-[width,opacity,margin-right] duration-400 ease-[linear(0,0.897_14.4%,1.311_31.2%,1.338_46%,1.054_80.4%,1)]',
|
||||
isLoading ? 'w-4 opacity-100 mr-2' : 'w-0 opacity-0'
|
||||
isLoading ? 'mr-2 w-4 opacity-100' : 'w-0 opacity-0'
|
||||
)}
|
||||
aria-hidden={!isLoading}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import type Calendar from './calendar.svelte';
|
||||
import CalendarMonthSelect from './calendar-month-select.svelte';
|
||||
import CalendarYearSelect from './calendar-year-select.svelte';
|
||||
import { DateFormatter, getLocalTimeZone, type DateValue } from '@internationalized/date';
|
||||
|
||||
let {
|
||||
captionLayout,
|
||||
months,
|
||||
monthFormat,
|
||||
years,
|
||||
yearFormat,
|
||||
month,
|
||||
locale,
|
||||
placeholder = $bindable(),
|
||||
monthIndex = 0
|
||||
}: {
|
||||
captionLayout: ComponentProps<typeof Calendar>['captionLayout'];
|
||||
months: ComponentProps<typeof CalendarMonthSelect>['months'];
|
||||
monthFormat: ComponentProps<typeof CalendarMonthSelect>['monthFormat'];
|
||||
years: ComponentProps<typeof CalendarYearSelect>['years'];
|
||||
yearFormat: ComponentProps<typeof CalendarYearSelect>['yearFormat'];
|
||||
month: DateValue;
|
||||
placeholder: DateValue | undefined;
|
||||
locale: string;
|
||||
monthIndex: number;
|
||||
} = $props();
|
||||
|
||||
function formatYear(date: DateValue) {
|
||||
const dateObj = date.toDate(getLocalTimeZone());
|
||||
if (typeof yearFormat === 'function') return yearFormat(dateObj.getFullYear());
|
||||
return new DateFormatter(locale, { year: yearFormat }).format(dateObj);
|
||||
}
|
||||
|
||||
function formatMonth(date: DateValue) {
|
||||
const dateObj = date.toDate(getLocalTimeZone());
|
||||
if (typeof monthFormat === 'function') return monthFormat(dateObj.getMonth() + 1);
|
||||
return new DateFormatter(locale, { month: monthFormat }).format(dateObj);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet MonthSelect()}
|
||||
<CalendarMonthSelect
|
||||
{months}
|
||||
{monthFormat}
|
||||
value={month.month}
|
||||
onchange={(e) => {
|
||||
if (!placeholder) return;
|
||||
const v = Number.parseInt(e.currentTarget.value);
|
||||
const newPlaceholder = placeholder.set({ month: v });
|
||||
placeholder = newPlaceholder.subtract({ months: monthIndex });
|
||||
}}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
{#snippet YearSelect()}
|
||||
<CalendarYearSelect {years} {yearFormat} value={month.year} />
|
||||
{/snippet}
|
||||
|
||||
{#if captionLayout === 'dropdown'}
|
||||
{@render MonthSelect()}
|
||||
{@render YearSelect()}
|
||||
{:else if captionLayout === 'dropdown-months'}
|
||||
{@render MonthSelect()}
|
||||
{#if placeholder}
|
||||
{formatYear(placeholder)}
|
||||
{/if}
|
||||
{:else if captionLayout === 'dropdown-years'}
|
||||
{#if placeholder}
|
||||
{formatMonth(placeholder)}
|
||||
{/if}
|
||||
{@render YearSelect()}
|
||||
{:else}
|
||||
{formatMonth(month)} {formatYear(month)}
|
||||
{/if}
|
||||
@@ -12,7 +12,7 @@
|
||||
<CalendarPrimitive.Cell
|
||||
bind:ref
|
||||
class={cn(
|
||||
'[&: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',
|
||||
'relative size-(--cell-size) p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-s-(--cell-radius) [&:last-child[data-selected]_[data-bits-day]]:rounded-e-(--cell-radius)',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
import { Calendar as CalendarPrimitive } from 'bits-ui';
|
||||
|
||||
@@ -13,17 +12,21 @@
|
||||
<CalendarPrimitive.Day
|
||||
bind:ref
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'size-8 p-0 font-normal select-none',
|
||||
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
|
||||
// Selected
|
||||
'data-selected:bg-primary data-selected:text-primary-foreground data-selected:hover:bg-primary data-selected:hover:text-primary-foreground data-selected:focus:bg-primary data-selected:focus:text-primary-foreground dark:data-selected:hover:bg-primary dark:data-selected:focus:bg-primary data-selected:opacity-100',
|
||||
// Disabled
|
||||
'data-disabled:text-muted-foreground data-disabled:opacity-50',
|
||||
// Unavailable
|
||||
'data-unavailable:text-destructive-foreground data-unavailable:line-through',
|
||||
'flex size-(--cell-size) flex-col items-center justify-center gap-1 rounded-(--cell-radius) p-0 leading-none font-normal whitespace-nowrap select-none',
|
||||
'[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)',
|
||||
'not-data-selected:hover:bg-accent/50 not-data-selected:hover:text-accent-foreground',
|
||||
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground',
|
||||
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:text-foreground',
|
||||
// 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]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground',
|
||||
// Disabled
|
||||
'data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
// Unavailable
|
||||
'data-[unavailable]:text-muted-foreground data-[unavailable]:line-through',
|
||||
// focus
|
||||
'focus:border-ring focus:ring-ring/50 focus:relative',
|
||||
// inner spans
|
||||
'[&>span]:text-xs [&>span]:opacity-70',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
|
||||
<CalendarPrimitive.Grid
|
||||
bind:ref
|
||||
class={cn('w-full border-collapse space-y-1', className)}
|
||||
class={cn('flex w-full border-collapse flex-col', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
<CalendarPrimitive.HeadCell
|
||||
bind:ref
|
||||
class={cn('text-muted-foreground w-8 rounded-md text-[0.8rem] font-normal', className)}
|
||||
class={cn(
|
||||
'text-muted-foreground w-(--cell-size) rounded-md text-[0.8rem] font-normal',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
<CalendarPrimitive.Header
|
||||
bind:ref
|
||||
class={cn('relative flex w-full items-center justify-between pt-1', className)}
|
||||
class={cn(
|
||||
'flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -9,4 +9,8 @@
|
||||
}: CalendarPrimitive.HeadingProps = $props();
|
||||
</script>
|
||||
|
||||
<CalendarPrimitive.Heading bind:ref class={cn('text-sm font-medium', className)} {...restProps} />
|
||||
<CalendarPrimitive.Heading
|
||||
bind:ref
|
||||
class={cn('px-(--cell-size) text-sm font-medium', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { Calendar as CalendarPrimitive } from 'bits-ui';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value,
|
||||
onchange,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<CalendarPrimitive.MonthSelectProps> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={cn(
|
||||
'has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<CalendarPrimitive.MonthSelect
|
||||
bind:ref
|
||||
class="bg-background dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet child({ props, monthItems, selectedMonthItem })}
|
||||
<select {...props} {value} {onchange}>
|
||||
{#each monthItems as monthItem (monthItem.value)}
|
||||
<option
|
||||
value={monthItem.value}
|
||||
selected={value !== undefined
|
||||
? monthItem.value === value
|
||||
: monthItem.value === selectedMonthItem.value}
|
||||
>
|
||||
{monthItem.label}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span
|
||||
class="[&>svg]:text-muted-foreground flex h-(--cell-size) items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}
|
||||
<ChevronDownIcon class={cn('size-4', className)} />
|
||||
</span>
|
||||
{/snippet}
|
||||
</CalendarPrimitive.MonthSelect>
|
||||
</span>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { type WithElementRef, cn } from '$lib/utils/style.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div {...restProps} bind:this={ref} class={cn('flex w-full flex-col gap-4', className)}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<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('relative flex flex-col gap-4 md:flex-row', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
19
frontend/src/lib/components/ui/calendar/calendar-nav.svelte
Normal file
19
frontend/src/lib/components/ui/calendar/calendar-nav.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<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<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<nav
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
class={cn('absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1', className)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</nav>
|
||||
@@ -1,28 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { Calendar as CalendarPrimitive } from 'bits-ui';
|
||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import { buttonVariants, type ButtonVariant } from '$lib/components/ui/button/index.js';
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
variant = 'ghost',
|
||||
...restProps
|
||||
}: CalendarPrimitive.PrevButtonProps = $props();
|
||||
}: CalendarPrimitive.NextButtonProps & {
|
||||
variant?: ButtonVariant;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
{#snippet Fallback()}
|
||||
<ChevronRightIcon class="size-4" />
|
||||
<ChevronRightIcon class={cn('size-4', className)} />
|
||||
{/snippet}
|
||||
|
||||
<CalendarPrimitive.NextButton
|
||||
bind:ref
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
buttonVariants({ variant }),
|
||||
'size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180',
|
||||
className
|
||||
)}
|
||||
children={children || Fallback}
|
||||
{...restProps}
|
||||
/>
|
||||
>
|
||||
{#if children}
|
||||
{@render children?.()}
|
||||
{:else}
|
||||
{@render Fallback()}
|
||||
{/if}
|
||||
</CalendarPrimitive.NextButton>
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { Calendar as CalendarPrimitive } from 'bits-ui';
|
||||
import ChevronLeftIcon from '@lucide/svelte/icons/chevron-left';
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import { buttonVariants, type ButtonVariant } from '$lib/components/ui/button/index.js';
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
variant = 'ghost',
|
||||
...restProps
|
||||
}: CalendarPrimitive.PrevButtonProps = $props();
|
||||
}: CalendarPrimitive.PrevButtonProps & {
|
||||
variant?: ButtonVariant;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
{#snippet Fallback()}
|
||||
<ChevronLeftIcon class="size-4" />
|
||||
<ChevronLeftIcon class={cn('size-4', className)} />
|
||||
{/snippet}
|
||||
|
||||
<CalendarPrimitive.PrevButton
|
||||
bind:ref
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
buttonVariants({ variant }),
|
||||
'size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180',
|
||||
className
|
||||
)}
|
||||
children={children || Fallback}
|
||||
{...restProps}
|
||||
/>
|
||||
>
|
||||
{#if children}
|
||||
{@render children?.()}
|
||||
{:else}
|
||||
{@render Fallback()}
|
||||
{/if}
|
||||
</CalendarPrimitive.PrevButton>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { Calendar as CalendarPrimitive } from 'bits-ui';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<CalendarPrimitive.YearSelectProps> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={cn(
|
||||
'has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<CalendarPrimitive.YearSelect
|
||||
bind:ref
|
||||
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet child({ props, yearItems, selectedYearItem })}
|
||||
<select {...props} {value}>
|
||||
{#each yearItems as yearItem (yearItem.value)}
|
||||
<option
|
||||
value={yearItem.value}
|
||||
selected={value !== undefined
|
||||
? yearItem.value === value
|
||||
: yearItem.value === selectedYearItem.value}
|
||||
>
|
||||
{yearItem.label}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span
|
||||
class="[&>svg]:text-muted-foreground flex h-(--cell-size) items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}
|
||||
<ChevronDownIcon class={cn('size-4', className)} />
|
||||
</span>
|
||||
{/snippet}
|
||||
</CalendarPrimitive.YearSelect>
|
||||
</span>
|
||||
@@ -1,109 +1,89 @@
|
||||
<script lang="ts">
|
||||
import * as Calendar from '$lib/components/ui/calendar/index.js';
|
||||
import * as Select from '$lib/components/ui/select/index.js';
|
||||
import { Calendar as CalendarPrimitive } from 'bits-ui';
|
||||
import * as Calendar from './index.js';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import { getLocale } from '$lib/paraglide/runtime';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date';
|
||||
import { Calendar as CalendarPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
|
||||
import type { ButtonVariant } from '../button/button.svelte';
|
||||
import { isEqualMonth, type DateValue } from '@internationalized/date';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
placeholder = $bindable()
|
||||
placeholder = $bindable(),
|
||||
class: className,
|
||||
weekdayFormat = 'short',
|
||||
buttonVariant = 'ghost',
|
||||
captionLayout = 'dropdown',
|
||||
locale = getLocale(),
|
||||
months: monthsProp,
|
||||
years,
|
||||
monthFormat: monthFormatProp,
|
||||
yearFormat = 'numeric',
|
||||
day,
|
||||
disableDaysOutsideMonth = false,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<CalendarPrimitive.RootProps> & {
|
||||
value: CalendarDate | undefined;
|
||||
buttonVariant?: ButtonVariant;
|
||||
captionLayout?: 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'label';
|
||||
months?: CalendarPrimitive.MonthSelectProps['months'];
|
||||
years?: CalendarPrimitive.YearSelectProps['years'];
|
||||
monthFormat?: CalendarPrimitive.MonthSelectProps['monthFormat'];
|
||||
yearFormat?: CalendarPrimitive.YearSelectProps['yearFormat'];
|
||||
day?: Snippet<[{ day: DateValue; outsideMonth: boolean }]>;
|
||||
} = $props();
|
||||
|
||||
const locale = getLocale();
|
||||
const currentDate = today(getLocalTimeZone());
|
||||
|
||||
const monthFmt = new DateFormatter(locale, {
|
||||
month: 'long'
|
||||
const monthFormat = $derived.by(() => {
|
||||
if (monthFormatProp) return monthFormatProp;
|
||||
if (captionLayout.startsWith('dropdown')) return 'short';
|
||||
return 'long';
|
||||
});
|
||||
|
||||
const monthOptions = Array.from({ length: 12 }, (_, i) => {
|
||||
const month = currentDate.set({ month: i + 1 });
|
||||
return {
|
||||
value: month.month,
|
||||
label: monthFmt.format(month.toDate(getLocalTimeZone()))
|
||||
};
|
||||
});
|
||||
|
||||
const yearOptions = Array.from({ length: 100 }, (_, i) => ({
|
||||
label: String(new Date().getFullYear() + i),
|
||||
value: new Date().getFullYear() + i
|
||||
}));
|
||||
|
||||
const defaultYear = $derived(
|
||||
placeholder ? { value: placeholder.year, label: String(placeholder.year) } : undefined
|
||||
);
|
||||
|
||||
const defaultMonth = $derived(
|
||||
placeholder
|
||||
? {
|
||||
value: placeholder.month,
|
||||
label: monthFmt.format(placeholder.toDate(getLocalTimeZone()))
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
const monthLabel = $derived(
|
||||
monthOptions.find((m) => m.value === defaultMonth?.value)?.label ?? 'Select a month'
|
||||
);
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Discriminated Unions + Destructing (required for bindable) do not
|
||||
get along, so we shut typescript up by casting `value` to `never`.
|
||||
-->
|
||||
<CalendarPrimitive.Root
|
||||
type="single"
|
||||
weekdayFormat="short"
|
||||
class={cn('rounded-md border p-3')}
|
||||
{locale}
|
||||
bind:value
|
||||
bind:value={value as never}
|
||||
bind:ref
|
||||
bind:placeholder
|
||||
{weekdayFormat}
|
||||
{disableDaysOutsideMonth}
|
||||
class={cn(
|
||||
'p-3 [--cell-radius:var(--radius-4xl)] [--cell-size:--spacing(8)] bg-background group/calendar in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent',
|
||||
className
|
||||
)}
|
||||
{locale}
|
||||
{monthFormat}
|
||||
{yearFormat}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ months, weekdays })}
|
||||
<Calendar.Header class="flex w-full items-center justify-between gap-2">
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={`${defaultMonth?.value}`}
|
||||
onValueChange={(v) => {
|
||||
if (!placeholder) return;
|
||||
if (v === `${placeholder.month}`) return;
|
||||
placeholder = placeholder.set({ month: Number.parseInt(v) });
|
||||
}}
|
||||
>
|
||||
<Select.Trigger aria-label="Select month" class="w-[60%]">
|
||||
{monthLabel}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[200px] overflow-y-auto">
|
||||
{#each monthOptions as { value, label } (value)}
|
||||
<Select.Item value={`${value}`} {label} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={`${defaultYear?.value}`}
|
||||
onValueChange={(v) => {
|
||||
if (!v || !placeholder) return;
|
||||
if (v === `${placeholder?.year}`) return;
|
||||
placeholder = placeholder.set({ year: Number.parseInt(v) });
|
||||
}}
|
||||
>
|
||||
<Select.Trigger aria-label="Select year" class="w-[40%]">
|
||||
{defaultYear?.label ?? 'Select year'}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[200px] overflow-y-auto">
|
||||
{#each yearOptions as { value, label } (value)}
|
||||
<Select.Item value={`${value}`} {label} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</Calendar.Header>
|
||||
<Calendar.Months>
|
||||
{#each months as month (month)}
|
||||
<Calendar.Nav>
|
||||
<Calendar.PrevButton variant={buttonVariant} />
|
||||
<Calendar.NextButton variant={buttonVariant} />
|
||||
</Calendar.Nav>
|
||||
{#each months as month, monthIndex (month)}
|
||||
<Calendar.Month>
|
||||
<Calendar.Header>
|
||||
<Calendar.Caption
|
||||
{captionLayout}
|
||||
months={monthsProp}
|
||||
{monthFormat}
|
||||
{years}
|
||||
{yearFormat}
|
||||
month={month.value}
|
||||
bind:placeholder
|
||||
{locale}
|
||||
{monthIndex}
|
||||
/>
|
||||
</Calendar.Header>
|
||||
<Calendar.Grid>
|
||||
<Calendar.GridHead>
|
||||
<Calendar.GridRow class="flex">
|
||||
{#each weekdays as weekday (weekday)}
|
||||
<Calendar.GridRow class="select-none">
|
||||
{#each weekdays as weekday, i (i)}
|
||||
<Calendar.HeadCell>
|
||||
{weekday.slice(0, 2)}
|
||||
</Calendar.HeadCell>
|
||||
@@ -114,14 +94,22 @@
|
||||
{#each month.weeks as weekDates (weekDates)}
|
||||
<Calendar.GridRow class="mt-2 w-full">
|
||||
{#each weekDates as date (date)}
|
||||
<Calendar.Cell class="select-none" {date} month={month.value}>
|
||||
<Calendar.Cell {date} month={month.value}>
|
||||
{#if day}
|
||||
{@render day({
|
||||
day: date,
|
||||
outsideMonth: !isEqualMonth(date, month.value)
|
||||
})}
|
||||
{:else}
|
||||
<Calendar.Day />
|
||||
{/if}
|
||||
</Calendar.Cell>
|
||||
{/each}
|
||||
</Calendar.GridRow>
|
||||
{/each}
|
||||
</Calendar.GridBody>
|
||||
</Calendar.Grid>
|
||||
</Calendar.Month>
|
||||
{/each}
|
||||
</Calendar.Months>
|
||||
{/snippet}
|
||||
|
||||
@@ -11,6 +11,11 @@ import GridHead from './calendar-grid-head.svelte';
|
||||
import HeadCell from './calendar-head-cell.svelte';
|
||||
import NextButton from './calendar-next-button.svelte';
|
||||
import PrevButton from './calendar-prev-button.svelte';
|
||||
import MonthSelect from './calendar-month-select.svelte';
|
||||
import YearSelect from './calendar-year-select.svelte';
|
||||
import Month from './calendar-month.svelte';
|
||||
import Nav from './calendar-nav.svelte';
|
||||
import Caption from './calendar-caption.svelte';
|
||||
|
||||
export {
|
||||
Day,
|
||||
@@ -25,6 +30,11 @@ export {
|
||||
HeadCell,
|
||||
NextButton,
|
||||
PrevButton,
|
||||
Nav,
|
||||
Month,
|
||||
YearSelect,
|
||||
MonthSelect,
|
||||
Caption,
|
||||
//
|
||||
Root as Calendar
|
||||
};
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
<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)}
|
||||
class={cn(
|
||||
'cn-card-action col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} data-slot="card-content" class={cn('px-6', className)} {...restProps}>
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-content"
|
||||
class={cn('px-6 group-data-[size=sm]/card:px-4', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn('text-muted-foreground mt-1 text-sm', className)}
|
||||
class={cn('text-muted-foreground text-sm', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
||||
class={cn(
|
||||
'rounded-b-4xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
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',
|
||||
'gap-1.5 rounded-t-4xl px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
size = 'default',
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: 'default' | 'sm' } = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
'bg-card text-card-foreground flex flex-col gap-6 rounded-4xl border py-6 shadow-sm',
|
||||
'bg-card text-card-foreground border-foreground/5 dark:border-foreground/10 gap-6 overflow-hidden rounded-4xl py-6 text-sm shadow-sm border-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-4xl *:[img:last-child]:rounded-b-4xl group/card flex flex-col',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -17,7 +17,7 @@
|
||||
bind:ref
|
||||
data-slot="checkbox"
|
||||
class={cn(
|
||||
'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',
|
||||
'bg-input/90 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[5px] border border-transparent transition-shadow group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
@@ -27,12 +27,12 @@
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<div
|
||||
data-slot="checkbox-indicator"
|
||||
class="flex items-center justify-center text-current transition-none"
|
||||
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
||||
>
|
||||
{#if checked}
|
||||
<CheckIcon class="size-3.5" />
|
||||
<CheckIcon />
|
||||
{:else if indeterminate}
|
||||
<MinusIcon class="size-3.5" />
|
||||
<MinusIcon />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
import type { Snippet } from 'svelte';
|
||||
import Command from './command.svelte';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import type { WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(''),
|
||||
title = 'Command Palette',
|
||||
description = 'Search for a command to run',
|
||||
description = 'Search for a command to run...',
|
||||
showCloseButton = false,
|
||||
portalProps,
|
||||
children,
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.RootProps> &
|
||||
WithoutChildrenOrChild<CommandPrimitive.RootProps> & {
|
||||
@@ -20,6 +22,8 @@
|
||||
children: Snippet;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showCloseButton?: boolean;
|
||||
class?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
@@ -28,13 +32,11 @@
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Content class="overflow-hidden p-0" {portalProps}>
|
||||
<Command
|
||||
class="**:data-[slot=command-input-wrapper]:h-12 [&_[data-command-group]]:px-2 [&_[data-command-group]:not([hidden])_~[data-command-group]]:pt-0 [&_[data-command-input-wrapper]_svg]:h-5 [&_[data-command-input-wrapper]_svg]:w-5 [&_[data-command-input]]:h-12 [&_[data-command-item]]:px-2 [&_[data-command-item]]:py-3 [&_[data-command-item]_svg]:h-5 [&_[data-command-item]_svg]:w-5"
|
||||
{...restProps}
|
||||
bind:value
|
||||
bind:ref
|
||||
{children}
|
||||
/>
|
||||
<Dialog.Content
|
||||
class={cn('rounded-4xl! p-0 top-1/3 translate-y-0 overflow-hidden p-0', className)}
|
||||
{showCloseButton}
|
||||
{portalProps}
|
||||
>
|
||||
<Command {...restProps} bind:value bind:ref {children} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
<CommandPrimitive.Group
|
||||
bind:ref
|
||||
data-slot="command-group"
|
||||
class={cn('text-foreground overflow-hidden p-1', className)}
|
||||
class={cn(
|
||||
'text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden py-2 px-1 **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:py-2 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium',
|
||||
className
|
||||
)}
|
||||
value={value ?? heading ?? `----${useId()}`}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<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 * as InputGroup from '$lib/components/ui/input-group/index.js';
|
||||
import SearchIcon from '@lucide/svelte/icons/search';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,16 +12,23 @@
|
||||
}: CommandPrimitive.InputProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex h-9 items-center gap-2 border-b px-3" data-slot="command-input-wrapper">
|
||||
<SearchIcon class="size-4 shrink-0 opacity-50" />
|
||||
<div data-slot="command-input-wrapper" class="p-1 pb-0">
|
||||
<InputGroup.Root class="bg-input/50 h-9">
|
||||
<CommandPrimitive.Input
|
||||
{value}
|
||||
data-slot="command-input"
|
||||
class={cn(
|
||||
'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',
|
||||
'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
bind:ref
|
||||
{...restProps}
|
||||
bind:value
|
||||
/>
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<InputGroup.Input {...props} bind:value bind:ref />
|
||||
{/snippet}
|
||||
</CommandPrimitive.Input>
|
||||
<InputGroup.Addon>
|
||||
<SearchIcon class="size-4 shrink-0 opacity-50" />
|
||||
</InputGroup.Addon>
|
||||
</InputGroup.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CommandPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
@@ -13,8 +15,13 @@
|
||||
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",
|
||||
"group/command-item data-selected:bg-muted data-selected:text-foreground data-selected:*:[svg]:text-foreground relative flex cursor-default items-center gap-2 rounded-full px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! 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}
|
||||
/>
|
||||
>
|
||||
{@render children?.()}
|
||||
<CheckIcon
|
||||
class="cn-command-item-indicator ml-auto opacity-0 group-has-[[data-slot=command-shortcut]]/command-item:hidden group-data-[checked=true]/command-item:opacity-100"
|
||||
/>
|
||||
</CommandPrimitive.Item>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<CommandPrimitive.List
|
||||
bind:ref
|
||||
data-slot="command-list"
|
||||
class={cn('max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto', className)}
|
||||
class={cn(
|
||||
'no-scrollbar max-h-72 scroll-py-1 outline-none overflow-x-hidden overflow-y-auto',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: CommandPrimitive.LoadingProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Loading bind:ref {...restProps} />
|
||||
@@ -12,6 +12,6 @@
|
||||
<CommandPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="command-separator"
|
||||
class={cn('bg-border -mx-1 h-px', className)}
|
||||
class={cn('bg-border/50 my-1.5 h-px', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
<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 group-data-selected/command-item:text-foreground ml-auto text-xs tracking-widest',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
|
||||
export type CommandRootApi = CommandPrimitive.Root;
|
||||
|
||||
let {
|
||||
api = $bindable(null),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(''),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.RootProps = $props();
|
||||
}: CommandPrimitive.RootProps & {
|
||||
api?: CommandRootApi | null;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Root
|
||||
bind:this={api}
|
||||
bind:value
|
||||
bind:ref
|
||||
data-slot="command"
|
||||
class={cn(
|
||||
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
|
||||
'bg-popover text-popover-foreground rounded-4xl p-1 flex size-full flex-col overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Command as CommandPrimitive } from 'bits-ui';
|
||||
|
||||
import Root from './command.svelte';
|
||||
import Loading from './command-loading.svelte';
|
||||
import Dialog from './command-dialog.svelte';
|
||||
import Empty from './command-empty.svelte';
|
||||
import Group from './command-group.svelte';
|
||||
@@ -11,8 +10,6 @@ import Separator from './command-separator.svelte';
|
||||
import Shortcut from './command-shortcut.svelte';
|
||||
import LinkItem from './command-link-item.svelte';
|
||||
|
||||
const Loading = CommandPrimitive.Loading;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Dialog,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
type = 'button',
|
||||
...restProps
|
||||
}: DialogPrimitive.CloseProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {type} {...restProps} />
|
||||
|
||||
@@ -1,39 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
import DialogPortal from './dialog-portal.svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import * as Dialog from './index.js';
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DialogPortal>>;
|
||||
children: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal {...portalProps}>
|
||||
<DialogPortal {...portalProps}>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dialog-content"
|
||||
class={cn(
|
||||
'bg-popover 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-4xl border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/5 dark:ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-6 rounded-4xl p-6 text-sm shadow-xl ring-1 duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<DialogPrimitive.Close
|
||||
class="ring-offset-popover 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"
|
||||
{#if showCloseButton}
|
||||
<DialogPrimitive.Close data-slot="dialog-close">
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="bg-secondary absolute top-4 right-4"
|
||||
size="icon-sm"
|
||||
{...props}
|
||||
>
|
||||
<XIcon />
|
||||
<span class="sr-only">Close</span>
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
</DialogPortal>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<DialogPrimitive.Description
|
||||
bind:ref
|
||||
data-slot="dialog-description"
|
||||
class={cn('text-muted-foreground text-sm', className)}
|
||||
class={cn(
|
||||
'text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
showCloseButton = false,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
showCloseButton?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-footer"
|
||||
class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
||||
class={cn('gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{#if showCloseButton}
|
||||
<DialogPrimitive.Close>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" {...props}>Close</Button>
|
||||
{/snippet}
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-header"
|
||||
class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
||||
class={cn('gap-1.5 flex flex-col', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="dialog-overlay"
|
||||
class={cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm fixed inset-0 isolate z-50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
let { children, ...rest }: Props = $props();
|
||||
type $$Props = DialogPrimitive.PortalProps;
|
||||
let { ...restProps }: DialogPrimitive.PortalProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Portal {...rest}>
|
||||
{@render children?.()}
|
||||
</DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Portal {...restProps} />
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
<DialogPrimitive.Title
|
||||
bind:ref
|
||||
data-slot="dialog-title"
|
||||
class={cn('text-lg leading-none font-semibold', className)}
|
||||
class={cn('text-base leading-none font-medium', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
type = 'button',
|
||||
...restProps
|
||||
}: DialogPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {type} {...restProps} />
|
||||
|
||||
7
frontend/src/lib/components/ui/dialog/dialog.svelte
Normal file
7
frontend/src/lib/components/ui/dialog/dialog.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
|
||||
let { open = $bindable(false), ...restProps }: DialogPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Root bind:open {...restProps} />
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
|
||||
import Root from './dialog.svelte';
|
||||
import Portal from './dialog-portal.svelte';
|
||||
import Title from './dialog-title.svelte';
|
||||
import Footer from './dialog-footer.svelte';
|
||||
import Header from './dialog-header.svelte';
|
||||
@@ -9,9 +9,6 @@ import Description from './dialog-description.svelte';
|
||||
import Trigger from './dialog-trigger.svelte';
|
||||
import Close from './dialog-close.svelte';
|
||||
|
||||
const Root = DialogPrimitive.Root;
|
||||
const Portal = DialogPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
|
||||
export type DropdownButtonContentProps = DropdownMenuPrimitive.ContentProps;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
portalProps?: DropdownMenuPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md outline-none',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<DropdownMenuPrimitive.Arrow />
|
||||
{@render children?.()}
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
bind:ref
|
||||
class={cn(
|
||||
'data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,38 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
import {
|
||||
buttonVariants,
|
||||
type ButtonVariant,
|
||||
type ButtonSize
|
||||
} from '$lib/components/ui/button/button.svelte';
|
||||
|
||||
export type DropdownButtonMainProps = WithElementRef<HTMLButtonAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
class: className,
|
||||
variant = 'default',
|
||||
size = 'default',
|
||||
ref = $bindable(null),
|
||||
type = 'button',
|
||||
disabled,
|
||||
children,
|
||||
...restProps
|
||||
}: DropdownButtonMainProps = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-button-main"
|
||||
class={cn(buttonVariants({ variant, size }), 'rounded-r-none border-r-0', className)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
@@ -1,21 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { cn } from '$lib/utils/style.js';
|
||||
|
||||
export type DropdownButtonSeparatorProps = DropdownMenuPrimitive.SeparatorProps;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
class={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,51 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
import {
|
||||
buttonVariants,
|
||||
type ButtonVariant,
|
||||
type ButtonSize
|
||||
} from '$lib/components/ui/button/button.svelte';
|
||||
|
||||
export type DropdownButtonTriggerProps = WithElementRef<HTMLButtonAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
builders?: any[];
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
||||
|
||||
let {
|
||||
class: className,
|
||||
variant = 'default',
|
||||
size = 'default',
|
||||
ref = $bindable(null),
|
||||
type = 'button',
|
||||
disabled,
|
||||
builders = [],
|
||||
children,
|
||||
...restProps
|
||||
}: DropdownButtonTriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={ref}
|
||||
use:builders[0]
|
||||
data-slot="dropdown-button-trigger"
|
||||
class={cn(
|
||||
buttonVariants({ variant, size }),
|
||||
'border-l-background/20 rounded-l-none border-l px-2',
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<ChevronDown class="size-4" />
|
||||
{/if}
|
||||
</button>
|
||||
@@ -1,30 +0,0 @@
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import Root from './dropdown-button.svelte';
|
||||
import Main from './dropdown-button-main.svelte';
|
||||
import Trigger from './dropdown-button-trigger.svelte';
|
||||
import Content from './dropdown-button-content.svelte';
|
||||
import Item from './dropdown-button-item.svelte';
|
||||
import Separator from './dropdown-button-separator.svelte';
|
||||
|
||||
const DropdownRoot = DropdownMenuPrimitive.Root;
|
||||
const DropdownTrigger = DropdownMenuPrimitive.Trigger;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Main,
|
||||
Trigger,
|
||||
Content,
|
||||
Item,
|
||||
Separator,
|
||||
DropdownRoot,
|
||||
DropdownTrigger,
|
||||
//
|
||||
Root as DropdownButton,
|
||||
Main as DropdownButtonMain,
|
||||
Trigger as DropdownButtonTrigger,
|
||||
Content as DropdownButtonContent,
|
||||
Item as DropdownButtonItem,
|
||||
Separator as DropdownButtonSeparator,
|
||||
DropdownRoot as DropdownButtonRoot,
|
||||
DropdownTrigger as DropdownButtonPrimitiveTrigger
|
||||
};
|
||||
@@ -23,7 +23,7 @@
|
||||
bind:indeterminate
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1.5 pr-9 pl-2 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithoutChildrenOrChild } from '$lib/utils/style.js';
|
||||
import DropdownMenuPortal from './dropdown-menu-portal.svelte';
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import DropdownMenuPortal from './dropdown-menu-portal.svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -23,7 +23,7 @@
|
||||
{sideOffset}
|
||||
{align}
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/5 dark:ring-foreground/10 text-popover-foreground min-w-48 rounded-3xl p-1.5 shadow-lg ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-2 py-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2.5 rounded-2xl px-3 py-1.5 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
class={cn(
|
||||
'text-muted-foreground px-1.5 py-1 text-xs font-medium data-inset:pl-7 data-[inset]:pl-8',
|
||||
'text-muted-foreground px-3 py-2.5 text-xs data-inset:pl-9.5 data-[inset]:pl-8',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<span
|
||||
class="absolute right-2 flex items-center justify-center"
|
||||
class="absolute right-2 flex items-center justify-center pointer-events-none"
|
||||
data-slot="dropdown-menu-radio-item-indicator"
|
||||
>
|
||||
{#if checked}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn('bg-border -mx-1 my-1 h-px', className)}
|
||||
class={cn('bg-border/50 -mx-1.5 my-1.5 h-px', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-lg p-1 shadow-lg ring-1 duration-100 w-auto',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/5 dark:ring-foreground/10 text-popover-foreground min-w-36 rounded-3xl p-1.5 shadow-lg ring-1 duration-100 w-auto animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-2xl px-3 py-1.5 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="field-content"
|
||||
class={cn('group/field-content flex flex-1 flex-col gap-1.5 leading-snug', className)}
|
||||
class={cn('gap-1 group/field-content flex flex-1 flex-col leading-snug', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-description"
|
||||
class={cn(
|
||||
'text-muted-foreground -mt-1 mb-0 text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
||||
'text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
||||
'last:mt-0 nth-last-2:-mt-1',
|
||||
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
if (children) return true;
|
||||
|
||||
// no errors
|
||||
if (!errors) return false;
|
||||
if (!errors || errors.length === 0) return false;
|
||||
|
||||
// has an error but no message
|
||||
if (errors.length === 1 && !errors[0]?.message) {
|
||||
@@ -46,7 +46,7 @@
|
||||
{:else if singleErrorMessage}
|
||||
{singleErrorMessage}
|
||||
{:else if isMultipleErrors}
|
||||
<ul class="ms-4 flex list-disc flex-col gap-1">
|
||||
<ul class="ml-4 flex list-disc flex-col gap-1">
|
||||
{#each errors ?? [] as error, index (index)}
|
||||
{#if error?.message}
|
||||
<li>{error.message}</li>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-group"
|
||||
class={cn(
|
||||
'group/field-group @container/field-group flex w-full flex-col gap-4 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
|
||||
'gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
<Label
|
||||
bind:ref
|
||||
data-slot="field-label"
|
||||
{required}
|
||||
class={cn(
|
||||
'group/field-label peer/field-label mb-0 flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
|
||||
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4',
|
||||
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
|
||||
'has-data-checked:bg-input/30 gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-2xl has-[>[data-slot=field]]:border *:data-[slot=field]:p-4 group/field-label peer/field-label flex w-fit leading-snug',
|
||||
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',
|
||||
required && "after:text-red-500 after:content-['*']",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
data-slot="field-legend"
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
'mb-3 font-medium',
|
||||
'data-[variant=legend]:text-base',
|
||||
'data-[variant=label]:text-sm',
|
||||
'mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-separator"
|
||||
data-content={hasContent}
|
||||
class={cn('relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2', className)}
|
||||
class={cn('-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative', className)}
|
||||
{...restProps}
|
||||
>
|
||||
<Separator class="absolute inset-0 top-1/2" />
|
||||
{#if children}
|
||||
<span
|
||||
class="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
|
||||
class="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
|
||||
data-slot="field-separator-content"
|
||||
>
|
||||
{@render children()}
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-set"
|
||||
class={cn(
|
||||
'flex flex-col gap-4',
|
||||
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
||||
'gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="field-title"
|
||||
data-slot="field-label"
|
||||
class={cn(
|
||||
'flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50',
|
||||
'gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -2,20 +2,14 @@
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const fieldVariants = tv({
|
||||
base: 'group/field data-[invalid=true]:text-destructive flex w-full gap-1.5',
|
||||
base: 'data-[invalid=true]:text-destructive gap-3 group/field flex w-full',
|
||||
variants: {
|
||||
orientation: {
|
||||
vertical: 'flex-col [&>*]:w-full [&>.sr-only]:w-auto',
|
||||
horizontal: [
|
||||
'flex-row items-center',
|
||||
'[&>[data-slot=field-label]]:flex-auto',
|
||||
'has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start'
|
||||
],
|
||||
responsive: [
|
||||
'@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto',
|
||||
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
|
||||
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
|
||||
]
|
||||
vertical: 'cn-field-orientation-vertical flex-col [&>*]:w-full [&>.sr-only]:w-auto',
|
||||
horizontal:
|
||||
'cn-field-orientation-horizontal flex-row items-center has-[>[data-slot=field-content]]:items-start [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
||||
responsive:
|
||||
'cn-field-orientation-responsive flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:has-[>[data-slot=field-content]]:items-start [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
import { Button, type ButtonProps } from '$lib/components/ui/button/index.js';
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: Button.Props = $props();
|
||||
let { ref = $bindable(null), ...restProps }: ButtonProps = $props();
|
||||
</script>
|
||||
|
||||
<Button.Root bind:ref type="submit" {...restProps} />
|
||||
<Button bind:ref type="submit" {...restProps} />
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<script lang="ts" module>
|
||||
import type { FormPathLeaves as _FormPathLeaves } from 'sveltekit-superforms';
|
||||
type T = Record<string, unknown>;
|
||||
type U = _FormPathLeaves<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends _FormPathLeaves<T>">
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { FormPathLeaves } from 'sveltekit-superforms';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn, type WithElementRef, type WithoutChildren } from '$lib/utils/style.js';
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<script lang="ts" module>
|
||||
import type { FormPath as _FormPath } from 'sveltekit-superforms';
|
||||
type T = Record<string, unknown>;
|
||||
type U = _FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends _FormPath<T>">
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { FormPath } from 'sveltekit-superforms';
|
||||
import { cn, type WithElementRef, type WithoutChildren } from '$lib/utils/style.js';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user