@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
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# @nqmcreative/ui
|
|
2
2
|
|
|
3
3
|
Svelte 5 (runes) + Tailwind CSS v4 component library. One package, **three
|
|
4
|
-
styles**,
|
|
4
|
+
styles**, 70 components each — the same props, the same behaviour, a different
|
|
5
5
|
look. Eight tones, light and dark, no runtime dependencies.
|
|
6
6
|
|
|
7
7
|
| style | import | character |
|
|
8
8
|
| -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
9
9
|
| `matte` | `@nqmcreative/ui/matte` | Flat and sharp-cornered — no radius, no shadow, no gloss. Hairline rules and mono micro-labels do the work borders usually do. |
|
|
10
10
|
| `paper` | `@nqmcreative/ui/paper` | The neutral one. Start here if unsure: soft corners, light shadows, generous spacing, and nothing that fights the content. |
|
|
11
|
-
| `sprout` | `@nqmcreative/ui/sprout` | Warm and fully rounded — leaf green on
|
|
11
|
+
| `sprout` | `@nqmcreative/ui/sprout` | Warm and fully rounded — leaf green on white, pill buttons, and a round display face. For school, community and family sites. |
|
|
12
12
|
|
|
13
13
|
The docs site lives in this repo — every component with a live demo, plus
|
|
14
|
-
theming
|
|
14
|
+
theming and contributing guides. `bun run dev` to read it.
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
@@ -307,33 +307,31 @@ Each style's `theme.css` ships both palettes; nothing in a component says
|
|
|
307
307
|
`dark:`. `<html class="dark">` or `class="light"` forces one, bare `<html>`
|
|
308
308
|
follows the OS. `ThemeToggle` handles all three.
|
|
309
309
|
|
|
310
|
-
###
|
|
310
|
+
### Built-in strings
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
Components render English text of their own — ARIA names, `No data`, `Choose
|
|
313
|
+
file` — written inline at the call site. Where a caller would plausibly want to
|
|
314
|
+
change one, it is a prop: `<Table empty="…">`, `<Dropzone label="…">`,
|
|
315
|
+
`<FileInput placeholder="…">`.
|
|
314
316
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
`enUS` and `idID` ship with the package; pass a partial to change a few keys.
|
|
322
|
-
Under SSR use `<LocaleProvider>` instead, so the locale is scoped to a request.
|
|
317
|
+
Dates are the one place the browser decides: `Calendar` and `DatePicker` take
|
|
318
|
+
month and weekday names from `Intl` with no locale argument, so they follow the
|
|
319
|
+
reader's own system. The order of the parts the field parses and prints is
|
|
320
|
+
explicit instead — `<DatePicker format="dmy">`, defaulting to `dmy`.
|
|
323
321
|
|
|
324
322
|
## Layout
|
|
325
323
|
|
|
326
324
|
```
|
|
327
325
|
src/lib/core/ behaviour, a11y, types — no markup, no classes
|
|
328
|
-
src/lib/styles/matte/
|
|
329
|
-
src/lib/styles/paper/
|
|
330
|
-
src/lib/styles/sprout/
|
|
326
|
+
src/lib/styles/matte/ 70 components
|
|
327
|
+
src/lib/styles/paper/ 70 components
|
|
328
|
+
src/lib/styles/sprout/ 70 components
|
|
331
329
|
scripts/catalogue.mjs the list every style must implement
|
|
332
330
|
```
|
|
333
331
|
|
|
334
332
|
`core` holds what has no opinion about looks: focus traps, roving keyboard
|
|
335
333
|
navigation, viewport-aware anchoring, date maths, file validation, table
|
|
336
|
-
sorting, the toast queue
|
|
334
|
+
sorting, the toast queue. There is no `matte` version of a
|
|
337
335
|
focus trap, so no style writes one.
|
|
338
336
|
|
|
339
337
|
A style holds only markup and classes. It may differ from another completely —
|
|
@@ -354,7 +352,7 @@ same.
|
|
|
354
352
|
| App shell | `Navbar`, `Sidebar`, `Footer` |
|
|
355
353
|
| Overlay | `Modal`, `ConfirmDialog`, `Drawer`, `Dropdown` + `MenuItem` + `MenuSeparator`, `ContextMenu`, `CommandPalette`, `Popover`, `Tooltip` |
|
|
356
354
|
| Marketing | `HeroSection`, `FeatureGrid`, `FeatureCard`, `PricingCard`, `Testimonial`, `LogoCloud`, `CTASection`, `StatsBand` |
|
|
357
|
-
| System | `ThemeToggle
|
|
355
|
+
| System | `ThemeToggle` |
|
|
358
356
|
|
|
359
357
|
Overlays are built on the native top layer and three in-house actions
|
|
360
358
|
(`portal`, `focusTrap`, `anchored`) rather than a positioning dependency.
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare function calendarKeyMove(key: string, focused: Date, weekStart: 0 | 1, shiftKey?: boolean): Date | null;
|
|
2
|
-
/** The placeholder a text date field shows for a
|
|
2
|
+
/** The placeholder a text date field shows for a given part order. */
|
|
3
3
|
export declare function dateHint(format: 'dmy' | 'mdy' | 'ymd'): string;
|
package/dist/core/calendar.js
CHANGED
|
@@ -30,7 +30,7 @@ export function calendarKeyMove(key, focused, weekStart, shiftKey = false) {
|
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
/** The placeholder a text date field shows for a
|
|
33
|
+
/** The placeholder a text date field shows for a given part order. */
|
|
34
34
|
export function dateHint(format) {
|
|
35
35
|
return { dmy: 'DD/MM/YYYY', mdy: 'MM/DD/YYYY', ymd: 'YYYY/MM/DD' }[format];
|
|
36
36
|
}
|
package/dist/core/date.d.ts
CHANGED
|
@@ -21,12 +21,12 @@ export declare function addMonths(date: Date, months: number): Date;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function monthGrid(month: Date, weekStart?: 0 | 1): Date[];
|
|
23
23
|
/** Localised weekday initials, starting on `weekStart`. */
|
|
24
|
-
export declare function weekdayNames(locale
|
|
25
|
-
export declare function monthLabel(month: Date, locale
|
|
24
|
+
export declare function weekdayNames(locale?: string, weekStart?: 0 | 1): string[];
|
|
25
|
+
export declare function monthLabel(month: Date, locale?: string): string;
|
|
26
26
|
/** `true` when `date` falls outside `min`/`max`, both inclusive and optional. */
|
|
27
27
|
export declare function isOutOfRange(date: Date, min?: string, max?: string): boolean;
|
|
28
28
|
export type DateFormat = 'dmy' | 'mdy' | 'ymd';
|
|
29
|
-
/** Prints `YYYY-MM-DD` in the
|
|
29
|
+
/** Prints `YYYY-MM-DD` in the chosen part order, e.g. `31/12/2026`. */
|
|
30
30
|
export declare function formatISO(value: string, format: DateFormat, separator?: string): string;
|
|
31
31
|
/**
|
|
32
32
|
* The inverse of `formatISO`, forgiving about separators and single digits.
|
package/dist/core/date.js
CHANGED
|
@@ -69,7 +69,7 @@ export function isOutOfRange(date, min, max) {
|
|
|
69
69
|
return true;
|
|
70
70
|
return false;
|
|
71
71
|
}
|
|
72
|
-
/** Prints `YYYY-MM-DD` in the
|
|
72
|
+
/** Prints `YYYY-MM-DD` in the chosen part order, e.g. `31/12/2026`. */
|
|
73
73
|
export function formatISO(value, format, separator = '/') {
|
|
74
74
|
const date = fromISO(value);
|
|
75
75
|
if (!date)
|
package/dist/core/files.d.ts
CHANGED
|
@@ -29,7 +29,6 @@ export declare function sortFiles(incoming: FileList | File[] | null, rules?: Fi
|
|
|
29
29
|
rejected: RejectedFile[];
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
|
-
* `900 B`, `2 MB`, `1.5 GB`.
|
|
33
|
-
* suffix can be translated together.
|
|
32
|
+
* `900 B`, `2 MB`, `1.5 GB`. Pass `units` to relabel the suffixes.
|
|
34
33
|
*/
|
|
35
|
-
export declare function formatSize(bytes: number, units
|
|
34
|
+
export declare function formatSize(bytes: number, units?: readonly string[]): string;
|
package/dist/core/files.js
CHANGED
|
@@ -46,10 +46,9 @@ export function sortFiles(incoming, rules = {}) {
|
|
|
46
46
|
return { accepted, rejected };
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* `900 B`, `2 MB`, `1.5 GB`.
|
|
50
|
-
* suffix can be translated together.
|
|
49
|
+
* `900 B`, `2 MB`, `1.5 GB`. Pass `units` to relabel the suffixes.
|
|
51
50
|
*/
|
|
52
|
-
export function formatSize(bytes, units) {
|
|
51
|
+
export function formatSize(bytes, units = ['B', 'KB', 'MB', 'GB']) {
|
|
53
52
|
if (bytes < 1024)
|
|
54
53
|
return `${bytes} ${units[0]}`;
|
|
55
54
|
let value = bytes / 1024;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* @nqmcreative/ui/core — everything the styles share.
|
|
3
3
|
*
|
|
4
4
|
* Behaviour, not looks: focus containment, keyboard navigation, date maths,
|
|
5
|
-
* file validation, the toast queue
|
|
5
|
+
* file validation, the toast queue. Nothing in here
|
|
6
6
|
* renders anything, and nothing in here knows which style is being used.
|
|
7
7
|
*
|
|
8
8
|
* Styles are free to ignore any of it — but there is no `matte` version of a
|
|
9
9
|
* focus trap, so none of them should have to write one.
|
|
10
10
|
*/
|
|
11
|
-
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText } from './tones.js';
|
|
11
|
+
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, focusWithinRing, toneFocusWithinRing, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText } from './tones.js';
|
|
12
12
|
export type { Tone } from './tones.js';
|
|
13
13
|
export { anchored } from './actions/anchor.js';
|
|
14
14
|
export type { Placement, AnchorOptions } from './actions/anchor.js';
|
|
@@ -40,5 +40,3 @@ export { toast } from './toast.svelte.js';
|
|
|
40
40
|
export type { Toast, ToastOptions, ToastAction } from './toast.svelte.js';
|
|
41
41
|
export { applyTheme, storedTheme, persistTheme, nextTheme } from './theme.svelte.js';
|
|
42
42
|
export type { Theme } from './theme.svelte.js';
|
|
43
|
-
export { locale, setLocale, provideLocale, useLocale, enUS, idID, LocaleStore } from './locale.svelte.js';
|
|
44
|
-
export type { Locale } from './locale.svelte.js';
|
package/dist/core/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* @nqmcreative/ui/core — everything the styles share.
|
|
3
3
|
*
|
|
4
4
|
* Behaviour, not looks: focus containment, keyboard navigation, date maths,
|
|
5
|
-
* file validation, the toast queue
|
|
5
|
+
* file validation, the toast queue. Nothing in here
|
|
6
6
|
* renders anything, and nothing in here knows which style is being used.
|
|
7
7
|
*
|
|
8
8
|
* Styles are free to ignore any of it — but there is no `matte` version of a
|
|
9
9
|
* focus trap, so none of them should have to write one.
|
|
10
10
|
*/
|
|
11
11
|
/* --- tones --- */
|
|
12
|
-
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText } from './tones.js';
|
|
12
|
+
export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, focusWithinRing, toneFocusWithinRing, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText } from './tones.js';
|
|
13
13
|
/* --- actions --- */
|
|
14
14
|
export { anchored } from './actions/anchor.js';
|
|
15
15
|
export { clickOutside, focusTrap, focusable, navigateList, portal } from './actions/dismissable.js';
|
|
@@ -35,5 +35,3 @@ export { calendarKeyMove, dateHint } from './calendar.js';
|
|
|
35
35
|
export { toast } from './toast.svelte.js';
|
|
36
36
|
/* --- theme --- */
|
|
37
37
|
export { applyTheme, storedTheme, persistTheme, nextTheme } from './theme.svelte.js';
|
|
38
|
-
/* --- locale --- */
|
|
39
|
-
export { locale, setLocale, provideLocale, useLocale, enUS, idID, LocaleStore } from './locale.svelte.js';
|
package/dist/core/password.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Password strength, 0–4,
|
|
2
|
+
* Password strength, 0–4, for labelling as you see fit.
|
|
3
3
|
*
|
|
4
4
|
* Deliberately simple — length plus character variety. It is a nudge in the
|
|
5
5
|
* form, not a security control, and anything cleverer would need a dictionary
|
package/dist/core/password.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Password strength, 0–4,
|
|
2
|
+
* Password strength, 0–4, for labelling as you see fit.
|
|
3
3
|
*
|
|
4
4
|
* Deliberately simple — length plus character variety. It is a nudge in the
|
|
5
5
|
* form, not a security control, and anything cleverer would need a dictionary
|
package/dist/core/tones.d.ts
CHANGED
|
@@ -30,6 +30,16 @@ export declare const toneRing: Record<Tone, string>;
|
|
|
30
30
|
export declare const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2";
|
|
31
31
|
/** Border colour applied while the control itself has focus. */
|
|
32
32
|
export declare const toneFocusBorder: Record<Tone, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Focus-ring geometry for a shell that holds the real control — `Input`'s
|
|
35
|
+
* wrapper, an input group. The ring belongs on the box the reader sees, not on
|
|
36
|
+
* the bare `<input>` inside it, so it is keyed off a focused descendant.
|
|
37
|
+
* `:has(:focus-visible)` rather than `:focus-within` so a pointer click into a
|
|
38
|
+
* field does not draw a keyboard affordance.
|
|
39
|
+
*/
|
|
40
|
+
export declare const focusWithinRing = "has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2";
|
|
41
|
+
/** Ring colour for `focusWithinRing`. */
|
|
42
|
+
export declare const toneFocusWithinRing: Record<Tone, string>;
|
|
33
43
|
/** Border colour applied while a descendant has focus (wrapped controls). */
|
|
34
44
|
export declare const toneFocusWithinBorder: Record<Tone, string>;
|
|
35
45
|
/** Tinted surface only (no text colour) — alerts, callouts, table rows. */
|
package/dist/core/tones.js
CHANGED
|
@@ -128,6 +128,25 @@ export const toneFocusBorder = {
|
|
|
128
128
|
danger: 'focus:border-danger',
|
|
129
129
|
neutral: 'focus:border-neutral'
|
|
130
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* Focus-ring geometry for a shell that holds the real control — `Input`'s
|
|
133
|
+
* wrapper, an input group. The ring belongs on the box the reader sees, not on
|
|
134
|
+
* the bare `<input>` inside it, so it is keyed off a focused descendant.
|
|
135
|
+
* `:has(:focus-visible)` rather than `:focus-within` so a pointer click into a
|
|
136
|
+
* field does not draw a keyboard affordance.
|
|
137
|
+
*/
|
|
138
|
+
export const focusWithinRing = 'has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2';
|
|
139
|
+
/** Ring colour for `focusWithinRing`. */
|
|
140
|
+
export const toneFocusWithinRing = {
|
|
141
|
+
brand: 'has-[:focus-visible]:outline-brand',
|
|
142
|
+
accent: 'has-[:focus-visible]:outline-accent',
|
|
143
|
+
violet: 'has-[:focus-visible]:outline-violet',
|
|
144
|
+
info: 'has-[:focus-visible]:outline-info',
|
|
145
|
+
success: 'has-[:focus-visible]:outline-success',
|
|
146
|
+
warning: 'has-[:focus-visible]:outline-warning',
|
|
147
|
+
danger: 'has-[:focus-visible]:outline-danger',
|
|
148
|
+
neutral: 'has-[:focus-visible]:outline-neutral'
|
|
149
|
+
};
|
|
131
150
|
/** Border colour applied while a descendant has focus (wrapped controls). */
|
|
132
151
|
export const toneFocusWithinBorder = {
|
|
133
152
|
brand: 'focus-within:border-brand',
|
package/dist/core/trigger.d.ts
CHANGED
|
@@ -31,6 +31,14 @@ export declare function popupTrigger(node: HTMLElement, options: PopupTriggerOpt
|
|
|
31
31
|
* wrapper, so screen readers announce the state on the thing being pressed.
|
|
32
32
|
*/
|
|
33
33
|
export declare function describeTrigger(wrapper: HTMLElement | null, kind: 'menu' | 'dialog' | 'listbox', open: boolean): void;
|
|
34
|
+
/**
|
|
35
|
+
* Points the real control inside a wrapper at the element that describes it —
|
|
36
|
+
* a tooltip's text. Same reason as `describeTrigger`: the control is whatever
|
|
37
|
+
* the consumer passed in a snippet, so the attribute has to be attached rather
|
|
38
|
+
* than written in the template. Without it a tooltip is decoration: sighted
|
|
39
|
+
* hover users get the hint, keyboard and screen-reader users get nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function describedBy(wrapper: HTMLElement | null, id: string): void;
|
|
34
42
|
/** The menu items a roving-focus menu should walk, in DOM order. */
|
|
35
43
|
export declare function menuItems(menu: HTMLElement | null): HTMLElement[];
|
|
36
44
|
/**
|
package/dist/core/trigger.js
CHANGED
|
@@ -71,6 +71,17 @@ export function describeTrigger(wrapper, kind, open) {
|
|
|
71
71
|
control.setAttribute('aria-haspopup', kind);
|
|
72
72
|
control.setAttribute('aria-expanded', String(open));
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Points the real control inside a wrapper at the element that describes it —
|
|
76
|
+
* a tooltip's text. Same reason as `describeTrigger`: the control is whatever
|
|
77
|
+
* the consumer passed in a snippet, so the attribute has to be attached rather
|
|
78
|
+
* than written in the template. Without it a tooltip is decoration: sighted
|
|
79
|
+
* hover users get the hint, keyboard and screen-reader users get nothing.
|
|
80
|
+
*/
|
|
81
|
+
export function describedBy(wrapper, id) {
|
|
82
|
+
const control = wrapper?.querySelector('button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
83
|
+
control?.setAttribute('aria-describedby', id);
|
|
84
|
+
}
|
|
74
85
|
/** The menu items a roving-focus menu should walk, in DOM order. */
|
|
75
86
|
export function menuItems(menu) {
|
|
76
87
|
return menu ? [...menu.querySelectorAll('[role="menuitem"]:not([disabled])')] : [];
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
toneText,
|
|
9
9
|
type Tone
|
|
10
10
|
} from '../../core/tones.js';
|
|
11
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
12
11
|
|
|
13
12
|
export type AlertVariant = 'soft' | 'outline' | 'accent';
|
|
14
13
|
|
|
@@ -36,7 +35,6 @@
|
|
|
36
35
|
...rest
|
|
37
36
|
}: Props = $props();
|
|
38
37
|
|
|
39
|
-
const t = useLocale();
|
|
40
38
|
let open = $state(true);
|
|
41
39
|
|
|
42
40
|
const variants = $derived({
|
|
@@ -71,7 +69,7 @@
|
|
|
71
69
|
|
|
72
70
|
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
73
71
|
{#if title}
|
|
74
|
-
<p class="text-[15px] leading-snug font-medium {toneText[tone]}">{title}</p>
|
|
72
|
+
<p class="font-heading text-[15px] leading-snug font-medium {toneText[tone]}">{title}</p>
|
|
75
73
|
{/if}
|
|
76
74
|
{#if children}
|
|
77
75
|
<div class="text-sm leading-relaxed text-text-secondary">{@render children()}</div>
|
|
@@ -82,7 +80,7 @@
|
|
|
82
80
|
<button
|
|
83
81
|
type="button"
|
|
84
82
|
onclick={dismiss}
|
|
85
|
-
aria-label=
|
|
83
|
+
aria-label="Dismiss"
|
|
86
84
|
class="-m-1 shrink-0 p-1 text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
87
85
|
>
|
|
88
86
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
@@ -47,6 +47,12 @@
|
|
|
47
47
|
xl: 'grid place-items-center *:size-8'
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
// A src that 404s left the reader with the browser's broken-image mark while
|
|
51
|
+
// the initials sat right there unused. Holding the failed URL rather than a
|
|
52
|
+
// flag means a new src is tried again on its own.
|
|
53
|
+
let failed = $state('');
|
|
54
|
+
const broken = $derived(failed !== '' && failed === src);
|
|
55
|
+
|
|
50
56
|
const initials = $derived(
|
|
51
57
|
name
|
|
52
58
|
.trim()
|
|
@@ -60,11 +66,18 @@
|
|
|
60
66
|
|
|
61
67
|
<span
|
|
62
68
|
class="inline-flex shrink-0 items-center justify-center overflow-hidden font-mono font-medium select-none
|
|
63
|
-
{squared ? '' : 'rounded-full'} {sizes[size]} {src
|
|
69
|
+
{squared ? '' : 'rounded-full'} {sizes[size]} {src && !broken
|
|
70
|
+
? 'bg-bg-inset'
|
|
71
|
+
: toneSoft[tone]} {className}"
|
|
64
72
|
{...rest}
|
|
65
73
|
>
|
|
66
|
-
{#if src}
|
|
67
|
-
<img
|
|
74
|
+
{#if src && !broken}
|
|
75
|
+
<img
|
|
76
|
+
{src}
|
|
77
|
+
alt={alt ?? name}
|
|
78
|
+
onerror={() => (failed = src ?? '')}
|
|
79
|
+
class="size-full object-cover"
|
|
80
|
+
/>
|
|
68
81
|
{:else if fallback}
|
|
69
82
|
<span class={glyphs[size]}>{@render fallback()}</span>
|
|
70
83
|
{:else}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { toneHoverText, type Tone } from '../../core/tones.js';
|
|
5
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
6
5
|
import { iconMd } from './icon.js';
|
|
7
6
|
|
|
8
7
|
export interface BreadcrumbItem {
|
|
@@ -19,11 +18,9 @@
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
let { items, separator = '/', tone = 'brand', class: className = '', ...rest }: Props = $props();
|
|
22
|
-
|
|
23
|
-
const t = useLocale();
|
|
24
21
|
</script>
|
|
25
22
|
|
|
26
|
-
<nav aria-label=
|
|
23
|
+
<nav aria-label="Breadcrumb" class="font-sans text-sm {className}" {...rest}>
|
|
27
24
|
<ol class="flex flex-wrap items-center gap-2">
|
|
28
25
|
{#each items as item, i (item.label)}
|
|
29
26
|
<li class="flex items-center gap-2">
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
}: Props = $props();
|
|
64
64
|
|
|
65
65
|
const base =
|
|
66
|
-
'inline-flex items-center justify-center gap-2 border font-sans font-medium whitespace-nowrap transition-
|
|
66
|
+
'inline-flex items-center justify-center gap-2 border font-sans font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,gap] duration-150 ease-brand-out disabled:opacity-50 disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:pointer-events-none';
|
|
67
67
|
|
|
68
68
|
const sizes: Record<ButtonSize, string> = {
|
|
69
69
|
sm: 'h-8 px-3.5 text-[13px]',
|
|
@@ -105,6 +105,10 @@
|
|
|
105
105
|
const iconClass = $derived(size === 'sm' ? iconSm : iconMd);
|
|
106
106
|
|
|
107
107
|
async function handleClick(event: MouseEvent) {
|
|
108
|
+
if (inert) {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
108
112
|
const result = onclick?.(event);
|
|
109
113
|
// Anything else is a plain handler: nothing to wait for, nothing to lock.
|
|
110
114
|
if (!(result instanceof Promise)) return;
|
|
@@ -123,7 +127,7 @@
|
|
|
123
127
|
disabled={href ? undefined : inert}
|
|
124
128
|
aria-disabled={href && inert ? 'true' : undefined}
|
|
125
129
|
aria-busy={busy ? 'true' : undefined}
|
|
126
|
-
|
|
130
|
+
tabindex={href && inert ? -1 : undefined}
|
|
127
131
|
class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink
|
|
128
132
|
? ''
|
|
129
133
|
: iconOnly
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
weekdayNames
|
|
12
12
|
} from '../../core/date.js';
|
|
13
13
|
import { calendarKeyMove } from '../../core/calendar.js';
|
|
14
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
15
14
|
import { focusRing, toneFill, toneRing, toneText, type Tone } from '../../core/tones.js';
|
|
16
15
|
|
|
17
16
|
interface Props {
|
|
@@ -46,8 +45,6 @@
|
|
|
46
45
|
class: className = ''
|
|
47
46
|
}: Props = $props();
|
|
48
47
|
|
|
49
|
-
const t = useLocale();
|
|
50
|
-
|
|
51
48
|
const selected = $derived(fromISO(value));
|
|
52
49
|
const now = today();
|
|
53
50
|
|
|
@@ -64,7 +61,7 @@
|
|
|
64
61
|
});
|
|
65
62
|
|
|
66
63
|
const days = $derived(monthGrid(view, weekStart));
|
|
67
|
-
const weekdays = $derived(weekdayNames(
|
|
64
|
+
const weekdays = $derived(weekdayNames(undefined, weekStart));
|
|
68
65
|
|
|
69
66
|
function blocked(date: Date) {
|
|
70
67
|
return isOutOfRange(date, min, max) || (isDisabled?.(date) ?? false);
|
|
@@ -109,7 +106,7 @@
|
|
|
109
106
|
<button
|
|
110
107
|
type="button"
|
|
111
108
|
onclick={() => show(addMonths(view, -1))}
|
|
112
|
-
aria-label=
|
|
109
|
+
aria-label="Previous month"
|
|
113
110
|
class="inline-flex size-8 items-center justify-center text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text {focusRing} {toneRing[
|
|
114
111
|
tone
|
|
115
112
|
]}"
|
|
@@ -120,13 +117,13 @@
|
|
|
120
117
|
</button>
|
|
121
118
|
|
|
122
119
|
<span aria-live="polite" class="text-sm font-medium text-text">
|
|
123
|
-
{monthLabel(view
|
|
120
|
+
{monthLabel(view)}
|
|
124
121
|
</span>
|
|
125
122
|
|
|
126
123
|
<button
|
|
127
124
|
type="button"
|
|
128
125
|
onclick={() => show(addMonths(view, 1))}
|
|
129
|
-
aria-label=
|
|
126
|
+
aria-label="Next month"
|
|
130
127
|
class="inline-flex size-8 items-center justify-center text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text {focusRing} {toneRing[
|
|
131
128
|
tone
|
|
132
129
|
]}"
|
|
@@ -148,7 +145,7 @@
|
|
|
148
145
|
bind:this={grid}
|
|
149
146
|
role="grid"
|
|
150
147
|
tabindex="-1"
|
|
151
|
-
aria-label=
|
|
148
|
+
aria-label="Select a date"
|
|
152
149
|
onkeydown={onKeydown}
|
|
153
150
|
class="grid grid-cols-7 gap-px focus:outline-none"
|
|
154
151
|
>
|
|
@@ -190,7 +187,7 @@
|
|
|
190
187
|
tone
|
|
191
188
|
]} hover:underline hover:underline-offset-4 {focusRing} {toneRing[tone]}"
|
|
192
189
|
>
|
|
193
|
-
|
|
190
|
+
Today
|
|
194
191
|
</button>
|
|
195
192
|
{/if}
|
|
196
193
|
</div>
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
...rest
|
|
26
26
|
}: Props = $props();
|
|
27
27
|
|
|
28
|
+
const uid = $props.id();
|
|
29
|
+
|
|
28
30
|
let el: HTMLInputElement | null = $state(null);
|
|
29
31
|
|
|
30
32
|
$effect(() => {
|
|
@@ -32,38 +34,55 @@
|
|
|
32
34
|
});
|
|
33
35
|
</script>
|
|
34
36
|
|
|
35
|
-
<
|
|
36
|
-
class="
|
|
37
|
-
|
|
37
|
+
<div
|
|
38
|
+
class="inline-block font-sans text-[15px] {disabled
|
|
39
|
+
? 'pointer-events-none opacity-50'
|
|
40
|
+
: ''} {className}"
|
|
38
41
|
>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
<label class="group inline-flex items-start gap-3 {disabled ? '' : 'cursor-pointer'}">
|
|
43
|
+
<input
|
|
44
|
+
bind:this={el}
|
|
45
|
+
type="checkbox"
|
|
46
|
+
bind:checked
|
|
47
|
+
{disabled}
|
|
48
|
+
aria-describedby={description ? `${uid}-description` : undefined}
|
|
49
|
+
class="peer sr-only"
|
|
50
|
+
{...rest}
|
|
51
|
+
/>
|
|
52
|
+
<span
|
|
53
|
+
class="mt-0.5 flex size-[18px] shrink-0 items-center justify-center border border-hairline-strong bg-bg transition-colors duration-150 ease-brand-out
|
|
42
54
|
{tonePeerChecked[tone]} {peerFocusRing} {tonePeerFocus[tone]}"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{/if}
|
|
56
|
-
</span>
|
|
57
|
-
{#if label || description || children}
|
|
58
|
-
<span class="flex flex-col gap-0.5">
|
|
59
|
-
{#if children}
|
|
60
|
-
{@render children()}
|
|
61
|
-
{:else if label}
|
|
62
|
-
<span class="leading-snug text-text">{label}</span>
|
|
63
|
-
{/if}
|
|
64
|
-
{#if description}
|
|
65
|
-
<span class="text-sm leading-snug text-text-muted">{description}</span>
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
>
|
|
57
|
+
{#if indeterminate}
|
|
58
|
+
<span class="h-px w-2.5 bg-text-muted"></span>
|
|
59
|
+
{:else}
|
|
60
|
+
<svg
|
|
61
|
+
class="size-3 text-text-inverse opacity-0 transition-opacity duration-150 group-has-[:checked]:opacity-100"
|
|
62
|
+
viewBox="0 0 12 12"
|
|
63
|
+
fill="none"
|
|
64
|
+
>
|
|
65
|
+
<path d="m2 6 2.6 2.6L10 3.2" stroke="currentColor" stroke-width="1.8" />
|
|
66
|
+
</svg>
|
|
66
67
|
{/if}
|
|
67
68
|
</span>
|
|
69
|
+
{#if label || children}
|
|
70
|
+
<span class="flex flex-col gap-0.5">
|
|
71
|
+
{#if children}
|
|
72
|
+
{@render children()}
|
|
73
|
+
{:else if label}
|
|
74
|
+
<span class="leading-snug text-text">{label}</span>
|
|
75
|
+
{/if}
|
|
76
|
+
</span>
|
|
77
|
+
{/if}
|
|
78
|
+
</label>
|
|
79
|
+
{#if description}
|
|
80
|
+
<!-- ponytail: the indent tracks the 18px box plus the label's gap; move one, move both. -->
|
|
81
|
+
<span
|
|
82
|
+
id="{uid}-description"
|
|
83
|
+
class="mt-0.5 block pl-[30px] text-sm leading-snug text-text-muted"
|
|
84
|
+
>
|
|
85
|
+
{description}
|
|
86
|
+
</span>
|
|
68
87
|
{/if}
|
|
69
|
-
</
|
|
88
|
+
</div>
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
...rest
|
|
51
51
|
}: Props = $props();
|
|
52
52
|
|
|
53
|
+
const uid = $props.id();
|
|
54
|
+
// The group's own hint and error sit outside every control in it, so the
|
|
55
|
+
// fieldset is what has to point at them.
|
|
56
|
+
const messageId = $derived(error ? `${uid}-error` : hint ? `${uid}-hint` : undefined);
|
|
57
|
+
|
|
53
58
|
const full = $derived(max > 0 && value.length >= max);
|
|
54
59
|
|
|
55
60
|
function toggle(option: CheckboxOption) {
|
|
@@ -60,11 +65,17 @@
|
|
|
60
65
|
}
|
|
61
66
|
</script>
|
|
62
67
|
|
|
63
|
-
<fieldset
|
|
68
|
+
<fieldset
|
|
69
|
+
aria-describedby={messageId}
|
|
70
|
+
class="flex flex-col gap-3 border-0 p-0 {className}"
|
|
71
|
+
{...rest}
|
|
72
|
+
>
|
|
64
73
|
{#if legend}
|
|
65
74
|
<legend class="mb-1 font-sans text-sm font-medium text-text">
|
|
66
75
|
{legend}
|
|
67
|
-
{#if required}<span class="text-danger" aria-hidden="true">*</span
|
|
76
|
+
{#if required}<span class="text-danger" aria-hidden="true">*</span><span class="sr-only"
|
|
77
|
+
>(required)</span
|
|
78
|
+
>{/if}
|
|
68
79
|
</legend>
|
|
69
80
|
{/if}
|
|
70
81
|
|
|
@@ -108,8 +119,8 @@
|
|
|
108
119
|
</div>
|
|
109
120
|
|
|
110
121
|
{#if error}
|
|
111
|
-
<p class="font-sans text-sm text-danger">{error}</p>
|
|
122
|
+
<p id="{uid}-error" role="alert" class="font-sans text-sm text-danger">{error}</p>
|
|
112
123
|
{:else if hint}
|
|
113
|
-
<p class="font-sans text-sm text-text-muted">{hint}</p>
|
|
124
|
+
<p id="{uid}-hint" class="font-sans text-sm text-text-muted">{hint}</p>
|
|
114
125
|
{/if}
|
|
115
126
|
</fieldset>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
import { normaliseHex } from '../../core/color.js';
|
|
4
|
-
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
4
|
import type { Tone } from '../../core/tones.js';
|
|
6
5
|
import Input, { type InputSize } from './Input.svelte';
|
|
7
6
|
|
|
@@ -27,8 +26,6 @@
|
|
|
27
26
|
...rest
|
|
28
27
|
}: Props = $props();
|
|
29
28
|
|
|
30
|
-
const t = useLocale();
|
|
31
|
-
|
|
32
29
|
// The text can be mid-typing (`#a2`) while the swatch needs a real colour,
|
|
33
30
|
// so the swatch follows the last value that parsed rather than the field.
|
|
34
31
|
let text = $state('');
|
|
@@ -82,7 +79,7 @@
|
|
|
82
79
|
type="color"
|
|
83
80
|
value={resolved}
|
|
84
81
|
{disabled}
|
|
85
|
-
aria-label=
|
|
82
|
+
aria-label="Pick a colour"
|
|
86
83
|
oninput={(event) => choose(event.currentTarget.value)}
|
|
87
84
|
class="absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed"
|
|
88
85
|
/>
|