@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,98 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value?: number;
4
+ min?: number;
5
+ max?: number;
6
+ step?: number;
7
+ disabled?: boolean;
8
+ id?: string;
9
+ name?: string;
10
+ error?: string;
11
+ class?: string;
12
+ onchange?: (v: number) => void;
13
+ }
14
+
15
+ let {
16
+ value = $bindable(0),
17
+ min = undefined,
18
+ max = undefined,
19
+ step = 1,
20
+ disabled = false,
21
+ id = undefined,
22
+ name = undefined,
23
+ error = undefined,
24
+ class: cls = '',
25
+ onchange = undefined,
26
+ }: Props = $props();
27
+
28
+ function dec() {
29
+ const next = value - step;
30
+ if (min !== undefined && next < min) return;
31
+ value = next;
32
+ onchange?.(value);
33
+ }
34
+
35
+ function inc() {
36
+ const next = value + step;
37
+ if (max !== undefined && next > max) return;
38
+ value = next;
39
+ onchange?.(value);
40
+ }
41
+
42
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
43
+ const n = parseFloat(e.currentTarget.value);
44
+ if (!isNaN(n)) {
45
+ value = n;
46
+ onchange?.(n);
47
+ }
48
+ }
49
+
50
+ let canDec = $derived(!(disabled || (min !== undefined && value <= min)));
51
+ let canInc = $derived(!(disabled || (max !== undefined && value >= max)));
52
+ </script>
53
+
54
+ <div class="flex flex-col gap-1 {cls}">
55
+ <div class="flex h-9">
56
+ <button
57
+ type="button"
58
+ onclick={dec}
59
+ disabled={!canDec}
60
+ aria-label="Decrease"
61
+ class="flex w-9 shrink-0 items-center justify-center rounded-l-lg border border-r-0 border-line
62
+ bg-surface-2 text-lg leading-none text-muted transition-colors
63
+ hover:bg-line hover:text-fg
64
+ disabled:cursor-not-allowed disabled:opacity-40">−</button
65
+ >
66
+
67
+ <input
68
+ type="number"
69
+ {id}
70
+ {name}
71
+ {min}
72
+ {max}
73
+ {step}
74
+ {disabled}
75
+ {value}
76
+ oninput={handleInput}
77
+ class="min-w-0 flex-1 border-y bg-surface-2 text-center text-sm text-fg
78
+ focus:outline-none focus:border-brand transition-colors
79
+ disabled:cursor-not-allowed disabled:opacity-50
80
+ [appearance:textfield]
81
+ [&::-webkit-inner-spin-button]:appearance-none
82
+ [&::-webkit-outer-spin-button]:appearance-none
83
+ {error ? 'border-danger' : 'border-line'}"
84
+ />
85
+
86
+ <button
87
+ type="button"
88
+ onclick={inc}
89
+ disabled={!canInc}
90
+ aria-label="Increase"
91
+ class="flex w-9 shrink-0 items-center justify-center rounded-r-lg border border-l-0 border-line
92
+ bg-surface-2 text-lg leading-none text-muted transition-colors
93
+ hover:bg-line hover:text-fg
94
+ disabled:cursor-not-allowed disabled:opacity-40">+</button
95
+ >
96
+ </div>
97
+ {#if error}<p class="text-xs text-danger">{error}</p>{/if}
98
+ </div>
@@ -0,0 +1,15 @@
1
+ interface Props {
2
+ value?: number;
3
+ min?: number;
4
+ max?: number;
5
+ step?: number;
6
+ disabled?: boolean;
7
+ id?: string;
8
+ name?: string;
9
+ error?: string;
10
+ class?: string;
11
+ onchange?: (v: number) => void;
12
+ }
13
+ declare const NumberInput: import("svelte").Component<Props, {}, "value">;
14
+ type NumberInput = ReturnType<typeof NumberInput>;
15
+ export default NumberInput;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ title: string;
6
+ description?: string;
7
+ class?: string;
8
+ actions?: Snippet;
9
+ }
10
+
11
+ let { title, description = undefined, class: klass = '', actions }: Props = $props();
12
+ </script>
13
+
14
+ <header class="mb-8 flex flex-wrap items-start justify-between gap-4 {klass}">
15
+ <div class="min-w-0">
16
+ <h1 class="text-2xl font-bold text-fg">{title}</h1>
17
+ {#if description}<p class="mt-1 text-sm text-muted">{description}</p>{/if}
18
+ </div>
19
+ {#if actions}
20
+ <div class="flex shrink-0 items-center gap-2">{@render actions()}</div>
21
+ {/if}
22
+ </header>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title: string;
4
+ description?: string;
5
+ class?: string;
6
+ actions?: Snippet;
7
+ }
8
+ declare const PageHeader: import("svelte").Component<Props, {}, "">;
9
+ type PageHeader = ReturnType<typeof PageHeader>;
10
+ export default PageHeader;
@@ -0,0 +1,112 @@
1
+ <script lang="ts">
2
+ let {
3
+ page,
4
+ total,
5
+ perPage = 20,
6
+ class: cls = '',
7
+ onchange,
8
+ }: {
9
+ page: number;
10
+ total: number;
11
+ perPage?: number;
12
+ class?: string;
13
+ onchange: (page: number) => void;
14
+ } = $props();
15
+
16
+ let safeTotal = $derived(isFinite(total) && total >= 0 ? total : 0);
17
+ let safePage = $derived(isFinite(page) && page >= 1 ? page : 1);
18
+ let totalPages = $derived(Math.max(1, Math.ceil(safeTotal / perPage)));
19
+ let from = $derived(Math.min((safePage - 1) * perPage + 1, safeTotal));
20
+ let to = $derived(Math.min(safePage * perPage, safeTotal));
21
+
22
+ function pageNumbers(current: number, last: number): (number | '…')[] {
23
+ if (last <= 7) return Array.from({ length: last }, (_, i) => i + 1);
24
+ const pages: (number | '…')[] = [1];
25
+ if (current > 3) pages.push('…');
26
+ for (let i = Math.max(2, current - 1); i <= Math.min(last - 1, current + 1); i++) {
27
+ pages.push(i);
28
+ }
29
+ if (current < last - 2) pages.push('…');
30
+ pages.push(last);
31
+ return pages;
32
+ }
33
+
34
+ let nums = $derived(pageNumbers(safePage, totalPages));
35
+
36
+ const btnBase =
37
+ 'inline-flex items-center justify-center w-7 h-7 rounded text-xs font-medium transition-colors';
38
+ </script>
39
+
40
+ {#if totalPages > 1}
41
+ <div class="flex items-center gap-3 {cls}">
42
+ <span class="text-xs text-faint shrink-0">
43
+ {total === 0 ? 'No results' : `${from}–${to} of ${total}`}
44
+ </span>
45
+
46
+ <div class="flex items-center gap-0.5 ml-auto">
47
+ <button
48
+ type="button"
49
+ disabled={safePage <= 1}
50
+ onclick={() => onchange(safePage - 1)}
51
+ aria-label="Previous page"
52
+ class="{btnBase} text-muted hover:text-fg hover:bg-surface-2
53
+ disabled:opacity-30 disabled:cursor-not-allowed"
54
+ >
55
+ <svg
56
+ width="14"
57
+ height="14"
58
+ viewBox="0 0 24 24"
59
+ fill="none"
60
+ stroke="currentColor"
61
+ stroke-width="2"
62
+ stroke-linecap="round"
63
+ stroke-linejoin="round"
64
+ aria-hidden="true"
65
+ >
66
+ <path d="M15 18l-6-6 6-6" />
67
+ </svg>
68
+ </button>
69
+
70
+ {#each nums as n, i (i)}
71
+ {#if n === '…'}
72
+ <span class="w-7 text-center text-xs text-faint">…</span>
73
+ {:else}
74
+ <button
75
+ type="button"
76
+ onclick={() => onchange(n as number)}
77
+ aria-current={safePage === n ? 'page' : undefined}
78
+ class="{btnBase}
79
+ {safePage === n
80
+ ? 'bg-brand text-ink'
81
+ : 'text-muted hover:text-fg hover:bg-surface-2'}"
82
+ >
83
+ {n}
84
+ </button>
85
+ {/if}
86
+ {/each}
87
+
88
+ <button
89
+ type="button"
90
+ disabled={safePage >= totalPages}
91
+ onclick={() => onchange(safePage + 1)}
92
+ aria-label="Next page"
93
+ class="{btnBase} text-muted hover:text-fg hover:bg-surface-2
94
+ disabled:opacity-30 disabled:cursor-not-allowed"
95
+ >
96
+ <svg
97
+ width="14"
98
+ height="14"
99
+ viewBox="0 0 24 24"
100
+ fill="none"
101
+ stroke="currentColor"
102
+ stroke-width="2"
103
+ stroke-linecap="round"
104
+ stroke-linejoin="round"
105
+ aria-hidden="true"
106
+ >
107
+ <path d="M9 18l6-6-6-6" />
108
+ </svg>
109
+ </button>
110
+ </div>
111
+ </div>
112
+ {/if}
@@ -0,0 +1,10 @@
1
+ type $$ComponentProps = {
2
+ page: number;
3
+ total: number;
4
+ perPage?: number;
5
+ class?: string;
6
+ onchange: (page: number) => void;
7
+ };
8
+ declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type Pagination = ReturnType<typeof Pagination>;
10
+ export default Pagination;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ type Tone = 'brand' | 'violet' | 'success' | 'warn' | 'danger';
3
+ type Size = 'xs' | 'sm' | 'md';
4
+
5
+ interface Props {
6
+ value: number;
7
+ max?: number;
8
+ tone?: Tone;
9
+ size?: Size;
10
+ label?: string;
11
+ showValue?: boolean;
12
+ animated?: boolean;
13
+ class?: string;
14
+ }
15
+
16
+ let {
17
+ value,
18
+ max = 100,
19
+ tone = 'brand',
20
+ size = 'sm',
21
+ label = undefined,
22
+ showValue = false,
23
+ animated = false,
24
+ class: cls = '',
25
+ }: Props = $props();
26
+
27
+ let pct = $derived(
28
+ isFinite(value) && isFinite(max) && max > 0
29
+ ? Math.min(100, Math.max(0, (value / max) * 100))
30
+ : 0,
31
+ );
32
+
33
+ const tones: Record<Tone, string> = {
34
+ brand: 'bg-brand',
35
+ violet: 'bg-violet',
36
+ success: 'bg-success',
37
+ warn: 'bg-warn',
38
+ danger: 'bg-danger',
39
+ };
40
+
41
+ const sizes: Record<Size, string> = {
42
+ xs: 'h-1',
43
+ sm: 'h-1.5',
44
+ md: 'h-2.5',
45
+ };
46
+ </script>
47
+
48
+ <div class={cls}>
49
+ {#if label || showValue}
50
+ <div class="flex items-center justify-between mb-1.5">
51
+ {#if label}<span class="text-xs font-medium text-muted">{label}</span>{/if}
52
+ {#if showValue}<span class="text-xs tabular-nums text-faint">{Math.round(pct)}%</span>{/if}
53
+ </div>
54
+ {/if}
55
+ <div
56
+ class="w-full overflow-hidden rounded-full bg-surface-2 {sizes[size]}"
57
+ role="progressbar"
58
+ aria-valuenow={value}
59
+ aria-valuemin={0}
60
+ aria-valuemax={max}
61
+ aria-label={label}
62
+ >
63
+ <div
64
+ class="h-full rounded-full transition-[width] duration-500
65
+ {tones[tone]}
66
+ {animated ? 'animate-pulse' : ''}"
67
+ style="width: {pct}%"
68
+ ></div>
69
+ </div>
70
+ </div>
@@ -0,0 +1,15 @@
1
+ type Tone = 'brand' | 'violet' | 'success' | 'warn' | 'danger';
2
+ type Size = 'xs' | 'sm' | 'md';
3
+ interface Props {
4
+ value: number;
5
+ max?: number;
6
+ tone?: Tone;
7
+ size?: Size;
8
+ label?: string;
9
+ showValue?: boolean;
10
+ animated?: boolean;
11
+ class?: string;
12
+ }
13
+ declare const Progress: import("svelte").Component<Props, {}, "">;
14
+ type Progress = ReturnType<typeof Progress>;
15
+ export default Progress;
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value: string;
4
+ group?: string;
5
+ label?: string;
6
+ hint?: string;
7
+ required?: boolean;
8
+ disabled?: boolean;
9
+ id?: string;
10
+ name?: string;
11
+ onchange?: (value: string) => void;
12
+ }
13
+
14
+ let {
15
+ value,
16
+ group = $bindable(''),
17
+ label = undefined,
18
+ hint = undefined,
19
+ required = false,
20
+ disabled = false,
21
+ id = undefined,
22
+ name = undefined,
23
+ onchange = undefined,
24
+ }: Props = $props();
25
+
26
+ function handleChange() {
27
+ group = value;
28
+ onchange?.(value);
29
+ }
30
+ </script>
31
+
32
+ <label
33
+ class="inline-flex cursor-pointer select-none items-start gap-2.5
34
+ {disabled ? 'cursor-not-allowed opacity-50' : ''}"
35
+ >
36
+ <span class="relative mt-0.5 flex shrink-0 items-center justify-center">
37
+ <input
38
+ type="radio"
39
+ {id}
40
+ {name}
41
+ {value}
42
+ {required}
43
+ {disabled}
44
+ checked={group === value}
45
+ onchange={handleChange}
46
+ class="peer sr-only"
47
+ />
48
+ <span
49
+ class="flex h-4 w-4 items-center justify-center rounded-full border-2 transition-colors
50
+ {group === value
51
+ ? 'border-brand bg-surface-2'
52
+ : 'border-line bg-surface-2 peer-focus-visible:border-brand'}"
53
+ >
54
+ {#if group === value}
55
+ <span class="h-2 w-2 rounded-full bg-brand"></span>
56
+ {/if}
57
+ </span>
58
+ </span>
59
+ {#if label || hint}
60
+ <span class="flex flex-col gap-0.5">
61
+ {#if label}<span class="text-sm text-fg">{label}</span>{/if}
62
+ {#if hint}<span class="text-xs text-faint">{hint}</span>{/if}
63
+ </span>
64
+ {/if}
65
+ </label>
@@ -0,0 +1,14 @@
1
+ interface Props {
2
+ value: string;
3
+ group?: string;
4
+ label?: string;
5
+ hint?: string;
6
+ required?: boolean;
7
+ disabled?: boolean;
8
+ id?: string;
9
+ name?: string;
10
+ onchange?: (value: string) => void;
11
+ }
12
+ declare const Radio: import("svelte").Component<Props, {}, "group">;
13
+ type Radio = ReturnType<typeof Radio>;
14
+ export default Radio;
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ // RadioGroup renders a managed set of radio options bound to a single value.
3
+ // Matches the visual of Radio.svelte; use this when you have a fixed option list.
4
+ interface Option {
5
+ value: string;
6
+ label: string;
7
+ hint?: string;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ let {
12
+ value = $bindable(''),
13
+ options,
14
+ name = undefined,
15
+ label = undefined,
16
+ hint = undefined,
17
+ error = undefined,
18
+ required = false,
19
+ disabled = false,
20
+ orientation = 'vertical',
21
+ class: cls = '',
22
+ onchange = undefined,
23
+ }: {
24
+ value?: string;
25
+ options: Option[];
26
+ name?: string;
27
+ label?: string;
28
+ hint?: string;
29
+ error?: string;
30
+ required?: boolean;
31
+ disabled?: boolean;
32
+ orientation?: 'vertical' | 'horizontal';
33
+ class?: string;
34
+ onchange?: (value: string) => void;
35
+ } = $props();
36
+
37
+ const fallbackName = `rg-${Math.random().toString(36).slice(2, 9)}`;
38
+ const groupName = $derived(name ?? fallbackName);
39
+
40
+ function select(opt: Option) {
41
+ if (disabled || opt.disabled) return;
42
+ value = opt.value;
43
+ onchange?.(opt.value);
44
+ }
45
+ </script>
46
+
47
+ <fieldset class="flex flex-col gap-2 {cls}" {disabled}>
48
+ {#if label}
49
+ <legend class="text-sm font-medium text-fg mb-0.5">
50
+ {label}{#if required}<span class="text-danger ml-0.5">*</span>{/if}
51
+ </legend>
52
+ {/if}
53
+
54
+ <div
55
+ class="flex gap-x-5 gap-y-2 {orientation === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col'}"
56
+ >
57
+ {#each options as opt (opt.value)}
58
+ <label
59
+ class="inline-flex cursor-pointer select-none items-start gap-2.5
60
+ {disabled || opt.disabled ? 'cursor-not-allowed opacity-50' : ''}"
61
+ >
62
+ <span class="relative mt-0.5 flex shrink-0 items-center justify-center">
63
+ <input
64
+ type="radio"
65
+ name={groupName}
66
+ value={opt.value}
67
+ disabled={disabled || opt.disabled}
68
+ checked={value === opt.value}
69
+ onchange={() => select(opt)}
70
+ class="peer sr-only"
71
+ />
72
+ <span
73
+ class="flex h-4 w-4 items-center justify-center rounded-full border-2 transition-colors
74
+ {value === opt.value
75
+ ? 'border-brand bg-surface-2'
76
+ : 'border-line bg-surface-2 peer-focus-visible:border-brand'}"
77
+ >
78
+ {#if value === opt.value}
79
+ <span class="h-2 w-2 rounded-full bg-brand"></span>
80
+ {/if}
81
+ </span>
82
+ </span>
83
+ <span class="flex flex-col gap-0.5">
84
+ <span class="text-sm text-fg">{opt.label}</span>
85
+ {#if opt.hint}<span class="text-xs text-faint">{opt.hint}</span>{/if}
86
+ </span>
87
+ </label>
88
+ {/each}
89
+ </div>
90
+
91
+ {#if error}
92
+ <p class="text-xs text-danger">{error}</p>
93
+ {:else if hint}
94
+ <p class="text-xs text-faint">{hint}</p>
95
+ {/if}
96
+ </fieldset>
@@ -0,0 +1,22 @@
1
+ interface Option {
2
+ value: string;
3
+ label: string;
4
+ hint?: string;
5
+ disabled?: boolean;
6
+ }
7
+ type $$ComponentProps = {
8
+ value?: string;
9
+ options: Option[];
10
+ name?: string;
11
+ label?: string;
12
+ hint?: string;
13
+ error?: string;
14
+ required?: boolean;
15
+ disabled?: boolean;
16
+ orientation?: 'vertical' | 'horizontal';
17
+ class?: string;
18
+ onchange?: (value: string) => void;
19
+ };
20
+ declare const RadioGroup: import("svelte").Component<$$ComponentProps, {}, "value">;
21
+ type RadioGroup = ReturnType<typeof RadioGroup>;
22
+ export default RadioGroup;
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value?: string;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ class?: string;
7
+ oninput?: (value: string) => void;
8
+ }
9
+
10
+ let {
11
+ value = $bindable(''),
12
+ placeholder = 'Search…',
13
+ disabled = false,
14
+ class: cls = '',
15
+ oninput = undefined,
16
+ }: Props = $props();
17
+
18
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
19
+ value = e.currentTarget.value;
20
+ oninput?.(value);
21
+ }
22
+
23
+ function clear() {
24
+ value = '';
25
+ oninput?.('');
26
+ }
27
+ </script>
28
+
29
+ <div class="relative {cls}">
30
+ <svg
31
+ width="15"
32
+ height="15"
33
+ viewBox="0 0 24 24"
34
+ fill="none"
35
+ stroke="currentColor"
36
+ stroke-width="2"
37
+ stroke-linecap="round"
38
+ stroke-linejoin="round"
39
+ class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-faint"
40
+ aria-hidden="true"
41
+ >
42
+ <circle cx="11" cy="11" r="8" /><path d="M21 21l-4.35-4.35" />
43
+ </svg>
44
+
45
+ <input
46
+ type="search"
47
+ {placeholder}
48
+ {disabled}
49
+ {value}
50
+ oninput={handleInput}
51
+ class="w-full rounded-lg border border-line bg-surface-2
52
+ py-2 pl-9 text-sm text-fg placeholder:text-faint
53
+ focus:border-brand focus:outline-none transition-colors
54
+ disabled:cursor-not-allowed disabled:opacity-50
55
+ [&::-webkit-search-cancel-button]:appearance-none
56
+ {value ? 'pr-8' : 'pr-3'}"
57
+ />
58
+
59
+ {#if value}
60
+ <button
61
+ type="button"
62
+ onclick={clear}
63
+ aria-label="Clear search"
64
+ class="absolute right-2.5 top-1/2 -translate-y-1/2 text-faint
65
+ transition-colors hover:text-fg"
66
+ >
67
+ <svg
68
+ width="14"
69
+ height="14"
70
+ viewBox="0 0 24 24"
71
+ fill="none"
72
+ stroke="currentColor"
73
+ stroke-width="2"
74
+ stroke-linecap="round"
75
+ aria-hidden="true"
76
+ >
77
+ <path d="M18 6L6 18M6 6l12 12" />
78
+ </svg>
79
+ </button>
80
+ {/if}
81
+ </div>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ value?: string;
3
+ placeholder?: string;
4
+ disabled?: boolean;
5
+ class?: string;
6
+ oninput?: (value: string) => void;
7
+ }
8
+ declare const SearchInput: import("svelte").Component<Props, {}, "value">;
9
+ type SearchInput = ReturnType<typeof SearchInput>;
10
+ export default SearchInput;