@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
@@ -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 type { InputSize } from './Input.svelte';
6
5
 
@@ -18,9 +17,9 @@
18
17
  size?: InputSize;
19
18
  tone?: Tone;
20
19
  invalid?: boolean;
21
- /** Text on the button. Defaults to the locale's `chooseFile`. */
20
+ /** Text on the button. Defaults to `Choose file`. */
22
21
  label?: string;
23
- /** Shown while nothing is chosen. Defaults to the locale's `noFile`. */
22
+ /** Shown while nothing is chosen. Defaults to `No file chosen`. */
24
23
  placeholder?: string;
25
24
  /** Show the list of held files below the row. */
26
25
  showList?: boolean;
@@ -50,18 +49,16 @@
50
49
  class: className = ''
51
50
  }: Props = $props();
52
51
 
53
- const t = useLocale();
54
-
55
52
  let input: HTMLInputElement | null = $state(null);
56
53
  let rejected = $state<RejectedFile[]>([]);
57
54
 
58
- const formatSize = (bytes: number) => toReadableSize(bytes, t.current.byteUnits);
55
+ const formatSize = (bytes: number) => toReadableSize(bytes);
59
56
 
60
57
  /** One file reads as its name and weight; several read as a count. */
61
58
  const summary = $derived.by(() => {
62
- if (files.length === 0) return placeholder ?? t.current.noFile;
59
+ if (files.length === 0) return placeholder ?? 'No file chosen';
63
60
  if (files.length === 1) return `${files[0].name} · ${formatSize(files[0].size)}`;
64
- return `${files.length} ${t.current.selected}`;
61
+ return `${files.length} $selected`;
65
62
  });
66
63
 
67
64
  function add(incoming: FileList | File[] | null) {
@@ -90,9 +87,9 @@
90
87
  }
91
88
 
92
89
  const reasons: Record<RejectedFile['reason'], string> = $derived({
93
- type: t.current.rejectedType,
94
- size: t.current.rejectedSize,
95
- count: t.current.rejectedCount
90
+ type: 'wrong file type',
91
+ size: 'too large',
92
+ count: 'over the file limit'
96
93
  });
97
94
 
98
95
  const sizes: Record<InputSize, string> = {
@@ -124,7 +121,7 @@
124
121
  class="shrink-0 rounded 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
125
122
  {buttons[size]}"
126
123
  >
127
- {label ?? t.current.chooseFile}
124
+ {label ?? 'Choose file'}
128
125
  </button>
129
126
 
130
127
  <span
@@ -137,7 +134,7 @@
137
134
  <button
138
135
  type="button"
139
136
  onclick={clear}
140
- aria-label={t.current.remove}
137
+ aria-label="Remove"
141
138
  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"
142
139
  >
143
140
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -185,7 +182,7 @@
185
182
  <button
186
183
  type="button"
187
184
  onclick={() => (files = files.filter((_, at) => at !== i))}
188
- aria-label="{t.current.remove} {file.name}"
185
+ aria-label="Remove {file.name}"
189
186
  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"
190
187
  >
191
188
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -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 the locale's `chooseFile`. */
18
+ /** Text on the button. Defaults to `Choose file`. */
19
19
  label?: string;
20
- /** Shown while nothing is chosen. Defaults to the locale's `noFile`. */
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 locale picker. */
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;
@@ -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 locale picker. */
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;
@@ -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 ModalSize = 'sm' | 'md' | 'lg';
@@ -40,7 +39,6 @@
40
39
  ...rest
41
40
  }: Props = $props();
42
41
 
43
- const t = useLocale();
44
42
  let dialog: HTMLDialogElement | null = $state(null);
45
43
 
46
44
  const closable = $derived(showClose ?? dismissible);
@@ -92,7 +90,7 @@
92
90
  <button
93
91
  type="button"
94
92
  onclick={close}
95
- aria-label={t.current.close}
93
+ aria-label="Close"
96
94
  class="-m-1 shrink-0 rounded-md 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"
