@eifi1/ui-kit 0.6.1 → 0.7.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 (271) hide show
  1. package/README.md +69 -33
  2. package/dist/chart.d.ts +1 -0
  3. package/dist/components/account-settings.js +3 -2
  4. package/dist/components/account-settings.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +5 -0
  6. package/dist/components/amount-input.js +6 -4
  7. package/dist/components/amount-input.js.map +1 -1
  8. package/dist/components/autocomplete.d.ts +33 -6
  9. package/dist/components/autocomplete.js +62 -16
  10. package/dist/components/autocomplete.js.map +1 -1
  11. package/dist/components/calculator.d.ts +5 -0
  12. package/dist/components/calculator.js +2 -1
  13. package/dist/components/calculator.js.map +1 -1
  14. package/dist/components/chart-zoom.js +2 -1
  15. package/dist/components/chart-zoom.js.map +1 -1
  16. package/dist/components/chart.d.ts +33 -2
  17. package/dist/components/chart.js +20 -8
  18. package/dist/components/chart.js.map +1 -1
  19. package/dist/components/chip.d.ts +34 -14
  20. package/dist/components/chip.js +63 -28
  21. package/dist/components/chip.js.map +1 -1
  22. package/dist/components/choice-card.d.ts +18 -1
  23. package/dist/components/choice-card.js +21 -8
  24. package/dist/components/choice-card.js.map +1 -1
  25. package/dist/components/combobox-core.d.ts +61 -2
  26. package/dist/components/combobox-core.js +68 -17
  27. package/dist/components/combobox-core.js.map +1 -1
  28. package/dist/components/combobox.d.ts +11 -7
  29. package/dist/components/combobox.js +51 -28
  30. package/dist/components/combobox.js.map +1 -1
  31. package/dist/components/currency-select.js +3 -3
  32. package/dist/components/currency-select.js.map +1 -1
  33. package/dist/components/danger-confirm.d.ts +30 -6
  34. package/dist/components/danger-confirm.js +51 -32
  35. package/dist/components/danger-confirm.js.map +1 -1
  36. package/dist/components/data-table-filter-popover.d.ts +1 -1
  37. package/dist/components/data-table-filters.d.ts +1 -1
  38. package/dist/components/data-table-pagination.d.ts +6 -3
  39. package/dist/components/data-table-pagination.js +6 -5
  40. package/dist/components/data-table-pagination.js.map +1 -1
  41. package/dist/components/data-table.d.ts +1 -1
  42. package/dist/components/data-table.js +332 -248
  43. package/dist/components/data-table.js.map +1 -1
  44. package/dist/components/date-picker.js +17 -7
  45. package/dist/components/date-picker.js.map +1 -1
  46. package/dist/components/dialog-frame.d.ts +28 -3
  47. package/dist/components/dialog-frame.js +47 -11
  48. package/dist/components/dialog-frame.js.map +1 -1
  49. package/dist/components/disclosure.d.ts +22 -2
  50. package/dist/components/disclosure.js +41 -8
  51. package/dist/components/disclosure.js.map +1 -1
  52. package/dist/components/dropdown.d.ts +7 -3
  53. package/dist/components/dropdown.js +6 -2
  54. package/dist/components/dropdown.js.map +1 -1
  55. package/dist/components/entity-combobox.d.ts +17 -8
  56. package/dist/components/entity-combobox.js +7 -5
  57. package/dist/components/entity-combobox.js.map +1 -1
  58. package/dist/components/file-button.d.ts +38 -5
  59. package/dist/components/file-button.js +23 -2
  60. package/dist/components/file-button.js.map +1 -1
  61. package/dist/components/file-dropzone.d.ts +11 -5
  62. package/dist/components/file-dropzone.js +14 -19
  63. package/dist/components/file-dropzone.js.map +1 -1
  64. package/dist/components/grouped-picker.js +2 -2
  65. package/dist/components/grouped-picker.js.map +1 -1
  66. package/dist/components/hover-menu.d.ts +8 -3
  67. package/dist/components/hover-menu.js +88 -4
  68. package/dist/components/hover-menu.js.map +1 -1
  69. package/dist/components/icon-picker.d.ts +10 -1
  70. package/dist/components/icon-picker.js +5 -1
  71. package/dist/components/icon-picker.js.map +1 -1
  72. package/dist/components/measured-grid.d.ts +165 -0
  73. package/dist/components/measured-grid.js +559 -0
  74. package/dist/components/measured-grid.js.map +1 -0
  75. package/dist/components/mini-calendar.js +7 -8
  76. package/dist/components/mini-calendar.js.map +1 -1
  77. package/dist/components/month-picker.js +13 -9
  78. package/dist/components/month-picker.js.map +1 -1
  79. package/dist/components/multi-entity-combobox.js +2 -2
  80. package/dist/components/multi-entity-combobox.js.map +1 -1
  81. package/dist/components/multi-select.d.ts +9 -1
  82. package/dist/components/multi-select.js +22 -8
  83. package/dist/components/multi-select.js.map +1 -1
  84. package/dist/components/number-field.d.ts +5 -0
  85. package/dist/components/number-input.d.ts +5 -0
  86. package/dist/components/number-input.js +3 -3
  87. package/dist/components/number-input.js.map +1 -1
  88. package/dist/components/numpad-sheet.d.ts +5 -0
  89. package/dist/components/numpad-sheet.js +3 -3
  90. package/dist/components/numpad-sheet.js.map +1 -1
  91. package/dist/components/picker-sheet.js +1 -1
  92. package/dist/components/picker-sheet.js.map +1 -1
  93. package/dist/components/popover.js +11 -2
  94. package/dist/components/popover.js.map +1 -1
  95. package/dist/components/series-chart-ticks.d.ts +13 -0
  96. package/dist/components/series-chart-ticks.js +29 -0
  97. package/dist/components/series-chart-ticks.js.map +1 -0
  98. package/dist/components/series-chart.js +33 -27
  99. package/dist/components/series-chart.js.map +1 -1
  100. package/dist/components/settings-fields.d.ts +2 -0
  101. package/dist/components/swatch-picker.d.ts +10 -1
  102. package/dist/components/swatch-picker.js +2 -0
  103. package/dist/components/swatch-picker.js.map +1 -1
  104. package/dist/components/swipeable-row.d.ts +11 -2
  105. package/dist/components/swipeable-row.js +5 -3
  106. package/dist/components/swipeable-row.js.map +1 -1
  107. package/dist/components/tile-radio.js +5 -7
  108. package/dist/components/tile-radio.js.map +1 -1
  109. package/dist/components/time-input.d.ts +2 -0
  110. package/dist/components/toggle-group.d.ts +13 -3
  111. package/dist/components/toggle-group.js +21 -1
  112. package/dist/components/toggle-group.js.map +1 -1
  113. package/dist/components/toggle-legend.js +1 -2
  114. package/dist/components/toggle-legend.js.map +1 -1
  115. package/dist/components/tooltip.d.ts +10 -4
  116. package/dist/components/tooltip.js +23 -6
  117. package/dist/components/tooltip.js.map +1 -1
  118. package/dist/components/treemap.d.ts +6 -1
  119. package/dist/components/treemap.js +16 -6
  120. package/dist/components/treemap.js.map +1 -1
  121. package/dist/components/ui.d.ts +36 -8
  122. package/dist/components/ui.js +27 -19
  123. package/dist/components/ui.js.map +1 -1
  124. package/dist/components/use-anchor-dir.d.ts +15 -0
  125. package/dist/components/use-anchor-dir.js +14 -0
  126. package/dist/components/use-anchor-dir.js.map +1 -0
  127. package/dist/components/use-mobile-reveal.d.ts +8 -1
  128. package/dist/components/use-mobile-reveal.js +3 -2
  129. package/dist/components/use-mobile-reveal.js.map +1 -1
  130. package/dist/components/use-table-state.d.ts +4 -4
  131. package/dist/components/use-table-state.js +12 -5
  132. package/dist/components/use-table-state.js.map +1 -1
  133. package/dist/{data-table-filters-DM1auUfH.d.ts → data-table-filters-noy0Abvi.d.ts} +40 -7
  134. package/dist/data-table.d.ts +1 -1
  135. package/dist/data-table.js.map +1 -1
  136. package/dist/feedback/feedback-attachment.d.ts +22 -3
  137. package/dist/feedback/feedback-attachment.js +15 -7
  138. package/dist/feedback/feedback-attachment.js.map +1 -1
  139. package/dist/feedback/feedback-dialog.d.ts +3 -2
  140. package/dist/feedback/feedback-dialog.js.map +1 -1
  141. package/dist/feedback.d.ts +1 -1
  142. package/dist/hooks/use-windowed-rows.d.ts +29 -0
  143. package/dist/hooks/use-windowed-rows.js +31 -0
  144. package/dist/hooks/use-windowed-rows.js.map +1 -0
  145. package/dist/i18n/defaults.d.ts +5 -0
  146. package/dist/i18n/defaults.js +5 -1
  147. package/dist/i18n/defaults.js.map +1 -1
  148. package/dist/i18n/kit-labels.d.ts +7 -0
  149. package/dist/i18n/kit-labels.js.map +1 -1
  150. package/dist/i18n/locales/de-CH.d.ts +45 -0
  151. package/dist/i18n/locales/de-CH.js +21 -0
  152. package/dist/i18n/locales/de-CH.js.map +1 -0
  153. package/dist/i18n/locales/de.d.ts +45 -0
  154. package/dist/i18n/locales/de.js +344 -0
  155. package/dist/i18n/locales/de.js.map +1 -0
  156. package/dist/i18n/locales/es.d.ts +45 -0
  157. package/dist/i18n/locales/es.js +339 -0
  158. package/dist/i18n/locales/es.js.map +1 -0
  159. package/dist/i18n/locales/fr.d.ts +45 -0
  160. package/dist/i18n/locales/fr.js +344 -0
  161. package/dist/i18n/locales/fr.js.map +1 -0
  162. package/dist/i18n/locales/hu.d.ts +45 -0
  163. package/dist/i18n/locales/hu.js +343 -0
  164. package/dist/i18n/locales/hu.js.map +1 -0
  165. package/dist/i18n/locales/it.d.ts +45 -0
  166. package/dist/i18n/locales/it.js +342 -0
  167. package/dist/i18n/locales/it.js.map +1 -0
  168. package/dist/i18n/locales/zh.d.ts +45 -0
  169. package/dist/i18n/locales/zh.js +338 -0
  170. package/dist/i18n/locales/zh.js.map +1 -0
  171. package/dist/index.d.ts +7 -4
  172. package/dist/index.js +4 -1
  173. package/dist/index.js.map +1 -1
  174. package/dist/lib/direction.d.ts +24 -0
  175. package/dist/lib/direction.js +19 -0
  176. package/dist/lib/direction.js.map +1 -0
  177. package/dist/rhf/form.d.ts +2 -0
  178. package/dist/rhf/form.js.map +1 -1
  179. package/dist/rhf.d.ts +2 -0
  180. package/dist/search/command-palette.d.ts +5 -0
  181. package/dist/search/command-palette.js +80 -50
  182. package/dist/search/command-palette.js.map +1 -1
  183. package/dist/shell/app-shell.d.ts +13 -1
  184. package/dist/shell/app-shell.js +134 -85
  185. package/dist/shell/app-shell.js.map +1 -1
  186. package/dist/shell/topbar-action-menu.js +1 -1
  187. package/dist/shell/topbar-action-menu.js.map +1 -1
  188. package/dist/shell/topbar-controls.d.ts +7 -3
  189. package/dist/shell/topbar-controls.js +38 -31
  190. package/dist/shell/topbar-controls.js.map +1 -1
  191. package/dist/theme/chart-palette.d.ts +7 -1
  192. package/dist/theme/chart-palette.js +2 -1
  193. package/dist/theme/chart-palette.js.map +1 -1
  194. package/dist/tour/tour.d.ts +11 -5
  195. package/dist/tour/tour.js +124 -58
  196. package/dist/tour/tour.js.map +1 -1
  197. package/dist/wizard/use-wizard.js +30 -13
  198. package/dist/wizard/use-wizard.js.map +1 -1
  199. package/dist/wizard/wizard-summary.js +1 -1
  200. package/dist/wizard/wizard-summary.js.map +1 -1
  201. package/package.json +12 -2
  202. package/src/components/account-settings.tsx +6 -2
  203. package/src/components/amount-input.tsx +10 -8
  204. package/src/components/autocomplete.tsx +110 -24
  205. package/src/components/calculator.tsx +5 -1
  206. package/src/components/chart-zoom.tsx +8 -1
  207. package/src/components/chart.tsx +68 -7
  208. package/src/components/chip.tsx +131 -49
  209. package/src/components/choice-card.tsx +77 -36
  210. package/src/components/combobox-core.tsx +147 -20
  211. package/src/components/combobox.tsx +100 -44
  212. package/src/components/currency-select.tsx +6 -6
  213. package/src/components/danger-confirm.tsx +90 -36
  214. package/src/components/data-table-pagination.tsx +32 -19
  215. package/src/components/data-table.tsx +198 -36
  216. package/src/components/date-picker.tsx +24 -7
  217. package/src/components/dialog-frame.tsx +97 -22
  218. package/src/components/disclosure.tsx +75 -13
  219. package/src/components/dropdown.tsx +17 -5
  220. package/src/components/entity-combobox.tsx +26 -13
  221. package/src/components/file-button.tsx +74 -5
  222. package/src/components/file-dropzone.tsx +44 -35
  223. package/src/components/grouped-picker.tsx +2 -2
  224. package/src/components/hover-menu.tsx +139 -10
  225. package/src/components/icon-picker.tsx +14 -1
  226. package/src/components/measured-grid.tsx +852 -0
  227. package/src/components/mini-calendar.tsx +11 -8
  228. package/src/components/month-picker.tsx +18 -11
  229. package/src/components/multi-entity-combobox.tsx +2 -2
  230. package/src/components/multi-select.tsx +34 -7
  231. package/src/components/number-input.tsx +4 -4
  232. package/src/components/numpad-sheet.tsx +5 -3
  233. package/src/components/picker-sheet.tsx +1 -1
  234. package/src/components/popover.tsx +18 -3
  235. package/src/components/series-chart-ticks.ts +52 -0
  236. package/src/components/series-chart.tsx +39 -31
  237. package/src/components/swatch-picker.tsx +11 -0
  238. package/src/components/swipeable-row.tsx +21 -6
  239. package/src/components/tile-radio.tsx +5 -7
  240. package/src/components/toggle-group.tsx +39 -3
  241. package/src/components/toggle-legend.tsx +5 -2
  242. package/src/components/tooltip.tsx +40 -12
  243. package/src/components/treemap.tsx +28 -5
  244. package/src/components/ui.tsx +79 -33
  245. package/src/components/use-anchor-dir.ts +20 -0
  246. package/src/components/use-mobile-reveal.ts +10 -2
  247. package/src/components/use-table-state.ts +44 -7
  248. package/src/data-table.ts +1 -0
  249. package/src/feedback/feedback-attachment.tsx +36 -9
  250. package/src/feedback/feedback-dialog.tsx +3 -2
  251. package/src/hooks/use-windowed-rows.ts +69 -0
  252. package/src/i18n/defaults.ts +4 -0
  253. package/src/i18n/kit-labels.tsx +4 -0
  254. package/src/i18n/locales/de-CH.ts +30 -0
  255. package/src/i18n/locales/de.ts +365 -0
  256. package/src/i18n/locales/es.ts +358 -0
  257. package/src/i18n/locales/fr.ts +371 -0
  258. package/src/i18n/locales/hu.ts +359 -0
  259. package/src/i18n/locales/it.ts +360 -0
  260. package/src/i18n/locales/zh.ts +348 -0
  261. package/src/index.ts +10 -1
  262. package/src/lib/direction.ts +37 -0
  263. package/src/rhf/form.tsx +21 -0
  264. package/src/search/command-palette.tsx +77 -19
  265. package/src/shell/app-shell.tsx +201 -130
  266. package/src/shell/topbar-action-menu.tsx +1 -1
  267. package/src/shell/topbar-controls.tsx +19 -8
  268. package/src/theme/chart-palette.ts +9 -2
  269. package/src/tour/tour.tsx +119 -18
  270. package/src/wizard/use-wizard.ts +55 -17
  271. package/src/wizard/wizard-summary.tsx +1 -1
