@lyeve-labs/ui-kit 0.8.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/PageHeader.svelte +3 -1
- 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
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import {
|
|
4
|
+
CONTROL_BASE,
|
|
5
|
+
FIELD_ERROR,
|
|
6
|
+
FIELD_HINT,
|
|
7
|
+
FIELD_LABEL,
|
|
8
|
+
FIELD_WRAP,
|
|
9
|
+
controlBorder,
|
|
10
|
+
describedBy,
|
|
11
|
+
} from '../internal/field.js';
|
|
3
12
|
|
|
4
13
|
type SE = Event & { currentTarget: HTMLSelectElement };
|
|
5
14
|
|
|
@@ -7,6 +16,8 @@
|
|
|
7
16
|
value,
|
|
8
17
|
id,
|
|
9
18
|
name,
|
|
19
|
+
label,
|
|
20
|
+
hint,
|
|
10
21
|
required = false,
|
|
11
22
|
disabled = false,
|
|
12
23
|
error,
|
|
@@ -17,6 +28,8 @@
|
|
|
17
28
|
value?: string | null;
|
|
18
29
|
id?: string;
|
|
19
30
|
name?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
hint?: string;
|
|
20
33
|
required?: boolean;
|
|
21
34
|
disabled?: boolean;
|
|
22
35
|
error?: string;
|
|
@@ -25,26 +38,27 @@
|
|
|
25
38
|
children?: Snippet;
|
|
26
39
|
} = $props();
|
|
27
40
|
|
|
28
|
-
const
|
|
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';
|
|
41
|
+
const fieldId = $derived(id ?? (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined));
|
|
32
42
|
</script>
|
|
33
43
|
|
|
34
|
-
<div class="
|
|
44
|
+
<div class="{FIELD_WRAP} {cls}">
|
|
45
|
+
{#if label}
|
|
46
|
+
<label for={fieldId} class={FIELD_LABEL}>
|
|
47
|
+
{label}{#if required}<span class="text-danger ml-0.5" aria-label="required">*</span>{/if}
|
|
48
|
+
</label>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
35
51
|
<div class="relative">
|
|
36
52
|
<select
|
|
37
|
-
{
|
|
53
|
+
id={fieldId}
|
|
38
54
|
{name}
|
|
39
55
|
{required}
|
|
40
56
|
{disabled}
|
|
41
57
|
value={value ?? ''}
|
|
42
58
|
{onchange}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
: 'border border-line focus:border-brand/50'}
|
|
47
|
-
pr-8"
|
|
59
|
+
aria-invalid={error ? 'true' : undefined}
|
|
60
|
+
aria-describedby={describedBy(fieldId, error, hint)}
|
|
61
|
+
class="{CONTROL_BASE} {controlBorder(!!error)} appearance-none cursor-pointer pr-8"
|
|
48
62
|
>
|
|
49
63
|
{@render children?.()}
|
|
50
64
|
</select>
|
|
@@ -63,7 +77,10 @@
|
|
|
63
77
|
</svg>
|
|
64
78
|
</span>
|
|
65
79
|
</div>
|
|
80
|
+
|
|
66
81
|
{#if error}
|
|
67
|
-
<p
|
|
82
|
+
<p id={fieldId ? `${fieldId}-error` : undefined} class={FIELD_ERROR}>{error}</p>
|
|
83
|
+
{:else if hint}
|
|
84
|
+
<p id={fieldId ? `${fieldId}-hint` : undefined} class={FIELD_HINT}>{hint}</p>
|
|
68
85
|
{/if}
|
|
69
86
|
</div>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
? ''
|
|
30
30
|
: done || active
|
|
31
31
|
? 'bg-brand'
|
|
32
|
-
: 'bg-line'} h-px transition-colors"
|
|
32
|
+
: 'bg-line'} h-px transition-colors duration-150"
|
|
33
33
|
></div>
|
|
34
34
|
|
|
35
35
|
<!-- Step circle -->
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
? ''
|
|
63
63
|
: done
|
|
64
64
|
? 'bg-brand'
|
|
65
|
-
: 'bg-line'} h-px transition-colors"
|
|
65
|
+
: 'bg-line'} h-px transition-colors duration-150"
|
|
66
66
|
></div>
|
|
67
67
|
</div>
|
|
68
68
|
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
? 'text-fg'
|
|
73
73
|
: done
|
|
74
74
|
? 'text-muted'
|
|
75
|
-
: 'text-faint'} transition-colors"
|
|
75
|
+
: 'text-faint'} transition-colors duration-150"
|
|
76
76
|
>
|
|
77
77
|
{step.label}
|
|
78
78
|
</p>
|
|
@@ -117,7 +117,9 @@
|
|
|
117
117
|
</div>
|
|
118
118
|
{#if i < steps.length - 1}
|
|
119
119
|
<div
|
|
120
|
-
class="my-1 w-px flex-1 {done
|
|
120
|
+
class="my-1 w-px flex-1 {done
|
|
121
|
+
? 'bg-brand/40'
|
|
122
|
+
: 'bg-line'} transition-colors duration-150"
|
|
121
123
|
></div>
|
|
122
124
|
{/if}
|
|
123
125
|
</div>
|
|
@@ -127,7 +129,7 @@
|
|
|
127
129
|
? 'text-fg'
|
|
128
130
|
: done
|
|
129
131
|
? 'text-muted'
|
|
130
|
-
: 'text-faint'} transition-colors"
|
|
132
|
+
: 'text-faint'} transition-colors duration-150"
|
|
131
133
|
>
|
|
132
134
|
{step.label}
|
|
133
135
|
</p>
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
[&_tbody_td]:px-4 [&_tbody_td]:py-3 [&_tbody_td]:text-fg [&_tbody_td]:align-middle
|
|
30
30
|
[&_tbody_tr]:border-b [&_tbody_tr]:border-line [&_tbody_tr:last-child]:border-0
|
|
31
31
|
{striped ? '[&_tbody_tr:nth-child(even)]:bg-surface-2/40' : ''}
|
|
32
|
-
{hoverable
|
|
32
|
+
{hoverable
|
|
33
|
+
? '[&_tbody_tr:hover]:bg-surface-2/60 [&_tbody_tr]:transition-colors duration-150'
|
|
34
|
+
: ''}"
|
|
33
35
|
>
|
|
34
36
|
{@render children()}
|
|
35
37
|
</table>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
role="tab"
|
|
20
20
|
aria-selected={active === tab.id}
|
|
21
21
|
onclick={() => onchange(tab.id)}
|
|
22
|
-
class="relative px-4 py-2.5 text-sm font-medium transition-colors whitespace-nowrap
|
|
22
|
+
class="relative px-4 py-2.5 text-sm font-medium transition-colors duration-150 whitespace-nowrap
|
|
23
23
|
{active === tab.id
|
|
24
24
|
? 'text-fg after:absolute after:bottom-0 after:inset-x-0 after:h-0.5 after:bg-brand after:rounded-t'
|
|
25
25
|
: 'text-faint hover:text-fg'}"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
type
|
|
2
|
+
import type { AccentTone } from '../internal/tone.js';
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
label: string;
|
|
6
|
-
tone?:
|
|
6
|
+
tone?: AccentTone;
|
|
7
7
|
removable?: boolean;
|
|
8
8
|
onremove?: () => void;
|
|
9
9
|
class?: string;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
class: cls = '',
|
|
18
18
|
}: Props = $props();
|
|
19
19
|
|
|
20
|
-
const tones: Record<
|
|
20
|
+
const tones: Record<AccentTone, string> = {
|
|
21
21
|
neutral: 'bg-surface-2 text-muted border-line',
|
|
22
22
|
brand: 'bg-brand/10 text-brand border-brand/20',
|
|
23
23
|
violet: 'bg-violet/10 text-violet border-violet/20',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { AccentTone } from '../internal/tone.js';
|
|
2
2
|
interface Props {
|
|
3
3
|
label: string;
|
|
4
|
-
tone?:
|
|
4
|
+
tone?: AccentTone;
|
|
5
5
|
removable?: boolean;
|
|
6
6
|
onremove?: () => void;
|
|
7
7
|
class?: string;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
CONTROL_MULTILINE,
|
|
4
|
+
FIELD_ERROR,
|
|
5
|
+
FIELD_HINT,
|
|
6
|
+
FIELD_LABEL,
|
|
7
|
+
FIELD_WRAP,
|
|
8
|
+
controlBorder,
|
|
9
|
+
describedBy,
|
|
10
|
+
} from '../internal/field.js';
|
|
11
|
+
|
|
2
12
|
type TA = HTMLTextAreaElement;
|
|
3
13
|
type TAE = Event & { currentTarget: TA };
|
|
4
14
|
|
|
@@ -6,6 +16,7 @@
|
|
|
6
16
|
value = $bindable(''),
|
|
7
17
|
id,
|
|
8
18
|
name,
|
|
19
|
+
label,
|
|
9
20
|
placeholder,
|
|
10
21
|
rows = 4,
|
|
11
22
|
maxlength,
|
|
@@ -22,6 +33,7 @@
|
|
|
22
33
|
value?: string;
|
|
23
34
|
id?: string;
|
|
24
35
|
name?: string;
|
|
36
|
+
label?: string;
|
|
25
37
|
placeholder?: string;
|
|
26
38
|
rows?: number;
|
|
27
39
|
maxlength?: number;
|
|
@@ -36,11 +48,7 @@
|
|
|
36
48
|
onblur?: (e: FocusEvent & { currentTarget: TA }) => void;
|
|
37
49
|
} = $props();
|
|
38
50
|
|
|
39
|
-
const
|
|
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';
|
|
51
|
+
const fieldId = $derived(id ?? (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined));
|
|
44
52
|
|
|
45
53
|
function handleInput(e: TAE) {
|
|
46
54
|
value = e.currentTarget.value;
|
|
@@ -48,9 +56,15 @@
|
|
|
48
56
|
}
|
|
49
57
|
</script>
|
|
50
58
|
|
|
51
|
-
<div class="
|
|
59
|
+
<div class="{FIELD_WRAP} {cls}">
|
|
60
|
+
{#if label}
|
|
61
|
+
<label for={fieldId} class={FIELD_LABEL}>
|
|
62
|
+
{label}{#if required}<span class="text-danger ml-0.5" aria-label="required">*</span>{/if}
|
|
63
|
+
</label>
|
|
64
|
+
{/if}
|
|
65
|
+
|
|
52
66
|
<textarea
|
|
53
|
-
{
|
|
67
|
+
id={fieldId}
|
|
54
68
|
{name}
|
|
55
69
|
{rows}
|
|
56
70
|
{maxlength}
|
|
@@ -58,18 +72,16 @@
|
|
|
58
72
|
{disabled}
|
|
59
73
|
{readonly}
|
|
60
74
|
{placeholder}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
: 'border border-line focus:border-brand/50'}
|
|
65
|
-
{resize ? 'resize-y' : 'resize-none'}"
|
|
75
|
+
aria-invalid={error ? 'true' : undefined}
|
|
76
|
+
aria-describedby={describedBy(fieldId, error, hint)}
|
|
77
|
+
class="{CONTROL_MULTILINE} {controlBorder(!!error)} {resize ? 'resize-y' : 'resize-none'}"
|
|
66
78
|
oninput={handleInput}
|
|
67
79
|
{onblur}>{value}</textarea
|
|
68
80
|
>
|
|
81
|
+
|
|
69
82
|
{#if error}
|
|
70
|
-
<p
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
<p class="text-xs text-faint">{hint}</p>
|
|
83
|
+
<p id={fieldId ? `${fieldId}-error` : undefined} class={FIELD_ERROR}>{error}</p>
|
|
84
|
+
{:else if hint}
|
|
85
|
+
<p id={fieldId ? `${fieldId}-hint` : undefined} class={FIELD_HINT}>{hint}</p>
|
|
74
86
|
{/if}
|
|
75
87
|
</div>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
type="button"
|
|
24
24
|
onclick={flip}
|
|
25
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}"
|
|
26
|
+
class="rounded-lg p-2 text-faint transition-colors duration-150 hover:bg-surface-2 hover:text-fg {klass}"
|
|
27
27
|
>
|
|
28
28
|
{#if theme === 'dark'}
|
|
29
29
|
<Sun size={16} aria-hidden="true" />
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { toast
|
|
2
|
+
import { toast } from '../stores/toast.svelte';
|
|
3
|
+
import { TONE_GLYPH, statusTone, type StatusTone } from '../internal/tone.js';
|
|
3
4
|
|
|
4
|
-
const styles: Record<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const styles: Record<StatusTone, { bar: string; icon: string }> = {
|
|
6
|
+
neutral: { bar: 'bg-muted', icon: 'text-muted' },
|
|
7
|
+
brand: { bar: 'bg-brand', icon: 'text-brand' },
|
|
8
|
+
success: { bar: 'bg-success', icon: 'text-success' },
|
|
9
|
+
warn: { bar: 'bg-warn', icon: 'text-warn' },
|
|
10
|
+
danger: { bar: 'bg-danger', icon: 'text-danger' },
|
|
9
11
|
};
|
|
10
12
|
</script>
|
|
11
13
|
|
|
@@ -18,23 +20,43 @@
|
|
|
18
20
|
bg-surface pl-0 pr-3 py-3 shadow-xl animate-[toast-in_140ms_ease-out]"
|
|
19
21
|
role="status"
|
|
20
22
|
>
|
|
21
|
-
<span class="w-1 self-stretch shrink-0 {styles[t.tone].bar}"></span>
|
|
23
|
+
<span class="w-1 self-stretch shrink-0 {styles[statusTone(t.tone)].bar}"></span>
|
|
22
24
|
<span
|
|
23
|
-
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-current
|
|
24
|
-
t.tone
|
|
25
|
+
class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-current {styles[
|
|
26
|
+
statusTone(t.tone)
|
|
25
27
|
].icon}"
|
|
26
28
|
aria-hidden="true"
|
|
27
29
|
>
|
|
28
|
-
|
|
30
|
+
<svg
|
|
31
|
+
width="11"
|
|
32
|
+
height="11"
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
stroke-width="2.5"
|
|
37
|
+
stroke-linecap="round"
|
|
38
|
+
stroke-linejoin="round"
|
|
39
|
+
>
|
|
40
|
+
<path d={TONE_GLYPH[statusTone(t.tone)]} />
|
|
41
|
+
</svg>
|
|
29
42
|
</span>
|
|
30
43
|
<p class="flex-1 text-sm text-fg leading-snug">{t.message}</p>
|
|
31
44
|
<button
|
|
32
45
|
type="button"
|
|
33
|
-
class="
|
|
46
|
+
class="shrink-0 text-faint transition-colors duration-150 hover:text-fg"
|
|
34
47
|
aria-label="Dismiss"
|
|
35
48
|
onclick={() => toast.dismiss(t.id)}
|
|
36
49
|
>
|
|
37
|
-
|
|
50
|
+
<svg
|
|
51
|
+
width="16"
|
|
52
|
+
height="16"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="none"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
stroke-width="2"
|
|
57
|
+
stroke-linecap="round"
|
|
58
|
+
aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg
|
|
59
|
+
>
|
|
38
60
|
</button>
|
|
39
61
|
</div>
|
|
40
62
|
{/each}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { FIELD_HINT } from '../internal/field.js';
|
|
2
3
|
let {
|
|
3
4
|
checked = $bindable(false),
|
|
4
5
|
label,
|
|
5
6
|
hint,
|
|
6
7
|
disabled = false,
|
|
7
8
|
size = 'md',
|
|
9
|
+
class: cls = '',
|
|
8
10
|
onchange,
|
|
9
11
|
}: {
|
|
10
12
|
checked?: boolean;
|
|
@@ -12,6 +14,7 @@
|
|
|
12
14
|
hint?: string;
|
|
13
15
|
disabled?: boolean;
|
|
14
16
|
size?: 'sm' | 'md';
|
|
17
|
+
class?: string;
|
|
15
18
|
onchange?: (checked: boolean) => void;
|
|
16
19
|
} = $props();
|
|
17
20
|
|
|
@@ -40,7 +43,7 @@
|
|
|
40
43
|
<label
|
|
41
44
|
class="inline-flex items-start gap-2.5 cursor-pointer select-none {disabled
|
|
42
45
|
? 'opacity-50 cursor-not-allowed'
|
|
43
|
-
: ''}"
|
|
46
|
+
: ''} {cls}"
|
|
44
47
|
>
|
|
45
48
|
<button
|
|
46
49
|
type="button"
|
|
@@ -49,7 +52,7 @@
|
|
|
49
52
|
aria-label={label ?? 'Toggle'}
|
|
50
53
|
{disabled}
|
|
51
54
|
onclick={handleClick}
|
|
52
|
-
class="relative shrink-0 rounded-full transition-colors
|
|
55
|
+
class="relative shrink-0 rounded-full transition-colors duration-150 outline-none
|
|
53
56
|
focus-visible:ring-2 focus-visible:ring-brand/50 focus-visible:ring-offset-1
|
|
54
57
|
focus-visible:ring-offset-ink mt-0.5
|
|
55
58
|
{track[size]}
|
|
@@ -67,7 +70,7 @@
|
|
|
67
70
|
<span class="text-sm text-fg">{label}</span>
|
|
68
71
|
{/if}
|
|
69
72
|
{#if hint}
|
|
70
|
-
<span class=
|
|
73
|
+
<span class={FIELD_HINT}>{hint}</span>
|
|
71
74
|
{/if}
|
|
72
75
|
</span>
|
|
73
76
|
{/if}
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
{#if !entry.options.persistent}
|
|
196
196
|
<button
|
|
197
197
|
class="inline-flex items-center justify-center w-8 h-8 -mr-2 rounded-lg
|
|
198
|
-
text-muted hover:text-fg hover:bg-surface-2 transition-colors shrink-0"
|
|
198
|
+
text-muted hover:text-fg hover:bg-surface-2 transition-colors duration-150 shrink-0"
|
|
199
199
|
onclick={() => handleClose()}
|
|
200
200
|
aria-label="Close"
|
|
201
201
|
>
|
package/dist/index.d.ts
CHANGED
|
@@ -60,4 +60,4 @@ export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, set
|
|
|
60
60
|
export type { DialogOptions, DialogEntry, DialogSize } from './components/dialog/types.js';
|
|
61
61
|
export { cn, type ClassValue } from './utils/cn.js';
|
|
62
62
|
export { getTheme, setTheme, toggleTheme, themeBootScript, type Theme } from './utils/theme.js';
|
|
63
|
-
export declare const VERSION = "0.
|
|
63
|
+
export declare const VERSION = "0.10.0";
|
package/dist/index.js
CHANGED
|
@@ -69,4 +69,6 @@ export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, set
|
|
|
69
69
|
export { cn } from './utils/cn.js';
|
|
70
70
|
export { getTheme, setTheme, toggleTheme, themeBootScript } from './utils/theme.js';
|
|
71
71
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
72
|
-
|
|
72
|
+
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
73
|
+
// this line; the build and the test suite fail when the two disagree.
|
|
74
|
+
export const VERSION = '0.10.0';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for how a form control looks and announces itself.
|
|
3
|
+
*
|
|
4
|
+
* Every text-entry control in the library composes its classes from here rather
|
|
5
|
+
* than spelling them out inline. The nine controls had drifted into three focus
|
|
6
|
+
* treatments, two wrapper gaps, two error-border opacities and two heights, so
|
|
7
|
+
* an Input and a NumberInput placed side by side did not line up. Changing a
|
|
8
|
+
* control's appearance now means changing one of these constants.
|
|
9
|
+
*
|
|
10
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
11
|
+
*/
|
|
12
|
+
/** Vertical rhythm inside a labelled field: label, control, hint/error. */
|
|
13
|
+
export declare const FIELD_WRAP = "flex flex-col gap-1.5";
|
|
14
|
+
/** The label above a control. */
|
|
15
|
+
export declare const FIELD_LABEL = "text-sm font-medium text-fg";
|
|
16
|
+
/** Hint text below a control. Shown only when there is no error. */
|
|
17
|
+
export declare const FIELD_HINT = "text-xs text-faint";
|
|
18
|
+
/** Error text below a control. Replaces the hint rather than stacking with it. */
|
|
19
|
+
export declare const FIELD_ERROR = "text-xs text-danger";
|
|
20
|
+
/**
|
|
21
|
+
* Everything a single-line control needs except its border colour.
|
|
22
|
+
*
|
|
23
|
+
* `h-control` is a theme token (2.375rem / 38px), not a literal, so the height
|
|
24
|
+
* is stated once. Controls that grow with their content - Textarea, the
|
|
25
|
+
* MultiSelect chip well - use `CONTROL_MULTILINE` instead and keep the token as
|
|
26
|
+
* a minimum.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CONTROL_BASE: string;
|
|
29
|
+
/** As CONTROL_BASE, for controls whose height is driven by their content. */
|
|
30
|
+
export declare const CONTROL_MULTILINE: string;
|
|
31
|
+
/**
|
|
32
|
+
* The border a control carries at rest and on focus.
|
|
33
|
+
*
|
|
34
|
+
* Focus moves the border to full-strength brand. Two controls previously used
|
|
35
|
+
* `border-brand/50`, which reads as a weaker focus for no reason a user could
|
|
36
|
+
* infer, and the error focus was split between `border-danger` and
|
|
37
|
+
* `border-danger/70`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function controlBorder(error: boolean): string;
|
|
40
|
+
/**
|
|
41
|
+
* Wires a control to whichever of its hint or error is on screen.
|
|
42
|
+
*
|
|
43
|
+
* Returns undefined when neither is present, so the attribute is omitted rather
|
|
44
|
+
* than pointing at an element that was never rendered. Only Input carried
|
|
45
|
+
* `aria-invalid` before this; nothing carried `aria-describedby`, so a screen
|
|
46
|
+
* reader announced the control and never the reason it was rejected.
|
|
47
|
+
*/
|
|
48
|
+
export declare function describedBy(id: string | undefined, error?: string, hint?: string): string | undefined;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for how a form control looks and announces itself.
|
|
3
|
+
*
|
|
4
|
+
* Every text-entry control in the library composes its classes from here rather
|
|
5
|
+
* than spelling them out inline. The nine controls had drifted into three focus
|
|
6
|
+
* treatments, two wrapper gaps, two error-border opacities and two heights, so
|
|
7
|
+
* an Input and a NumberInput placed side by side did not line up. Changing a
|
|
8
|
+
* control's appearance now means changing one of these constants.
|
|
9
|
+
*
|
|
10
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
11
|
+
*/
|
|
12
|
+
/** Vertical rhythm inside a labelled field: label, control, hint/error. */
|
|
13
|
+
export const FIELD_WRAP = 'flex flex-col gap-1.5';
|
|
14
|
+
/** The label above a control. */
|
|
15
|
+
export const FIELD_LABEL = 'text-sm font-medium text-fg';
|
|
16
|
+
/** Hint text below a control. Shown only when there is no error. */
|
|
17
|
+
export const FIELD_HINT = 'text-xs text-faint';
|
|
18
|
+
/** Error text below a control. Replaces the hint rather than stacking with it. */
|
|
19
|
+
export const FIELD_ERROR = 'text-xs text-danger';
|
|
20
|
+
/**
|
|
21
|
+
* Everything a single-line control needs except its border colour.
|
|
22
|
+
*
|
|
23
|
+
* `h-control` is a theme token (2.375rem / 38px), not a literal, so the height
|
|
24
|
+
* is stated once. Controls that grow with their content - Textarea, the
|
|
25
|
+
* MultiSelect chip well - use `CONTROL_MULTILINE` instead and keep the token as
|
|
26
|
+
* a minimum.
|
|
27
|
+
*/
|
|
28
|
+
export const CONTROL_BASE = 'w-full h-control rounded-lg bg-surface-2 border px-3 text-sm text-fg ' +
|
|
29
|
+
'placeholder:text-faint transition-colors duration-150 outline-none ' +
|
|
30
|
+
'disabled:opacity-50 disabled:cursor-not-allowed';
|
|
31
|
+
/** As CONTROL_BASE, for controls whose height is driven by their content. */
|
|
32
|
+
export const CONTROL_MULTILINE = 'w-full min-h-control rounded-lg bg-surface-2 border px-3 py-2 text-sm text-fg ' +
|
|
33
|
+
'placeholder:text-faint transition-colors duration-150 outline-none ' +
|
|
34
|
+
'disabled:opacity-50 disabled:cursor-not-allowed';
|
|
35
|
+
/**
|
|
36
|
+
* The border a control carries at rest and on focus.
|
|
37
|
+
*
|
|
38
|
+
* Focus moves the border to full-strength brand. Two controls previously used
|
|
39
|
+
* `border-brand/50`, which reads as a weaker focus for no reason a user could
|
|
40
|
+
* infer, and the error focus was split between `border-danger` and
|
|
41
|
+
* `border-danger/70`.
|
|
42
|
+
*/
|
|
43
|
+
export function controlBorder(error) {
|
|
44
|
+
return error ? 'border-danger focus:border-danger' : 'border-line focus:border-brand';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Wires a control to whichever of its hint or error is on screen.
|
|
48
|
+
*
|
|
49
|
+
* Returns undefined when neither is present, so the attribute is omitted rather
|
|
50
|
+
* than pointing at an element that was never rendered. Only Input carried
|
|
51
|
+
* `aria-invalid` before this; nothing carried `aria-describedby`, so a screen
|
|
52
|
+
* reader announced the control and never the reason it was rejected.
|
|
53
|
+
*/
|
|
54
|
+
export function describedBy(id, error, hint) {
|
|
55
|
+
if (!id)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (error)
|
|
58
|
+
return `${id}-error`;
|
|
59
|
+
if (hint)
|
|
60
|
+
return `${id}-hint`;
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tone vocabulary, stated once.
|
|
3
|
+
*
|
|
4
|
+
* Alert, Banner and the toast store each named their neutral-informational tone
|
|
5
|
+
* `info`, while Badge, Tag, Indicator and Progress named the same brand-cyan
|
|
6
|
+
* tone `brand`. A consumer building a status row had to remember which
|
|
7
|
+
* component wanted which word for the same colour. `brand` is canonical because
|
|
8
|
+
* it names the token the tone actually resolves to; `info` still works and maps
|
|
9
|
+
* onto it, so nothing that already ships has to change.
|
|
10
|
+
*/
|
|
11
|
+
/** Tones a status surface can carry. */
|
|
12
|
+
export type StatusTone = 'neutral' | 'brand' | 'success' | 'warn' | 'danger';
|
|
13
|
+
/** What a status surface accepts, including the retained `info` spelling. */
|
|
14
|
+
export type StatusToneInput = StatusTone | 'info';
|
|
15
|
+
/** Status tones plus the decorative accent, for labels rather than states. */
|
|
16
|
+
export type AccentTone = StatusTone | 'violet';
|
|
17
|
+
/** Resolves the accepted spelling to the canonical one. */
|
|
18
|
+
export declare function statusTone(tone: StatusToneInput): StatusTone;
|
|
19
|
+
/**
|
|
20
|
+
* The glyph each status tone draws, as SVG path data on a 24x24 grid.
|
|
21
|
+
*
|
|
22
|
+
* Alert and Toaster drew these as the literal characters ℹ ✓ ! ×, which pick up
|
|
23
|
+
* whatever the user's font does with them - the check and the cross landed at
|
|
24
|
+
* different optical weights from every other icon in the library, all of which
|
|
25
|
+
* are stroked SVG.
|
|
26
|
+
*/
|
|
27
|
+
export declare const TONE_GLYPH: Record<StatusTone, string>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tone vocabulary, stated once.
|
|
3
|
+
*
|
|
4
|
+
* Alert, Banner and the toast store each named their neutral-informational tone
|
|
5
|
+
* `info`, while Badge, Tag, Indicator and Progress named the same brand-cyan
|
|
6
|
+
* tone `brand`. A consumer building a status row had to remember which
|
|
7
|
+
* component wanted which word for the same colour. `brand` is canonical because
|
|
8
|
+
* it names the token the tone actually resolves to; `info` still works and maps
|
|
9
|
+
* onto it, so nothing that already ships has to change.
|
|
10
|
+
*/
|
|
11
|
+
/** Resolves the accepted spelling to the canonical one. */
|
|
12
|
+
export function statusTone(tone) {
|
|
13
|
+
return tone === 'info' ? 'brand' : tone;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The glyph each status tone draws, as SVG path data on a 24x24 grid.
|
|
17
|
+
*
|
|
18
|
+
* Alert and Toaster drew these as the literal characters ℹ ✓ ! ×, which pick up
|
|
19
|
+
* whatever the user's font does with them - the check and the cross landed at
|
|
20
|
+
* different optical weights from every other icon in the library, all of which
|
|
21
|
+
* are stroked SVG.
|
|
22
|
+
*/
|
|
23
|
+
export const TONE_GLYPH = {
|
|
24
|
+
neutral: 'M12 8h.01M11 12h1v4h1',
|
|
25
|
+
brand: 'M12 8h.01M11 12h1v4h1',
|
|
26
|
+
success: 'M20 6L9 17l-5-5',
|
|
27
|
+
warn: 'M12 8v5M12 17h.01',
|
|
28
|
+
danger: 'M18 6L6 18M6 6l12 12',
|
|
29
|
+
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { StatusToneInput } from '../internal/tone.js';
|
|
2
|
+
/**
|
|
3
|
+
* A toast carries the same tone vocabulary as Alert and Banner. `brand` is the
|
|
4
|
+
* canonical name; `info` is kept because `toast.info(...)` already ships.
|
|
5
|
+
*/
|
|
6
|
+
export type ToastTone = StatusToneInput;
|
|
2
7
|
export interface Toast {
|
|
3
8
|
id: number;
|
|
4
9
|
tone: ToastTone;
|
|
@@ -13,7 +13,7 @@ class ToastStore {
|
|
|
13
13
|
this.items = this.items.filter((t) => t.id !== id);
|
|
14
14
|
}
|
|
15
15
|
info(message, timeout) {
|
|
16
|
-
return this.push('
|
|
16
|
+
return this.push('brand', message, timeout);
|
|
17
17
|
}
|
|
18
18
|
success(message, timeout) {
|
|
19
19
|
return this.push('success', message, timeout);
|