97
95
  >
98
96
  <svg class="size-4" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -16,7 +16,6 @@
16
16
  import { anchored } from '../../core/actions/anchor.js';
17
17
  import { clickOutside, portal } from '../../core/actions/dismissable.js';
18
18
  import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
19
- import { useLocale } from '../../core/locale.svelte.js';
20
19
  import { iconMd, iconSm } from './icon.js';
21
20
  import { toneFocusWithinBorder, toneSoft, toneText, type Tone } from '../../core/tones.js';
22
21
 
@@ -58,8 +57,6 @@
58
57
  class: className = ''
59
58
  }: Props = $props();
60
59
 
61
- const t = useLocale();
62
-
63
60
  let open = $state(false);
64
61
  let query = $state('');
65
62
  let wrapper: HTMLElement | null = $state(null);
@@ -149,7 +146,7 @@
149
146
  class="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium {toneSoft[tone]}"
150
147
  >
151
148
  {chosen.length}
152
- {t.current.selected}
149
+ selected
153
150
  </span>
154
151
  {:else}
155
152
  {#each chosen as option (option.value)}
@@ -162,7 +159,7 @@
162
159
  <button
163
160
  type="button"
164
161
  onclick={() => remove(option)}
165
- aria-label="{t.current.removeItem} {option.label}"
162
+ aria-label="Remove {option.label}"
166
163
  class="inline-flex size-4 items-center justify-center rounded transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
167
164
  >
168
165
  <svg class="size-2.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -189,7 +186,7 @@
189
186
  aria-expanded={open}
190
187
  aria-controls="{id ?? 'multiselect'}-listbox"
191
188
  aria-invalid={invalid ? 'true' : undefined}
192
- placeholder={chosen.length ? '' : (placeholder ?? t.current.comboboxPlaceholder)}
189
+ placeholder={chosen.length ? '' : (placeholder ?? 'Search…')}
193
190
  oninput={() => {
194
191
  open = true;
195
192
  cursor.index = 0;
@@ -211,7 +208,7 @@
211
208
  onchange?.([]);
212
209
  inputEl?.focus();
213
210
  }}
214
- aria-label={t.current.clear}
211
+ aria-label="Clear selection"
215
212
  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"
216
213
  >
217
214
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -239,7 +236,7 @@
239
236
  >
240
237
  {#if flat.length === 0}
241
238
  <p class="px-3 py-6 text-center text-sm text-text-muted">
242
- {emptyText ?? t.current.noMatches}
239
+ {emptyText ?? 'No matches'}
243
240
  </p>
244
241
  {:else}
245
242
  {#each groups as [group, items] (group)}
@@ -16,7 +16,6 @@
16
16
 
17
17
  <script lang="ts">
18
18
  import type { Snippet } from 'svelte';
19
- import { useLocale } from '../../core/locale.svelte.js';
20
19
  import { iconMd } from './icon.js';
21
20
  import { focusRing, toneRing, toneSoft, toneText, type Tone } from '../../core/tones.js';
22
21
  import Drawer from './Drawer.svelte';
@@ -53,8 +52,6 @@
53
52
  class: className = ''
54
53
  }: Props = $props();
55
54
 
56
- const t = useLocale();
57
-
58
55
  // paper gives the active link a pill rather than a colour change, so the
59
56
  // current page is legible without relying on hue alone.
60
57
  const link =
@@ -66,10 +63,7 @@
66
63
  ? 'border-b border-hairline'
67
64
  : ''} {className}"
68
65
  >
69
- <nav
70
- aria-label={t.current.navigation}
71
- class="mx-auto flex h-16 w-full max-w-6xl items-center gap-6 px-6"
72
- >
66
+ <nav aria-label="Navigation" class="mx-auto flex h-16 w-full max-w-6xl items-center gap-6 px-6">
73
67
  {#if brand}
74
68
  <div class="flex shrink-0 items-center">{@render brand()}</div>
75
69
  {/if}
@@ -140,7 +134,7 @@
140
134
  <button
141
135
  type="button"
142
136
  onclick={() => (menuOpen = true)}
143
- aria-label={t.current.openMenu}
137
+ aria-label="Open menu"
144
138
  aria-expanded={menuOpen}
145
139
  class="inline-flex size-9 items-center justify-center rounded-md text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text md:hidden {focusRing} {toneRing[
146
140
  tone
@@ -159,7 +153,7 @@
159
153
  </nav>
160
154
  </header>
161
155
 
162
- <Drawer bind:open={menuOpen} side="right" size="sm" title={t.current.navigation}>
156
+ <Drawer bind:open={menuOpen} side="right" size="sm" title="Navigation">
163
157
  <ul class="flex flex-col gap-0.5">
164
158
  {#each items as item (item.label)}
165
159
  <li>
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import { focusRing, toneRing, toneSolid, type Tone } from '../../core/tones.js';
4
- import { useLocale } from '../../core/locale.svelte.js';
5
4
  import { pageRange } from '../../core/pagination.js';
6
5
 
7
6
  interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
@@ -25,8 +24,6 @@
25
24
  ...rest
26
25
  }: Props = $props();
27
26
 
28
- const t = useLocale();
29
-
30
27
  const pages = $derived(pageRange(page, total, siblings));
31
28
 
32
29
  function go(next: number) {
@@ -39,12 +36,12 @@
39
36
  const cell = `inline-flex h-9 min-w-9 items-center justify-center rounded-md px-2 font-sans text-sm font-medium tabular-nums transition-colors duration-150 ease-brand-out disabled:opacity-40 disabled:pointer-events-none ${focusRing}`;
40
37
  </script>
41
38
 
42
- <nav aria-label={t.current.pagination} class="flex items-center gap-1 {className}" {...rest}>
39
+ <nav aria-label="Pagination" class="flex items-center gap-1 {className}" {...rest}>
43
40
  <button
44
41
  type="button"
45
42
  class="{cell} {toneRing[tone]} text-text-secondary hover:bg-bg-inset"
46
43
  disabled={page <= 1}
47
- aria-label={t.current.previousPage}
44
+ aria-label="Previous page"
48
45
  onclick={() => go(page - 1)}
49
46
  >
50
47
  <svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
@@ -73,7 +70,7 @@
73
70
  type="button"
74
71
  class="{cell} {toneRing[tone]} text-text-secondary hover:bg-bg-inset"
75
72
  disabled={page >= total}
76
- aria-label={t.current.nextPage}
73
+ aria-label="Next page"
77
74
  onclick={() => go(page + 1)}
78
75
  >
79
76
  <svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { useLocale } from '../../core/locale.svelte.js';
3
2
  import { caretIndex, clearFrom, fillFrom, sanitisePin, type PinMode } from '../../core/pin.js';
4
3
  import { toneFocusBorder, type Tone } from '../../core/tones.js';
5
4
 
@@ -38,8 +37,6 @@
38
37
  class: className = ''
39
38
  }: Props = $props();
40
39
 
41
- const t = useLocale();
42
-
43
40
  let boxes: HTMLInputElement[] = $state([]);
44
41
 
45
42
  const chars = $derived(Array.from({ length }, (_, i) => value[i] ?? ''));
@@ -119,7 +116,7 @@
119
116
  autocapitalize="characters"
120
117
  spellcheck="false"
121
118
  maxlength={length}
122
- aria-label="{t.current.digit} {index + 1}"
119
+ aria-label="Digit {index + 1}"
123
120
  aria-invalid={invalid ? 'true' : undefined}
124
121
  oninput={(event) => onInput(event, index)}
125
122
  onkeydown={(event) => onKeydown(event, index)}
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLInputAttributes } from 'svelte/elements';
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
 
@@ -37,8 +36,6 @@
37
36
  ...rest
38
37
  }: Props = $props();