@@ -1,8 +1,9 @@
1
1
  import { useEffect, useId, useRef, useState } from "react";
2
- import type { ComponentPropsWithoutRef, FormEvent, ReactNode } from "react";
2
+ import type { ChangeEvent, ComponentPropsWithoutRef, FormEvent, ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { useKitLabels } from "../i18n/kit-labels";
5
- import { Button, Input, Spinner } from "./ui";
5
+ import { Button, Input, Label, Spinner } from "./ui";
6
+ import { Tooltip } from "./tooltip";
6
7
 
7
8
  /**
8
9
  * Every string the tile renders — the `dangerConfirm` namespace of
@@ -20,20 +21,30 @@ export interface DangerConfirmLabels {
20
21
  prompt: string;
21
22
  /** Label of the password field (`requirePassword`). */
22
23
  password: string;
23
- /** Label of the type-to-confirm field, given the phrase to type. A FUNCTION, like
24
+ /** Label of the type-to-confirm field. A FUNCTION of the phrase by default, like
24
25
  * every message that carries a value: where the phrase sits in the sentence moves
25
- * with the language. */
26
- phrase: (phrase: string) => string;
26
+ * with the language. A plain STRING is taken as the finished label — for an app
27
+ * whose catalogue already words it ("Type DELETE to confirm") and would otherwise
28
+ * wrap it as `() => label`. */
29
+ phrase: string | ((phrase: string) => string);
30
+ /** Placeholder of the type-to-confirm field — a string, or a function of the
31
+ * phrase (`(p) => p` echoes it). Optional and unset by default: the field keeps its
32
+ * floating label, as before. Given, the label moves ABOVE the field (a floating
33
+ * label occupies the empty field, which is exactly where a placeholder shows) and
34
+ * the placeholder fills the field. An empty string means none. */
35
+ phrasePlaceholder?: string | ((phrase: string) => string);
27
36
  }
