@nqmcreative/ui 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +16 -18
  2. package/dist/core/calendar.d.ts +1 -1
  3. package/dist/core/calendar.js +1 -1
  4. package/dist/core/date.d.ts +3 -3
  5. package/dist/core/date.js +1 -1
  6. package/dist/core/files.d.ts +2 -3
  7. package/dist/core/files.js +2 -3
  8. package/dist/core/index.d.ts +1 -3
  9. package/dist/core/index.js +1 -3
  10. package/dist/core/password.d.ts +1 -1
  11. package/dist/core/password.js +1 -1
  12. package/dist/styles/matte/Alert.svelte +1 -3
  13. package/dist/styles/matte/Breadcrumb.svelte +1 -4
  14. package/dist/styles/matte/Calendar.svelte +6 -9
  15. package/dist/styles/matte/ColorInput.svelte +1 -4
  16. package/dist/styles/matte/Combobox.svelte +3 -6
  17. package/dist/styles/matte/CommandPalette.svelte +3 -6
  18. package/dist/styles/matte/ConfirmDialog.svelte +2 -5
  19. package/dist/styles/matte/ContextMenu.svelte +1 -4
  20. package/dist/styles/matte/CurrencyInput.svelte +1 -1
  21. package/dist/styles/matte/CurrencyInput.svelte.d.ts +1 -1
  22. package/dist/styles/matte/DatePicker.svelte +9 -9
  23. package/dist/styles/matte/DatePicker.svelte.d.ts +3 -0
  24. package/dist/styles/matte/Drawer.svelte +1 -3
  25. package/dist/styles/matte/Dropdown.svelte +2 -5
  26. package/dist/styles/matte/Dropdown.svelte.d.ts +1 -1
  27. package/dist/styles/matte/Dropzone.svelte +9 -12
  28. package/dist/styles/matte/Dropzone.svelte.d.ts +1 -1
  29. package/dist/styles/matte/FileInput.svelte +11 -14
  30. package/dist/styles/matte/FileInput.svelte.d.ts +2 -2
  31. package/dist/styles/matte/Footer.svelte +1 -1
  32. package/dist/styles/matte/Footer.svelte.d.ts +1 -1
  33. package/dist/styles/matte/Modal.svelte +1 -3
  34. package/dist/styles/matte/MultiSelect.svelte +5 -8
  35. package/dist/styles/matte/Navbar.svelte +3 -9
  36. package/dist/styles/matte/Pagination.svelte +3 -6
  37. package/dist/styles/matte/PinInput.svelte +1 -4
  38. package/dist/styles/matte/SearchInput.svelte +1 -4
  39. package/dist/styles/matte/Sidebar.svelte +3 -6
  40. package/dist/styles/matte/Spinner.svelte +2 -5
  41. package/dist/styles/matte/Spinner.svelte.d.ts +1 -1
  42. package/dist/styles/matte/Table.svelte +5 -8
  43. package/dist/styles/matte/TagsInput.svelte +5 -8
  44. package/dist/styles/matte/ThemeToggle.svelte +5 -8
  45. package/dist/styles/matte/TimeInput.svelte +2 -5
  46. package/dist/styles/matte/Toaster.svelte +2 -5
  47. package/dist/styles/matte/index.d.ts +2 -3
  48. package/dist/styles/matte/index.js +1 -3
  49. package/dist/styles/paper/Alert.svelte +1 -3
  50. package/dist/styles/paper/Breadcrumb.svelte +1 -4
  51. package/dist/styles/paper/Calendar.svelte +6 -9
  52. package/dist/styles/paper/ColorInput.svelte +1 -4
  53. package/dist/styles/paper/Combobox.svelte +3 -6
  54. package/dist/styles/paper/CommandPalette.svelte +3 -6
  55. package/dist/styles/paper/ConfirmDialog.svelte +2 -5
  56. package/dist/styles/paper/ContextMenu.svelte +1 -4
  57. package/dist/styles/paper/CurrencyInput.svelte +1 -1
  58. package/dist/styles/paper/CurrencyInput.svelte.d.ts +1 -1
  59. package/dist/styles/paper/DatePicker.svelte +9 -9
  60. package/dist/styles/paper/DatePicker.svelte.d.ts +3 -0
  61. package/dist/styles/paper/Drawer.svelte +1 -3
  62. package/dist/styles/paper/Dropdown.svelte +2 -5
  63. package/dist/styles/paper/Dropdown.svelte.d.ts +1 -1
  64. package/dist/styles/paper/Dropzone.svelte +9 -12
  65. package/dist/styles/paper/Dropzone.svelte.d.ts +1 -1
  66. package/dist/styles/paper/FileInput.svelte +11 -14
  67. package/dist/styles/paper/FileInput.svelte.d.ts +2 -2
  68. package/dist/styles/paper/Footer.svelte +1 -1
  69. package/dist/styles/paper/Footer.svelte.d.ts +1 -1
  70. package/dist/styles/paper/Modal.svelte +1 -3
  71. package/dist/styles/paper/MultiSelect.svelte +5 -8
  72. package/dist/styles/paper/Navbar.svelte +3 -9
  73. package/dist/styles/paper/Pagination.svelte +3 -6
  74. package/dist/styles/paper/PinInput.svelte +1 -4
  75. package/dist/styles/paper/SearchInput.svelte +1 -4
  76. package/dist/styles/paper/Sidebar.svelte +3 -6
  77. package/dist/styles/paper/Spinner.svelte +2 -5
  78. package/dist/styles/paper/Spinner.svelte.d.ts +1 -1
  79. package/dist/styles/paper/Table.svelte +5 -8
  80. package/dist/styles/paper/TagsInput.svelte +5 -8
  81. package/dist/styles/paper/ThemeToggle.svelte +5 -8
  82. package/dist/styles/paper/TimeInput.svelte +2 -5
  83. package/dist/styles/paper/Toaster.svelte +3 -6
  84. package/dist/styles/paper/index.d.ts +2 -3
  85. package/dist/styles/paper/index.js +1 -3
  86. package/dist/styles/sprout/Alert.svelte +1 -3
  87. package/dist/styles/sprout/Breadcrumb.svelte +1 -4
  88. package/dist/styles/sprout/Calendar.svelte +6 -9
  89. package/dist/styles/sprout/ColorInput.svelte +1 -4
  90. package/dist/styles/sprout/Combobox.svelte +3 -6
  91. package/dist/styles/sprout/CommandPalette.svelte +3 -6
  92. package/dist/styles/sprout/ConfirmDialog.svelte +2 -5
  93. package/dist/styles/sprout/ContextMenu.svelte +1 -4
  94. package/dist/styles/sprout/CurrencyInput.svelte +1 -1
  95. package/dist/styles/sprout/CurrencyInput.svelte.d.ts +1 -1
  96. package/dist/styles/sprout/DatePicker.svelte +9 -9
  97. package/dist/styles/sprout/DatePicker.svelte.d.ts +3 -0
  98. package/dist/styles/sprout/Drawer.svelte +1 -3
  99. package/dist/styles/sprout/Dropdown.svelte +2 -5
  100. package/dist/styles/sprout/Dropdown.svelte.d.ts +1 -1
  101. package/dist/styles/sprout/Dropzone.svelte +9 -12
  102. package/dist/styles/sprout/Dropzone.svelte.d.ts +1 -1
  103. package/dist/styles/sprout/FileInput.svelte +11 -14
  104. package/dist/styles/sprout/FileInput.svelte.d.ts +2 -2
  105. package/dist/styles/sprout/Footer.svelte +1 -1
  106. package/dist/styles/sprout/Footer.svelte.d.ts +1 -1
  107. package/dist/styles/sprout/Modal.svelte +1 -3
  108. package/dist/styles/sprout/MultiSelect.svelte +5 -8
  109. package/dist/styles/sprout/Navbar.svelte +3 -9
  110. package/dist/styles/sprout/Pagination.svelte +3 -6
  111. package/dist/styles/sprout/PinInput.svelte +1 -4
  112. package/dist/styles/sprout/SearchInput.svelte +1 -4
  113. package/dist/styles/sprout/Sidebar.svelte +3 -6
  114. package/dist/styles/sprout/Spinner.svelte +2 -5
  115. package/dist/styles/sprout/Spinner.svelte.d.ts +1 -1
  116. package/dist/styles/sprout/Table.svelte +5 -8
  117. package/dist/styles/sprout/TagsInput.svelte +5 -8
  118. package/dist/styles/sprout/ThemeToggle.svelte +5 -8
  119. package/dist/styles/sprout/TimeInput.svelte +2 -5
  120. package/dist/styles/sprout/Toaster.svelte +3 -6
  121. package/dist/styles/sprout/index.d.ts +2 -3
  122. package/dist/styles/sprout/index.js +1 -3
  123. package/package.json +2 -19
  124. package/registry.json +3 -44
  125. package/dist/core/locale.svelte.d.ts +0 -101
  126. package/dist/core/locale.svelte.js +0 -167
  127. package/dist/styles/matte/LocaleProvider.svelte +0 -24
  128. package/dist/styles/matte/LocaleProvider.svelte.d.ts +0 -10
  129. package/dist/styles/paper/LocaleProvider.svelte +0 -24
  130. package/dist/styles/paper/LocaleProvider.svelte.d.ts +0 -10
  131. package/dist/styles/sprout/LocaleProvider.svelte +0 -24
  132. package/dist/styles/sprout/LocaleProvider.svelte.d.ts +0 -10
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @nqmcreative/ui
2
2
 
