@nqmcreative/ui 0.4.0 → 0.4.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/README.md +17 -19
- package/dist/core/calendar.d.ts +1 -1
- package/dist/core/calendar.js +1 -1
- package/dist/core/date.d.ts +3 -3
- package/dist/core/date.js +1 -1
- package/dist/core/files.d.ts +2 -3
- package/dist/core/files.js +2 -3
- package/dist/core/index.d.ts +2 -4
- package/dist/core/index.js +2 -4
- package/dist/core/password.d.ts +1 -1
- package/dist/core/password.js +1 -1
- package/dist/core/tones.d.ts +10 -0
- package/dist/core/tones.js +19 -0
- package/dist/core/trigger.d.ts +8 -0
- package/dist/core/trigger.js +11 -0
- package/dist/styles/matte/Alert.svelte +2 -4
- package/dist/styles/matte/Avatar.svelte +16 -3
- package/dist/styles/matte/Breadcrumb.svelte +1 -4
- package/dist/styles/matte/Button.svelte +6 -2
- package/dist/styles/matte/Calendar.svelte +6 -9
- package/dist/styles/matte/Checkbox.svelte +49 -30
- package/dist/styles/matte/CheckboxGroup.svelte +15 -4
- package/dist/styles/matte/ColorInput.svelte +1 -4
- package/dist/styles/matte/Combobox.svelte +14 -8
- package/dist/styles/matte/CommandPalette.svelte +3 -6
- package/dist/styles/matte/ConfirmDialog.svelte +11 -6
- package/dist/styles/matte/ContextMenu.svelte +1 -4
- package/dist/styles/matte/CurrencyInput.svelte +1 -1
- package/dist/styles/matte/CurrencyInput.svelte.d.ts +1 -1
- package/dist/styles/matte/DatePicker.svelte +18 -11
- package/dist/styles/matte/DatePicker.svelte.d.ts +3 -0
- package/dist/styles/matte/Divider.svelte +1 -1
- package/dist/styles/matte/Drawer.svelte +11 -5
- package/dist/styles/matte/Dropdown.svelte +2 -5
- package/dist/styles/matte/Dropdown.svelte.d.ts +1 -1
- package/dist/styles/matte/Dropzone.svelte +9 -12
- package/dist/styles/matte/Dropzone.svelte.d.ts +1 -1
- package/dist/styles/matte/Field.svelte +59 -3
- package/dist/styles/matte/FileInput.svelte +11 -14
- package/dist/styles/matte/FileInput.svelte.d.ts +2 -2
- package/dist/styles/matte/Footer.svelte +15 -4
- package/dist/styles/matte/Footer.svelte.d.ts +1 -1
- package/dist/styles/matte/Input.svelte +9 -2
- package/dist/styles/matte/Label.svelte +3 -1
- package/dist/styles/matte/Link.svelte +13 -2
- package/dist/styles/matte/MenuItem.svelte +11 -4
- package/dist/styles/matte/Modal.svelte +11 -5
- package/dist/styles/matte/MultiSelect.svelte +5 -8
- package/dist/styles/matte/Navbar.svelte +5 -11
- package/dist/styles/matte/Pagination.svelte +3 -6
- package/dist/styles/matte/PinInput.svelte +1 -4
- package/dist/styles/matte/Radio.svelte +41 -22
- package/dist/styles/matte/RadioGroup.svelte +15 -4
- package/dist/styles/matte/SearchInput.svelte +1 -4
- package/dist/styles/matte/Select.svelte +4 -2
- package/dist/styles/matte/Sidebar.svelte +3 -6
- package/dist/styles/matte/Skeleton.svelte +2 -1
- package/dist/styles/matte/Spinner.svelte +2 -5
- package/dist/styles/matte/Spinner.svelte.d.ts +1 -1
- package/dist/styles/matte/Stat.svelte +27 -3
- package/dist/styles/matte/Switch.svelte +6 -1
- package/dist/styles/matte/Table.svelte +5 -8
- package/dist/styles/matte/Tabs.svelte +1 -1
- package/dist/styles/matte/TagsInput.svelte +5 -8
- package/dist/styles/matte/Textarea.svelte +4 -2
- package/dist/styles/matte/ThemeToggle.svelte +5 -8
- package/dist/styles/matte/TimeInput.svelte +2 -5
- package/dist/styles/matte/Toaster.svelte +23 -8
- package/dist/styles/matte/Tooltip.svelte +8 -1
- package/dist/styles/matte/index.d.ts +2 -3
- package/dist/styles/matte/index.js +1 -3
- package/dist/styles/matte/theme.css +21 -0
- package/dist/styles/paper/Alert.svelte +3 -5
- package/dist/styles/paper/Avatar.svelte +14 -3
- package/dist/styles/paper/Breadcrumb.svelte +1 -4
- package/dist/styles/paper/Button.svelte +5 -1
- package/dist/styles/paper/Calendar.svelte +6 -9
- package/dist/styles/paper/Card.svelte +1 -1
- package/dist/styles/paper/Checkbox.svelte +55 -36
- package/dist/styles/paper/CheckboxGroup.svelte +15 -4
- package/dist/styles/paper/ColorInput.svelte +1 -4
- package/dist/styles/paper/Combobox.svelte +14 -8
- package/dist/styles/paper/CommandPalette.svelte +3 -6
- package/dist/styles/paper/ConfirmDialog.svelte +9 -6
- package/dist/styles/paper/ContextMenu.svelte +1 -4
- package/dist/styles/paper/CurrencyInput.svelte +1 -1
- package/dist/styles/paper/CurrencyInput.svelte.d.ts +1 -1
- package/dist/styles/paper/DatePicker.svelte +18 -11
- package/dist/styles/paper/DatePicker.svelte.d.ts +3 -0
- package/dist/styles/paper/Divider.svelte +1 -1
- package/dist/styles/paper/Drawer.svelte +9 -5
- package/dist/styles/paper/Dropdown.svelte +2 -5
- package/dist/styles/paper/Dropdown.svelte.d.ts +1 -1
- package/dist/styles/paper/Dropzone.svelte +9 -12
- package/dist/styles/paper/Dropzone.svelte.d.ts +1 -1
- package/dist/styles/paper/Field.svelte +59 -3
- package/dist/styles/paper/FileInput.svelte +11 -14
- package/dist/styles/paper/FileInput.svelte.d.ts +2 -2
- package/dist/styles/paper/Footer.svelte +15 -4
- package/dist/styles/paper/Footer.svelte.d.ts +1 -1
- package/dist/styles/paper/Input.svelte +9 -2
- package/dist/styles/paper/Label.svelte +3 -1
- package/dist/styles/paper/Link.svelte +13 -2
- package/dist/styles/paper/MenuItem.svelte +11 -4
- package/dist/styles/paper/Modal.svelte +9 -5
- package/dist/styles/paper/MultiSelect.svelte +5 -8
- package/dist/styles/paper/Navbar.svelte +5 -11
- package/dist/styles/paper/Pagination.svelte +3 -6
- package/dist/styles/paper/PinInput.svelte +1 -4
- package/dist/styles/paper/Radio.svelte +41 -22
- package/dist/styles/paper/RadioGroup.svelte +15 -4
- package/dist/styles/paper/SearchInput.svelte +1 -4
- package/dist/styles/paper/SegmentedControl.svelte +2 -2
- package/dist/styles/paper/Select.svelte +4 -2
- package/dist/styles/paper/Sidebar.svelte +3 -6
- package/dist/styles/paper/Skeleton.svelte +2 -1
- package/dist/styles/paper/Spinner.svelte +2 -5
- package/dist/styles/paper/Spinner.svelte.d.ts +1 -1
- package/dist/styles/paper/Stat.svelte +24 -2
- package/dist/styles/paper/Switch.svelte +6 -1
- package/dist/styles/paper/Table.svelte +5 -8
- package/dist/styles/paper/Tabs.svelte +1 -1
- package/dist/styles/paper/TagsInput.svelte +5 -8
- package/dist/styles/paper/Textarea.svelte +4 -2
- package/dist/styles/paper/ThemeToggle.svelte +5 -8
- package/dist/styles/paper/TimeInput.svelte +2 -5
- package/dist/styles/paper/Toaster.svelte +22 -9
- package/dist/styles/paper/Tooltip.svelte +8 -1
- package/dist/styles/paper/index.d.ts +2 -3
- package/dist/styles/paper/index.js +1 -3
- package/dist/styles/paper/theme.css +21 -0
- package/dist/styles/sprout/Alert.svelte +4 -6
- package/dist/styles/sprout/Avatar.svelte +14 -3
- package/dist/styles/sprout/Breadcrumb.svelte +2 -5
- package/dist/styles/sprout/Button.svelte +8 -4
- package/dist/styles/sprout/Calendar.svelte +7 -10
- package/dist/styles/sprout/Card.svelte +2 -2
- package/dist/styles/sprout/Checkbox.svelte +55 -36
- package/dist/styles/sprout/CheckboxGroup.svelte +15 -4
- package/dist/styles/sprout/ColorInput.svelte +2 -5
- package/dist/styles/sprout/Combobox.svelte +15 -9
- package/dist/styles/sprout/CommandPalette.svelte +3 -6
- package/dist/styles/sprout/ConfirmDialog.svelte +9 -6
- package/dist/styles/sprout/ContextMenu.svelte +1 -4
- package/dist/styles/sprout/CurrencyInput.svelte +1 -1
- package/dist/styles/sprout/CurrencyInput.svelte.d.ts +1 -1
- package/dist/styles/sprout/DatePicker.svelte +20 -13
- package/dist/styles/sprout/DatePicker.svelte.d.ts +3 -0
- package/dist/styles/sprout/Divider.svelte +1 -1
- package/dist/styles/sprout/Drawer.svelte +9 -5
- package/dist/styles/sprout/Dropdown.svelte +2 -5
- package/dist/styles/sprout/Dropdown.svelte.d.ts +1 -1
- package/dist/styles/sprout/Dropzone.svelte +10 -13
- package/dist/styles/sprout/Dropzone.svelte.d.ts +1 -1
- package/dist/styles/sprout/Field.svelte +59 -3
- package/dist/styles/sprout/FileInput.svelte +14 -17
- package/dist/styles/sprout/FileInput.svelte.d.ts +2 -2
- package/dist/styles/sprout/Footer.svelte +15 -4
- package/dist/styles/sprout/Footer.svelte.d.ts +1 -1
- package/dist/styles/sprout/Input.svelte +9 -2
- package/dist/styles/sprout/Label.svelte +3 -1
- package/dist/styles/sprout/Link.svelte +13 -2
- package/dist/styles/sprout/LogoCloud.svelte +1 -1
- package/dist/styles/sprout/MenuItem.svelte +11 -4
- package/dist/styles/sprout/Modal.svelte +9 -5
- package/dist/styles/sprout/MultiSelect.svelte +8 -11
- package/dist/styles/sprout/Navbar.svelte +5 -11
- package/dist/styles/sprout/Pagination.svelte +3 -6
- package/dist/styles/sprout/PinInput.svelte +1 -4
- package/dist/styles/sprout/Radio.svelte +41 -22
- package/dist/styles/sprout/RadioGroup.svelte +15 -4
- package/dist/styles/sprout/SearchInput.svelte +2 -5
- package/dist/styles/sprout/SegmentedControl.svelte +2 -2
- package/dist/styles/sprout/Select.svelte +4 -2
- package/dist/styles/sprout/Sidebar.svelte +5 -7
- package/dist/styles/sprout/Skeleton.svelte +2 -1
- package/dist/styles/sprout/Spinner.svelte +2 -5
- package/dist/styles/sprout/Spinner.svelte.d.ts +1 -1
- package/dist/styles/sprout/Stat.svelte +24 -2
- package/dist/styles/sprout/Switch.svelte +6 -1
- package/dist/styles/sprout/Table.svelte +6 -9
- package/dist/styles/sprout/Tabs.svelte +1 -1
- package/dist/styles/sprout/TagsInput.svelte +6 -9
- package/dist/styles/sprout/Textarea.svelte +4 -2
- package/dist/styles/sprout/ThemeToggle.svelte +5 -8
- package/dist/styles/sprout/TimeInput.svelte +3 -6
- package/dist/styles/sprout/Toaster.svelte +25 -12
- package/dist/styles/sprout/Tooltip.svelte +8 -1
- package/dist/styles/sprout/index.d.ts +3 -4
- package/dist/styles/sprout/index.js +2 -4
- package/dist/styles/sprout/lift.d.ts +14 -0
- package/dist/styles/sprout/lift.js +23 -0
- package/dist/styles/sprout/theme.css +50 -24
- package/package.json +2 -19
- package/registry.json +7 -46
- package/dist/core/locale.svelte.d.ts +0 -101
- package/dist/core/locale.svelte.js +0 -167
- package/dist/styles/matte/LocaleProvider.svelte +0 -24
- package/dist/styles/matte/LocaleProvider.svelte.d.ts +0 -10
- package/dist/styles/paper/LocaleProvider.svelte +0 -24
- package/dist/styles/paper/LocaleProvider.svelte.d.ts +0 -10
- package/dist/styles/sprout/LocaleProvider.svelte +0 -24
- package/dist/styles/sprout/LocaleProvider.svelte.d.ts +0 -10
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
import { anchored } from '../../core/actions/anchor.js';
|
|
3
3
|
import { clickOutside, portal } from '../../core/actions/dismissable.js';
|
|
4
4
|
import { dateHint } from '../../core/calendar.js';
|
|
5
|
-
import { formatISO, parseFormatted } from '../../core/date.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { formatISO, parseFormatted, type DateFormat } from '../../core/date.js';
|
|
6
|
+
import {
|
|
7
|
+
focusWithinRing,
|
|
8
|
+
toneFocusWithinBorder,
|
|
9
|
+
toneFocusWithinRing,
|
|
10
|
+
type Tone
|
|
11
|
+
} from '../../core/tones.js';
|
|
8
12
|
import Calendar from './Calendar.svelte';
|
|
9
13
|
import { edge, soft } from './lift.js';
|
|
10
14
|
|
|
@@ -16,6 +20,8 @@
|
|
|
16
20
|
max?: string;
|
|
17
21
|
isDisabled?: (date: Date) => boolean;
|
|
18
22
|
weekStart?: 0 | 1;
|
|
23
|
+
/** Order of the date parts the field accepts and prints. */
|
|
24
|
+
format?: DateFormat;
|
|
19
25
|
tone?: Tone;
|
|
20
26
|
invalid?: boolean;
|
|
21
27
|
disabled?: boolean;
|
|
@@ -34,6 +40,7 @@
|
|
|
34
40
|
max,
|
|
35
41
|
isDisabled,
|
|
36
42
|
weekStart = 1,
|
|
43
|
+
format = 'dmy',
|
|
37
44
|
tone = 'brand',
|
|
38
45
|
invalid = false,
|
|
39
46
|
disabled = false,
|
|
@@ -45,8 +52,6 @@
|
|
|
45
52
|
class: className = ''
|
|
46
53
|
}: Props = $props();
|
|
47
54
|
|
|
48
|
-
const t = useLocale();
|
|
49
|
-
|
|
50
55
|
let open = $state(false);
|
|
51
56
|
let text = $state('');
|
|
52
57
|
let editing = $state(false);
|
|
@@ -54,8 +59,8 @@
|
|
|
54
59
|
let inputEl: HTMLInputElement | null = $state(null);
|
|
55
60
|
|
|
56
61
|
/** While typing the field shows the raw text; otherwise the formatted value. */
|
|
57
|
-
const display = $derived(editing ? text : formatISO(value,
|
|
58
|
-
const hint = $derived(placeholder ?? dateHint(
|
|
62
|
+
const display = $derived(editing ? text : formatISO(value, format));
|
|
63
|
+
const hint = $derived(placeholder ?? dateHint(format));
|
|
59
64
|
|
|
60
65
|
function commit() {
|
|
61
66
|
editing = false;
|
|
@@ -66,7 +71,7 @@
|
|
|
66
71
|
}
|
|
67
72
|
return;
|
|
68
73
|
}
|
|
69
|
-
const parsed = parseFormatted(text,
|
|
74
|
+
const parsed = parseFormatted(text, format);
|
|
70
75
|
if (parsed && parsed !== value) {
|
|
71
76
|
value = parsed;
|
|
72
77
|
onchange?.(parsed);
|
|
@@ -95,7 +100,9 @@
|
|
|
95
100
|
|
|
96
101
|
<div
|
|
97
102
|
bind:this={wrapper}
|
|
98
|
-
class="inline-flex w-full items-center gap-1 rounded-xl border bg-bg pr-1 pl-3 {edge} transition-colors duration-150 ease-brand-out
|
|
103
|
+
class="inline-flex w-full items-center gap-1 rounded-xl border bg-bg pr-1 pl-3 {edge} transition-colors duration-150 ease-brand-out {focusWithinRing} {toneFocusWithinRing[
|
|
104
|
+
tone
|
|
105
|
+
]}
|
|
99
106
|
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
|
|
100
107
|
{disabled ? 'pointer-events-none opacity-50' : ''} {className}"
|
|
101
108
|
>
|
|
@@ -133,8 +140,8 @@
|
|
|
133
140
|
onchange?.('');
|
|
134
141
|
inputEl?.focus();
|
|
135
142
|
}}
|
|
136
|
-
aria-label=
|
|
137
|
-
class="shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
143
|
+
aria-label="Clear selection"
|
|
144
|
+
class="shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
138
145
|
>
|
|
139
146
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
140
147
|
<path
|
|
@@ -151,9 +158,9 @@
|
|
|
151
158
|
type="button"
|
|
152
159
|
{disabled}
|
|
153
160
|
onclick={() => (open = !open)}
|
|
154
|
-
aria-label=
|
|
161
|
+
aria-label="Select a date"
|
|
155
162
|
aria-expanded={open}
|
|
156
|
-
class="shrink-0 rounded p-1.5 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
163
|
+
class="shrink-0 rounded-xl p-1.5 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
157
164
|
>
|
|
158
165
|
<svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
159
166
|
<rect x="3" y="4.5" width="14" height="12" rx="2" stroke="currentColor" stroke-width="1.4" />
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DateFormat } from '../../core/date.js';
|
|
1
2
|
import { type Tone } from '../../core/tones.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
/** Bindable `YYYY-MM-DD` — the same shape as `<input type="date">`. */
|
|
@@ -7,6 +8,8 @@ interface Props {
|
|
|
7
8
|
max?: string;
|
|
8
9
|
isDisabled?: (date: Date) => boolean;
|
|
9
10
|
weekStart?: 0 | 1;
|
|
11
|
+
/** Order of the date parts the field accepts and prints. */
|
|
12
|
+
format?: DateFormat;
|
|
10
13
|
tone?: Tone;
|
|
11
14
|
invalid?: boolean;
|
|
12
15
|
disabled?: boolean;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
{...rest}
|
|
21
21
|
></div>
|
|
22
22
|
{:else if label}
|
|
23
|
-
<div class="flex items-center gap-3 {className}" {...rest}>
|
|
23
|
+
<div role="separator" class="flex items-center gap-3 {className}" {...rest}>
|
|
24
24
|
<span class="h-px flex-1 bg-hairline"></span>
|
|
25
25
|
<span class="font-sans text-xs font-medium text-text-muted">{label}</span>
|
|
26
26
|
<span class="h-px flex-1 bg-hairline"></span>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { iconLg } from './icon.js';
|
|
4
4
|
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
5
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
6
5
|
import { isBackdropClick, syncDialog } from '../../core/dialog.svelte.js';
|
|
7
6
|
import { float } from './lift.js';
|
|
8
7
|
|
|
@@ -40,7 +39,8 @@
|
|
|
40
39
|
...rest
|
|
41
40
|
}: Props = $props();
|
|
42
41
|
|
|
43
|
-
const
|
|
42
|
+
const uid = $props.id();
|
|
43
|
+
|
|
44
44
|
let dialog: HTMLDialogElement | null = $state(null);
|
|
45
45
|
|
|
46
46
|
const horizontal = $derived(side === 'left' || side === 'right');
|
|
@@ -83,6 +83,8 @@
|
|
|
83
83
|
|
|
84
84
|
<dialog
|
|
85
85
|
bind:this={dialog}
|
|
86
|
+
aria-labelledby={title ? `${uid}-title` : undefined}
|
|
87
|
+
aria-describedby={description ? `${uid}-description` : undefined}
|
|
86
88
|
onclose={close}
|
|
87
89
|
onclick={(event) => {
|
|
88
90
|
if (dismissible && isBackdropClick(event, dialog)) close();
|
|
@@ -100,17 +102,19 @@
|
|
|
100
102
|
{#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
|
|
101
103
|
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
102
104
|
{#if title}
|
|
103
|
-
<h2 class="font-heading text-base font-semibold text-text">{title}</h2>
|
|
105
|
+
<h2 id="{uid}-title" class="font-heading text-base font-semibold text-text">{title}</h2>
|
|
104
106
|
{/if}
|
|
105
107
|
{#if description}
|
|
106
|
-
<p class="font-sans text-[13px] text-text-muted">
|
|
108
|
+
<p id="{uid}-description" class="font-sans text-[13px] text-text-muted">
|
|
109
|
+
{description}
|
|
110
|
+
</p>
|
|
107
111
|
{/if}
|
|
108
112
|
</div>
|
|
109
113
|
{#if dismissible}
|
|
110
114
|
<button
|
|
111
115
|
type="button"
|
|
112
116
|
onclick={close}
|
|
113
|
-
aria-label=
|
|
117
|
+
aria-label="Close"
|
|
114
118
|
class="-m-1 shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
|
115
119
|
>
|
|
116
120
|
<svg class="size-4" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { anchored, type Placement } from '../../core/actions/anchor.js';
|
|
4
4
|
import { clickOutside, focusTrap, navigateList, portal } from '../../core/actions/dismissable.js';
|
|
5
5
|
import { describeTrigger, menuItems, popupTrigger } from '../../core/trigger.js';
|
|
6
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
7
6
|
import { soft } from './lift.js';
|
|
8
7
|
|
|
9
8
|
interface Props {
|
|
@@ -18,7 +17,7 @@
|
|
|
18
17
|
matchWidth?: boolean;
|
|
19
18
|
/** Close as soon as an item inside is activated. Default true. */
|
|
20
19
|
closeOnSelect?: boolean;
|
|
21
|
-
/** Accessible name for the menu. Defaults to
|
|
20
|
+
/** Accessible name for the menu. Defaults to `Menu`. */
|
|
22
21
|
label?: string;
|
|
23
22
|
class?: string;
|
|
24
23
|
/** Classes for the wrapper around the trigger. */
|
|
@@ -38,8 +37,6 @@
|
|
|
38
37
|
triggerClass = ''
|
|
39
38
|
}: Props = $props();
|
|
40
39
|
|
|
41
|
-
const t = useLocale();
|
|
42
|
-
|
|
43
40
|
let anchorEl: HTMLElement | null = $state(null);
|
|
44
41
|
let menuEl: HTMLElement | null = $state(null);
|
|
45
42
|
|
|
@@ -86,7 +83,7 @@
|
|
|
86
83
|
onkeydown={onMenuKeydown}
|
|
87
84
|
onclick={onMenuClick}
|
|
88
85
|
role="menu"
|
|
89
|
-
aria-label={label ??
|
|
86
|
+
aria-label={label ?? 'Menu'}
|
|
90
87
|
tabindex="-1"
|
|
91
88
|
class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 flex-col overflow-y-auto rounded-2xl border border-hairline bg-bg py-1 font-sans {soft} {className}"
|
|
92
89
|
>
|
|
@@ -12,7 +12,7 @@ interface Props {
|
|
|
12
12
|
matchWidth?: boolean;
|
|
13
13
|
/** Close as soon as an item inside is activated. Default true. */
|
|
14
14
|
closeOnSelect?: boolean;
|
|
15
|
-
/** Accessible name for the menu. Defaults to
|
|
15
|
+
/** Accessible name for the menu. Defaults to `Menu`. */
|
|
16
16
|
label?: string;
|
|
17
17
|
class?: string;
|
|
18
18
|
/** Classes for the wrapper around the trigger. */
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { toneBorder, toneSoft, toneSurface, toneText, type Tone } from '../../core/tones.js';
|
|
4
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
4
|
import { formatSize as toReadableSize, sortFiles, type RejectedFile } from '../../core/files.js';
|
|
6
5
|
import { edge } from './lift.js';
|
|
7
6
|
|
|
@@ -17,7 +16,7 @@
|
|
|
17
16
|
maxFiles?: number;
|
|
18
17
|
disabled?: boolean;
|
|
19
18
|
tone?: Tone;
|
|
20
|
-
/** Headline inside the zone. Defaults to
|
|
19
|
+
/** Headline inside the zone. Defaults to `Drop files here`. */
|
|
21
20
|
label?: string;
|
|
22
21
|
hint?: string;
|
|
23
22
|
/** Hide the built-in file list to render your own. */
|
|
@@ -46,15 +45,13 @@
|
|
|
46
45
|
class: className = ''
|
|
47
46
|
}: Props = $props();
|
|
48
47
|
|
|
49
|
-
const t = useLocale();
|
|
50
|
-
|
|
51
48
|
let input: HTMLInputElement | null = $state(null);
|
|
52
49
|
let dragging = $state(false);
|
|
53
50
|
let rejected = $state<RejectedFile[]>([]);
|
|
54
51
|
/** Nested elements fire dragleave too, so count depth instead of toggling. */
|
|
55
52
|
let depth = 0;
|
|
56
53
|
|
|
57
|
-
const formatSize = (bytes: number) => toReadableSize(bytes
|
|
54
|
+
const formatSize = (bytes: number) => toReadableSize(bytes);
|
|
58
55
|
|
|
59
56
|
const defaultHint = $derived.by(() => {
|
|
60
57
|
if (hint) return hint;
|
|
@@ -97,9 +94,9 @@
|
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
const reasons = $derived({
|
|
100
|
-
type:
|
|
101
|
-
size:
|
|
102
|
-
count:
|
|
97
|
+
type: 'wrong file type',
|
|
98
|
+
size: 'too large',
|
|
99
|
+
count: 'over the file limit'
|
|
103
100
|
});
|
|
104
101
|
</script>
|
|
105
102
|
|
|
@@ -147,10 +144,10 @@
|
|
|
147
144
|
{/if}
|
|
148
145
|
</span>
|
|
149
146
|
<span class="flex flex-col gap-1">
|
|
150
|
-
<span class="text-sm font-semibold text-text">{label ??
|
|
147
|
+
<span class="text-sm font-semibold text-text">{label ?? 'Drop files here'}</span>
|
|
151
148
|
<span class="text-[13px] text-text-secondary">
|
|
152
|
-
|
|
153
|
-
<span class="underline underline-offset-2">
|
|
149
|
+
or
|
|
150
|
+
<span class="underline underline-offset-2">browse</span>
|
|
154
151
|
</span>
|
|
155
152
|
{#if defaultHint}
|
|
156
153
|
<span class="text-xs text-text-muted">{defaultHint}</span>
|
|
@@ -192,8 +189,8 @@
|
|
|
192
189
|
<button
|
|
193
190
|
type="button"
|
|
194
191
|
onclick={() => remove(i)}
|
|
195
|
-
aria-label="
|
|
196
|
-
class="-m-1 shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger"
|
|
192
|
+
aria-label="Remove {file.name}"
|
|
193
|
+
class="-m-1 shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger"
|
|
197
194
|
>
|
|
198
195
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
199
196
|
<path
|
|
@@ -13,7 +13,7 @@ interface Props {
|
|
|
13
13
|
maxFiles?: number;
|
|
14
14
|
disabled?: boolean;
|
|
15
15
|
tone?: Tone;
|
|
16
|
-
/** Headline inside the zone. Defaults to
|
|
16
|
+
/** Headline inside the zone. Defaults to `Drop files here`. */
|
|
17
17
|
label?: string;
|
|
18
18
|
hint?: string;
|
|
19
19
|
/** Hide the built-in file list to render your own. */
|
|
@@ -24,9 +24,65 @@
|
|
|
24
24
|
children,
|
|
25
25
|
...rest
|
|
26
26
|
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const uid = $props.id();
|
|
29
|
+
const messageId = $derived(error ? `${uid}-error` : hint ? `${uid}-hint` : undefined);
|
|
30
|
+
|
|
31
|
+
interface Described {
|
|
32
|
+
for?: string;
|
|
33
|
+
messageId?: string;
|
|
34
|
+
required: boolean;
|
|
35
|
+
invalid: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A hint nobody can hear is not a hint. `aria-describedby` has to sit on the
|
|
40
|
+
* control, and the control arrives through `children` — out of reach of this
|
|
41
|
+
* template — so the field finds it once the DOM exists and keeps the three
|
|
42
|
+
* attributes in step itself. It touches only what it set: an `aria-invalid`
|
|
43
|
+
* the control already carries from its own `invalid` prop stays that
|
|
44
|
+
* control's business.
|
|
45
|
+
*/
|
|
46
|
+
function describes(node: HTMLElement, state: Described) {
|
|
47
|
+
let ourId: string | undefined;
|
|
48
|
+
let ownsInvalid = false;
|
|
49
|
+
|
|
50
|
+
const apply = (next: Described) => {
|
|
51
|
+
const control =
|
|
52
|
+
(next.for ? node.querySelector<HTMLElement>(`#${CSS.escape(next.for)}`) : null) ??
|
|
53
|
+
node.querySelector<HTMLElement>('input, select, textarea');
|
|
54
|
+
if (!control) return;
|
|
55
|
+
|
|
56
|
+
const ids = (control.getAttribute('aria-describedby') ?? '')
|
|
57
|
+
.split(/\s+/)
|
|
58
|
+
.filter((id) => id && id !== ourId);
|
|
59
|
+
if (next.messageId) ids.push(next.messageId);
|
|
60
|
+
if (ids.length) control.setAttribute('aria-describedby', ids.join(' '));
|
|
61
|
+
else control.removeAttribute('aria-describedby');
|
|
62
|
+
ourId = next.messageId;
|
|
63
|
+
|
|
64
|
+
if (next.required) control.setAttribute('aria-required', 'true');
|
|
65
|
+
else control.removeAttribute('aria-required');
|
|
66
|
+
|
|
67
|
+
if (next.invalid) {
|
|
68
|
+
ownsInvalid ||= !control.hasAttribute('aria-invalid');
|
|
69
|
+
control.setAttribute('aria-invalid', 'true');
|
|
70
|
+
} else if (ownsInvalid) {
|
|
71
|
+
control.removeAttribute('aria-invalid');
|
|
72
|
+
ownsInvalid = false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
apply(state);
|
|
77
|
+
return { update: apply };
|
|
78
|
+
}
|
|
27
79
|
</script>
|
|
28
80
|
|
|
29
|
-
<div
|
|
81
|
+
<div
|
|
82
|
+
use:describes={{ for: htmlFor, messageId, required, invalid: Boolean(error) }}
|
|
83
|
+
class="flex w-full flex-col gap-1.5 {className}"
|
|
84
|
+
{...rest}
|
|
85
|
+
>
|
|
30
86
|
{#if label}
|
|
31
87
|
<label for={htmlFor} class="font-sans text-sm font-medium text-text">
|
|
32
88
|
{label}
|
|
@@ -35,8 +91,8 @@
|
|
|
35
91
|
{/if}
|
|
36
92
|
{@render children()}
|
|
37
93
|
{#if error}
|
|
38
|
-
<p class="font-sans text-[13px] text-danger">{error}</p>
|
|
94
|
+
<p id="{uid}-error" role="alert" class="font-sans text-[13px] text-danger">{error}</p>
|
|
39
95
|
{:else if hint}
|
|
40
|
-
<p class="font-sans text-[13px] text-text-muted">{hint}</p>
|
|
96
|
+
<p id="{uid}-hint" class="font-sans text-[13px] text-text-muted">{hint}</p>
|
|
41
97
|
{/if}
|
|
42
98
|
</div>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { formatSize as toReadableSize, sortFiles, type RejectedFile } from '../../core/files.js';
|
|
3
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
4
3
|
import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
|
|
5
4
|
import { edge } from './lift.js';
|
|
6
5
|
import type { InputSize } from './Input.svelte';
|
|
@@ -19,9 +18,9 @@
|
|
|
19
18
|
size?: InputSize;
|
|
20
19
|
tone?: Tone;
|
|
21
20
|
invalid?: boolean;
|
|
22
|
-
/** Text on the button. Defaults to
|
|
21
|
+
/** Text on the button. Defaults to `Choose file`. */
|
|
23
22
|
label?: string;
|
|
24
|
-
/** Shown while nothing is chosen. Defaults to
|
|
23
|
+
/** Shown while nothing is chosen. Defaults to `No file chosen`. */
|
|
25
24
|
placeholder?: string;
|
|
26
25
|
/** Show the list of held files below the row. */
|
|
27
26
|
showList?: boolean;
|
|
@@ -51,18 +50,16 @@
|
|
|
51
50
|
class: className = ''
|
|
52
51
|
}: Props = $props();
|
|
53
52
|
|
|
54
|
-
const t = useLocale();
|
|
55
|
-
|
|
56
53
|
let input: HTMLInputElement | null = $state(null);
|
|
57
54
|
let rejected = $state<RejectedFile[]>([]);
|
|
58
55
|
|
|
59
|
-
const formatSize = (bytes: number) => toReadableSize(bytes
|
|
56
|
+
const formatSize = (bytes: number) => toReadableSize(bytes);
|
|
60
57
|
|
|
61
58
|
/** One file reads as its name and weight; several read as a count. */
|
|
62
59
|
const summary = $derived.by(() => {
|
|
63
|
-
if (files.length === 0) return placeholder ??
|
|
60
|
+
if (files.length === 0) return placeholder ?? 'No file chosen';
|
|
64
61
|
if (files.length === 1) return `${files[0].name} · ${formatSize(files[0].size)}`;
|
|
65
|
-
return `${files.length} $
|
|
62
|
+
return `${files.length} $selected`;
|
|
66
63
|
});
|
|
67
64
|
|
|
68
65
|
function add(incoming: FileList | File[] | null) {
|
|
@@ -91,9 +88,9 @@
|
|
|
91
88
|
}
|
|
92
89
|
|
|
93
90
|
const reasons: Record<RejectedFile['reason'], string> = $derived({
|
|
94
|
-
type:
|
|
95
|
-
size:
|
|
96
|
-
count:
|
|
91
|
+
type: 'wrong file type',
|
|
92
|
+
size: 'too large',
|
|
93
|
+
count: 'over the file limit'
|
|
97
94
|
});
|
|
98
95
|
|
|
99
96
|
const sizes: Record<InputSize, string> = {
|
|
@@ -122,10 +119,10 @@
|
|
|
122
119
|
type="button"
|
|
123
120
|
{disabled}
|
|
124
121
|
onclick={() => input?.click()}
|
|
125
|
-
class="shrink-0 rounded-
|
|
122
|
+
class="shrink-0 rounded-xl border border-hairline-strong bg-bg-alt font-sans font-medium text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current
|
|
126
123
|
{buttons[size]}"
|
|
127
124
|
>
|
|
128
|
-
{label ??
|
|
125
|
+
{label ?? 'Choose file'}
|
|
129
126
|
</button>
|
|
130
127
|
|
|
131
128
|
<span
|
|
@@ -138,8 +135,8 @@
|
|
|
138
135
|
<button
|
|
139
136
|
type="button"
|
|
140
137
|
onclick={clear}
|
|
141
|
-
aria-label=
|
|
142
|
-
class="mr-1 shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger"
|
|
138
|
+
aria-label="Remove"
|
|
139
|
+
class="mr-1 shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger"
|
|
143
140
|
>
|
|
144
141
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
145
142
|
<path
|
|
@@ -186,8 +183,8 @@
|
|
|
186
183
|
<button
|
|
187
184
|
type="button"
|
|
188
185
|
onclick={() => (files = files.filter((_, at) => at !== i))}
|
|
189
|
-
aria-label="
|
|
190
|
-
class="-m-1 shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger"
|
|
186
|
+
aria-label="Remove {file.name}"
|
|
187
|
+
class="-m-1 shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger"
|
|
191
188
|
>
|
|
192
189
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
193
190
|
<path
|
|
@@ -15,9 +15,9 @@ interface Props {
|
|
|
15
15
|
size?: InputSize;
|
|
16
16
|
tone?: Tone;
|
|
17
17
|
invalid?: boolean;
|
|
18
|
-
/** Text on the button. Defaults to
|
|
18
|
+
/** Text on the button. Defaults to `Choose file`. */
|
|
19
19
|
label?: string;
|
|
20
|
-
/** Shown while nothing is chosen. Defaults to
|
|
20
|
+
/** Shown while nothing is chosen. Defaults to `No file chosen`. */
|
|
21
21
|
placeholder?: string;
|
|
22
22
|
/** Show the list of held files below the row. */
|
|
23
23
|
showList?: boolean;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
columns?: FooterColumn[];
|
|
23
23
|
/** Logo, wordmark and a line of copy on the left. */
|
|
24
24
|
brand?: Snippet;
|
|
25
|
-
/** Right side of the bottom bar — social links, a
|
|
25
|
+
/** Right side of the bottom bar — social links, a language picker. */
|
|
26
26
|
bottom?: Snippet;
|
|
27
27
|
/** Left side of the bottom bar. Ignored when `bottom` covers both. */
|
|
28
28
|
copyright?: string;
|
|
@@ -59,15 +59,26 @@
|
|
|
59
59
|
<a
|
|
60
60
|
href={item.href}
|
|
61
61
|
target={item.external ? '_blank' : undefined}
|
|
62
|
-
rel={item.external ? 'noreferrer' : undefined}
|
|
62
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
63
63
|
class="inline-flex items-center gap-2 font-sans text-[13px] text-text-muted transition-colors duration-150 ease-brand-out {toneHoverText[
|
|
64
64
|
tone
|
|
65
65
|
]}"
|
|
66
66
|
>
|
|
67
67
|
{#if item.icon}<span class={iconSm}>{@render item.icon()}</span
|
|
68
68
|
>{/if}{item.label}
|
|
69
|
-
{#if item.external}<
|
|
70
|
-
|
|
69
|
+
{#if item.external}<svg
|
|
70
|
+
class="inline-block size-[0.85em] align-[-0.05em]"
|
|
71
|
+
viewBox="0 0 14 14"
|
|
72
|
+
fill="none"
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
><path
|
|
75
|
+
d="M5.5 2.5h6v6M11.5 2.5 3.5 10.5"
|
|
76
|
+
stroke="currentColor"
|
|
77
|
+
stroke-width="1.5"
|
|
78
|
+
stroke-linecap="round"
|
|
79
|
+
stroke-linejoin="round"
|
|
80
|
+
/></svg
|
|
81
|
+
><span class="sr-only">(opens in a new tab)</span>{/if}
|
|
71
82
|
</a>
|
|
72
83
|
</li>
|
|
73
84
|
{/each}
|
|
@@ -15,7 +15,7 @@ interface Props {
|
|
|
15
15
|
columns?: FooterColumn[];
|
|
16
16
|
/** Logo, wordmark and a line of copy on the left. */
|
|
17
17
|
brand?: Snippet;
|
|
18
|
-
/** Right side of the bottom bar — social links, a
|
|
18
|
+
/** Right side of the bottom bar — social links, a language picker. */
|
|
19
19
|
bottom?: Snippet;
|
|
20
20
|
/** Left side of the bottom bar. Ignored when `bottom` covers both. */
|
|
21
21
|
copyright?: string;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
focusWithinRing,
|
|
6
|
+
toneFocusWithinBorder,
|
|
7
|
+
toneFocusWithinRing,
|
|
8
|
+
type Tone
|
|
9
|
+
} from '../../core/tones.js';
|
|
5
10
|
import { edge } from './lift.js';
|
|
6
11
|
|
|
7
12
|
export type InputSize = 'sm' | 'md' | 'lg';
|
|
@@ -38,7 +43,9 @@
|
|
|
38
43
|
</script>
|
|
39
44
|
|
|
40
45
|
<span
|
|
41
|
-
class="inline-flex w-full items-center gap-2 rounded-xl border bg-bg px-3 {edge} transition-colors duration-150 ease-brand-out
|
|
46
|
+
class="inline-flex w-full items-center gap-2 rounded-xl border bg-bg px-3 {edge} transition-colors duration-150 ease-brand-out {focusWithinRing} {toneFocusWithinRing[
|
|
47
|
+
tone
|
|
48
|
+
]}
|
|
42
49
|
{sizes[size]}
|
|
43
50
|
{invalid
|
|
44
51
|
? 'border-danger focus-within:border-danger'
|
|
@@ -12,5 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<label class="font-sans text-sm font-medium text-text {className}" {...rest}>
|
|
14
14
|
{@render children()}
|
|
15
|
-
{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span
|
|
15
|
+
{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span><span class="sr-only"
|
|
16
|
+
>(required)</span
|
|
17
|
+
>{/if}
|
|
16
18
|
</label>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
<a
|
|
42
42
|
target={external ? '_blank' : undefined}
|
|
43
|
-
rel={external ? 'noreferrer' : undefined}
|
|
43
|
+
rel={external ? 'noopener noreferrer' : undefined}
|
|
44
44
|
class="inline-flex items-center gap-1 rounded-md font-sans transition-colors duration-150 ease-brand-out
|
|
45
45
|
{muted ? `text-text-secondary ${toneHoverText[tone]}` : toneText[tone]}
|
|
46
46
|
{underlines[underline]} {focusRing} {toneRing[tone]} {className}"
|
|
@@ -49,5 +49,16 @@
|
|
|
49
49
|
{#if icon}<span class={iconSm}>{@render icon()}</span>{/if}
|
|
50
50
|
{@render children()}
|
|
51
51
|
{#if iconEnd}<span class={iconSm}>{@render iconEnd()}</span>{/if}
|
|
52
|
-
{#if external}
|
|
52
|
+
{#if external}
|
|
53
|
+
<svg class="size-[0.85em] shrink-0" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
54
|
+
<path
|
|
55
|
+
d="M5.5 2.5h6v6M11.5 2.5 3.5 10.5"
|
|
56
|
+
stroke="currentColor"
|
|
57
|
+
stroke-width="1.5"
|
|
58
|
+
stroke-linecap="round"
|
|
59
|
+
stroke-linejoin="round"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
<span class="sr-only">(opens in a new tab)</span>
|
|
63
|
+
{/if}
|
|
53
64
|
</a>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
<svelte:element
|
|
43
43
|
this={logo.href ? 'a' : 'div'}
|
|
44
44
|
href={logo.href}
|
|
45
|
-
class="flex items-center rounded transition-all duration-200 ease-brand-out {muted
|
|
45
|
+
class="flex items-center rounded-md transition-all duration-200 ease-brand-out {muted
|
|
46
46
|
? 'opacity-60 grayscale hover:opacity-100 hover:grayscale-0'
|
|
47
47
|
: ''}"
|
|
48
48
|
>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
-
import { toneSoftHover, toneText, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { toneSoft, toneSoftHover, toneText, type Tone } from '../../core/tones.js';
|
|
5
5
|
import { iconMd } from './icon.js';
|
|
6
6
|
|
|
7
7
|
interface Props extends HTMLButtonAttributes {
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
children,
|
|
29
29
|
...rest
|
|
30
30
|
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
// A chosen row speaks in the brand, the same as an active nav link — a tick
|
|
33
|
+
// alone leaves it the same grey as every row above it. An item that already
|
|
34
|
+
// carries a tone (a red `Delete`) keeps its own.
|
|
35
|
+
const chosen = $derived(toneSoft[tone === 'neutral' ? 'brand' : tone]);
|
|
31
36
|
</script>
|
|
32
37
|
|
|
33
38
|
<svelte:element
|
|
@@ -39,9 +44,11 @@
|
|
|
39
44
|
aria-disabled={disabled ? 'true' : undefined}
|
|
40
45
|
tabindex={disabled ? undefined : -1}
|
|
41
46
|
class="mx-1 flex items-center gap-2.5 rounded-xl px-2.5 py-1.5 text-left text-sm transition-colors duration-100 ease-brand-out focus:outline-none disabled:pointer-events-none disabled:opacity-40
|
|
42
|
-
{
|
|
43
|
-
?
|
|
44
|
-
:
|
|
47
|
+
{selected
|
|
48
|
+
? `${chosen} font-medium`
|
|
49
|
+
: tone === 'neutral'
|
|
50
|
+
? 'text-text-secondary hover:bg-bg-inset focus-visible:bg-bg-inset'
|
|
51
|
+
: `${toneText[tone]} ${toneSoftHover[tone]}`}
|
|
45
52
|
{className}"
|
|
46
53
|
{...rest}
|
|
47
54
|
>
|