@nucel/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +235 -0
  2. package/package.json +88 -0
  3. package/src/lib/components/ui/Backdrop.svelte +19 -0
  4. package/src/lib/components/ui/CountBadge.svelte +40 -0
  5. package/src/lib/components/ui/EmptyState.svelte +68 -0
  6. package/src/lib/components/ui/KbdShortcut.svelte +20 -0
  7. package/src/lib/components/ui/MarkdownRenderer.svelte +85 -0
  8. package/src/lib/components/ui/ProgressRing.svelte +61 -0
  9. package/src/lib/components/ui/ProviderIcon.svelte +57 -0
  10. package/src/lib/components/ui/ReviewBadge.svelte +54 -0
  11. package/src/lib/components/ui/Sparkline.svelte +61 -0
  12. package/src/lib/components/ui/StatusBadge.svelte +32 -0
  13. package/src/lib/components/ui/StatusDot.svelte +65 -0
  14. package/src/lib/components/ui/TabBar.svelte +127 -0
  15. package/src/lib/components/ui/VerticalSeparator.svelte +9 -0
  16. package/src/lib/components/ui/accordion/accordion-content.svelte +22 -0
  17. package/src/lib/components/ui/accordion/accordion-item.svelte +17 -0
  18. package/src/lib/components/ui/accordion/accordion-trigger.svelte +32 -0
  19. package/src/lib/components/ui/accordion/accordion.svelte +16 -0
  20. package/src/lib/components/ui/accordion/index.ts +16 -0
  21. package/src/lib/components/ui/avatar/avatar-fallback.svelte +17 -0
  22. package/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
  23. package/src/lib/components/ui/avatar/avatar.svelte +19 -0
  24. package/src/lib/components/ui/avatar/index.ts +13 -0
  25. package/src/lib/components/ui/badge/badge.svelte +49 -0
  26. package/src/lib/components/ui/badge/index.ts +2 -0
  27. package/src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  28. package/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte +20 -0
  29. package/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte +31 -0
  30. package/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte +23 -0
  31. package/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte +23 -0
  32. package/src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte +27 -0
  33. package/src/lib/components/ui/breadcrumb/breadcrumb.svelte +21 -0
  34. package/src/lib/components/ui/breadcrumb/index.ts +25 -0
  35. package/src/lib/components/ui/button/button.svelte +82 -0
  36. package/src/lib/components/ui/button/index.ts +17 -0
  37. package/src/lib/components/ui/card/card-action.svelte +20 -0
  38. package/src/lib/components/ui/card/card-content.svelte +15 -0
  39. package/src/lib/components/ui/card/card-description.svelte +20 -0
  40. package/src/lib/components/ui/card/card-footer.svelte +20 -0
  41. package/src/lib/components/ui/card/card-header.svelte +23 -0
  42. package/src/lib/components/ui/card/card-title.svelte +20 -0
  43. package/src/lib/components/ui/card/card.svelte +23 -0
  44. package/src/lib/components/ui/card/index.ts +25 -0
  45. package/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
  46. package/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
  47. package/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
  48. package/src/lib/components/ui/collapsible/index.ts +13 -0
  49. package/src/lib/components/ui/dialog/dialog-close.svelte +7 -0
  50. package/src/lib/components/ui/dialog/dialog-content.svelte +45 -0
  51. package/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  52. package/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  53. package/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  54. package/src/lib/components/ui/dialog/dialog-overlay.svelte +20 -0
  55. package/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  56. package/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  57. package/src/lib/components/ui/dialog/dialog-trigger.svelte +7 -0
  58. package/src/lib/components/ui/dialog/dialog.svelte +7 -0
  59. package/src/lib/components/ui/dialog/index.ts +34 -0
  60. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  61. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +41 -0
  62. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +29 -0
  63. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  64. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  65. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  66. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  67. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  68. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  69. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +31 -0
  70. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  71. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  72. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
  73. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  74. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  75. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  76. package/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  77. package/src/lib/components/ui/dropdown-menu/index.ts +54 -0
  78. package/src/lib/components/ui/input/index.ts +7 -0
  79. package/src/lib/components/ui/input/input.svelte +52 -0
  80. package/src/lib/components/ui/label/index.ts +7 -0
  81. package/src/lib/components/ui/label/label.svelte +20 -0
  82. package/src/lib/components/ui/navigation-menu/index.ts +28 -0
  83. package/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte +21 -0
  84. package/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte +22 -0
  85. package/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte +17 -0
  86. package/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte +20 -0
  87. package/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte +17 -0
  88. package/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte +34 -0
  89. package/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte +22 -0
  90. package/src/lib/components/ui/navigation-menu/navigation-menu.svelte +32 -0
  91. package/src/lib/components/ui/progress/index.ts +1 -0
  92. package/src/lib/components/ui/progress/progress.svelte +33 -0
  93. package/src/lib/components/ui/scroll-area/index.ts +10 -0
  94. package/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte +31 -0
  95. package/src/lib/components/ui/scroll-area/scroll-area.svelte +43 -0
  96. package/src/lib/components/ui/select/index.ts +37 -0
  97. package/src/lib/components/ui/select/select-content.svelte +45 -0
  98. package/src/lib/components/ui/select/select-group-heading.svelte +21 -0
  99. package/src/lib/components/ui/select/select-group.svelte +7 -0
  100. package/src/lib/components/ui/select/select-item.svelte +38 -0
  101. package/src/lib/components/ui/select/select-label.svelte +20 -0
  102. package/src/lib/components/ui/select/select-portal.svelte +7 -0
  103. package/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
  104. package/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
  105. package/src/lib/components/ui/select/select-separator.svelte +18 -0
  106. package/src/lib/components/ui/select/select-trigger.svelte +29 -0
  107. package/src/lib/components/ui/select/select.svelte +11 -0
  108. package/src/lib/components/ui/separator/index.ts +7 -0
  109. package/src/lib/components/ui/separator/separator.svelte +21 -0
  110. package/src/lib/components/ui/sheet/index.ts +34 -0
  111. package/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
  112. package/src/lib/components/ui/sheet/sheet-content.svelte +62 -0
  113. package/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
  114. package/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  115. package/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
  116. package/src/lib/components/ui/sheet/sheet-overlay.svelte +20 -0
  117. package/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  118. package/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
  119. package/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  120. package/src/lib/components/ui/sheet/sheet.svelte +7 -0
  121. package/src/lib/components/ui/skeleton/index.ts +1 -0
  122. package/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  123. package/src/lib/components/ui/sonner/index.ts +1 -0
  124. package/src/lib/components/ui/sonner/sonner.svelte +10 -0
  125. package/src/lib/components/ui/tabs/index.ts +16 -0
  126. package/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  127. package/src/lib/components/ui/tabs/tabs-list.svelte +16 -0
  128. package/src/lib/components/ui/tabs/tabs-trigger.svelte +20 -0
  129. package/src/lib/components/ui/tabs/tabs.svelte +19 -0
  130. package/src/lib/components/ui/textarea/index.ts +7 -0
  131. package/src/lib/components/ui/textarea/textarea.svelte +23 -0
  132. package/src/lib/components/ui/toggle/index.ts +13 -0
  133. package/src/lib/components/ui/toggle/toggle.svelte +52 -0
  134. package/src/lib/components/ui/toggle-group/index.ts +10 -0
  135. package/src/lib/components/ui/toggle-group/toggle-group-item.svelte +35 -0
  136. package/src/lib/components/ui/toggle-group/toggle-group.svelte +65 -0
  137. package/src/lib/components/ui/tooltip/index.ts +19 -0
  138. package/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  139. package/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  140. package/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  141. package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  142. package/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  143. package/src/lib/index.ts +244 -0
  144. package/src/lib/utils/cn.test.ts +993 -0
  145. package/src/lib/utils/cn.ts +6 -0
  146. package/src/lib/utils.ts +12 -0
  147. package/src/styles.css +127 -0
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { Badge } from '../../index.js';
3
+
4
+ let { status, reviewer }: { status: string; reviewer: string } = $props();
5
+
6
+ let icon = $derived(
7
+ status === 'approved' ? 'check' : status === 'changes_requested' ? 'x' : 'comment',
8
+ );
9
+
10
+ let label = $derived(
11
+ status === 'approved'
12
+ ? 'approved'
13
+ : status === 'changes_requested'
14
+ ? 'requested changes'
15
+ : 'commented',
16
+ );
17
+
18
+ const variantMap: Record<string, string> = {
19
+ approved: 'border-success/30 bg-success/10 text-success',
20
+ changes_requested: 'border-destructive/30 bg-destructive/10 text-destructive',
21
+ commented: '',
22
+ };
23
+
24
+ const variant = $derived(status === 'commented' ? 'secondary' : 'outline');
25
+ const extraClass = $derived(variantMap[status] ?? '');
26
+ </script>
27
+
28
+ <Badge {variant} class="inline-flex items-center gap-1.5 rounded-full {extraClass}">
29
+ {#if icon === 'check'}
30
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
31
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
32
+ </svg>
33
+ {:else if icon === 'x'}
34
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35
+ <path
36
+ stroke-linecap="round"
37
+ stroke-linejoin="round"
38
+ stroke-width="2"
39
+ d="M6 18L18 6M6 6l12 12"
40
+ />
41
+ </svg>
42
+ {:else}
43
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path
45
+ stroke-linecap="round"
46
+ stroke-linejoin="round"
47
+ stroke-width="2"
48
+ d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
49
+ />
50
+ </svg>
51
+ {/if}
52
+ <span class="font-medium">{reviewer}</span>
53
+ <span>{label}</span>
54
+ </Badge>
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../utils.js';
3
+
4
+ let {
5
+ ref = $bindable(null),
6
+ data,
7
+ color = 'stroke-primary',
8
+ fillColor = 'fill-primary/10',
9
+ width = 80,
10
+ height = 24,
11
+ class: className,
12
+ ...restProps
13
+ }: {
14
+ ref?: SVGSVGElement | null;
15
+ data: number[];
16
+ color?: string;
17
+ fillColor?: string;
18
+ width?: number;
19
+ height?: number;
20
+ class?: string;
21
+ } = $props();
22
+
23
+ const points = $derived.by(() => {
24
+ if (data.length === 0) return '';
25
+ const max = Math.max(...data, 1);
26
+ const step = width / Math.max(data.length - 1, 1);
27
+ return data.map((v, i) => `${i * step},${height - (v / max) * (height - 2) - 1}`).join(' ');
28
+ });
29
+
30
+ const areaPoints = $derived.by(() => {
31
+ if (data.length === 0) return '';
32
+ const max = Math.max(...data, 1);
33
+ const step = width / Math.max(data.length - 1, 1);
34
+ const linePoints = data
35
+ .map((v, i) => `${i * step},${height - (v / max) * (height - 2) - 1}`)
36
+ .join(' ');
37
+ return `0,${height} ${linePoints} ${(data.length - 1) * step},${height}`;
38
+ });
39
+ </script>
40
+
41
+ <svg
42
+ bind:this={ref}
43
+ viewBox="0 0 {width} {height}"
44
+ class={cn('overflow-visible', className)}
45
+ style="width: {width}px; height: {height}px;"
46
+ {...restProps}
47
+ >
48
+ {#if data.length > 1}
49
+ <polygon points={areaPoints} class={fillColor} />
50
+ <polyline
51
+ {points}
52
+ fill="none"
53
+ class={color}
54
+ stroke-width="1.5"
55
+ stroke-linecap="round"
56
+ stroke-linejoin="round"
57
+ />
58
+ {:else if data.length === 1}
59
+ <line x1="0" y1={height / 2} x2={width} y2={height / 2} class={color} stroke-width="1.5" />
60
+ {/if}
61
+ </svg>
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import { Badge } from '../../index.js';
3
+ import StatusDot from './StatusDot.svelte';
4
+
5
+ let { status, label }: { status: string; label?: string } = $props();
6
+
7
+ const displayLabel = $derived(label ?? status);
8
+
9
+ const classes: Record<string, string> = {
10
+ open: 'border-success/30 bg-success/10 text-success',
11
+ closed: '',
12
+ merged: 'border-purple-500/30 bg-purple-500/10 text-purple-400',
13
+ running: 'border-blue-500/30 bg-blue-500/10 text-blue-400',
14
+ active: 'border-blue-500/30 bg-blue-500/10 text-blue-400',
15
+ passed: 'border-success/30 bg-success/10 text-success',
16
+ succeeded: 'border-success/30 bg-success/10 text-success',
17
+ failed: 'border-destructive/30 bg-destructive/10 text-destructive',
18
+ error: 'border-destructive/30 bg-destructive/10 text-destructive',
19
+ cancelled: '',
20
+ paused: '',
21
+ };
22
+
23
+ const variant = $derived(
24
+ status === 'closed' || status === 'cancelled' || status === 'paused' ? 'secondary' : 'outline',
25
+ );
26
+ const extraClass = $derived(classes[status] ?? '');
27
+ </script>
28
+
29
+ <Badge {variant} class="inline-flex items-center gap-1 {extraClass}">
30
+ <StatusDot {status} />
31
+ <span>{displayLabel}</span>
32
+ </Badge>
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn, type WithElementRef } from '../../utils.js';
4
+
5
+ type Status =
6
+ | 'open'
7
+ | 'closed'
8
+ | 'running'
9
+ | 'succeeded'
10
+ | 'failed'
11
+ | 'active'
12
+ | 'paused'
13
+ | 'merged'
14
+ | 'error'
15
+ | 'warning'
16
+ | 'passed'
17
+ | 'cancelled'
18
+ | 'terminated'
19
+ | string;
20
+
21
+ let {
22
+ ref = $bindable(null),
23
+ color,
24
+ status,
25
+ animated = false,
26
+ size = 'md',
27
+ class: className,
28
+ ...restProps
29
+ }: WithElementRef<HTMLAttributes<HTMLSpanElement>> & {
30
+ color?: string;
31
+ status?: Status;
32
+ animated?: boolean;
33
+ size?: 'xs' | 'sm' | 'md';
34
+ } = $props();
35
+
36
+ const colorMap: Record<string, string> = {
37
+ open: 'bg-success',
38
+ active: 'bg-success',
39
+ succeeded: 'bg-success',
40
+ passed: 'bg-success',
41
+ closed: 'bg-muted-foreground',
42
+ paused: 'bg-muted-foreground',
43
+ terminated: 'bg-muted-foreground',
44
+ running: 'bg-blue-500',
45
+ merged: 'bg-purple-500',
46
+ failed: 'bg-destructive',
47
+ error: 'bg-destructive',
48
+ warning: 'bg-yellow-500',
49
+ };
50
+
51
+ const resolvedColor = $derived(
52
+ color ?? (status ? (colorMap[status] ?? 'bg-muted-foreground') : 'bg-muted-foreground'),
53
+ );
54
+ const isAnimated = $derived(animated || status === 'running');
55
+ const sizeClass = $derived(size === 'xs' ? 'h-1 w-1' : size === 'sm' ? 'h-1.5 w-1.5' : 'h-2 w-2');
56
+ </script>
57
+
58
+ <span bind:this={ref} class="relative flex {sizeClass} shrink-0 {cn(className)}" {...restProps}>
59
+ {#if isAnimated}
60
+ <span
61
+ class="absolute inline-flex h-full w-full animate-ping rounded-full {resolvedColor} opacity-50"
62
+ ></span>
63
+ {/if}
64
+ <span class="relative inline-flex {sizeClass} rounded-full {resolvedColor}"></span>
65
+ </span>
@@ -0,0 +1,127 @@
1
+ <script lang="ts">
2
+ import type { Component } from 'svelte';
3
+
4
+ type TabItem = {
5
+ id: string;
6
+ label: string;
7
+ icon?: Component;
8
+ count?: number;
9
+ closable?: boolean;
10
+ };
11
+
12
+ let {
13
+ items,
14
+ selected,
15
+ onselect,
16
+ onclose,
17
+ variant = 'underline',
18
+ orientation = 'horizontal',
19
+ size = 'sm',
20
+ class: className = '',
21
+ }: {
22
+ items: TabItem[];
23
+ selected: string;
24
+ onselect: (id: string) => void;
25
+ onclose?: (id: string) => void;
26
+ variant?: 'underline' | 'pill' | 'fill';
27
+ orientation?: 'horizontal' | 'vertical';
28
+ size?: 'xs' | 'sm';
29
+ class?: string;
30
+ } = $props();
31
+
32
+ const isVertical = $derived(orientation === 'vertical');
33
+
34
+ const containerClass = $derived.by(() => {
35
+ const base = isVertical ? 'flex flex-col gap-0.5' : 'flex items-center gap-0';
36
+ return `${base} ${className}`;
37
+ });
38
+
39
+ const itemClass = $derived.by(() => {
40
+ const sizeStyles = size === 'xs' ? 'text-[10px] px-2 py-1' : 'text-xs px-3 py-1.5';
41
+
42
+ if (variant === 'pill') {
43
+ return {
44
+ base: `rounded-full font-medium transition-all duration-150 ${sizeStyles}`,
45
+ active: 'bg-primary/15 text-primary font-medium',
46
+ inactive: 'text-muted-foreground hover:text-foreground',
47
+ };
48
+ }
49
+
50
+ if (variant === 'fill') {
51
+ const fillSize = isVertical
52
+ ? `w-full text-xs px-2 py-1.5 rounded-md`
53
+ : `${sizeStyles} rounded`;
54
+ return {
55
+ base: `font-medium transition-colors ${fillSize}`,
56
+ active: 'bg-accent text-foreground',
57
+ inactive: 'text-muted-foreground hover:text-foreground',
58
+ };
59
+ }
60
+
61
+ // underline (default)
62
+ return {
63
+ base: `relative font-medium transition-colors shrink-0 ${sizeStyles}`,
64
+ active: 'text-foreground',
65
+ inactive: 'text-muted-foreground hover:text-foreground',
66
+ };
67
+ });
68
+ </script>
69
+
70
+ <div class={containerClass} role="tablist" aria-orientation={orientation}>
71
+ {#each items as item (item.id)}
72
+ {@const isActive = selected === item.id}
73
+ <button
74
+ role="tab"
75
+ aria-selected={isActive}
76
+ class="group flex items-center gap-1.5 {itemClass.base} {isActive
77
+ ? itemClass.active
78
+ : itemClass.inactive}"
79
+ onclick={() => onselect(item.id)}
80
+ >
81
+ {#if item.icon}
82
+ {@const Icon = item.icon}
83
+ <Icon class={size === 'xs' ? 'h-3 w-3' : 'h-3.5 w-3.5'} />
84
+ {/if}
85
+ {item.label}
86
+ {#if item.count !== undefined && item.count > 0}
87
+ {#if variant === 'underline'}
88
+ <span class="bg-primary/10 text-primary rounded-full px-1.5 text-[10px] font-bold"
89
+ >{item.count}</span
90
+ >
91
+ {:else}
92
+ <span class="bg-muted ml-1 rounded-full px-1.5 text-[10px] tabular-nums"
93
+ >{item.count}</span
94
+ >
95
+ {/if}
96
+ {/if}
97
+ {#if item.closable && onclose}
98
+ <span
99
+ class="hover:bg-muted ml-0.5 hidden h-3 w-3 items-center justify-center rounded-sm group-hover:inline-flex"
100
+ role="button"
101
+ tabindex="0"
102
+ onclick={(e) => {
103
+ e.stopPropagation();
104
+ onclose?.(item.id);
105
+ }}
106
+ onkeydown={(e) => {
107
+ if (e.key === 'Enter') {
108
+ e.stopPropagation();
109
+ onclose?.(item.id);
110
+ }
111
+ }}
112
+ >
113
+ <svg
114
+ class="h-2 w-2"
115
+ viewBox="0 0 24 24"
116
+ fill="none"
117
+ stroke="currentColor"
118
+ stroke-width="2"><path d="M18 6L6 18M6 6l12 12" /></svg
119
+ >
120
+ </span>
121
+ {/if}
122
+ {#if variant === 'underline' && isActive}
123
+ <span class="bg-primary absolute right-2 bottom-0 left-2 h-0.5 rounded-full"></span>
124
+ {/if}
125
+ </button>
126
+ {/each}
127
+ </div>
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ let {
3
+ class: className = '',
4
+ }: {
5
+ class?: string;
6
+ } = $props();
7
+ </script>
8
+
9
+ <span class="bg-border/60 mx-0.5 h-3.5 w-px shrink-0 {className}"></span>
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from 'bits-ui';
3
+ import { cn, type WithoutChild } from '../../../utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithoutChild<AccordionPrimitive.ContentProps> = $props();
11
+ </script>
12
+
13
+ <AccordionPrimitive.Content
14
+ bind:ref
15
+ data-slot="accordion-content"
16
+ class="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
17
+ {...restProps}
18
+ >
19
+ <div class={cn('pt-0 pb-4', className)}>
20
+ {@render children?.()}
21
+ </div>
22
+ </AccordionPrimitive.Content>
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: AccordionPrimitive.ItemProps = $props();
10
+ </script>
11
+
12
+ <AccordionPrimitive.Item
13
+ bind:ref
14
+ data-slot="accordion-item"
15
+ class={cn('border-b last:border-b-0', className)}
16
+ {...restProps}
17
+ />
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from 'bits-ui';
3
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
4
+ import { cn, type WithoutChild } from '../../../utils.js';
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ level = 3,
10
+ children,
11
+ ...restProps
12
+ }: WithoutChild<AccordionPrimitive.TriggerProps> & {
13
+ level?: AccordionPrimitive.HeaderProps['level'];
14
+ } = $props();
15
+ </script>
16
+
17
+ <AccordionPrimitive.Header {level} class="flex">
18
+ <AccordionPrimitive.Trigger
19
+ data-slot="accordion-trigger"
20
+ bind:ref
21
+ class={cn(
22
+ 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-start text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
23
+ className,
24
+ )}
25
+ {...restProps}
26
+ >
27
+ {@render children?.()}
28
+ <ChevronDownIcon
29
+ class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200"
30
+ />
31
+ </AccordionPrimitive.Trigger>
32
+ </AccordionPrimitive.Header>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from 'bits-ui';
3
+
4
+ let {
5
+ ref = $bindable(null),
6
+ value = $bindable(),
7
+ ...restProps
8
+ }: AccordionPrimitive.RootProps = $props();
9
+ </script>
10
+
11
+ <AccordionPrimitive.Root
12
+ bind:ref
13
+ bind:value={value as never}
14
+ data-slot="accordion"
15
+ {...restProps}
16
+ />
@@ -0,0 +1,16 @@
1
+ import Root from './accordion.svelte';
2
+ import Content from './accordion-content.svelte';
3
+ import Item from './accordion-item.svelte';
4
+ import Trigger from './accordion-trigger.svelte';
5
+
6
+ export {
7
+ Root,
8
+ Content,
9
+ Item,
10
+ Trigger,
11
+ //
12
+ Root as Accordion,
13
+ Content as AccordionContent,
14
+ Item as AccordionItem,
15
+ Trigger as AccordionTrigger,
16
+ };
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { Avatar as AvatarPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: AvatarPrimitive.FallbackProps = $props();
10
+ </script>
11
+
12
+ <AvatarPrimitive.Fallback
13
+ bind:ref
14
+ data-slot="avatar-fallback"
15
+ class={cn('bg-muted flex size-full items-center justify-center rounded-full', className)}
16
+ {...restProps}
17
+ />
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { Avatar as AvatarPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: AvatarPrimitive.ImageProps = $props();
10
+ </script>
11
+
12
+ <AvatarPrimitive.Image
13
+ bind:ref
14
+ data-slot="avatar-image"
15
+ class={cn('aspect-square size-full', className)}
16
+ {...restProps}
17
+ />
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import { Avatar as AvatarPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ loadingStatus = $bindable('loading'),
8
+ class: className,
9
+ ...restProps
10
+ }: AvatarPrimitive.RootProps = $props();
11
+ </script>
12
+
13
+ <AvatarPrimitive.Root
14
+ bind:ref
15
+ bind:loadingStatus
16
+ data-slot="avatar"
17
+ class={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
18
+ {...restProps}
19
+ />
@@ -0,0 +1,13 @@
1
+ import Root from './avatar.svelte';
2
+ import Image from './avatar-image.svelte';
3
+ import Fallback from './avatar-fallback.svelte';
4
+
5
+ export {
6
+ Root,
7
+ Image,
8
+ Fallback,
9
+ //
10
+ Root as Avatar,
11
+ Image as AvatarImage,
12
+ Fallback as AvatarFallback,
13
+ };
@@ -0,0 +1,49 @@
1
+ <script lang="ts" module>
2
+ import { type VariantProps, tv } from 'tailwind-variants';
3
+
4
+ export const badgeVariants = tv({
5
+ base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3',
6
+ variants: {
7
+ variant: {
8
+ default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
9
+ secondary:
10
+ 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
11
+ destructive:
12
+ 'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white',
13
+ outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: 'default',
18
+ },
19
+ });
20
+
21
+ export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
22
+ </script>
23
+
24
+ <script lang="ts">
25
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
26
+ import { cn, type WithElementRef } from '../../../utils.js';
27
+
28
+ let {
29
+ ref = $bindable(null),
30
+ href,
31
+ class: className,
32
+ variant = 'default',
33
+ children,
34
+ ...restProps
35
+ }: WithElementRef<HTMLAnchorAttributes> & {
36
+ variant?: BadgeVariant;
37
+ } = $props();
38
+ </script>
39
+
40
+ <svelte:element
41
+ this={href ? 'a' : 'span'}
42
+ bind:this={ref}
43
+ data-slot="badge"
44
+ {href}
45
+ class={cn(badgeVariants({ variant }), className)}
46
+ {...restProps}
47
+ >
48
+ {@render children?.()}
49
+ </svelte:element>
@@ -0,0 +1,2 @@
1
+ export { default as Badge } from './badge.svelte';
2
+ export { badgeVariants, type BadgeVariant } from './badge.svelte';
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn, type WithElementRef, type WithoutChildren } from '../../../utils.js';
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
11
+ </script>
12
+
13
+ <span
14
+ bind:this={ref}
15
+ data-slot="breadcrumb-ellipsis"
16
+ role="presentation"
17
+ aria-hidden="true"
18
+ class={cn('flex size-9 items-center justify-center', className)}
19
+ {...restProps}
20
+ >
21
+ <EllipsisIcon class="size-4" />
22
+ <span class="sr-only">More</span>
23
+ </span>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import type { HTMLLiAttributes } from 'svelte/elements';
3
+ import { cn, type WithElementRef } from '../../../utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLLiAttributes> = $props();
11
+ </script>
12
+
13
+ <li
14
+ bind:this={ref}
15
+ data-slot="breadcrumb-item"
16
+ class={cn('inline-flex items-center gap-1.5', className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </li>
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
3
+ import type { Snippet } from 'svelte';
4
+ import { cn, type WithElementRef } from '../../../utils.js';
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ href = undefined,
10
+ child,
11
+ children,
12
+ ...restProps
13
+ }: WithElementRef<HTMLAnchorAttributes> & {
14
+ child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
15
+ } = $props();
16
+
17
+ const attrs = $derived({
18
+ 'data-slot': 'breadcrumb-link',
19
+ class: cn('hover:text-foreground transition-colors', className),
20
+ href,
21
+ ...restProps,
22
+ });
23
+ </script>
24
+
25
+ {#if child}
26
+ {@render child({ props: attrs })}
27
+ {:else}
28
+ <a bind:this={ref} {...attrs}>
29
+ {@render children?.()}
30
+ </a>
31
+ {/if}