28
37
 
29
- export const DEFAULT_DANGER_CONFIRM_LABELS: DangerConfirmLabels = {
38
+ /** `satisfies` rather than a type annotation, so `DEFAULT_DANGER_CONFIRM_LABELS.phrase`
39
+ * stays callable for a caller that composes its own label from it. */
40
+ export const DEFAULT_DANGER_CONFIRM_LABELS = {
30
41
  arm: "Delete…",
31
42
  confirm: "Delete",
32
43
  cancel: "Cancel",
33
44
  prompt: "This cannot be undone.",
34
45
  password: "Password",
35
- phrase: (phrase) => `Type “${phrase}” to confirm`,
36
- };
46
+ phrase: (phrase: string) => `Type “${phrase}” to confirm`,
47
+ } satisfies DangerConfirmLabels;
37
48
 
38
49
  export interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"> {
39
50
  /**
@@ -48,8 +59,15 @@ export interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<"div">
48
59
  /** Show a password field; confirm stays disabled until it is filled. */
49
60
  requirePassword?: boolean;
50
61
  /** Show a "type <phrase> to confirm" field; confirm stays disabled until the field
51
- * matches exactly (case-sensitive, surrounding spaces ignored). */
62
+ * matches (case-sensitive; surrounding spaces ignored unless `phraseMatch="exact"`). */
52
63
  phrase?: string;
