@lyeve-labs/ui-kit 0.9.1 → 0.10.1
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/dist/components/AccordionItem.svelte +1 -1
- package/dist/components/Alert.svelte +42 -15
- package/dist/components/Alert.svelte.d.ts +2 -2
- package/dist/components/Autocomplete.svelte +32 -12
- package/dist/components/Avatar.svelte +4 -8
- package/dist/components/Badge.svelte +4 -4
- package/dist/components/Badge.svelte.d.ts +2 -2
- package/dist/components/Banner.svelte +10 -9
- package/dist/components/Banner.svelte.d.ts +2 -2
- package/dist/components/Breadcrumb.svelte +4 -1
- package/dist/components/Button.svelte +4 -2
- package/dist/components/Button.svelte.d.ts +1 -0
- package/dist/components/Card.svelte +1 -1
- package/dist/components/Checkbox.svelte +7 -4
- package/dist/components/Checkbox.svelte.d.ts +1 -0
- package/dist/components/DatePicker.svelte +19 -11
- package/dist/components/Drawer.svelte +13 -2
- package/dist/components/Dropdown.svelte +1 -1
- package/dist/components/FileInput.svelte +14 -5
- package/dist/components/Indicator.svelte +3 -3
- package/dist/components/Indicator.svelte.d.ts +2 -2
- package/dist/components/Input.svelte +17 -8
- package/dist/components/Modal.svelte +11 -2
- package/dist/components/MultiSelect.svelte +33 -12
- package/dist/components/NumberInput.svelte +68 -13
- package/dist/components/NumberInput.svelte.d.ts +3 -0
- package/dist/components/Pagination.svelte +1 -1
- package/dist/components/Progress.svelte +4 -3
- package/dist/components/Progress.svelte.d.ts +2 -2
- package/dist/components/Radio.svelte +6 -3
- package/dist/components/Radio.svelte.d.ts +1 -0
- package/dist/components/RadioGroup.svelte +6 -5
- package/dist/components/SearchInput.svelte +64 -49
- package/dist/components/SearchInput.svelte.d.ts +3 -0
- package/dist/components/Select.svelte +29 -12
- package/dist/components/Select.svelte.d.ts +2 -0
- package/dist/components/StepIndicator.svelte +7 -5
- package/dist/components/Table.svelte +3 -1
- package/dist/components/Tabs.svelte +1 -1
- package/dist/components/Tag.svelte +3 -3
- package/dist/components/Tag.svelte.d.ts +2 -2
- package/dist/components/Textarea.svelte +28 -16
- package/dist/components/Textarea.svelte.d.ts +1 -0
- package/dist/components/ThemeToggle.svelte +1 -1
- package/dist/components/Toaster.svelte +34 -12
- package/dist/components/Toggle.svelte +6 -3
- package/dist/components/Toggle.svelte.d.ts +1 -0
- package/dist/components/dialog/Dialog.svelte +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/internal/field.d.ts +48 -0
- package/dist/internal/field.js +62 -0
- package/dist/internal/tone.d.ts +27 -0
- package/dist/internal/tone.js +29 -0
- package/dist/stores/toast.svelte.d.ts +6 -1
- package/dist/stores/toast.svelte.js +1 -1
- package/dist/styles/theme.css +30 -4
- package/dist/utils/broken-image.d.ts +21 -0
- package/dist/utils/broken-image.js +40 -0
- package/package.json +7 -6
- package/src/lib/styles/theme.css +30 -4
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
onclick={() => ctx?.toggle(id)}
|
|
30
30
|
aria-expanded={open}
|
|
31
31
|
class="flex w-full items-center justify-between px-5 py-4 text-left text-sm font-medium
|
|
32
|
-
text-fg transition-colors hover:bg-surface-2/50"
|
|
32
|
+
text-fg transition-colors duration-150 hover:bg-surface-2/50"
|
|
33
33
|
>
|
|
34
34
|
<span>{title}</span>
|
|
35
35
|
<svg
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
TONE_GLYPH,
|
|
5
|
+
statusTone,
|
|
6
|
+
type StatusTone,
|
|
7
|
+
type StatusToneInput,
|
|
8
|
+
} from '../internal/tone.js';
|
|
5
9
|
|
|
6
10
|
interface Props {
|
|
7
|
-
tone?:
|
|
11
|
+
tone?: StatusToneInput;
|
|
8
12
|
title?: string;
|
|
9
13
|
dismissible?: boolean;
|
|
10
14
|
ondismiss?: () => void;
|
|
@@ -13,7 +17,7 @@
|
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
let {
|
|
16
|
-
tone = '
|
|
20
|
+
tone = 'brand',
|
|
17
21
|
title = undefined,
|
|
18
22
|
dismissible = false,
|
|
19
23
|
ondismiss = undefined,
|
|
@@ -21,25 +25,39 @@
|
|
|
21
25
|
children,
|
|
22
26
|
}: Props = $props();
|
|
23
27
|
|
|
24
|
-
const tones: Record<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const tones: Record<StatusTone, { wrap: string; icon: string }> = {
|
|
29
|
+
neutral: { wrap: 'bg-surface-2 border-line', icon: 'text-muted' },
|
|
30
|
+
brand: { wrap: 'bg-brand/8 border-brand/25', icon: 'text-brand' },
|
|
31
|
+
success: { wrap: 'bg-success/8 border-success/25', icon: 'text-success' },
|
|
32
|
+
warn: { wrap: 'bg-warn/8 border-warn/25', icon: 'text-warn' },
|
|
33
|
+
danger: { wrap: 'bg-danger/8 border-danger/25', icon: 'text-danger' },
|
|
29
34
|
};
|
|
35
|
+
|
|
36
|
+
const t = $derived(statusTone(tone));
|
|
30
37
|
</script>
|
|
31
38
|
|
|
32
39
|
<div
|
|
33
|
-
class="flex items-start gap-3 rounded-lg border px-4 py-3 {tones[
|
|
40
|
+
class="flex items-start gap-3 rounded-lg border px-4 py-3 {tones[t].wrap} {klass}"
|
|
34
41
|
role="alert"
|
|
35
42
|
>
|
|
36
43
|
<span
|
|
37
|
-
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-current
|
|
38
|
-
|
|
44
|
+
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-current {tones[
|
|
45
|
+
t
|
|
39
46
|
].icon}"
|
|
40
47
|
aria-hidden="true"
|
|
41
48
|
>
|
|
42
|
-
|
|
49
|
+
<svg
|
|
50
|
+
width="11"
|
|
51
|
+
height="11"
|
|
52
|
+
viewBox="0 0 24 24"
|
|
53
|
+
fill="none"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
stroke-width="2.5"
|
|
56
|
+
stroke-linecap="round"
|
|
57
|
+
stroke-linejoin="round"
|
|
58
|
+
>
|
|
59
|
+
<path d={TONE_GLYPH[t]} />
|
|
60
|
+
</svg>
|
|
43
61
|
</span>
|
|
44
62
|
<div class="flex-1 min-w-0">
|
|
45
63
|
{#if title}<p class="text-sm font-semibold text-fg">{title}</p>{/if}
|
|
@@ -51,10 +69,19 @@
|
|
|
51
69
|
<button
|
|
52
70
|
type="button"
|
|
53
71
|
onclick={ondismiss}
|
|
54
|
-
class="
|
|
72
|
+
class="shrink-0 text-faint transition-colors duration-150 hover:text-fg"
|
|
55
73
|
aria-label="Dismiss"
|
|
56
74
|
>
|
|
57
|
-
|
|
75
|
+
<svg
|
|
76
|
+
width="16"
|
|
77
|
+
height="16"
|
|
78
|
+
viewBox="0 0 24 24"
|
|
79
|
+
fill="none"
|
|
80
|
+
stroke="currentColor"
|
|
81
|
+
stroke-width="2"
|
|
82
|
+
stroke-linecap="round"
|
|
83
|
+
aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg
|
|
84
|
+
>
|
|
58
85
|
</button>
|
|
59
86
|
{/if}
|
|
60
87
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
|
|
2
|
+
import { type StatusToneInput } from '../internal/tone.js';
|
|
3
3
|
interface Props {
|
|
4
|
-
tone?:
|
|
4
|
+
tone?: StatusToneInput;
|
|
5
5
|
title?: string;
|
|
6
6
|
dismissible?: boolean;
|
|
7
7
|
ondismiss?: () => void;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
CONTROL_BASE,
|
|
4
|
+
FIELD_ERROR,
|
|
5
|
+
FIELD_HINT,
|
|
6
|
+
FIELD_LABEL,
|
|
7
|
+
FIELD_WRAP,
|
|
8
|
+
controlBorder,
|
|
9
|
+
describedBy,
|
|
10
|
+
} from '../internal/field.js';
|
|
2
11
|
// Autocomplete (combobox): type to filter a list, navigate with ↑/↓, select with
|
|
3
12
|
// Enter. Binds `value` to the chosen option's value; shows its label in the input.
|
|
4
13
|
interface Option {
|
|
@@ -111,10 +120,10 @@
|
|
|
111
120
|
});
|
|
112
121
|
</script>
|
|
113
122
|
|
|
114
|
-
<div class="
|
|
123
|
+
<div class="{FIELD_WRAP} {cls}" bind:this={containerEl}>
|
|
115
124
|
{#if label}
|
|
116
|
-
<label for={id} class=
|
|
117
|
-
{label}{#if required}<span class="text-danger ml-0.5">*</span>{/if}
|
|
125
|
+
<label for={id} class={FIELD_LABEL}>
|
|
126
|
+
{label}{#if required}<span class="text-danger ml-0.5" aria-label="required">*</span>{/if}
|
|
118
127
|
</label>
|
|
119
128
|
{/if}
|
|
120
129
|
|
|
@@ -135,19 +144,30 @@
|
|
|
135
144
|
onkeydown={onKeydown}
|
|
136
145
|
onfocus={() => (open = true)}
|
|
137
146
|
onblur={() => setTimeout(() => (open = false), 150)}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{error ? 'border-danger focus:border-danger' : 'border-line focus:border-brand'}"
|
|
147
|
+
aria-invalid={error ? 'true' : undefined}
|
|
148
|
+
aria-describedby={describedBy(id, error, hint)}
|
|
149
|
+
class="{CONTROL_BASE} {controlBorder(!!error)} {allowClear && value ? 'pr-8' : 'pr-3'}"
|
|
142
150
|
/>
|
|
143
151
|
{#if allowClear && value && !disabled}
|
|
144
152
|
<button
|
|
145
153
|
type="button"
|
|
146
154
|
aria-label="Clear"
|
|
147
155
|
onclick={clear}
|
|
148
|
-
class="absolute right-2.5 top-1/2 -translate-y-1/2 text-faint hover:text-fg
|
|
149
|
-
>×</button
|
|
156
|
+
class="absolute right-2.5 top-1/2 -translate-y-1/2 text-faint transition-colors duration-150 hover:text-fg"
|
|
150
157
|
>
|
|
158
|
+
<svg
|
|
159
|
+
width="12"
|
|
160
|
+
height="12"
|
|
161
|
+
viewBox="0 0 24 24"
|
|
162
|
+
fill="none"
|
|
163
|
+
stroke="currentColor"
|
|
164
|
+
stroke-width="2.5"
|
|
165
|
+
stroke-linecap="round"
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
>
|
|
168
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
169
|
+
</svg>
|
|
170
|
+
</button>
|
|
151
171
|
{/if}
|
|
152
172
|
|
|
153
173
|
{#if open}
|
|
@@ -165,7 +185,7 @@
|
|
|
165
185
|
disabled={opt.disabled}
|
|
166
186
|
onmouseenter={() => (active = i)}
|
|
167
187
|
onclick={() => choose(opt)}
|
|
168
|
-
class="w-full flex items-center justify-between px-3 py-2 text-sm text-left transition-colors
|
|
188
|
+
class="w-full flex items-center justify-between px-3 py-2 text-sm text-left transition-colors duration-150
|
|
169
189
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
170
190
|
{i === active ? 'bg-surface-2' : ''}
|
|
171
191
|
{opt.value === value ? 'text-brand' : 'text-fg'}"
|
|
@@ -191,8 +211,8 @@
|
|
|
191
211
|
</div>
|
|
192
212
|
|
|
193
213
|
{#if error}
|
|
194
|
-
<p
|
|
214
|
+
<p id={id ? `${id}-error` : undefined} class={FIELD_ERROR}>{error}</p>
|
|
195
215
|
{:else if hint}
|
|
196
|
-
<p
|
|
216
|
+
<p id={id ? `${id}-hint` : undefined} class={FIELD_HINT}>{hint}</p>
|
|
197
217
|
{/if}
|
|
198
218
|
</div>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { brokenImage } from '../utils/broken-image.js';
|
|
3
|
+
|
|
2
4
|
let {
|
|
3
5
|
name,
|
|
4
6
|
src,
|
|
@@ -49,16 +51,10 @@
|
|
|
49
51
|
class="inline-flex items-center justify-center rounded-full font-semibold shrink-0
|
|
50
52
|
{s.wrap} {hue} text-ink {cls}"
|
|
51
53
|
title={name}
|
|
54
|
+
use:brokenImage={() => (imgError = true)}
|
|
52
55
|
>
|
|
53
56
|
{#if src && !imgError}
|
|
54
|
-
<img
|
|
55
|
-
{src}
|
|
56
|
-
alt={name}
|
|
57
|
-
class="w-full h-full rounded-full object-cover"
|
|
58
|
-
onerror={() => {
|
|
59
|
-
imgError = true;
|
|
60
|
-
}}
|
|
61
|
-
/>
|
|
57
|
+
<img {src} alt={name} class="w-full h-full rounded-full object-cover" />
|
|
62
58
|
{:else}
|
|
63
59
|
<span class={s.text}>{initials}</span>
|
|
64
60
|
{/if}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { AccentTone } from '../internal/tone.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
|
|
4
|
-
type Tone = 'neutral' | 'brand' | 'violet' | 'success' | 'warn' | 'danger';
|
|
5
5
|
type Size = 'sm' | 'md';
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
|
-
tone?:
|
|
8
|
+
tone?: AccentTone;
|
|
9
9
|
size?: Size;
|
|
10
10
|
dot?: boolean;
|
|
11
11
|
class?: string;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
let { tone = 'neutral', size = 'sm', dot = false, class: klass = '', children }: Props = $props();
|
|
16
16
|
|
|
17
|
-
const tones: Record<
|
|
17
|
+
const tones: Record<AccentTone, string> = {
|
|
18
18
|
neutral: 'bg-surface-2 text-muted border-line',
|
|
19
19
|
brand: 'bg-brand/10 text-brand border-brand/20',
|
|
20
20
|
violet: 'bg-violet/10 text-violet border-violet/20',
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
danger: 'bg-danger/10 text-danger border-danger/20',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const dotColor: Record<
|
|
26
|
+
const dotColor: Record<AccentTone, string> = {
|
|
27
27
|
neutral: 'bg-muted',
|
|
28
28
|
brand: 'bg-brand',
|
|
29
29
|
violet: 'bg-violet',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { AccentTone } from '../internal/tone.js';
|
|
1
2
|
import type { Snippet } from 'svelte';
|
|
2
|
-
type Tone = 'neutral' | 'brand' | 'violet' | 'success' | 'warn' | 'danger';
|
|
3
3
|
type Size = 'sm' | 'md';
|
|
4
4
|
interface Props {
|
|
5
|
-
tone?:
|
|
5
|
+
tone?: AccentTone;
|
|
6
6
|
size?: Size;
|
|
7
7
|
dot?: boolean;
|
|
8
8
|
class?: string;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { statusTone, type StatusTone, type StatusToneInput } from '../internal/tone.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
|
|
4
|
-
type Tone = 'info' | 'success' | 'warn' | 'danger' | 'neutral';
|
|
5
|
-
|
|
6
5
|
interface Props {
|
|
7
|
-
tone?:
|
|
6
|
+
tone?: StatusToneInput;
|
|
8
7
|
dismissible?: boolean;
|
|
9
8
|
ondismiss?: () => void;
|
|
10
9
|
class?: string;
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
let {
|
|
16
|
-
tone = '
|
|
15
|
+
tone = 'brand',
|
|
17
16
|
dismissible = false,
|
|
18
17
|
ondismiss = undefined,
|
|
19
18
|
class: cls = '',
|
|
@@ -28,19 +27,21 @@
|
|
|
28
27
|
ondismiss?.();
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
const tones: Record<
|
|
32
|
-
|
|
30
|
+
const tones: Record<StatusTone, string> = {
|
|
31
|
+
neutral: 'bg-surface-2 border-line',
|
|
32
|
+
brand: 'bg-brand/10 border-brand/20',
|
|
33
33
|
success: 'bg-success/10 border-success/20',
|
|
34
34
|
warn: 'bg-warn/10 border-warn/20',
|
|
35
35
|
danger: 'bg-danger/10 border-danger/20',
|
|
36
|
-
neutral: 'bg-surface-2 border-line',
|
|
37
36
|
};
|
|
37
|
+
|
|
38
|
+
const t = $derived(statusTone(tone));
|
|
38
39
|
</script>
|
|
39
40
|
|
|
40
41
|
{#if visible}
|
|
41
42
|
<div
|
|
42
43
|
role="status"
|
|
43
|
-
class="relative flex items-center gap-3 border-b px-4 py-3 text-sm {tones[
|
|
44
|
+
class="relative flex items-center gap-3 border-b px-4 py-3 text-sm {tones[t]} {cls}"
|
|
44
45
|
>
|
|
45
46
|
<div class="flex flex-1 items-center justify-center gap-3 text-fg">
|
|
46
47
|
{@render children()}
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
type="button"
|
|
56
57
|
onclick={dismiss}
|
|
57
58
|
aria-label="Dismiss"
|
|
58
|
-
class="shrink-0 text-faint transition-colors hover:text-fg"
|
|
59
|
+
class="shrink-0 text-faint transition-colors duration-150 hover:text-fg"
|
|
59
60
|
>
|
|
60
61
|
<svg
|
|
61
62
|
width="16"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { type StatusToneInput } from '../internal/tone.js';
|
|
1
2
|
import type { Snippet } from 'svelte';
|
|
2
|
-
type Tone = 'info' | 'success' | 'warn' | 'danger' | 'neutral';
|
|
3
3
|
interface Props {
|
|
4
|
-
tone?:
|
|
4
|
+
tone?: StatusToneInput;
|
|
5
5
|
dismissible?: boolean;
|
|
6
6
|
ondismiss?: () => void;
|
|
7
7
|
class?: string;
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
{/if}
|
|
33
33
|
|
|
34
34
|
{#if item.href && i < items.length - 1}
|
|
35
|
-
<a
|
|
35
|
+
<a
|
|
36
|
+
href={safeUrl(item.href)}
|
|
37
|
+
class="text-muted hover:text-fg transition-colors duration-150 truncate"
|
|
38
|
+
>
|
|
36
39
|
{item.label}
|
|
37
40
|
</a>
|
|
38
41
|
{:else}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
type?: 'button' | 'submit' | 'reset';
|
|
14
14
|
href?: string;
|
|
15
15
|
full?: boolean;
|
|
16
|
+
class?: string;
|
|
16
17
|
onclick?: (e: MouseEvent) => void;
|
|
17
18
|
children: Snippet;
|
|
18
19
|
[key: string]: unknown;
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
type = 'button',
|
|
27
28
|
href = undefined,
|
|
28
29
|
full = false,
|
|
30
|
+
class: klass = '',
|
|
29
31
|
onclick,
|
|
30
32
|
children,
|
|
31
33
|
...rest
|
|
@@ -49,9 +51,9 @@
|
|
|
49
51
|
|
|
50
52
|
const spinnerSize = $derived(size === 'lg' ? 18 : 15);
|
|
51
53
|
const cls = $derived(
|
|
52
|
-
`inline-flex items-center justify-center font-medium transition-
|
|
54
|
+
`inline-flex items-center justify-center font-medium transition-colors duration-150 ` +
|
|
53
55
|
`disabled:opacity-50 disabled:cursor-not-allowed select-none ` +
|
|
54
|
-
`${full ? 'w-full' : ''} ${variants[variant]} ${sizes[size]}`,
|
|
56
|
+
`${full ? 'w-full' : ''} ${variants[variant]} ${sizes[size]} ${klass}`,
|
|
55
57
|
);
|
|
56
58
|
|
|
57
59
|
// Reject javascript: and data: URIs - only allow standard schemes and relative URLs.
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
so the element is interactive; the compiler can't narrow the dynamic role. -->
|
|
42
42
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
43
43
|
<div
|
|
44
|
-
class="bg-surface border border-line rounded-xl overflow-hidden transition-colors {hover
|
|
44
|
+
class="bg-surface border border-line rounded-xl overflow-hidden transition-colors duration-150 {hover
|
|
45
45
|
? 'hover:border-line/0 hover:ring-1 hover:ring-brand/30'
|
|
46
46
|
: ''} {onclick ? 'cursor-pointer' : ''} {klass}"
|
|
47
47
|
role={onclick ? 'button' : undefined}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { FIELD_HINT } from '../internal/field.js';
|
|
2
3
|
let {
|
|
3
4
|
checked = $bindable(false),
|
|
4
5
|
label,
|
|
@@ -8,6 +9,7 @@
|
|
|
8
9
|
id,
|
|
9
10
|
name,
|
|
10
11
|
value,
|
|
12
|
+
class: cls = '',
|
|
11
13
|
onchange,
|
|
12
14
|
}: {
|
|
13
15
|
checked?: boolean;
|
|
@@ -18,6 +20,7 @@
|
|
|
18
20
|
id?: string;
|
|
19
21
|
name?: string;
|
|
20
22
|
value?: string;
|
|
23
|
+
class?: string;
|
|
21
24
|
onchange?: (checked: boolean) => void;
|
|
22
25
|
} = $props();
|
|
23
26
|
|
|
@@ -28,9 +31,9 @@
|
|
|
28
31
|
</script>
|
|
29
32
|
|
|
30
33
|
<label
|
|
31
|
-
class="flex items-start gap-2.5 cursor-pointer select-none {disabled
|
|
34
|
+
class="inline-flex items-start gap-2.5 cursor-pointer select-none {disabled
|
|
32
35
|
? 'opacity-50 cursor-not-allowed'
|
|
33
|
-
: ''}"
|
|
36
|
+
: ''} {cls}"
|
|
34
37
|
>
|
|
35
38
|
<span class="relative flex items-center justify-center mt-0.5 shrink-0">
|
|
36
39
|
<input
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
class="peer sr-only"
|
|
46
49
|
/>
|
|
47
50
|
<span
|
|
48
|
-
class="w-4 h-4 rounded border transition-colors flex items-center justify-center
|
|
51
|
+
class="w-4 h-4 rounded border transition-colors duration-150 flex items-center justify-center
|
|
49
52
|
{checked
|
|
50
53
|
? 'bg-brand border-brand'
|
|
51
54
|
: 'bg-surface-2 border-line peer-focus-visible:border-brand'}"
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
</span>
|
|
74
77
|
{/if}
|
|
75
78
|
{#if hint}
|
|
76
|
-
<span class=
|
|
79
|
+
<span class={FIELD_HINT}>{hint}</span>
|
|
77
80
|
{/if}
|
|
78
81
|
</span>
|
|
79
82
|
{/if}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
CONTROL_BASE,
|
|
4
|
+
FIELD_ERROR,
|
|
5
|
+
FIELD_HINT,
|
|
6
|
+
FIELD_LABEL,
|
|
7
|
+
FIELD_WRAP,
|
|
8
|
+
controlBorder,
|
|
9
|
+
describedBy,
|
|
10
|
+
} from '../internal/field.js';
|
|
2
11
|
// DatePicker: a text trigger + calendar popover. Binds `value` to an ISO date
|
|
3
12
|
// string ("YYYY-MM-DD"). Pure local-date math (no timezone surprises).
|
|
4
13
|
let {
|
|
@@ -151,10 +160,10 @@
|
|
|
151
160
|
});
|
|
152
161
|
</script>
|
|
153
162
|
|
|
154
|
-
<div class="
|
|
163
|
+
<div class="{FIELD_WRAP} {cls}" bind:this={containerEl}>
|
|
155
164
|
{#if label}
|
|
156
|
-
<label for={id} class=
|
|
157
|
-
{label}{#if required}<span class="text-danger ml-0.5">*</span>{/if}
|
|
165
|
+
<label for={id} class={FIELD_LABEL}>
|
|
166
|
+
{label}{#if required}<span class="text-danger ml-0.5" aria-label="required">*</span>{/if}
|
|
158
167
|
</label>
|
|
159
168
|
{/if}
|
|
160
169
|
|
|
@@ -164,9 +173,8 @@
|
|
|
164
173
|
{id}
|
|
165
174
|
{disabled}
|
|
166
175
|
onclick={() => (open ? (open = false) : openCal())}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
{error ? 'border-danger focus:border-danger' : 'border-line focus:border-brand'}"
|
|
176
|
+
aria-describedby={describedBy(id, error, hint)}
|
|
177
|
+
class="{CONTROL_BASE} {controlBorder(!!error)} flex items-center justify-between text-left"
|
|
170
178
|
>
|
|
171
179
|
<span class={display ? 'text-fg' : 'text-faint'}>{display || placeholder}</span>
|
|
172
180
|
<span class="text-faint shrink-0" aria-hidden="true">
|
|
@@ -200,7 +208,7 @@
|
|
|
200
208
|
type="button"
|
|
201
209
|
aria-label="Previous month"
|
|
202
210
|
onclick={prevMonth}
|
|
203
|
-
class="p-1.5 rounded-md text-muted hover:bg-surface-2 hover:text-fg transition-colors"
|
|
211
|
+
class="p-1.5 rounded-md text-muted hover:bg-surface-2 hover:text-fg transition-colors duration-150"
|
|
204
212
|
>
|
|
205
213
|
<svg width="14" height="14" viewBox="0 0 12 12" fill="none"
|
|
206
214
|
><path
|
|
@@ -217,7 +225,7 @@
|
|
|
217
225
|
type="button"
|
|
218
226
|
aria-label="Next month"
|
|
219
227
|
onclick={nextMonth}
|
|
220
|
-
class="p-1.5 rounded-md text-muted hover:bg-surface-2 hover:text-fg transition-colors"
|
|
228
|
+
class="p-1.5 rounded-md text-muted hover:bg-surface-2 hover:text-fg transition-colors duration-150"
|
|
221
229
|
>
|
|
222
230
|
<svg width="14" height="14" viewBox="0 0 12 12" fill="none"
|
|
223
231
|
><path
|
|
@@ -251,7 +259,7 @@
|
|
|
251
259
|
onclick={() => pick(d)}
|
|
252
260
|
aria-current={iso === todayISO ? 'date' : undefined}
|
|
253
261
|
aria-label={iso}
|
|
254
|
-
class="h-8 w-8 mx-auto flex items-center justify-center rounded-md text-sm transition-colors
|
|
262
|
+
class="h-8 w-8 mx-auto flex items-center justify-center rounded-md text-sm transition-colors duration-150
|
|
255
263
|
disabled:opacity-30 disabled:cursor-not-allowed
|
|
256
264
|
{iso === value
|
|
257
265
|
? 'bg-brand text-ink font-medium'
|
|
@@ -267,8 +275,8 @@
|
|
|
267
275
|
</div>
|
|
268
276
|
|
|
269
277
|
{#if error}
|
|
270
|
-
<p
|
|
278
|
+
<p id={id ? `${id}-error` : undefined} class={FIELD_ERROR}>{error}</p>
|
|
271
279
|
{:else if hint}
|
|
272
|
-
<p
|
|
280
|
+
<p id={id ? `${id}-hint` : undefined} class={FIELD_HINT}>{hint}</p>
|
|
273
281
|
{/if}
|
|
274
282
|
</div>
|
|
@@ -75,9 +75,20 @@
|
|
|
75
75
|
<button
|
|
76
76
|
type="button"
|
|
77
77
|
onclick={close}
|
|
78
|
-
class="-mt-0.5 text-xl leading-none text-faint transition-colors hover:text-fg"
|
|
79
|
-
aria-label="Close"
|
|
78
|
+
class="-mt-0.5 text-xl leading-none text-faint transition-colors duration-150 hover:text-fg"
|
|
79
|
+
aria-label="Close"
|
|
80
80
|
>
|
|
81
|
+
<svg
|
|
82
|
+
width="16"
|
|
83
|
+
height="16"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
fill="none"
|
|
86
|
+
stroke="currentColor"
|
|
87
|
+
stroke-width="2"
|
|
88
|
+
stroke-linecap="round"
|
|
89
|
+
aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg
|
|
90
|
+
>
|
|
91
|
+
</button>
|
|
81
92
|
</div>
|
|
82
93
|
{/if}
|
|
83
94
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
role="menuitem"
|
|
71
71
|
disabled={item.disabled}
|
|
72
72
|
onclick={() => handleItemClick(item)}
|
|
73
|
-
class="w-full flex items-center gap-2.5 px-3 py-2 text-sm transition-colors
|
|
73
|
+
class="w-full flex items-center gap-2.5 px-3 py-2 text-sm transition-colors duration-150
|
|
74
74
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
75
75
|
{item.variant === 'danger'
|
|
76
76
|
? 'text-danger hover:bg-danger/10'
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
FIELD_ERROR,
|
|
4
|
+
FIELD_HINT,
|
|
5
|
+
FIELD_LABEL,
|
|
6
|
+
FIELD_WRAP,
|
|
7
|
+
describedBy,
|
|
8
|
+
} from '../internal/field.js';
|
|
2
9
|
interface Props {
|
|
3
10
|
id?: string;
|
|
4
11
|
name?: string;
|
|
@@ -39,15 +46,15 @@
|
|
|
39
46
|
}
|
|
40
47
|
</script>
|
|
41
48
|
|
|
42
|
-
<div class="
|
|
49
|
+
<div class="{FIELD_WRAP} {cls}">
|
|
43
50
|
{#if label}
|
|
44
|
-
<label for={id} class=
|
|
51
|
+
<label for={id} class={FIELD_LABEL}>{label}</label>
|
|
45
52
|
{/if}
|
|
46
53
|
|
|
47
54
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
48
55
|
<label
|
|
49
56
|
class="flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-xl
|
|
50
|
-
border-2 border-dashed px-4 py-8 text-center transition-colors
|
|
57
|
+
border-2 border-dashed px-4 py-8 text-center transition-colors duration-150
|
|
51
58
|
{error
|
|
52
59
|
? 'border-danger bg-danger/5 hover:bg-danger/8'
|
|
53
60
|
: dragOver
|
|
@@ -90,13 +97,15 @@
|
|
|
90
97
|
{multiple}
|
|
91
98
|
{disabled}
|
|
92
99
|
onchange={handleChange}
|
|
100
|
+
aria-invalid={error ? 'true' : undefined}
|
|
101
|
+
aria-describedby={describedBy(id, error, hint)}
|
|
93
102
|
class="sr-only"
|
|
94
103
|
/>
|
|
95
104
|
</label>
|
|
96
105
|
|
|
97
106
|
{#if error}
|
|
98
|
-
<p
|
|
107
|
+
<p id={id ? `${id}-error` : undefined} class={FIELD_ERROR}>{error}</p>
|
|
99
108
|
{:else if hint}
|
|
100
|
-
<p
|
|
109
|
+
<p id={id ? `${id}-hint` : undefined} class={FIELD_HINT}>{hint}</p>
|
|
101
110
|
{/if}
|
|
102
111
|
</div>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
type
|
|
2
|
+
import type { AccentTone } from '../internal/tone.js';
|
|
3
3
|
type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
|
-
tone?:
|
|
6
|
+
tone?: AccentTone;
|
|
7
7
|
pulse?: boolean;
|
|
8
8
|
size?: Size;
|
|
9
9
|
class?: string;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
let { tone = 'success', pulse = false, size = 'sm', class: cls = '' }: Props = $props();
|
|
13
13
|
|
|
14
|
-
const tones: Record<
|
|
14
|
+
const tones: Record<AccentTone, string> = {
|
|
15
15
|
brand: 'bg-brand',
|
|
16
16
|
success: 'bg-success',
|
|
17
17
|
warn: 'bg-warn',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { AccentTone } from '../internal/tone.js';
|
|
2
2
|
type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
3
3
|
interface Props {
|
|
4
|
-
tone?:
|
|
4
|
+
tone?: AccentTone;
|
|
5
5
|
pulse?: boolean;
|
|
6
6
|
size?: Size;
|
|
7
7
|
class?: string;
|