3
3
  Svelte 5 (runes) + Tailwind CSS v4 component library. One package, **three
4
- styles**, 71 components each — the same props, the same behaviour, a different
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 |
@@ -11,7 +11,7 @@ look. Eight tones, light and dark, no runtime dependencies.
11
11
  | `sprout` | `@nqmcreative/ui/sprout` | Warm and fully rounded — leaf green on cream, 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, locale and contributing guides. `bun run dev` to read it.
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
- ### Locale
310
+ ### Built-in strings
311
311
 
312
- Every built-in string — ARIA names, `No data`, the calendar's month names —
313
- comes from one object, shared by every style:
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
- ```js
316
- import { setLocale, idID } from '@nqmcreative/ui/core';
317
-
318
- setLocale(idID);
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/ 71 components
329
- src/lib/styles/paper/ 71 components
330
- src/lib/styles/sprout/ 71 components
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, the locale strings. There is no `matte` version of a
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`, `LocaleProvider` |
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.
@@ -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 locale's part order. */
2
+ /** The placeholder a text date field shows for a given part order. */
3
3
  export declare function dateHint(format: 'dmy' | 'mdy' | 'ymd'): string;
@@ -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 locale's part order. */
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
  }
@@ -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: string, weekStart?: 0 | 1): string[];
25
- export declare function monthLabel(month: Date, locale: string): string;
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 locale's part order, e.g. `31/12/2026`. */
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 locale's part order, e.g. `31/12/2026`. */
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)
@@ -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`. Units come from the locale so the number and its
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: readonly string[]): string;
34
+ export declare function formatSize(bytes: number, units?: readonly string[]): string;
@@ -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`. Units come from the locale so the number and its
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;
@@ -2,7 +2,7 @@
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, the locale strings. Nothing in here
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
@@ -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';
@@ -2,7 +2,7 @@
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, the locale strings. Nothing in here
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
@@ -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';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Password strength, 0–4, matching the five labels in the locale.
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Password strength, 0–4, matching the five labels in the locale.
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
@@ -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({
@@ -82,7 +80,7 @@
82
80
  <button
83
81
  type="button"
84
82
  onclick={dismiss}
85
- aria-label={t.current.dismiss}
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">
@@ -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={t.current.breadcrumb} class="font-sans text-sm {className}" {...rest}>
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">
@@ -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(t.current.dateLocale, weekStart));
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={t.current.previousMonth}
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, t.current.dateLocale)}
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={t.current.nextMonth}
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={t.current.selectDate}
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
- {t.current.today}
190
+ Today
194
191
  </button>