39
38
 
40
- const t = useLocale();
41
-
42
39
  // The timer is cleared by the effect's own teardown, so a keystroke that
43
40
  // lands mid-wait restarts the clock rather than queueing a second call.
44
41
  $effect(() => {
@@ -75,7 +72,7 @@
75
72
  <button
76
73
  type="button"
77
74
  onclick={clear}
78
- aria-label={t.current.clearSearch}
75
+ aria-label="Clear search"
79
76
  class="-mr-1 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"
80
77
  >
81
78
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -35,7 +35,6 @@
35
35
  </script>
36
36
 
37
37
  <script lang="ts">
38
- import { useLocale } from '../../core/locale.svelte.js';
39
38
  import { iconMd } from './icon.js';
40
39
  import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
41
40
 
@@ -69,8 +68,6 @@
69
68
  class: className = ''
70
69
  }: Props = $props();
71
70
 
72
- const t = useLocale();
73
-
74
71
  const keyOf = (item: SidebarItem) => item.id ?? item.href ?? item.label;
75
72
 
76
73
  /** Groups start open when asked, or when they contain the active item. */
@@ -141,7 +138,7 @@
141
138
  </div>
142
139
  {/if}
143
140
 
144
- <nav aria-label={t.current.navigation} class="flex-1 overflow-y-auto p-2">
141
+ <nav aria-label="Navigation" class="flex-1 overflow-y-auto p-2">
145
142
  {#each sections as section, i (section.label ?? i)}
146
143
  <div class="flex flex-col gap-0.5 {i > 0 ? 'mt-4' : ''}">
147
144
  {#if section.label && !collapsed}
@@ -230,7 +227,7 @@
230
227
  <button
231
228
  type="button"
232
229
  onclick={() => (collapsed = !collapsed)}
233
- aria-label={collapsed ? t.current.expandSidebar : t.current.collapseSidebar}
230
+ aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
234
231
  class="inline-flex h-8 items-center justify-center gap-2 rounded-md font-sans text-xs text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text {focusRing} {toneRing[
235
232
  tone
236
233
  ]}"
@@ -248,7 +245,7 @@
248
245
  stroke-linecap="round"
249
246
  />
250
247
  </svg>
251
- {#if !collapsed}<span>{t.current.collapseSidebar}</span>{/if}
248
+ {#if !collapsed}<span>Collapse sidebar</span>{/if}
252
249
  </button>
253
250
  {/if}
254
251
  </div>
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import { toneText, type Tone } from '../../core/tones.js';
4
- import { useLocale } from '../../core/locale.svelte.js';
5
4
 
6
5
  export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg';
7
6
 
@@ -9,12 +8,10 @@
9
8
  size?: SpinnerSize;
10
9
  /** Omit to inherit the parent's `currentColor` (e.g. inside a Button). */
11
10
  tone?: Tone;
12
- /** Defaults to the locale's loading string; pass `''` to hide it. */
11
+ /** Defaults to `Loading`; pass `''` to hide it. */
13
12
  label?: string;
14
13
  }
15
14
 
16
- const t = useLocale();
17
-
18
15
  let { size = 'md', tone, label, class: className = '', ...rest }: Props = $props();
19
16
 
20
17
  const sizes: Record<SpinnerSize, string> = {
@@ -27,7 +24,7 @@
27
24
 
28
25
  <span
29
26
  role="status"
30
- aria-label={label ?? t.current.loading}
27
+ aria-label={label ?? 'Loading'}
31
28
  class="inline-block shrink-0 animate-spin rounded-full border-current/25 border-t-current
32
29
  {sizes[size]} {tone ? toneText[tone] : ''} {className}"
33
30
  {...rest}
@@ -5,7 +5,7 @@ interface Props extends HTMLAttributes<HTMLSpanElement> {
5
5
  size?: SpinnerSize;
6
6
  /** Omit to inherit the parent's `currentColor` (e.g. inside a Button). */
7
7
  tone?: Tone;
8
- /** Defaults to the locale's loading string; pass `''` to hide it. */
8
+ /** Defaults to `Loading`; pass `''` to hide it. */
9
9
  label?: string;
10
10
  }
11
11
  declare const Spinner: import("svelte").Component<Props, {}, "">;
@@ -16,7 +16,6 @@
16
16
  import Checkbox from './Checkbox.svelte';
17
17
  import type { Tone } from '../../core/tones.js';
18
18
  import { nextSort, sortRows, toggleKey, type TableSort } from '../../core/table.js';
19
- import { useLocale } from '../../core/locale.svelte.js';
20
19
 
21
20
  interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onselect'> {
22
21
  columns: TableColumn[];
@@ -82,8 +81,6 @@
82
81
  ...rest
83
82
  }: Props = $props();
84
83
 
85
- const t = useLocale();
86
-
87
84
  const aligns = {
88
85
  left: 'text-left',
89
86
  center: 'text-center',
@@ -139,14 +136,14 @@
139
136
  >
140
137
  <span class="font-sans text-[13px] font-semibold text-text">
141
138
  {selected.length}
142
- {t.current.selected}
139
+ selected
143
140
  </span>
144
141
  <button
145
142
  type="button"
146
143
  onclick={clearSelection}
147
144
  class="rounded font-sans text-[13px] font-medium text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
148
145
  >
149
- {t.current.clear}
146
+ Clear selection
150
147
  </button>
151
148
  <div class="ml-auto flex flex-wrap items-center gap-2">
152
149
  {@render bulkActions?.({ selected, clear: clearSelection })}
@@ -176,7 +173,7 @@
176
173
  checked={allSelected}
177
174
  indeterminate={someSelected}
178
175
  {tone}
179
- aria-label={t.current.selectAllRows}
176
+ aria-label="Select all rows"
180
177
  onchange={toggleAll}
181
178
  />
182
179
  </th>
@@ -223,7 +220,7 @@
223
220
  colspan={columns.length + (selectable ? 1 : 0)}
224
221
  class="px-4 py-10 text-center text-sm text-text-muted"
225
222
  >
226
- {#if empty}{@render empty()}{:else}{t.current.noData}{/if}
223
+ {#if empty}{@render empty()}{:else}No data{/if}
227
224
  </td>
228
225
  </tr>
229
226
  {:else}
@@ -240,7 +237,7 @@
240
237
  <Checkbox
241
238
  checked={isSelected}
242
239
  {tone}
243
- aria-label={t.current.selectRow}
240
+ aria-label="Select row"
244
241
  onchange={() => toggleRow(row)}
245
242
  />
246
243
  </td>
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { useLocale } from '../../core/locale.svelte.js';
3
2
  import { addTag, splitTags, type TagRejection } from '../../core/tags.js';
4
3
  import { toneFocusWithinBorder, toneSoft, type Tone } from '../../core/tones.js';
5
4
 
@@ -46,8 +45,6 @@
46
45
  class: className = ''
47
46
  }: Props = $props();
48
47
 
49
- const t = useLocale();
50
-
51
48
  let draft = $state('');
52
49
  let field: HTMLInputElement | null = $state(null);
53
50
  /** The last refusal, cleared as soon as the next keystroke lands. */
@@ -111,8 +108,8 @@
111
108
  const messages: Record<TagRejection, string> = $derived({
112
109
  empty: '',
113
110
  short: `${minLength}+`,
114
- duplicate: t.current.duplicateTag,
115
- full: `${t.current.selected} ${tags.length}/${max}`
111
+ duplicate: 'Already added',
112
+ full: `$selected ${tags.length}/${max}`
116
113
  });
117
114
  </script>
118
115
 
@@ -136,7 +133,7 @@
136
133
  <button
137
134
  type="button"
138
135
  onclick={() => remove(index)}
139
- aria-label="{t.current.removeItem} {tag}"
136
+ aria-label="Remove {tag}"
140
137
  class="inline-flex size-4 items-center justify-center rounded transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
141
138
  >
142
139
  <svg class="size-2.5" viewBox="0 0 10 10" fill="none" aria-hidden="true">
@@ -156,8 +153,8 @@
156
153
  bind:value={draft}
157
154
  {disabled}
158
155
  readonly={full}
159
- placeholder={tags.length === 0 ? (placeholder ?? t.current.addTag) : ''}
160
- aria-label={placeholder ?? t.current.addTag}
156
+ placeholder={tags.length === 0 ? (placeholder ?? 'Add a tag') : ''}
157
+ aria-label={placeholder ?? 'Add a tag'}
161
158
  aria-invalid={invalid ? 'true' : undefined}
162
159
  onkeydown={onKeydown}
163
160
  onpaste={onPaste}
@@ -10,7 +10,6 @@
10
10
  storedTheme,
11
11
  type Theme
12
12
  } from '../../core/theme.svelte.js';
13
- import { useLocale } from '../../core/locale.svelte.js';
14
13
 
15
14
  interface Props {
16
15
  /** Bindable. `'system'` follows the OS until the visitor chooses. */
@@ -33,8 +32,6 @@
33
32
  class: className = ''
34
33
  }: Props = $props();
35
34
 
36
- const t = useLocale();
37
-
38
35
  $effect(() => {
39
36
  applyTheme(theme);
40
37
  if (persist) persistTheme(theme);
@@ -50,8 +47,8 @@
50
47
  }
51
48
 
52
49
  const options = $derived([
53
- { value: 'light' as Theme, label: t.current.lightMode },
54
- { value: 'dark' as Theme, label: t.current.darkMode },
50
+ { value: 'light' as Theme, label: 'Light' },
51
+ { value: 'dark' as Theme, label: 'Dark' },
55
52
  { value: 'system' as Theme, label: 'Auto' }
56
53
  ]);
57
54
  </script>
@@ -95,7 +92,7 @@
95
92
  {#if variant === 'segmented'}
96
93
  <div
97
94
  role="radiogroup"
98
- aria-label={t.current.toggleTheme}
95
+ aria-label="Toggle theme"
99
96
  class="inline-flex items-center gap-1 rounded-lg border border-hairline bg-bg-inset p-1 {className}"
100
97
  >
101
98
  {#each options as option (option.value)}
@@ -119,8 +116,8 @@
119
116
  <button
120
117
  type="button"
121
118
  onclick={cycle}
122
- aria-label="{t.current.toggleTheme} ({theme})"
123
- title={t.current.toggleTheme}
119
+ aria-label="Toggle theme ({theme})"
120
+ title="Toggle theme"
124
121
  class="inline-flex size-9 items-center justify-center rounded-md border border-hairline bg-bg text-text-secondary shadow-xs transition-colors duration-150 ease-brand-out hover:bg-bg-alt hover:text-text {focusRing} {toneRing[
125
122
  tone
126
123
  ]} {className}"
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLInputAttributes } from 'svelte/elements';
3
- import { useLocale } from '../../core/locale.svelte.js';
4
3
  import { clampTime, formatTime, parseTime, stepTime } from '../../core/time.js';
5
4
  import type { Tone } from '../../core/tones.js';
6
5
  import Input, { type InputSize } from './Input.svelte';
@@ -38,8 +37,6 @@
38
37
  ...rest
39
38
  }: Props = $props();
40
39
 
41
- const t = useLocale();
42
-
43
40
  // Typed text and the bound value are separate until the field settles:
44
41
  // `9` is a valid thing to be halfway through typing and an invalid time.
45
42
  let text = $state('');
@@ -120,7 +117,7 @@
120
117
  class={stepper}
121
118
  {disabled}
122
119
  onclick={() => nudge(1)}
123
- aria-label={t.current.increase}
120
+ aria-label="Increase"
124
121
  >
125
122
  <svg class="size-3" viewBox="0 0 12 12" fill="none" aria-hidden="true">
126
123
  <path
@@ -137,7 +134,7 @@
137
134
  class={stepper}
138
135
  {disabled}
139
136
  onclick={() => nudge(-1)}
140
- aria-label={t.current.decrease}
137
+ aria-label="Decrease"
141
138
  >
142
139
  <svg class="size-3" viewBox="0 0 12 12" fill="none" aria-hidden="true">
143
140
  <path
@@ -6,7 +6,6 @@
6
6
  import { toneText } from '../../core/tones.js';
7
7
  import { iconMd } from './icon.js';
8
8
  import Spinner from './Spinner.svelte';
9
- import { useLocale } from '../../core/locale.svelte.js';
10
9
 
11
10
  export type ToasterPosition =
12
11
  'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
@@ -18,8 +17,6 @@
18
17
 
19
18
  let { position = 'bottom-right', class: className = '' }: Props = $props();
20
19
 
21
- const t = useLocale();
22
-
23
20
  const positions: Record<ToasterPosition, string> = {
24
21
  'top-left': 'top-4 left-4 items-start',
25
22
  'top-center': 'top-4 left-1/2 -translate-x-1/2 items-center',
@@ -41,8 +38,8 @@
41
38
  <div
42
39
  use:portal
43
40
  aria-live="polite"
44
- aria-label={t.current.notifications}
45
- class="pointer-events-none fixed z-[100] flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-2.5 {positions[
41
+ aria-label="Notifications"
42
+ class="pointer-events-none fixed z-100 flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-2.5 {positions[
46
43
  position
47
44
  ]} {fromTop ? '' : 'flex-col-reverse'} {className}"
48
45
  >
@@ -118,7 +115,7 @@
118
115
  <button
119
116
  type="button"
120
117
  onclick={() => toast.dismiss(item.id)}
121
- aria-label={t.current.dismiss}
118
+ aria-label="Dismiss"
122
119
  class="-m-1 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"
123
120
  >
124
121
  <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
@@ -5,9 +5,8 @@
5
5
  * `@nqmcreative/ui/core`, re-exported below so this one import covers
6
6
  * the whole style.
7
7
  */
8
- export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText, anchored, clickOutside, focusTrap, focusable, navigateList, portal, toast, locale, setLocale, provideLocale, useLocale, enUS, idID, LocaleStore, applyTheme, storedTheme, toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from '../../core/index.js';
9
- export type { Tone, Placement, AnchorOptions, ClickOutsideOptions, FocusTrapOptions, Toast, ToastOptions, ToastAction, Locale, Theme, DateFormat, TableSort, SortDirection, RejectedFile } from '../../core/index.js';
10
- export { default as LocaleProvider } from './LocaleProvider.svelte';
8
+ export { TONES, focusRing, peerFocusRing, toneBorder, toneBorderLeft, toneBorderSoft, toneFill, toneFocusBorder, toneFocusWithinBorder, toneHoverBorder, toneHoverText, tonePeerChecked, tonePeerFocus, toneRing, toneSoft, toneSoftHover, toneSolid, toneSolidHover, toneSurface, toneText, anchored, clickOutside, focusTrap, focusable, navigateList, portal, toast, applyTheme, storedTheme, toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from '../../core/index.js';
9
+ export type { Tone, Placement, AnchorOptions, ClickOutsideOptions, FocusTrapOptions, Toast, ToastOptions, ToastAction, Theme, DateFormat, TableSort, SortDirection, RejectedFile } from '../../core/index.js';
11
10
  export { default as Button } from './Button.svelte';
12
11
  export type { ButtonVariant, ButtonSize } from './Button.svelte';
13
12
  export { default as Link } from './Link.svelte';