@lyeve-labs/ui-kit 0.8.2

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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/components/Accordion.svelte +48 -0
  4. package/dist/components/Accordion.svelte.d.ts +10 -0
  5. package/dist/components/AccordionItem.svelte +55 -0
  6. package/dist/components/AccordionItem.svelte.d.ts +10 -0
  7. package/dist/components/Alert.svelte +60 -0
  8. package/dist/components/Alert.svelte.d.ts +13 -0
  9. package/dist/components/Autocomplete.svelte +198 -0
  10. package/dist/components/Autocomplete.svelte.d.ts +23 -0
  11. package/dist/components/Avatar.svelte +65 -0
  12. package/dist/components/Avatar.svelte.d.ts +9 -0
  13. package/dist/components/AvatarGroup.svelte +45 -0
  14. package/dist/components/AvatarGroup.svelte.d.ts +14 -0
  15. package/dist/components/Badge.svelte +48 -0
  16. package/dist/components/Badge.svelte.d.ts +13 -0
  17. package/dist/components/Banner.svelte +73 -0
  18. package/dist/components/Banner.svelte.d.ts +13 -0
  19. package/dist/components/Breadcrumb.svelte +47 -0
  20. package/dist/components/Breadcrumb.svelte.d.ts +10 -0
  21. package/dist/components/Button.svelte +71 -0
  22. package/dist/components/Button.svelte.d.ts +18 -0
  23. package/dist/components/ButtonGroup.svelte +33 -0
  24. package/dist/components/ButtonGroup.svelte.d.ts +9 -0
  25. package/dist/components/Card.svelte +72 -0
  26. package/dist/components/Card.svelte.d.ts +17 -0
  27. package/dist/components/Checkbox.svelte +80 -0
  28. package/dist/components/Checkbox.svelte.d.ts +14 -0
  29. package/dist/components/DatePicker.svelte +274 -0
  30. package/dist/components/DatePicker.svelte.d.ts +17 -0
  31. package/dist/components/Divider.svelte +23 -0
  32. package/dist/components/Divider.svelte.d.ts +8 -0
  33. package/dist/components/Drawer.svelte +120 -0
  34. package/dist/components/Drawer.svelte.d.ts +16 -0
  35. package/dist/components/Dropdown.svelte +88 -0
  36. package/dist/components/Dropdown.svelte.d.ts +23 -0
  37. package/dist/components/EmptyState.svelte +51 -0
  38. package/dist/components/EmptyState.svelte.d.ts +13 -0
  39. package/dist/components/FileInput.svelte +102 -0
  40. package/dist/components/FileInput.svelte.d.ts +15 -0
  41. package/dist/components/Indicator.svelte +38 -0
  42. package/dist/components/Indicator.svelte.d.ts +11 -0
  43. package/dist/components/Input.svelte +60 -0
  44. package/dist/components/Input.svelte.d.ts +17 -0
  45. package/dist/components/Kbd.svelte +18 -0
  46. package/dist/components/Kbd.svelte.d.ts +8 -0
  47. package/dist/components/Label.svelte +30 -0
  48. package/dist/components/Label.svelte.d.ts +11 -0
  49. package/dist/components/Modal.svelte +105 -0
  50. package/dist/components/Modal.svelte.d.ts +14 -0
  51. package/dist/components/MultiSelect.svelte +212 -0
  52. package/dist/components/MultiSelect.svelte.d.ts +22 -0
  53. package/dist/components/NumberInput.svelte +98 -0
  54. package/dist/components/NumberInput.svelte.d.ts +15 -0
  55. package/dist/components/PageHeader.svelte +22 -0
  56. package/dist/components/PageHeader.svelte.d.ts +10 -0
  57. package/dist/components/Pagination.svelte +112 -0
  58. package/dist/components/Pagination.svelte.d.ts +10 -0
  59. package/dist/components/Progress.svelte +70 -0
  60. package/dist/components/Progress.svelte.d.ts +15 -0
  61. package/dist/components/Radio.svelte +65 -0
  62. package/dist/components/Radio.svelte.d.ts +14 -0
  63. package/dist/components/RadioGroup.svelte +96 -0
  64. package/dist/components/RadioGroup.svelte.d.ts +22 -0
  65. package/dist/components/SearchInput.svelte +81 -0
  66. package/dist/components/SearchInput.svelte.d.ts +10 -0
  67. package/dist/components/Select.svelte +69 -0
  68. package/dist/components/Select.svelte.d.ts +18 -0
  69. package/dist/components/Skeleton.svelte +20 -0
  70. package/dist/components/Skeleton.svelte.d.ts +9 -0
  71. package/dist/components/Spinner.svelte +19 -0
  72. package/dist/components/Spinner.svelte.d.ts +7 -0
  73. package/dist/components/Stat.svelte +45 -0
  74. package/dist/components/Stat.svelte.d.ts +14 -0
  75. package/dist/components/StepIndicator.svelte +142 -0
  76. package/dist/components/StepIndicator.svelte.d.ts +13 -0
  77. package/dist/components/Table.svelte +36 -0
  78. package/dist/components/Table.svelte.d.ts +11 -0
  79. package/dist/components/Tabs.svelte +39 -0
  80. package/dist/components/Tabs.svelte.d.ts +13 -0
  81. package/dist/components/Tag.svelte +56 -0
  82. package/dist/components/Tag.svelte.d.ts +11 -0
  83. package/dist/components/Textarea.svelte +75 -0
  84. package/dist/components/Textarea.svelte.d.ts +26 -0
  85. package/dist/components/ThemeToggle.svelte +33 -0
  86. package/dist/components/ThemeToggle.svelte.d.ts +6 -0
  87. package/dist/components/Toaster.svelte +54 -0
  88. package/dist/components/Toaster.svelte.d.ts +18 -0
  89. package/dist/components/Toggle.svelte +74 -0
  90. package/dist/components/Toggle.svelte.d.ts +11 -0
  91. package/dist/components/Tooltip.svelte +44 -0
  92. package/dist/components/Tooltip.svelte.d.ts +11 -0
  93. package/dist/components/dialog/ConfirmDialog.svelte +95 -0
  94. package/dist/components/dialog/ConfirmDialog.svelte.d.ts +8 -0
  95. package/dist/components/dialog/Dialog.svelte +225 -0
  96. package/dist/components/dialog/Dialog.svelte.d.ts +9 -0
  97. package/dist/components/dialog/DialogContainer.svelte +17 -0
  98. package/dist/components/dialog/DialogContainer.svelte.d.ts +18 -0
  99. package/dist/components/dialog/dialog-manager.svelte.d.ts +54 -0
  100. package/dist/components/dialog/dialog-manager.svelte.js +164 -0
  101. package/dist/components/dialog/types.d.ts +33 -0
  102. package/dist/components/dialog/types.js +10 -0
  103. package/dist/index.d.ts +63 -0
  104. package/dist/index.js +72 -0
  105. package/dist/stores/toast.svelte.d.ts +18 -0
  106. package/dist/stores/toast.svelte.js +28 -0
  107. package/dist/styles/theme.css +148 -0
  108. package/dist/utils/cn.d.ts +13 -0
  109. package/dist/utils/cn.js +24 -0
  110. package/dist/utils/theme.d.ts +26 -0
  111. package/dist/utils/theme.js +55 -0
  112. package/package.json +81 -0
  113. package/src/lib/styles/theme.css +148 -0
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ type SE = Event & { currentTarget: HTMLSelectElement };
5
+
6
+ let {
7
+ value,
8
+ id,
9
+ name,
10
+ required = false,
11
+ disabled = false,
12
+ error,
13
+ class: cls = '',
14
+ onchange,
15
+ children,
16
+ }: {
17
+ value?: string | null;
18
+ id?: string;
19
+ name?: string;
20
+ required?: boolean;
21
+ disabled?: boolean;
22
+ error?: string;
23
+ class?: string;
24
+ onchange?: (e: SE) => void;
25
+ children?: Snippet;
26
+ } = $props();
27
+
28
+ const base =
29
+ 'w-full bg-surface-2 rounded-lg px-3 py-2 text-sm text-fg ' +
30
+ 'focus:outline-none transition-colors appearance-none cursor-pointer ' +
31
+ 'disabled:opacity-50 disabled:cursor-not-allowed';
32
+ </script>
33
+
34
+ <div class="flex flex-col gap-1 {cls}">
35
+ <div class="relative">
36
+ <select
37
+ {id}
38
+ {name}
39
+ {required}
40
+ {disabled}
41
+ value={value ?? ''}
42
+ {onchange}
43
+ class="{base}
44
+ {error
45
+ ? 'border border-danger focus:border-danger/70'
46
+ : 'border border-line focus:border-brand/50'}
47
+ pr-8"
48
+ >
49
+ {@render children?.()}
50
+ </select>
51
+ <span
52
+ class="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-faint"
53
+ aria-hidden="true"
54
+ >
55
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
56
+ <path
57
+ d="M2 4l4 4 4-4"
58
+ stroke="currentColor"
59
+ stroke-width="1.5"
60
+ stroke-linecap="round"
61
+ stroke-linejoin="round"
62
+ />
63
+ </svg>
64
+ </span>
65
+ </div>
66
+ {#if error}
67
+ <p class="text-xs text-danger">{error}</p>
68
+ {/if}
69
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ type SE = Event & {
3
+ currentTarget: HTMLSelectElement;
4
+ };
5
+ type $$ComponentProps = {
6
+ value?: string | null;
7
+ id?: string;
8
+ name?: string;
9
+ required?: boolean;
10
+ disabled?: boolean;
11
+ error?: string;
12
+ class?: string;
13
+ onchange?: (e: SE) => void;
14
+ children?: Snippet;
15
+ };
16
+ declare const Select: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type Select = ReturnType<typeof Select>;
18
+ export default Select;
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ width?: string;
4
+ height?: string;
5
+ rounded?: string;
6
+ class?: string;
7
+ }
8
+ let {
9
+ width = '100%',
10
+ height = '1rem',
11
+ rounded = 'rounded-md',
12
+ class: klass = '',
13
+ }: Props = $props();
14
+ </script>
15
+
16
+ <div
17
+ class="animate-pulse bg-surface-2 {rounded} {klass}"
18
+ style="width: {width}; height: {height};"
19
+ aria-hidden="true"
20
+ ></div>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ width?: string;
3
+ height?: string;
4
+ rounded?: string;
5
+ class?: string;
6
+ }
7
+ declare const Skeleton: import("svelte").Component<Props, {}, "">;
8
+ type Skeleton = ReturnType<typeof Skeleton>;
9
+ export default Skeleton;
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ size?: number;
4
+ class?: string;
5
+ }
6
+ let { size = 16, class: klass = '' }: Props = $props();
7
+ </script>
8
+
9
+ <svg
10
+ class="animate-spin shrink-0 {klass}"
11
+ width={size}
12
+ height={size}
13
+ viewBox="0 0 24 24"
14
+ fill="none"
15
+ aria-hidden="true"
16
+ >
17
+ <circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="3" opacity="0.25" />
18
+ <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
19
+ </svg>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ size?: number;
3
+ class?: string;
4
+ }
5
+ declare const Spinner: import("svelte").Component<Props, {}, "">;
6
+ type Spinner = ReturnType<typeof Spinner>;
7
+ export default Spinner;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ type Trend = 'up' | 'down' | 'flat';
3
+ type Accent = 'brand' | 'violet' | 'success' | 'neutral';
4
+
5
+ interface Props {
6
+ label: string;
7
+ value: string | number;
8
+ sub?: string;
9
+ trend?: Trend;
10
+ change?: string;
11
+ accent?: Accent;
12
+ class?: string;
13
+ }
14
+
15
+ let {
16
+ label,
17
+ value,
18
+ sub = undefined,
19
+ trend = undefined,
20
+ change = undefined,
21
+ accent = 'brand',
22
+ class: klass = '',
23
+ }: Props = $props();
24
+
25
+ const accents = {
26
+ brand: 'text-brand',
27
+ violet: 'text-violet',
28
+ success: 'text-success',
29
+ neutral: 'text-fg',
30
+ } as const;
31
+
32
+ const trendColor = { up: 'text-success', down: 'text-danger', flat: 'text-muted' } as const;
33
+ const trendMark = { up: '↑', down: '↓', flat: '→' } as const;
34
+ </script>
35
+
36
+ <div class="rounded-xl border border-line bg-surface p-5 {klass}">
37
+ <p class="text-xs font-medium uppercase tracking-wide text-faint">{label}</p>
38
+ <p class="mt-2 text-2xl font-bold {accents[accent]}">{value}</p>
39
+ <div class="mt-1 flex items-center gap-2">
40
+ {#if trend && change}
41
+ <span class="text-xs font-medium {trendColor[trend]}">{trendMark[trend]} {change}</span>
42
+ {/if}
43
+ {#if sub}<span class="text-xs text-muted">{sub}</span>{/if}
44
+ </div>
45
+ </div>
@@ -0,0 +1,14 @@
1
+ type Trend = 'up' | 'down' | 'flat';
2
+ type Accent = 'brand' | 'violet' | 'success' | 'neutral';
3
+ interface Props {
4
+ label: string;
5
+ value: string | number;
6
+ sub?: string;
7
+ trend?: Trend;
8
+ change?: string;
9
+ accent?: Accent;
10
+ class?: string;
11
+ }
12
+ declare const Stat: import("svelte").Component<Props, {}, "">;
13
+ type Stat = ReturnType<typeof Stat>;
14
+ export default Stat;
@@ -0,0 +1,142 @@
1
+ <script lang="ts">
2
+ interface Step {
3
+ label: string;
4
+ description?: string;
5
+ }
6
+
7
+ interface Props {
8
+ steps: Step[];
9
+ current: number; // 1-based
10
+ orientation?: 'horizontal' | 'vertical';
11
+ class?: string;
12
+ }
13
+
14
+ let { steps, current, orientation = 'horizontal', class: cls = '' }: Props = $props();
15
+ </script>
16
+
17
+ {#if orientation === 'horizontal'}
18
+ <nav aria-label="Progress" class="w-full {cls}">
19
+ <ol class="flex items-start">
20
+ {#each steps as step, i}
21
+ {@const idx = i + 1}
22
+ {@const done = idx < current}
23
+ {@const active = idx === current}
24
+ <li class="flex flex-1 flex-col items-center">
25
+ <div class="flex w-full items-center">
26
+ <!-- Left connector -->
27
+ <div
28
+ class="flex-1 {i === 0
29
+ ? ''
30
+ : done || active
31
+ ? 'bg-brand'
32
+ : 'bg-line'} h-px transition-colors"
33
+ ></div>
34
+
35
+ <!-- Step circle -->
36
+ <div
37
+ class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2
38
+ font-bold text-sm transition-all
39
+ {done
40
+ ? 'border-brand bg-brand text-ink'
41
+ : active
42
+ ? 'border-brand bg-surface text-brand'
43
+ : 'border-line bg-surface text-faint'}"
44
+ >
45
+ {#if done}
46
+ <svg
47
+ width="13"
48
+ height="13"
49
+ viewBox="0 0 24 24"
50
+ fill="none"
51
+ stroke="currentColor"
52
+ stroke-width="2.5"
53
+ stroke-linecap="round"
54
+ aria-hidden="true"><path d="M20 6L9 17l-5-5" /></svg
55
+ >
56
+ {:else}{idx}{/if}
57
+ </div>
58
+
59
+ <!-- Right connector -->
60
+ <div
61
+ class="flex-1 {i === steps.length - 1
62
+ ? ''
63
+ : done
64
+ ? 'bg-brand'
65
+ : 'bg-line'} h-px transition-colors"
66
+ ></div>
67
+ </div>
68
+
69
+ <div class="mt-2 px-1 text-center">
70
+ <p
71
+ class="text-xs font-medium {active
72
+ ? 'text-fg'
73
+ : done
74
+ ? 'text-muted'
75
+ : 'text-faint'} transition-colors"
76
+ >
77
+ {step.label}
78
+ </p>
79
+ {#if step.description}
80
+ <p class="mt-0.5 text-[10px] text-faint">{step.description}</p>
81
+ {/if}
82
+ </div>
83
+ </li>
84
+ {/each}
85
+ </ol>
86
+ </nav>
87
+ {:else}
88
+ <nav aria-label="Progress" class={cls}>
89
+ <ol class="flex flex-col">
90
+ {#each steps as step, i}
91
+ {@const idx = i + 1}
92
+ {@const done = idx < current}
93
+ {@const active = idx === current}
94
+ <li class="flex gap-4">
95
+ <div class="flex flex-col items-center">
96
+ <div
97
+ class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2
98
+ font-bold text-sm transition-all
99
+ {done
100
+ ? 'border-brand bg-brand text-ink'
101
+ : active
102
+ ? 'border-brand bg-surface text-brand'
103
+ : 'border-line bg-surface text-faint'}"
104
+ >
105
+ {#if done}
106
+ <svg
107
+ width="13"
108
+ height="13"
109
+ viewBox="0 0 24 24"
110
+ fill="none"
111
+ stroke="currentColor"
112
+ stroke-width="2.5"
113
+ stroke-linecap="round"
114
+ aria-hidden="true"><path d="M20 6L9 17l-5-5" /></svg
115
+ >
116
+ {:else}{idx}{/if}
117
+ </div>
118
+ {#if i < steps.length - 1}
119
+ <div
120
+ class="my-1 w-px flex-1 {done ? 'bg-brand/40' : 'bg-line'} transition-colors"
121
+ ></div>
122
+ {/if}
123
+ </div>
124
+ <div class="pb-6 pt-1">
125
+ <p
126
+ class="text-sm font-medium {active
127
+ ? 'text-fg'
128
+ : done
129
+ ? 'text-muted'
130
+ : 'text-faint'} transition-colors"
131
+ >
132
+ {step.label}
133
+ </p>
134
+ {#if step.description}
135
+ <p class="mt-0.5 text-xs text-faint">{step.description}</p>
136
+ {/if}
137
+ </div>
138
+ </li>
139
+ {/each}
140
+ </ol>
141
+ </nav>
142
+ {/if}
@@ -0,0 +1,13 @@
1
+ interface Step {
2
+ label: string;
3
+ description?: string;
4
+ }
5
+ interface Props {
6
+ steps: Step[];
7
+ current: number;
8
+ orientation?: 'horizontal' | 'vertical';
9
+ class?: string;
10
+ }
11
+ declare const StepIndicator: import("svelte").Component<Props, {}, "">;
12
+ type StepIndicator = ReturnType<typeof StepIndicator>;
13
+ export default StepIndicator;
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ striped?: boolean;
6
+ hoverable?: boolean;
7
+ fixed?: boolean;
8
+ class?: string;
9
+ children: Snippet;
10
+ }
11
+
12
+ let {
13
+ striped = false,
14
+ hoverable = true,
15
+ fixed = false,
16
+ class: cls = '',
17
+ children,
18
+ }: Props = $props();
19
+ </script>
20
+
21
+ <div class="w-full overflow-x-auto rounded-xl border border-line {cls}">
22
+ <table
23
+ class="w-full text-sm text-left
24
+ {fixed ? 'table-fixed' : ''}
25
+ [&_thead_tr]:border-b [&_thead_tr]:border-line
26
+ [&_thead_th]:px-4 [&_thead_th]:py-3 [&_thead_th]:text-xs
27
+ [&_thead_th]:font-medium [&_thead_th]:text-faint
28
+ [&_thead_th]:uppercase [&_thead_th]:tracking-wider [&_thead_th]:whitespace-nowrap
29
+ [&_tbody_td]:px-4 [&_tbody_td]:py-3 [&_tbody_td]:text-fg [&_tbody_td]:align-middle
30
+ [&_tbody_tr]:border-b [&_tbody_tr]:border-line [&_tbody_tr:last-child]:border-0
31
+ {striped ? '[&_tbody_tr:nth-child(even)]:bg-surface-2/40' : ''}
32
+ {hoverable ? '[&_tbody_tr:hover]:bg-surface-2/60 [&_tbody_tr]:transition-colors' : ''}"
33
+ >
34
+ {@render children()}
35
+ </table>
36
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ striped?: boolean;
4
+ hoverable?: boolean;
5
+ fixed?: boolean;
6
+ class?: string;
7
+ children: Snippet;
8
+ }
9
+ declare const Table: import("svelte").Component<Props, {}, "">;
10
+ type Table = ReturnType<typeof Table>;
11
+ export default Table;
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ let {
3
+ items,
4
+ active,
5
+ class: cls = '',
6
+ onchange,
7
+ }: {
8
+ items: { id: string; label: string; count?: number }[];
9
+ active: string;
10
+ class?: string;
11
+ onchange: (id: string) => void;
12
+ } = $props();
13
+ </script>
14
+
15
+ <div role="tablist" class="flex gap-0.5 border-b border-line {cls}">
16
+ {#each items as tab (tab.id)}
17
+ <button
18
+ type="button"
19
+ role="tab"
20
+ aria-selected={active === tab.id}
21
+ onclick={() => onchange(tab.id)}
22
+ class="relative px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap
23
+ {active === tab.id
24
+ ? 'text-fg after:absolute after:bottom-0 after:inset-x-0 after:h-0.5 after:bg-brand after:rounded-t'
25
+ : 'text-faint hover:text-fg'}"
26
+ >
27
+ {tab.label}
28
+ {#if tab.count !== undefined}
29
+ <span
30
+ class="ml-1.5 inline-flex items-center justify-center min-w-4.5 h-4.5 px-1
31
+ rounded-full text-[10px] font-semibold
32
+ {active === tab.id ? 'bg-brand/40 text-brand' : 'bg-surface-2 text-faint'}"
33
+ >
34
+ {tab.count}
35
+ </span>
36
+ {/if}
37
+ </button>
38
+ {/each}
39
+ </div>
@@ -0,0 +1,13 @@
1
+ type $$ComponentProps = {
2
+ items: {
3
+ id: string;
4
+ label: string;
5
+ count?: number;
6
+ }[];
7
+ active: string;
8
+ class?: string;
9
+ onchange: (id: string) => void;
10
+ };
11
+ declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type Tabs = ReturnType<typeof Tabs>;
13
+ export default Tabs;
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ type Tone = 'neutral' | 'brand' | 'violet' | 'success' | 'warn' | 'danger';
3
+
4
+ interface Props {
5
+ label: string;
6
+ tone?: Tone;
7
+ removable?: boolean;
8
+ onremove?: () => void;
9
+ class?: string;
10
+ }
11
+
12
+ let {
13
+ label,
14
+ tone = 'neutral',
15
+ removable = false,
16
+ onremove = undefined,
17
+ class: cls = '',
18
+ }: Props = $props();
19
+
20
+ const tones: Record<Tone, string> = {
21
+ neutral: 'bg-surface-2 text-muted border-line',
22
+ brand: 'bg-brand/10 text-brand border-brand/20',
23
+ violet: 'bg-violet/10 text-violet border-violet/20',
24
+ success: 'bg-success/10 text-success border-success/20',
25
+ warn: 'bg-warn/10 text-warn border-warn/20',
26
+ danger: 'bg-danger/10 text-danger border-danger/20',
27
+ };
28
+ </script>
29
+
30
+ <span
31
+ class="inline-flex items-center gap-1 rounded-md border px-2 py-0.5
32
+ text-xs font-medium {tones[tone]} {cls}"
33
+ >
34
+ {label}
35
+ {#if removable}
36
+ <button
37
+ type="button"
38
+ onclick={onremove}
39
+ aria-label="Remove {label}"
40
+ class="ml-0.5 rounded transition-opacity hover:opacity-60 leading-none"
41
+ >
42
+ <svg
43
+ width="10"
44
+ height="10"
45
+ viewBox="0 0 24 24"
46
+ fill="none"
47
+ stroke="currentColor"
48
+ stroke-width="2.5"
49
+ stroke-linecap="round"
50
+ aria-hidden="true"
51
+ >
52
+ <path d="M18 6L6 18M6 6l12 12" />
53
+ </svg>
54
+ </button>
55
+ {/if}
56
+ </span>
@@ -0,0 +1,11 @@
1
+ type Tone = 'neutral' | 'brand' | 'violet' | 'success' | 'warn' | 'danger';
2
+ interface Props {
3
+ label: string;
4
+ tone?: Tone;
5
+ removable?: boolean;
6
+ onremove?: () => void;
7
+ class?: string;
8
+ }
9
+ declare const Tag: import("svelte").Component<Props, {}, "">;
10
+ type Tag = ReturnType<typeof Tag>;
11
+ export default Tag;
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ type TA = HTMLTextAreaElement;
3
+ type TAE = Event & { currentTarget: TA };
4
+
5
+ let {
6
+ value = $bindable(''),
7
+ id,
8
+ name,
9
+ placeholder,
10
+ rows = 4,
11
+ maxlength,
12
+ required = false,
13
+ disabled = false,
14
+ readonly = false,
15
+ resize = true,
16
+ error,
17
+ hint,
18
+ class: cls = '',
19
+ oninput,
20
+ onblur,
21
+ }: {
22
+ value?: string;
23
+ id?: string;
24
+ name?: string;
25
+ placeholder?: string;
26
+ rows?: number;
27
+ maxlength?: number;
28
+ required?: boolean;
29
+ disabled?: boolean;
30
+ readonly?: boolean;
31
+ resize?: boolean;
32
+ error?: string;
33
+ hint?: string;
34
+ class?: string;
35
+ oninput?: (e: TAE) => void;
36
+ onblur?: (e: FocusEvent & { currentTarget: TA }) => void;
37
+ } = $props();
38
+
39
+ const base =
40
+ 'w-full bg-surface-2 rounded-lg px-3 py-2 text-sm text-fg ' +
41
+ 'focus:outline-none transition-colors ' +
42
+ 'placeholder:text-faint ' +
43
+ 'disabled:opacity-50 disabled:cursor-not-allowed';
44
+
45
+ function handleInput(e: TAE) {
46
+ value = e.currentTarget.value;
47
+ oninput?.(e);
48
+ }
49
+ </script>
50
+
51
+ <div class="flex flex-col gap-1 {cls}">
52
+ <textarea
53
+ {id}
54
+ {name}
55
+ {rows}
56
+ {maxlength}
57
+ {required}
58
+ {disabled}
59
+ {readonly}
60
+ {placeholder}
61
+ class="{base}
62
+ {error
63
+ ? 'border border-danger focus:border-danger/70'
64
+ : 'border border-line focus:border-brand/50'}
65
+ {resize ? 'resize-y' : 'resize-none'}"
66
+ oninput={handleInput}
67
+ {onblur}>{value}</textarea
68
+ >
69
+ {#if error}
70
+ <p class="text-xs text-danger">{error}</p>
71
+ {/if}
72
+ {#if hint}
73
+ <p class="text-xs text-faint">{hint}</p>
74
+ {/if}
75
+ </div>
@@ -0,0 +1,26 @@
1
+ type TA = HTMLTextAreaElement;
2
+ type TAE = Event & {
3
+ currentTarget: TA;
4
+ };
5
+ type $$ComponentProps = {
6
+ value?: string;
7
+ id?: string;
8
+ name?: string;
9
+ placeholder?: string;
10
+ rows?: number;
11
+ maxlength?: number;
12
+ required?: boolean;
13
+ disabled?: boolean;
14
+ readonly?: boolean;
15
+ resize?: boolean;
16
+ error?: string;
17
+ hint?: string;
18
+ class?: string;
19
+ oninput?: (e: TAE) => void;
20
+ onblur?: (e: FocusEvent & {
21
+ currentTarget: TA;
22
+ }) => void;
23
+ };
24
+ declare const Textarea: import("svelte").Component<$$ComponentProps, {}, "value">;
25
+ type Textarea = ReturnType<typeof Textarea>;
26
+ export default Textarea;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import { Moon, Sun } from '@lucide/svelte';
3
+ import { getTheme, toggleTheme, type Theme } from '../utils/theme.js';
4
+
5
+ interface Props {
6
+ class?: string;
7
+ }
8
+
9
+ let { class: klass = '' }: Props = $props();
10
+
11
+ let theme = $state<Theme>('dark');
12
+
13
+ $effect(() => {
14
+ theme = getTheme();
15
+ });
16
+
17
+ function flip() {
18
+ theme = toggleTheme();
19
+ }
20
+ </script>
21
+
22
+ <button
23
+ type="button"
24
+ onclick={flip}
25
+ aria-label={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
26
+ class="rounded-lg p-2 text-faint transition-colors hover:bg-surface-2 hover:text-fg {klass}"
27
+ >
28
+ {#if theme === 'dark'}
29
+ <Sun size={16} aria-hidden="true" />
30
+ {:else}
31
+ <Moon size={16} aria-hidden="true" />
32
+ {/if}
33
+ </button>
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ class?: string;
3
+ }
4
+ declare const ThemeToggle: import("svelte").Component<Props, {}, "">;
5
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
6
+ export default ThemeToggle;