195
192
  {/if}
196
193
  </div>
@@ -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={t.current.pickColour}
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
  />
@@ -4,7 +4,6 @@
4
4
  import { clickOutside, portal } from '../../core/actions/dismissable.js';
5
5
  import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
6
6
  import { toneFocusWithinBorder, toneSoft, toneText, type Tone } from '../../core/tones.js';
7
- import { useLocale } from '../../core/locale.svelte.js';
8
7
  import { iconMd } from './icon.js';
9
8
 
10
9
  export interface ComboboxOption {
@@ -51,8 +50,6 @@
51
50
  class: className = ''
52
51
  }: Props = $props();
53
52
 
54
- const t = useLocale();
55
-
56
53
  let open = $state(false);
57
54
  let query = $state('');
58
55
  let wrapper: HTMLElement | null = $state(null);
@@ -161,7 +158,7 @@
161
158
  aria-controls="{id ?? 'combobox'}-listbox"
162
159
  aria-activedescendant={open && flat[active] ? `${id ?? 'combobox'}-opt-${active}` : undefined}
163
160
  aria-invalid={invalid ? 'true' : undefined}
164
- placeholder={selected ? selected.label : (placeholder ?? t.current.comboboxPlaceholder)}
161
+ placeholder={selected ? selected.label : (placeholder ?? 'Search…')}
165
162
  value={open ? query : (selected?.label ?? '')}
