@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.
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/components/Accordion.svelte +48 -0
- package/dist/components/Accordion.svelte.d.ts +10 -0
- package/dist/components/AccordionItem.svelte +55 -0
- package/dist/components/AccordionItem.svelte.d.ts +10 -0
- package/dist/components/Alert.svelte +60 -0
- package/dist/components/Alert.svelte.d.ts +13 -0
- package/dist/components/Autocomplete.svelte +198 -0
- package/dist/components/Autocomplete.svelte.d.ts +23 -0
- package/dist/components/Avatar.svelte +65 -0
- package/dist/components/Avatar.svelte.d.ts +9 -0
- package/dist/components/AvatarGroup.svelte +45 -0
- package/dist/components/AvatarGroup.svelte.d.ts +14 -0
- package/dist/components/Badge.svelte +48 -0
- package/dist/components/Badge.svelte.d.ts +13 -0
- package/dist/components/Banner.svelte +73 -0
- package/dist/components/Banner.svelte.d.ts +13 -0
- package/dist/components/Breadcrumb.svelte +47 -0
- package/dist/components/Breadcrumb.svelte.d.ts +10 -0
- package/dist/components/Button.svelte +71 -0
- package/dist/components/Button.svelte.d.ts +18 -0
- package/dist/components/ButtonGroup.svelte +33 -0
- package/dist/components/ButtonGroup.svelte.d.ts +9 -0
- package/dist/components/Card.svelte +72 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/Checkbox.svelte +80 -0
- package/dist/components/Checkbox.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +274 -0
- package/dist/components/DatePicker.svelte.d.ts +17 -0
- package/dist/components/Divider.svelte +23 -0
- package/dist/components/Divider.svelte.d.ts +8 -0
- package/dist/components/Drawer.svelte +120 -0
- package/dist/components/Drawer.svelte.d.ts +16 -0
- package/dist/components/Dropdown.svelte +88 -0
- package/dist/components/Dropdown.svelte.d.ts +23 -0
- package/dist/components/EmptyState.svelte +51 -0
- package/dist/components/EmptyState.svelte.d.ts +13 -0
- package/dist/components/FileInput.svelte +102 -0
- package/dist/components/FileInput.svelte.d.ts +15 -0
- package/dist/components/Indicator.svelte +38 -0
- package/dist/components/Indicator.svelte.d.ts +11 -0
- package/dist/components/Input.svelte +60 -0
- package/dist/components/Input.svelte.d.ts +17 -0
- package/dist/components/Kbd.svelte +18 -0
- package/dist/components/Kbd.svelte.d.ts +8 -0
- package/dist/components/Label.svelte +30 -0
- package/dist/components/Label.svelte.d.ts +11 -0
- package/dist/components/Modal.svelte +105 -0
- package/dist/components/Modal.svelte.d.ts +14 -0
- package/dist/components/MultiSelect.svelte +212 -0
- package/dist/components/MultiSelect.svelte.d.ts +22 -0
- package/dist/components/NumberInput.svelte +98 -0
- package/dist/components/NumberInput.svelte.d.ts +15 -0
- package/dist/components/PageHeader.svelte +22 -0
- package/dist/components/PageHeader.svelte.d.ts +10 -0
- package/dist/components/Pagination.svelte +112 -0
- package/dist/components/Pagination.svelte.d.ts +10 -0
- package/dist/components/Progress.svelte +70 -0
- package/dist/components/Progress.svelte.d.ts +15 -0
- package/dist/components/Radio.svelte +65 -0
- package/dist/components/Radio.svelte.d.ts +14 -0
- package/dist/components/RadioGroup.svelte +96 -0
- package/dist/components/RadioGroup.svelte.d.ts +22 -0
- package/dist/components/SearchInput.svelte +81 -0
- package/dist/components/SearchInput.svelte.d.ts +10 -0
- package/dist/components/Select.svelte +69 -0
- package/dist/components/Select.svelte.d.ts +18 -0
- package/dist/components/Skeleton.svelte +20 -0
- package/dist/components/Skeleton.svelte.d.ts +9 -0
- package/dist/components/Spinner.svelte +19 -0
- package/dist/components/Spinner.svelte.d.ts +7 -0
- package/dist/components/Stat.svelte +45 -0
- package/dist/components/Stat.svelte.d.ts +14 -0
- package/dist/components/StepIndicator.svelte +142 -0
- package/dist/components/StepIndicator.svelte.d.ts +13 -0
- package/dist/components/Table.svelte +36 -0
- package/dist/components/Table.svelte.d.ts +11 -0
- package/dist/components/Tabs.svelte +39 -0
- package/dist/components/Tabs.svelte.d.ts +13 -0
- package/dist/components/Tag.svelte +56 -0
- package/dist/components/Tag.svelte.d.ts +11 -0
- package/dist/components/Textarea.svelte +75 -0
- package/dist/components/Textarea.svelte.d.ts +26 -0
- package/dist/components/ThemeToggle.svelte +33 -0
- package/dist/components/ThemeToggle.svelte.d.ts +6 -0
- package/dist/components/Toaster.svelte +54 -0
- package/dist/components/Toaster.svelte.d.ts +18 -0
- package/dist/components/Toggle.svelte +74 -0
- package/dist/components/Toggle.svelte.d.ts +11 -0
- package/dist/components/Tooltip.svelte +44 -0
- package/dist/components/Tooltip.svelte.d.ts +11 -0
- package/dist/components/dialog/ConfirmDialog.svelte +95 -0
- package/dist/components/dialog/ConfirmDialog.svelte.d.ts +8 -0
- package/dist/components/dialog/Dialog.svelte +225 -0
- package/dist/components/dialog/Dialog.svelte.d.ts +9 -0
- package/dist/components/dialog/DialogContainer.svelte +17 -0
- package/dist/components/dialog/DialogContainer.svelte.d.ts +18 -0
- package/dist/components/dialog/dialog-manager.svelte.d.ts +54 -0
- package/dist/components/dialog/dialog-manager.svelte.js +164 -0
- package/dist/components/dialog/types.d.ts +33 -0
- package/dist/components/dialog/types.js +10 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +72 -0
- package/dist/stores/toast.svelte.d.ts +18 -0
- package/dist/stores/toast.svelte.js +28 -0
- package/dist/styles/theme.css +148 -0
- package/dist/utils/cn.d.ts +13 -0
- package/dist/utils/cn.js +24 -0
- package/dist/utils/theme.d.ts +26 -0
- package/dist/utils/theme.js +55 -0
- package/package.json +81 -0
- 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;
|