64
+ /**
65
+ * How the typed text is compared with `phrase`. `"trim"` (default) ignores spaces
66
+ * around it — a phone keyboard's autocomplete likes to add one. `"exact"` compares
67
+ * character for character, spaces included, for an app whose contract is "type
68
+ * exactly this".
69
+ */
70
+ phraseMatch?: "trim" | "exact";
53
71
  /** The warning above the fields. Defaults to `labels.prompt`. */
54
72
  prompt?: ReactNode;
55
73
  /** `"danger"` (default) for what cannot be undone; `"warning"` for what can, at a
@@ -103,6 +121,7 @@ export function DangerConfirm({
103
121
  onConfirm,
104
122
  requirePassword,
105
123
  phrase,
124
+ phraseMatch = "trim",
106
125
  prompt,
107
126
  tone = "danger",
108
127
  armLabel,
@@ -127,6 +146,7 @@ export function DangerConfirm({
127
146
  // that has focus (Enter was pressed in it) drops focus to <body>.
128
147
  const promptId = useId();
129
148
  const reasonId = useId();
149
+ const phraseFieldId = useId();
130
150
 
131
151
  // The kit's Button takes no ref, so the two buttons focus is moved to are found by id.
132
152
  const armId = useId();
@@ -180,7 +200,7 @@ export function DangerConfirm({
180
200
 
181
201
  const locked = lockedReason !== undefined && lockedReason !== null && lockedReason !== false && lockedReason !== "";
182
202
  const passwordOk = !requirePassword || password !== "";
183
- const phraseOk = phrase === undefined || typed.trim() === phrase;
203
+ const phraseOk = phrase === undefined || (phraseMatch === "exact" ? typed : typed.trim()) === phrase;
184
204
  const canConfirm = passwordOk && phraseOk && !busy;
185
205
 
186
206
  const submit = (e: FormEvent) => {
@@ -202,25 +222,61 @@ export function DangerConfirm({
202
222
  );
203
223
  };
204
224
 
225
+ const phrasePlaceholder =
226
+ phrase === undefined || labels.phrasePlaceholder === undefined
227
+ ? undefined
228
+ : typeof labels.phrasePlaceholder === "function"
229
+ ? labels.phrasePlaceholder(phrase)
230
+ : labels.phrasePlaceholder;
231
+
232
+ const phraseLabel =
233
+ phrase === undefined ? "" : typeof labels.phrase === "function" ? labels.phrase(phrase) : labels.phrase;
234
+ const phraseFieldProps = {
235
+ ref: firstFieldRef,
236
+ value: typed,
237
+ autoComplete: "off",
238
+ autoCapitalize: "off",
239
+ spellCheck: false,
240
+ readOnly: busy,
241
+ onChange: (e: ChangeEvent<HTMLInputElement>) => setTyped(e.target.value),
242
+ };
243
+
205
244
  const toneText = tone === "warning" ? "text-[var(--warning)]" : "text-[var(--danger)]";
206
245
 
207
246
  if (!armed) {
247
+ const arm = (
248
+ <Button
249
+ id={armId}
250
+ type="button"
251
+ variant={tone === "warning" ? "secondary" : "danger"}
252
+ disabled={disabled}
253
+ aria-disabled={locked || undefined}
254
+ aria-describedby={locked ? reasonId : undefined}
255
+ onClick={() => {
256
+ if (!locked) setArmed(true);
257
+ }}
258
+ className="aria-disabled:cursor-not-allowed aria-disabled:opacity-50"
259
+ >
260
+ {armLabel ?? labels.arm}
261
+ </Button>
262
+ );
208
263
  return (
209
264
  <div {...rest} className={cn("space-y-1", className)}>
210
- <Button
211
- id={armId}
212
- type="button"
213
- variant={tone === "warning" ? "secondary" : "danger"}
214
- disabled={disabled}
215
- aria-disabled={locked || undefined}
216
- aria-describedby={locked ? reasonId : undefined}
217
- onClick={() => {
218
- if (!locked) setArmed(true);
219
- }}
220
- className="aria-disabled:cursor-not-allowed aria-disabled:opacity-50"
221
- >
222
- {armLabel ?? labels.arm}
223
- </Button>
265
+ {locked ? (
266
+ // The reason on the button itself too, where the pointer that tried it is —
267
+ // the line below can sit out of view in a long settings card. Portalled, so
268
+ // a card that scrolls neither clips it nor grows by it.
269
+ //
270
+ // The button in a FRAGMENT on purpose: Tooltip clones an element child to
271
+ // add the bubble to its `aria-describedby`, and the button is already
272
+ // described by the visible line below — the same sentence twice, read out
273
+ // on every focus. A fragment is left as it is, so the bubble stays visual.
274
+ <Tooltip label={lockedReason} portal>
275
+ <>{arm}</>
276
+ </Tooltip>
277
+ ) : (
278
+ arm
279
+ )}
224
280
  {locked && (
225
281
  <p id={reasonId} className="text-xs text-[var(--text-muted)]">
226
282
  {lockedReason}
@@ -242,18 +298,16 @@ export function DangerConfirm({
242
298
  <p id={promptId} className={cn("text-xs font-medium", toneText)}>
243
299
  {prompt ?? labels.prompt}
244
300
  </p>
245
- {phrase !== undefined && (
246
- <Input
247
- ref={firstFieldRef}
248
- label={labels.phrase(phrase)}
249
- value={typed}
250
- autoComplete="off"
251
- autoCapitalize="off"
252
- spellCheck={false}
253
- readOnly={busy}
254
- onChange={(e) => setTyped(e.target.value)}
255
- />
256
- )}
301
+ {phrase !== undefined &&
302
+ (phrasePlaceholder ? (
303
+ // A static label above, so the placeholder has the empty field to itself.
304
+ <div className="space-y-1.5">
305
+ <Label htmlFor={phraseFieldId}>{phraseLabel}</Label>
306
+ <Input id={phraseFieldId} placeholder={phrasePlaceholder} {...phraseFieldProps} />
307
+ </div>
308
+ ) : (
309
+ <Input label={phraseLabel} {...phraseFieldProps} />
310
+ ))}
257
311
  {requirePassword && (
258
312
  <Input
259
313
  // The phrase field takes the ref when both are there — it comes first.
@@ -2,7 +2,7 @@ import { useMemo } from "react";
2
2
  import { ChevronLeft, ChevronRight } from "lucide-react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from "./data-table-labels";
5
- import { useKitLocale } from "../i18n/kit-labels";
5
+ import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
6
6
 
7
7
  export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;
8
8
 
@@ -12,8 +12,11 @@ interface PaginationProps {
12
12
  pageSize: number;
13
13
  total: number;
14
14
  onPage: (p: number) => void;
15
- onPageSize: (n: number) => void;
16
- labels?: DataTableLabels;
15
+ /** Omit when the page size is not the user's to change (a server that fixes it):
16
+ * the select is then not rendered at all, rather than rendered and inert. */
17
+ onPageSize?: (n: number) => void;
18
+ /** Over `dataTable` from `<UiKitProvider labels>`, over the English defaults. */
19
+ labels?: Partial<DataTableLabels>;
17
20
  /** For the page numbers and page-size options; falls back to the provider's. */