166
163
  oninput={(event) => {
167
164
  query = event.currentTarget.value;
@@ -179,7 +176,7 @@
179
176
  <button
180
177
  type="button"
181
178
  onclick={clear}
182
- aria-label={t.current.clear}
179
+ aria-label="Clear selection"
183
180
  class="shrink-0 p-1.5 text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
184
181
  >
185
182
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -212,7 +209,7 @@
212
209
  >
213
210
  {#if flat.length === 0}
214
211
  <p class="px-3 py-6 text-center text-sm text-text-muted">
215
- {emptyText ?? t.current.noMatches}
212
+ {emptyText ?? 'No matches'}
216
213
  </p>
217
214
  {:else}
218
215
  {#each groups as [group, items] (group)}
@@ -23,7 +23,6 @@
23
23
  import { focusTrap } from '../../core/actions/dismissable.js';
24
24
  import { syncDialog } from '../../core/dialog.svelte.js';
25
25
  import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
26
- import { useLocale } from '../../core/locale.svelte.js';
27
26
  import { toneSoft, type Tone } from '../../core/tones.js';
28
27
  import { isTypingTarget } from '../../core/trigger.js';
29
28
  import { iconMd } from './icon.js';
@@ -61,8 +60,6 @@
61
60
  class: className = ''
62
61
  }: Props = $props();
63
62
 
64
- const t = useLocale();
65
-
66
63
  let dialog: HTMLDialogElement | null = $state(null);
67
64
  let inputEl: HTMLInputElement | null = $state(null);
68
65
  let listEl: HTMLElement | null = $state(null);
@@ -182,8 +179,8 @@
182
179
  oninput={() => cursor.reset()}
183
180
  type="text"
184
181
  autocomplete="off"
185
- aria-label={placeholder ?? t.current.commandPlaceholder}
186
- placeholder={placeholder ?? t.current.commandPlaceholder}
182
+ aria-label={placeholder ?? 'Type a command or search…'}
183
+ placeholder={placeholder ?? 'Type a command or search…'}
187
184
  class="h-11 w-full min-w-0 bg-transparent font-sans text-[15px] text-text placeholder:text-text-muted focus:outline-none"
188
185
  />
189
186
  <Kbd>esc</Kbd>
@@ -197,7 +194,7 @@
197
194
  >
198
195
  {#if flat.length === 0}
199
196
  <p class="px-3.5 py-10 text-center font-sans text-sm text-text-muted">
200
- {emptyText ?? t.current.noResults}
197
+ {emptyText ?? 'No results'}
201
198
  </p>
202
199
  {:else}
203
200
  {#each groups as [group, groupItems] (group)}
@@ -2,7 +2,6 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import Button from './Button.svelte';
4
4
  import Modal from './Modal.svelte';
5
- import { useLocale } from '../../core/locale.svelte.js';
6
5
  import { toneSoft, type Tone } from '../../core/tones.js';
7
6
 
8
7
  interface Props {
@@ -37,8 +36,6 @@
37
36
  children
38
37
  }: Props = $props();
39
38
 
40
- const t = useLocale();
41
-
42
39
  async function confirm() {
43
40
  try {
44
41
  loading = true;
@@ -88,10 +85,10 @@
88
85
 
89
86
  {#snippet footer()}
90
87
  <Button variant="ghost" tone="neutral" onclick={cancel} disabled={loading}>
91
- {cancelLabel ?? t.current.cancel}
88
+ {cancelLabel ?? 'Cancel'}
92
89
  </Button>
93
90
  <Button {tone} onclick={confirm} {loading}>
94
- {confirmLabel ?? t.current.confirm}
91
+ {confirmLabel ?? 'Confirm'}
95
92
  </Button>
96
93
  {/snippet}
97
94
  </Modal>
@@ -3,7 +3,6 @@
3
3
  import { anchored } from '../../core/actions/anchor.js';
4
4
  import { clickOutside, focusTrap, navigateList, portal } from '../../core/actions/dismissable.js';
5
5
  import { menuItems } from '../../core/trigger.js';
6
- import { useLocale } from '../../core/locale.svelte.js';
7
6
 
8
7
  interface Props {
9
8
  /** The region that responds to a right-click. */
@@ -27,8 +26,6 @@
27
26
  menuClass = ''
28
27
  }: Props = $props();
29
28
 
30
- const t = useLocale();
31
-
32
29
  let menuEl: HTMLElement | null = $state(null);
33
30
  /**
34
31
  * A 0×0 element parked at the pointer, so the same `anchored` action that
@@ -79,7 +76,7 @@
79
76
  if ((event.target as HTMLElement).closest('[role="menuitem"]:not([disabled])')) open = false;
80
77
  }}
81
78
  role="menu"
82
- aria-label={label ?? t.current.menu}
79
+ aria-label={label ?? 'Menu'}
83
80
  tabindex="-1"
84
81
  class="z-50 flex max-h-[min(24rem,80vh)] min-w-44 flex-col overflow-y-auto border border-hairline bg-bg py-1 font-sans {menuClass}"
85
82
  >
@@ -20,7 +20,7 @@
20
20
  precision?: number;
21
21
  /** Leading mark — `Rp`, `$`, `€`. */
22
22
  currency?: string;
23
- /** Trailing mark instead, for the locales that write it that way. */
23
+ /** Trailing mark instead, for the regions that write it that way. */
24
24
  unit?: string;
25
25
  size?: InputSize;
26
26
  tone?: Tone;
@@ -14,7 +14,7 @@ interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix' |
14
14
  precision?: number;
15
15
  /** Leading mark — `Rp`, `$`, `€`. */
16
16
  currency?: string;
17
- /** Trailing mark instead, for the locales that write it that way. */
17
+ /** Trailing mark instead, for the regions that write it that way. */
18
18
  unit?: string;
19
19
  size?: InputSize;
20
20
  tone?: Tone;
@@ -2,8 +2,7 @@
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 { useLocale } from '../../core/locale.svelte.js';
5
+ import { formatISO, parseFormatted, type DateFormat } from '../../core/date.js';
7
6
  import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
8
7
  import Calendar from './Calendar.svelte';
9
8
 
@@ -15,6 +14,8 @@
15
14
  max?: string;
16
15
  isDisabled?: (date: Date) => boolean;
17
16
  weekStart?: 0 | 1;
17
+ /** Order of the date parts the field accepts and prints. */
18
+ format?: DateFormat;
18
19
  tone?: Tone;
19
20
  invalid?: boolean;
20
21
  disabled?: boolean;
@@ -33,6 +34,7 @@
33
34
  max,
34
35
  isDisabled,
35
36
  weekStart = 1,
37
+ format = 'dmy',
36
38
  tone = 'brand',
37
39
  invalid = false,
38
40
  disabled = false,
@@ -44,8 +46,6 @@
44
46
  class: className = ''
45
47
  }: Props = $props();
46
48
 
47
- const t = useLocale();
48
-
49
49
  let open = $state(false);
50
50
  let text = $state('');
51
51
  let editing = $state(false);
@@ -53,8 +53,8 @@
53
53
  let inputEl: HTMLInputElement | null = $state(null);
54
54
 
55
55
  /** While typing the field shows the raw text; otherwise the formatted value. */
56
- const display = $derived(editing ? text : formatISO(value, t.current.dateFormat));
57
- const hint = $derived(placeholder ?? dateHint(t.current.dateFormat));
56
+ const display = $derived(editing ? text : formatISO(value, format));
57
+ const hint = $derived(placeholder ?? dateHint(format));
58
58
 
59
59
  function commit() {
60
60
  editing = false;
@@ -65,7 +65,7 @@
65
65
  }
66
66
  return;
67
67
  }
68
- const parsed = parseFormatted(text, t.current.dateFormat);
68
+ const parsed = parseFormatted(text, format);
69
69
  if (parsed && parsed !== value) {
70
70
  value = parsed;
71
71
  onchange?.(parsed);
@@ -132,7 +132,7 @@
132
132
  onchange?.('');
133
133
  inputEl?.focus();
134
134
  }}
135
- aria-label={t.current.clear}
135
+ aria-label="Clear selection"
136
136
  class="shrink-0 p-1.5 text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
137
137
  >
138
138
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -145,7 +145,7 @@
145
145
  type="button"
146
146
  {disabled}
147
147
  onclick={() => (open = !open)}
148
- aria-label={t.current.selectDate}
148
+ aria-label="Select a date"
149
149
  aria-expanded={open}
150
150
  class="shrink-0 p-2 text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
151
151
  >
@@ -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;
@@ -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
 
8
7
  export type DrawerSide = 'left' | 'right' | 'top' | 'bottom';
@@ -39,7 +38,6 @@
39
38
  ...rest
40
39
  }: Props = $props();
41
40
 
42
- const t = useLocale();
43
41
  let dialog: HTMLDialogElement | null = $state(null);
44
42
 
45
43
  const horizontal = $derived(side === 'left' || side === 'right');
@@ -109,7 +107,7 @@
109
107
  <button
110
108
  type="button"
111
109
  onclick={close}
112
- aria-label={t.current.close}
110
+ aria-label="Close"
113
111
  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-brand"
114
112
  >
115
113
  <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
 
8
7
  interface Props {
9
8
  /** The control that opens the menu — put a `Button` in here. */
@@ -17,7 +16,7 @@
17
16
  matchWidth?: boolean;
18
17
  /** Close as soon as an item inside is activated. Default true. */
19
18
  closeOnSelect?: boolean;
20
- /** Accessible name for the menu. Defaults to the locale's `menu`. */
19
+ /** Accessible name for the menu. Defaults to `Menu`. */
21
20
  label?: string;
22
21
  class?: string;
23
22
  /** Classes for the wrapper around the trigger. */
@@ -37,8 +36,6 @@
37
36
  triggerClass = ''
38
37
  }: Props = $props();
39
38
 
40
- const t = useLocale();
41
-
42
39
  let anchorEl: HTMLElement | null = $state(null);
43
40
  let menuEl: HTMLElement | null = $state(null);
44
41
 
@@ -85,7 +82,7 @@
85
82
  onkeydown={onMenuKeydown}
86
83
  onclick={onMenuClick}
87
84
  role="menu"
88
- aria-label={label ?? t.current.menu}
85
+ aria-label={label ?? 'Menu'}
89
86
  tabindex="-1"
90
87
  class="z-50 flex max-h-[min(24rem,80vh)] min-w-44 flex-col overflow-y-auto border border-hairline bg-bg py-1 font-sans {className}"
91
88
  >
@@ -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 the locale's `menu`. */
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. */