18
21
  locale?: string;
19
22
  }
@@ -27,9 +30,14 @@ export function Pagination({
27
30
  total,
28
31
  onPage,
29
32
  onPageSize,
30
- labels = DEFAULT_DATA_TABLE_LABELS,
33
+ labels: labelsProp,
31
34
  locale: localeProp,
32
35
  }: PaginationProps) {
36
+ // prop > provider > English, like every other kit component. This defaulted straight
37
+ // to the English object, so a standalone pager under a translated provider still
38
+ // said "Rows per page" — only the pager INSIDE a DataTable was translated, because
39
+ // the table resolved the provider's labels and handed them down.
40
+ const labels = useKitLabels("dataTable", DEFAULT_DATA_TABLE_LABELS, labelsProp);
33
41
  const locale = useKitLocale(localeProp);
34
42
  // The bare numbers on the strip and in the select are text too: `String(n)` is
35
43
  // always ASCII digits, which is wrong for a locale that writes its own. The range
@@ -60,19 +68,23 @@ export function Pagination({
60
68
  ? labels.rowCount(total)
61
69
  : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total)}
62
70
  </span>
63
- <select
64
- value={pageSize === Infinity ? "all" : pageSize}
65
- onChange={(e) => onPageSize(e.target.value === "all" ? Infinity : Number(e.target.value))}
66
- className="rounded border border-[var(--border)] bg-[var(--bg-surface)] px-1 py-0.5 text-xs"
67
- aria-label={labels.pageSize}
68
- >
69
- {PAGE_SIZE_OPTIONS.map((n) => (
70
- <option key={n} value={n}>
71
- {num.format(n)}
72
- </option>
73
- ))}
74
- <option value="all">{labels.pageSizeAll}</option>
75
- </select>
71
+ {onPageSize && (
72
+ <select
73
+ value={pageSize === Infinity ? "all" : pageSize}
74
+ onChange={(e) =>
75
+ onPageSize(e.target.value === "all" ? Infinity : Number(e.target.value))
76
+ }
77
+ className="rounded border border-[var(--border)] bg-[var(--bg-surface)] px-1 py-0.5 text-xs"
78
+ aria-label={labels.pageSize}
79
+ >
80
+ {PAGE_SIZE_OPTIONS.map((n) => (
81
+ <option key={n} value={n}>
82
+ {num.format(n)}
83
+ </option>
84
+ ))}
85
+ <option value="all">{labels.pageSizeAll}</option>
86
+ </select>
87
+ )}
76
88
  </div>
77
89
  {pageSize !== Infinity && totalPages > 1 && (
78
90
  <div className="flex items-center gap-1">
@@ -83,7 +95,8 @@ export function Pagination({
83
95
  className="rounded p-1 hover:bg-[var(--bg-hover)] disabled:opacity-40"
84
96
  aria-label={labels.prevPage}
85
97
  >
86
- <ChevronLeft className="size-4" />
98
+ {/* "Previous" points back along the line — left in LTR, right in RTL. */}
99
+ <ChevronLeft className="size-4 rtl:-scale-x-100" />
87
100
  </button>
88
101
  {items.map((it, i) =>
89
102
  it === "ellipsis" ? (
@@ -117,7 +130,7 @@ export function Pagination({
117
130
  className="rounded p-1 hover:bg-[var(--bg-hover)] disabled:opacity-40"
118
131
  aria-label={labels.nextPage}
119
132
  >
120
- <ChevronRight className="size-4" />
133
+ <ChevronRight className="size-4 rtl:-scale-x-100" />
121
134
  </button>
122
135
  </div>
123
136
  )}