@eifi1/ui-kit 0.34.0 → 0.35.0

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 (241) hide show
  1. package/README.md +82 -38
  2. package/dist/billing/billing-banner.d.ts +5 -2
  3. package/dist/billing/billing-banner.js.map +1 -1
  4. package/dist/billing/plan-limit-notice.d.ts +5 -2
  5. package/dist/billing/plan-limit-notice.js.map +1 -1
  6. package/dist/chart.js.map +1 -1
  7. package/dist/components/amount-input.d.ts +0 -3
  8. package/dist/components/amount-input.js +2 -2
  9. package/dist/components/amount-input.js.map +1 -1
  10. package/dist/components/calculator.d.ts +1 -4
  11. package/dist/components/calculator.js +1 -3
  12. package/dist/components/calculator.js.map +1 -1
  13. package/dist/components/chart-zoom.d.ts +0 -37
  14. package/dist/components/chart-zoom.js +1 -28
  15. package/dist/components/chart-zoom.js.map +1 -1
  16. package/dist/components/chip-input.d.ts +0 -4
  17. package/dist/components/chip-input.js +1 -5
  18. package/dist/components/chip-input.js.map +1 -1
  19. package/dist/components/combobox.d.ts +1 -1
  20. package/dist/components/combobox.js.map +1 -1
  21. package/dist/components/copy-button.d.ts +5 -1
  22. package/dist/components/copy-button.js.map +1 -1
  23. package/dist/components/currency-option-list.d.ts +3 -8
  24. package/dist/components/currency-option-list.js +1 -29
  25. package/dist/components/currency-option-list.js.map +1 -1
  26. package/dist/components/danger-confirm.d.ts +3 -3
  27. package/dist/components/danger-confirm.js.map +1 -1
  28. package/dist/components/data-table-filter-popover.d.ts +1 -1
  29. package/dist/components/data-table-filter-popover.js +1 -1
  30. package/dist/components/data-table-filter-popover.js.map +1 -1
  31. package/dist/components/data-table-labels.d.ts +1 -42
  32. package/dist/components/data-table-labels.js +2 -31
  33. package/dist/components/data-table-labels.js.map +1 -1
  34. package/dist/components/data-table-shared.d.ts +17 -1
  35. package/dist/components/data-table-shared.js +22 -0
  36. package/dist/components/data-table-shared.js.map +1 -1
  37. package/dist/components/data-table.d.ts +12 -2
  38. package/dist/components/data-table.js +18 -14
  39. package/dist/components/data-table.js.map +1 -1
  40. package/dist/components/dialog-frame.d.ts +3 -1
  41. package/dist/components/dialog-frame.js.map +1 -1
  42. package/dist/components/dropdown.d.ts +18 -15
  43. package/dist/components/dropdown.js +11 -3
  44. package/dist/components/dropdown.js.map +1 -1
  45. package/dist/components/entity-combobox.d.ts +1 -1
  46. package/dist/components/entity-combobox.js.map +1 -1
  47. package/dist/components/field-base.d.ts +0 -5
  48. package/dist/components/field-base.js +0 -5
  49. package/dist/components/field-base.js.map +1 -1
  50. package/dist/components/field-parts.d.ts +3 -11
  51. package/dist/components/field-parts.js +1 -9
  52. package/dist/components/field-parts.js.map +1 -1
  53. package/dist/components/field-strip.d.ts +8 -23
  54. package/dist/components/field-strip.js +24 -22
  55. package/dist/components/field-strip.js.map +1 -1
  56. package/dist/components/field-sync.d.ts +0 -4
  57. package/dist/components/field-sync.js +0 -4
  58. package/dist/components/field-sync.js.map +1 -1
  59. package/dist/components/field.d.ts +4 -4
  60. package/dist/components/field.js.map +1 -1
  61. package/dist/components/full-bleed-dialog.js +2 -2
  62. package/dist/components/full-bleed-dialog.js.map +1 -1
  63. package/dist/components/hover-menu.d.ts +6 -13
  64. package/dist/components/hover-menu.js +3 -3
  65. package/dist/components/hover-menu.js.map +1 -1
  66. package/dist/components/inline-entity-combobox.d.ts +1 -1
  67. package/dist/components/inline-entity-combobox.js.map +1 -1
  68. package/dist/components/modal.d.ts +9 -1
  69. package/dist/components/modal.js +7 -4
  70. package/dist/components/modal.js.map +1 -1
  71. package/dist/components/multi-select.d.ts +5 -16
  72. package/dist/components/multi-select.js +5 -10
  73. package/dist/components/multi-select.js.map +1 -1
  74. package/dist/components/number-input.d.ts +0 -3
  75. package/dist/components/number-input.js +2 -4
  76. package/dist/components/number-input.js.map +1 -1
  77. package/dist/components/popover.js +2 -2
  78. package/dist/components/popover.js.map +1 -1
  79. package/dist/components/series-chart.js.map +1 -1
  80. package/dist/components/server-wake.d.ts +0 -6
  81. package/dist/components/server-wake.js.map +1 -1
  82. package/dist/components/settings-fields.d.ts +4 -2
  83. package/dist/components/settings-fields.js.map +1 -1
  84. package/dist/components/stat-tile.d.ts +8 -2
  85. package/dist/components/stat-tile.js.map +1 -1
  86. package/dist/components/swipeable-row.d.ts +2 -17
  87. package/dist/components/swipeable-row.js +11 -16
  88. package/dist/components/swipeable-row.js.map +1 -1
  89. package/dist/components/tooltip.d.ts +9 -1
  90. package/dist/components/tooltip.js +20 -5
  91. package/dist/components/tooltip.js.map +1 -1
  92. package/dist/components/translation-review.d.ts +10 -2
  93. package/dist/components/translation-review.js +11 -3
  94. package/dist/components/translation-review.js.map +1 -1
  95. package/dist/components/use-table-state.d.ts +31 -3
  96. package/dist/components/use-table-state.js +53 -8
  97. package/dist/components/use-table-state.js.map +1 -1
  98. package/dist/components/user-avatar.d.ts +2 -2
  99. package/dist/components/user-avatar.js.map +1 -1
  100. package/dist/components/wizard-stepper.d.ts +1 -4
  101. package/dist/components/wizard-stepper.js +2 -3
  102. package/dist/components/wizard-stepper.js.map +1 -1
  103. package/dist/currencies.d.ts +23 -0
  104. package/dist/currencies.js +38 -0
  105. package/dist/currencies.js.map +1 -0
  106. package/dist/demo/demo-banner.d.ts +7 -2
  107. package/dist/demo/demo-banner.js.map +1 -1
  108. package/dist/eslint/index.d.ts +83 -0
  109. package/dist/eslint/index.js +69 -0
  110. package/dist/eslint/index.js.map +1 -0
  111. package/dist/eslint/role-utilities.d.ts +48 -0
  112. package/dist/eslint/role-utilities.generated.d.ts +2 -0
  113. package/dist/eslint/role-utilities.generated.js +143 -0
  114. package/dist/eslint/role-utilities.generated.js.map +1 -0
  115. package/dist/eslint/role-utilities.js +43 -0
  116. package/dist/eslint/role-utilities.js.map +1 -0
  117. package/dist/feedback/feedback-menu.d.ts +2 -2
  118. package/dist/feedback/feedback-menu.js.map +1 -1
  119. package/dist/feedback/feedback-thread.d.ts +3 -8
  120. package/dist/feedback/feedback-thread.js +0 -2
  121. package/dist/feedback/feedback-thread.js.map +1 -1
  122. package/dist/hooks/use-dismiss.d.ts +16 -3
  123. package/dist/hooks/use-dismiss.js +14 -2
  124. package/dist/hooks/use-dismiss.js.map +1 -1
  125. package/dist/i18n/kit-labels.d.ts +14 -4
  126. package/dist/i18n/kit-labels.js.map +1 -1
  127. package/dist/i18n/locales/en.js +1 -1
  128. package/dist/i18n/locales/en.js.map +2 -2
  129. package/dist/i18n/locales/fr.js +3 -2
  130. package/dist/i18n/locales/fr.js.map +1 -1
  131. package/dist/i18n/locales/it.js +3 -2
  132. package/dist/i18n/locales/it.js.map +1 -1
  133. package/dist/index.d.ts +2 -1
  134. package/dist/index.js +2 -2
  135. package/dist/index.js.map +1 -1
  136. package/dist/lib/refs.d.ts +13 -0
  137. package/dist/lib/refs.js +15 -0
  138. package/dist/lib/refs.js.map +1 -0
  139. package/dist/rhf/fields.d.ts +10 -10
  140. package/dist/rhf/fields.js.map +1 -1
  141. package/dist/rhf/form-core.d.ts +5 -4
  142. package/dist/rhf/form-core.js.map +1 -1
  143. package/dist/rhf/form.d.ts +7 -36
  144. package/dist/rhf/form.js +1 -16
  145. package/dist/rhf/form.js.map +1 -1
  146. package/dist/rhf/line-items.d.ts +1 -1
  147. package/dist/rhf/line-items.js.map +1 -1
  148. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  149. package/dist/rhf.js.map +1 -1
  150. package/dist/shell/option-switcher-menu.d.ts +5 -17
  151. package/dist/shell/option-switcher-menu.js +1 -3
  152. package/dist/shell/option-switcher-menu.js.map +1 -1
  153. package/dist/shell/role-switcher.d.ts +1 -4
  154. package/dist/shell/role-switcher.js +2 -3
  155. package/dist/shell/role-switcher.js.map +1 -1
  156. package/dist/shell/topbar-action-menu.d.ts +7 -19
  157. package/dist/shell/topbar-action-menu.js +1 -3
  158. package/dist/shell/topbar-action-menu.js.map +1 -1
  159. package/dist/shell/topbar-controls.d.ts +3 -12
  160. package/dist/shell/topbar-controls.js +6 -9
  161. package/dist/shell/topbar-controls.js.map +1 -1
  162. package/dist/testing/index.d.ts +1 -1
  163. package/dist/testing/index.js +2 -0
  164. package/dist/testing/index.js.map +1 -1
  165. package/dist/testing/viewport.d.ts +25 -2
  166. package/dist/testing/viewport.js +61 -10
  167. package/dist/testing/viewport.js.map +1 -1
  168. package/dist/theme/palette-presets.d.ts +0 -7
  169. package/dist/theme/palette-presets.js +0 -279
  170. package/dist/theme/palette-presets.js.map +1 -1
  171. package/dist/wizard/types.d.ts +0 -5
  172. package/dist/wizard/types.js +1 -5
  173. package/dist/wizard/types.js.map +1 -1
  174. package/package.json +9 -1
  175. package/src/billing/billing-banner.tsx +5 -2
  176. package/src/billing/plan-limit-notice.tsx +5 -2
  177. package/src/chart.ts +5 -3
  178. package/src/components/amount-input.tsx +2 -6
  179. package/src/components/calculator.tsx +2 -7
  180. package/src/components/chart-zoom.tsx +4 -47
  181. package/src/components/chip-input.tsx +0 -7
  182. package/src/components/combobox.tsx +1 -1
  183. package/src/components/copy-button.tsx +5 -1
  184. package/src/components/currency-option-list.tsx +8 -42
  185. package/src/components/danger-confirm.tsx +3 -3
  186. package/src/components/data-table-filter-popover.tsx +2 -1
  187. package/src/components/data-table-labels.ts +2 -84
  188. package/src/components/data-table-shared.tsx +55 -2
  189. package/src/components/data-table.tsx +29 -17
  190. package/src/components/dialog-frame.tsx +3 -1
  191. package/src/components/dropdown.tsx +42 -18
  192. package/src/components/entity-combobox.tsx +1 -1
  193. package/src/components/field-base.tsx +0 -11
  194. package/src/components/field-parts.tsx +5 -19
  195. package/src/components/field-strip.tsx +9 -25
  196. package/src/components/field-sync.tsx +0 -7
  197. package/src/components/field.tsx +4 -4
  198. package/src/components/full-bleed-dialog.tsx +4 -3
  199. package/src/components/hover-menu.tsx +12 -16
  200. package/src/components/inline-entity-combobox.tsx +1 -1
  201. package/src/components/modal.tsx +20 -4
  202. package/src/components/multi-select.tsx +13 -30
  203. package/src/components/number-input.tsx +3 -8
  204. package/src/components/popover.tsx +5 -3
  205. package/src/components/series-chart.tsx +1 -1
  206. package/src/components/server-wake.tsx +0 -6
  207. package/src/components/settings-fields.tsx +4 -2
  208. package/src/components/stat-tile.tsx +8 -2
  209. package/src/components/swipeable-row.tsx +15 -37
  210. package/src/components/tooltip.tsx +37 -6
  211. package/src/components/translation-review.tsx +25 -7
  212. package/src/components/use-table-state.ts +91 -10
  213. package/src/components/user-avatar.tsx +2 -2
  214. package/src/components/wizard-stepper.tsx +2 -6
  215. package/src/currencies.ts +70 -0
  216. package/src/demo/demo-banner.tsx +7 -2
  217. package/src/eslint/index.ts +161 -0
  218. package/src/eslint/role-utilities.generated.ts +146 -0
  219. package/src/eslint/role-utilities.ts +99 -0
  220. package/src/feedback/feedback-menu.tsx +2 -2
  221. package/src/feedback/feedback-thread.tsx +3 -8
  222. package/src/hooks/use-dismiss.ts +39 -4
  223. package/src/i18n/kit-labels.tsx +15 -5
  224. package/src/i18n/locales/fr.ts +3 -2
  225. package/src/i18n/locales/it.ts +3 -2
  226. package/src/index.ts +3 -1
  227. package/src/lib/refs.ts +22 -0
  228. package/src/rhf/fields.tsx +10 -10
  229. package/src/rhf/form-core.tsx +5 -4
  230. package/src/rhf/form.tsx +7 -43
  231. package/src/rhf/line-items.tsx +1 -1
  232. package/src/rhf/use-rhf-wizard-step.ts +4 -4
  233. package/src/rhf.ts +3 -2
  234. package/src/shell/option-switcher-menu.tsx +5 -22
  235. package/src/shell/role-switcher.tsx +2 -6
  236. package/src/shell/topbar-action-menu.tsx +7 -24
  237. package/src/shell/topbar-controls.tsx +6 -18
  238. package/src/testing/index.ts +3 -1
  239. package/src/testing/viewport.ts +125 -19
  240. package/src/theme/palette-presets.ts +7 -293
  241. package/src/wizard/types.ts +0 -8
@@ -100,9 +100,6 @@ export interface AmountInputProps {
100
100
  id?: string;
101
101
  /** Names the field when it has no visible `label` (a table cell, the display shape). */
102
102
  "aria-label"?: string;
103
- /** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
104
- * Names the field only when `aria-label` is absent. */
105
- ariaLabel?: string;
106
103
  /**
107
104
  * Set on the `<input>`, with `id` above. Declared so `Field`'s render-prop spread —
108
105
  * `{(ids) => <AmountInput {...ids} … />}` — is typed and reaches the input: the
@@ -382,7 +379,7 @@ function isResultOf(previous: string, text: string, settle: (text: string) => st
382
379
  }
383
380
 
384
381
  export function AmountInput(props: AmountInputProps) {
385
- const { ref, value, onChange, currency, onCurrencyChange, placeholder, label, className, inputClassName, id, "aria-label": ariaLabelAttr, ariaLabel: ariaLabelProp, "aria-required": ariaRequired, autoFocus, tone = "neutral", negative = false, onNegativeChange, align = "start", labels, currencyNames, currencyOptions, digits: digitsProp, min, max, onCommit, disabledReasonDisplay } = props;
382
+ const { ref, value, onChange, currency, onCurrencyChange, placeholder, label, className, inputClassName, id, "aria-label": ariaLabel, "aria-required": ariaRequired, autoFocus, tone = "neutral", negative = false, onNegativeChange, align = "start", labels, currencyNames, currencyOptions, digits: digitsProp, min, max, onCommit, disabledReasonDisplay } = props;
386
383
  const generatedId = useId();
387
384
  const fieldId = id ?? generatedId;
388
385
  // The sign is DISPLAYED, not stored (live #201 rework). `value` is the
@@ -556,8 +553,7 @@ export function AmountInput(props: AmountInputProps) {
556
553
  <input
557
554
  ref={setRefs}
558
555
  id={fieldId}
559
- // The DOM spelling wins; `ariaLabel` is the fallback for call sites not moved yet.
560
- aria-label={ariaLabelAttr ?? ariaLabelProp}
556
+ aria-label={ariaLabel}
561
557
  type="text"
562
558
  // eslint-disable-next-line jsx-a11y/no-autofocus -- opt-in per call site (feedback #70)
563
559
  autoFocus={autoFocus}
@@ -37,9 +37,6 @@ interface CalculatorButtonProps {
37
37
  className?: string;
38
38
  /** The trigger's accessible name; wins over `calculator.open` from the provider. */
39
39
  "aria-label"?: string;
40
- /** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
41
- * Names the trigger only when `aria-label` is absent. */
42
- ariaLabel?: string;
43
40
  /** Screen-reader names for the panel's own controls. `aria-label` above already
44
41
  * covers the trigger; these three were hardcoded English inside the popover,
45
42
  * which a translating host had no way to reach. */
@@ -229,13 +226,11 @@ export function CalculatorButton({
229
226
  value,
230
227
  onChange,
231
228
  className,
232
- "aria-label": ariaLabelAttr,
233
- ariaLabel: ariaLabelProp,
229
+ "aria-label": ariaLabel,
234
230
  labels: labelsProp,
235
231
  }: CalculatorButtonProps) {
236
232
  // prop > provider > English, with the name prop folded in as the `open` key it has
237
- // always been. The DOM spelling wins over the deprecated `ariaLabel`.
238
- const ariaLabel = ariaLabelAttr ?? ariaLabelProp;
233
+ // always been.
239
234
  const fromProps = useMemo(
240
235
  () => (ariaLabel === undefined ? labelsProp : { ...labelsProp, open: ariaLabel }),
241
236
  [ariaLabel, labelsProp],
@@ -3,12 +3,10 @@
3
3
  // axes). `SeriesChart` is how an app zooms; `SharedXZoom` gives a column of charts one x
4
4
  // window.
5
5
  //
6
- // 0.34 (refactor audit 2026-10, API-6b = MOD-10): the arithmetic and the HOC leave the
7
- // public surface in 0.35. They were exported because lenkbank's, later Kurvenschmiede's,
8
- // own tests used them; no file in any of the three apps names one today. The kit's own
9
- // modules import them from the core, so their removal is the block below.
10
- import * as core from "./chart-zoom-core";
11
-
6
+ // Those two and the zoom's types are all that is public. The arithmetic and the HOC were
7
+ // too until 0.35 (refactor audit 2026-10, API-6b = MOD-10; deprecated in 0.34): exported
8
+ // because lenkbank's, later Kurvenschmiede's, own tests used them, and named by no file in
9
+ // any of the three apps since. The kit's own modules import them from the core.
12
10
  export { SharedXZoom } from "./chart-zoom-core";
13
11
  export type {
14
12
  ZoomAxes,
@@ -22,44 +20,3 @@ export type {
22
20
  ZoomState,
23
21
  ZoomTarget,
24
22
  } from "./chart-zoom-core";
25
-
26
- /* ── Deprecated 0.34, removed in 0.35 ─────────────────────────────────────
27
- * Each is the core's own value under its old public name: SeriesChart draws the zoom
28
- * these compute, and a chart's `zoomAxes` prop is what an app sets. */
29
-
30
- /** @deprecated 0.34 — a series without `axis` is on the chart's single y axis, `"y"`;
31
- * removed in 0.35 (API-6b). */
32
- export const DEFAULT_Y_AXIS = core.DEFAULT_Y_AXIS;
33
- /** @deprecated 0.34 — SeriesChart's zoom, which reads it; removed in 0.35 (API-6b). */
34
- export const ZOOM_SQUARE_ENOUGH = core.ZOOM_SQUARE_ENOUGH;
35
- /** @deprecated 0.34 — SeriesChart's zoom, which reads it; removed in 0.35 (API-6b). */
36
- export const ZOOM_MIN_DRAG = core.ZOOM_MIN_DRAG;
37
- /** @deprecated 0.34 — SeriesChart's zoom, which reads a drag this way; removed in 0.35
38
- * (API-6b). */
39
- export const zoomAxesFor = core.zoomAxesFor;
40
- /** @deprecated 0.34 — SeriesChart's zoom, which reads a drag this way; removed in 0.35
41
- * (API-6b). */
42
- export const selectionFromDrag = core.selectionFromDrag;
43
- /** @deprecated 0.34 — SeriesChart fits its axes with it; removed in 0.35 (API-6b). */
44
- export const axisExtent = core.axisExtent;
45
- /** @deprecated 0.34 — SeriesChart's zoom, which refits the axes a drag did not name;
46
- * removed in 0.35 (API-6b). */
47
- export const fitYToX = core.fitYToX;
48
- /** @deprecated 0.34 — SeriesChart's zoom, which refits the axes a drag did not name;
49
- * removed in 0.35 (API-6b). */
50
- export const fitXToY = core.fitXToY;
51
- /** @deprecated 0.34 — SeriesChart's zoom, which keeps its own state; removed in 0.35
52
- * (API-6b). */
53
- export const NO_ZOOM = core.NO_ZOOM;
54
- /** @deprecated 0.34 — SeriesChart's zoom, which keeps its own state; removed in 0.35
55
- * (API-6b). */
56
- export const zoomAfter = core.zoomAfter;
57
- /** @deprecated 0.34 — SeriesChart's zoom, which keeps its own state; removed in 0.35
58
- * (API-6b). */
59
- export const zoomDomains = core.zoomDomains;
60
- /** @deprecated 0.34 — SeriesChart's `zoomAxes` prop, which says what its default is;
61
- * removed in 0.35 (API-6b). */
62
- export const defaultZoomAxes = core.defaultZoomAxes;
63
- /** @deprecated 0.34 — SeriesChart, which is a chart wrapped in it, and SharedXZoom;
64
- * removed in 0.35 (API-6b). */
65
- export const withChartZoom = core.withChartZoom;
@@ -32,13 +32,6 @@ export interface ChipInputLabels {
32
32
  // Defined in ./chip-labels, which holds no component code (0.34, KS U8).
33
33
  export { DEFAULT_CHIP_INPUT_LABELS };
34
34
 
35
- /** @deprecated 0.34 — `useKitLabels("chipInput", DEFAULT_CHIP_INPUT_LABELS, labels)`, which
36
- * the provider reaches too, or ChipInput's own `labels`; removed in 0.35 (refactor audit
37
- * 2026-10, API-6a). A merge from before the provider, which no kit component calls. */
38
- export function resolveChipInputLabels(labels?: Partial<ChipInputLabels>): ChipInputLabels {
39
- return labels ? { ...DEFAULT_CHIP_INPUT_LABELS, ...labels } : DEFAULT_CHIP_INPUT_LABELS;
40
- }
41
-
42
35
  /**
43
36
  * `onChange` is the field's own — the whole LIST, not a DOM event — so the wrapper
44
37
  * div's `onChange` is omitted rather than shadowed. `placeholder` is likewise this
@@ -125,7 +125,7 @@ export interface ComboboxProps
125
125
  closeLabel?: string;
126
126
  /**
127
127
  * The phone sheet's heading — and, as a string, the dialog's accessible name — for a
128
- * field whose label is drawn by someone else: a form's `FormLabel` above it,
128
+ * field whose label is drawn by someone else: a bound field's label above it,
129
129
  * which `RhfTextCombobox` hands over here (0.24). Default: `label`. Without either, the
130
130
  * full-screen sheet opened untitled and unnamed, the one screen on a phone that has to
131
131
  * say what it is asking for (live #200).
@@ -1,4 +1,4 @@
1
- import type { ButtonHTMLAttributes } from "react";
1
+ import type { ButtonHTMLAttributes, Ref } from "react";
2
2
  import { Check, Copy, X } from "lucide-react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
@@ -62,6 +62,10 @@ export interface CopyButtonProps
62
62
  /** Called with the real outcome once the copy settles. */
63
63
  onCopied?: (ok: boolean) => void;
64
64
  labels?: LabelOverride<CopyButtonLabels>;
65
+ /** The `<button>` in either variant — the {@link IconButton}'s or the {@link Button}'s,
66
+ * never the tooltip's wrapper round it (0.35, round 0.35 R4, the follow-up to API-2).
67
+ * It rides the rest of the props on to that kit button, whose own ref it is. */
68
+ ref?: Ref<HTMLButtonElement>;
65
69
  }
66
70
 
67
71
  const ICONS: Record<CopyState, typeof Copy> = { idle: Copy, copied: Check, failed: X };
@@ -3,49 +3,15 @@ import type { ComponentPropsWithRef, ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { foldForSearch } from "../lib/text-match";
5
5
  import { ComboboxPanel, type ComboboxCore, type ComboOption } from "./combobox-core";
6
+ import { CURRENCIES, type CurrencyOption } from "../currencies";
6
7
 
7
- // The currency data and the one list both currency pickers render (round 0.34 Wave 2,
8
- // DUP-4). CurrencySelect re-exports the data under its own name, as it always has; the
9
- // list is package-private. Its own module, so AmountInput can render the list without a
10
- // cycle through currency-select.
11
-
12
- export interface CurrencyOption {
13
- code: string;
14
- name: string;
15
- symbol: string;
16
- /** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */
17
- country: string;
18
- }
19
-
20
- export const CURRENCIES: CurrencyOption[] = [
21
- { code: "EUR", name: "Euro", symbol: "€", country: "eu" },
22
- { code: "USD", name: "US Dollar", symbol: "$", country: "us" },
23
- { code: "GBP", name: "British Pound", symbol: "£", country: "gb" },
24
- { code: "CHF", name: "Swiss Franc", symbol: "CHF", country: "ch" },
25
- { code: "JPY", name: "Japanese Yen", symbol: "¥", country: "jp" },
26
- { code: "CAD", name: "Canadian Dollar", symbol: "CA$", country: "ca" },
27
- { code: "AUD", name: "Australian Dollar", symbol: "A$", country: "au" },
28
- { code: "NZD", name: "New Zealand Dollar", symbol: "NZ$", country: "nz" },
29
- { code: "SEK", name: "Swedish Krona", symbol: "kr", country: "se" },
30
- { code: "NOK", name: "Norwegian Krone", symbol: "kr", country: "no" },
31
- { code: "DKK", name: "Danish Krone", symbol: "kr", country: "dk" },
32
- { code: "PLN", name: "Polish Zloty", symbol: "zł", country: "pl" },
33
- { code: "CZK", name: "Czech Koruna", symbol: "Kč", country: "cz" },
34
- { code: "HUF", name: "Hungarian Forint", symbol: "Ft", country: "hu" },
35
- { code: "RON", name: "Romanian Leu", symbol: "lei", country: "ro" },
36
- { code: "BGN", name: "Bulgarian Lev", symbol: "лв", country: "bg" },
37
- { code: "TRY", name: "Turkish Lira", symbol: "₺", country: "tr" },
38
- { code: "RUB", name: "Russian Ruble", symbol: "₽", country: "ru" },
39
- { code: "CNY", name: "Chinese Yuan", symbol: "¥", country: "cn" },
40
- { code: "HKD", name: "Hong Kong Dollar", symbol: "HK$", country: "hk" },
41
- { code: "SGD", name: "Singapore Dollar", symbol: "S$", country: "sg" },
42
- { code: "INR", name: "Indian Rupee", symbol: "₹", country: "in" },
43
- { code: "BRL", name: "Brazilian Real", symbol: "R$", country: "br" },
44
- { code: "MXN", name: "Mexican Peso", symbol: "MX$", country: "mx" },
45
- { code: "ZAR", name: "South African Rand", symbol: "R", country: "za" },
46
- { code: "ILS", name: "Israeli Shekel", symbol: "₪", country: "il" },
47
- { code: "AED", name: "UAE Dirham", symbol: "د.إ", country: "ae" },
48
- ];
8
+ // The one list both currency pickers render (round 0.34 Wave 2, DUP-4). CurrencySelect
9
+ // re-exports the data under its own name, as it always has; the list is package-private.
10
+ // Its own module, so AmountInput can render the list without a cycle through
11
+ // currency-select. The data itself is ../currencies since 0.35: an entry of its own that
12
+ // imports nothing, which an app's build can read in Node (keksdose's flag subset).
13
+ export { CURRENCIES };
14
+ export type { CurrencyOption };
49
15
 
50
16
  export interface CurrencyFlagProps extends ComponentPropsWithRef<"span"> {
51
17
  /** ISO 3166-1 alpha-2, lowercase — the class flag-icons keys its sprite by. */
@@ -92,7 +92,7 @@ export interface CurrentPasswordInputProps extends Omit<InputProps, "type" | "au
92
92
  * is named another way (`aria-label` / `aria-labelledby`), which then stays the only
93
93
  * name. Pass the app's own word on a sign-in form ("Password", "Your password"), or
94
94
  * `null` for a field whose `<label htmlFor>` is drawn elsewhere (a form library's
95
- * `FormLabel`), which would otherwise be labelled twice.
95
+ * label), which would otherwise be labelled twice.
96
96
  */
97
97
  label?: ReactNode;
98
98
  /** The value as a string — the `(next) => …` shape, so a caller holding it in
@@ -125,8 +125,8 @@ export interface CurrentPasswordInputProps extends Omit<InputProps, "type" | "au
125
125
  *
126
126
  * Everything else is {@link Input}: the reveal toggle, `error` (under the field,
127
127
  * `aria-describedby`, `aria-invalid`), `invalid`, `name`, `id`, `autoFocus`, `required`.
128
- * The ref is the `<input>`, so it works with `@eifi1/ui-kit/rhf`'s `FormControl` /
129
- * a `register()` spread, and controlled or uncontrolled.
128
+ * The ref is the `<input>`, so it works with `@eifi1/ui-kit/rhf`'s `RhfField` / a
129
+ * `register()` spread, and controlled or uncontrolled.
130
130
  *
131
131
  * ```tsx
132
132
  * <CurrentPasswordInput name="password" label={t("password")} error={errors.password?.message}
@@ -9,7 +9,8 @@ import { dateRangePresets } from "../lib/dates";
9
9
  import { resolveFilter } from "./data-table-filters";
10
10
  import type { FilterValue } from "./data-table-filters";
11
11
  import type { DataTableColumn } from "./data-table";
12
- import { resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
12
+ import type { DataTableLabels } from "./data-table-labels";
13
+ import { resolveDataTableLabels } from "./data-table-shared";
13
14
  import { useKitLabelOverrides, useKitLocale } from "../i18n/kit-labels";
14
15
 
15
16
  interface FilterPopoverProps<T> {
@@ -6,7 +6,6 @@
6
6
  // A type-only import: erased at build time, so this module stays free of the table
7
7
  // itself (and of react-router), which `i18n/defaults` relies on.
8
8
  import type { DataTableSortLabels } from "./data-table";
9
- import type { LabelOverride } from "../i18n/kit-labels";
10
9
 
11
10
  /** The phone sort control's four keys (`sortBy`, `sortDefault`, `sortAscending`,
12
11
  * `sortDescending`, 0.22.0) are {@link DataTableSortLabels}', declared beside the
@@ -89,7 +88,7 @@ export interface DataTableLabels extends DataTableSortLabels {
89
88
  * A column's filter button and panel: "Filter Amount", and "Filter Amount (active)"
90
89
  * while the column is filtered (round 0.34 Wave 2, BUG-7c). Every column's used to be
91
90
  * "Filter" alike, and "filtered" was a colour. An app that translated only `filter`
92
- * gets `"<filter>: <column>"` from it — see {@link resolveDataTableLabels}.
91
+ * gets `"<filter>: <column>"` from it: DataTable derives the one from the other.
93
92
  */
94
93
  filterColumn: (column: string, active: boolean) => string;
95
94
 
@@ -171,90 +170,9 @@ export const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {
171
170
  // The phone sort control (0.22.0). The same words as `DEFAULT_DATA_TABLE_SORT_LABELS`
172
171
  // in data-table.tsx, written out rather than spread in: importing that VALUE here
173
172
  // would make data-table.tsx and this module import each other (data-table.tsx reads
174
- // its resolver from here), and would pull the whole table into `i18n/defaults`.
173
+ // these defaults from here), and would pull the whole table into `i18n/defaults`.
175
174
  sortBy: "Sort by",
176
175
  sortDefault: "Default order",
177
176
  sortAscending: "Ascending",
178
177
  sortDescending: "Descending",
179
178
  };
180
-
181
- /**
182
- * Merge caller overrides onto the English defaults (presets merged too, key by key, as
183
- * the provider merges them — so the parameter is a {@link LabelOverride}).
184
- *
185
- * The parameter is lenient on purpose — a table with no labels at all still
186
- * renders — but that is also how a translated app ends up half English and never
187
- * hears about it: lenkbank ships nineteen of these keys in English in a German UI
188
- * today, because every one of them is backfilled silently. The kit will not start
189
- * throwing over it (three apps depend on the lenient merge), so
190
- * {@link missingDataTableLabels} is the other half: it answers which keys fell back,
191
- * and an app asserts on that in its OWN test, where the answer is actionable.
192
- *
193
- * @deprecated 0.34 — pass `labels` to DataTable, or the provider's `dataTable`, which this
194
- * merge serves; removed in 0.35 from the public surface, where DataTable keeps it as its
195
- * own (refactor audit 2026-10, API-6a). One app test calls it.
196
- */
197
- export function resolveDataTableLabels(partial?: LabelOverride<DataTableLabels>): DataTableLabels {
198
- if (!partial) return DEFAULT_DATA_TABLE_LABELS;
199
- // An `undefined` preset is skipped, as the provider skips it, rather than blanking one.
200
- const presets = { ...DEFAULT_DATA_TABLE_LABELS.presets };
201
- for (const [key, text] of Object.entries(partial.presets ?? {})) if (text !== undefined) presets[key] = text;
202
- const merged: DataTableLabels = { ...DEFAULT_DATA_TABLE_LABELS, ...partial, presets };
203
- // `columnsCount` was split out of `columns` + a template literal. Consumers have
204
- // been translating `columns` since 0.1, and backfilling the English default over
205
- // that would hand them "Columns (4/9)" back for having translated the key that
206
- // existed at the time — the same silent-English failure the audit records against
207
- // this file. So the count is derived from the RESOLVED `columns` unless the caller
208
- // states the whole sentence, which is the escape hatch for a language where the
209
- // parenthetical does not fit.
210
- if (partial.columns !== undefined && partial.columnsCount === undefined) {
211
- merged.columnsCount = (visible, total) => `${merged.columns} (${visible}/${total})`;
212
- }
213
- // The same for `filterColumn` (0.34), from a translated `filter`: the app's own word
214
- // with the column, rather than English for having translated the key that existed.
215
- if (partial.filter !== undefined && partial.filterColumn === undefined) {
216
- merged.filterColumn = (column) => `${merged.filter}: ${column}`;
217
- }
218
- return merged;
219
- }
220
-
221
- /**
222
- * The label keys `labels` does not supply, and which therefore come back English
223
- * from {@link resolveDataTableLabels}.
224
- *
225
- * For a consumer's own test:
226
- *
227
- * ```ts
228
- * expect(missingDataTableLabels(de.dataTable)).toEqual([]);
229
- * ```
230
- *
231
- * which fails the day the kit adds a key — which is the point. Adding a label is
232
- * additive here and a silent regression in a translated app, and nothing else in
233
- * either repository can tell the two apart.
234
- *
235
- * Preset labels are reported one at a time (`presets.yesterday`), because `presets`
236
- * is a `Record` merged key by key: answering `"presets"` would say nothing about
237
- * which of the eleven are still English.
238
- *
239
- * @deprecated 0.34 — `missingKitLabels(labels, DEFAULT_UI_KIT_LABELS)`, which checks every
240
- * namespace, the table's among them, and which all three apps' tests already call;
241
- * removed in 0.35 (refactor audit 2026-10, API-6a).
242
- */
243
- export function missingDataTableLabels(labels?: Partial<DataTableLabels>): string[] {
244
- const missing: string[] = [];
245
- for (const key of Object.keys(DEFAULT_DATA_TABLE_LABELS) as Array<keyof DataTableLabels>) {
246
- if (key === "presets") continue;
247
- if (labels?.[key] !== undefined) continue;
248
- // `columnsCount` is DERIVED from a translated `columns` by the resolver above,
249
- // so a caller who set that one is not leaking English here. Reporting it would
250
- // send them to translate a key they have already covered, and an `toEqual([])`
251
- // assertion built on this would never go green.
252
- if (key === "columnsCount" && labels?.columns !== undefined) continue;
253
- if (key === "filterColumn" && labels?.filter !== undefined) continue;
254
- missing.push(key);
255
- }
256
- for (const preset of Object.keys(DEFAULT_DATA_TABLE_LABELS.presets)) {
257
- if (labels?.presets?.[preset] === undefined) missing.push(`presets.${preset}`);
258
- }
259
- return missing;
260
- }
@@ -1,13 +1,16 @@
1
1
  import type { CSSProperties, ReactNode } from "react";
2
2
  import { ROW_ACTIONS_KEY } from "./data-table-row-actions";
3
- import type { DataTableLabels } from "./data-table-labels";
3
+ import { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from "./data-table-labels";
4
4
  import type { DataTableCellProps, DataTableColumn, DataTableHeadProps } from "./data-table";
5
+ import type { LabelOverride } from "../i18n/kit-labels";
5
6
 
6
7
  // What DataTable shares with its header (data-table-head.tsx), its column panel and its
7
8
  // phone layout (data-table-mobile.tsx): the cell-attribute and alignment rules, a
8
9
  // column's plain-text name, and the row link. Moved out of data-table.tsx unchanged
9
10
  // (refactor audit 2026-10, MOD-3), into a module of their own so none of those has to
10
- // import data-table.tsx at run time — which imports them.
11
+ // import data-table.tsx at run time — which imports them. Since 0.35 also the table's
12
+ // label resolver, which DataTable and its FilterPopover share and the barrel no longer
13
+ // exports (round 0.35 R1).
11
14
 
12
15
  /**
13
16
  * Split a caller's cell attributes into what may reach the element and what the kit
@@ -80,6 +83,56 @@ export function columnLabel<T>(col: DataTableColumn<T>, labels?: DataTableLabels
80
83
  return col.headerText ?? (typeof col.header === "string" ? col.header : col.key);
81
84
  }
82
85
 
86
+ /**
87
+ * The table's labels: caller overrides merged onto the English defaults (presets merged
88
+ * too, key by key, as the provider merges them — so the parameter is a
89
+ * {@link LabelOverride}).
90
+ *
91
+ * DataTable's own, and FilterPopover's: public until 0.35, when it left the barrel for
92
+ * DataTable's `labels` and the provider's `dataTable`, which it serves (round 0.35 R1,
93
+ * refactor audit 2026-10 API-6a). It stays a resolver rather than `useKitLabels` for the
94
+ * two keys it derives (`columnsCount`, `filterColumn`), which a plain merge would not.
95
+ *
96
+ * Lenient on purpose — a table with no labels at all still renders — so a translated app
97
+ * finds out what fell back to English from `missingKitLabels(labels,
98
+ * DEFAULT_UI_KIT_LABELS)` in its own test, where the answer is actionable.
99
+ */
100
+ export function resolveDataTableLabels(
101
+ ...layers: (LabelOverride<DataTableLabels> | undefined)[]
102
+ ): DataTableLabels {
103
+ if (!layers.some(Boolean)) return DEFAULT_DATA_TABLE_LABELS;
104
+ // The layers in order, the provider's then the prop's, later winning key by key. An
105
+ // `undefined` key is skipped, as `useKitLabels` skips it, rather than blanking the word
106
+ // under it: DataTable spread `{ ...provider, ...prop }`, so `labels={{ autoSize:
107
+ // undefined }}` blanked the provider's word and the English one (round 0.35). `presets`
108
+ // is a record and is merged the same way, so renaming one keeps the other ten.
109
+ const partial: Partial<Omit<DataTableLabels, "presets">> = {};
110
+ const presets = { ...DEFAULT_DATA_TABLE_LABELS.presets };
111
+ for (const layer of layers) {
112
+ for (const [key, value] of Object.entries(layer ?? {})) {
113
+ if (value === undefined) continue;
114
+ if (key !== "presets") Object.assign(partial, { [key]: value });
115
+ else for (const [preset, text] of Object.entries(value)) if (text !== undefined) presets[preset] = text;
116
+ }
117
+ }
118
+ const merged: DataTableLabels = { ...DEFAULT_DATA_TABLE_LABELS, ...partial, presets };
119
+ // `columnsCount` was split out of `columns` + a template literal. Consumers have
120
+ // been translating `columns` since 0.1, and backfilling the English default over
121
+ // that would hand them "Columns (4/9)" back for having translated the key that
122
+ // existed at the time. So the count is derived from the RESOLVED `columns` unless the
123
+ // caller states the whole sentence, which is the escape hatch for a language where
124
+ // the parenthetical does not fit.
125
+ if (partial.columns !== undefined && partial.columnsCount === undefined) {
126
+ merged.columnsCount = (visible, total) => `${merged.columns} (${visible}/${total})`;
127
+ }
128
+ // The same for `filterColumn` (0.34), from a translated `filter`: the app's own word
129
+ // with the column, rather than English for having translated the key that existed.
130
+ if (partial.filter !== undefined && partial.filterColumn === undefined) {
131
+ merged.filterColumn = (column) => `${merged.filter}: ${column}`;
132
+ }
133
+ return merged;
134
+ }
135
+
83
136
  /**
84
137
  * Whether a click that reached `row`'s handler did not start in the row's own DOM.
85
138
  *
@@ -5,7 +5,15 @@ import { Spinner } from "./spinner";
5
5
  import { ROW_ACTIONS_KEY, useRowActionColumn } from "./data-table-row-actions";
6
6
  import { useColumnResize } from "./data-table-resize";
7
7
  import { useRangeSelection } from "./data-table-selection";
8
- import { RowLink, cleanAttrs, columnLabel, fromOutside, logicalAlign, splitCellProps } from "./data-table-shared";
8
+ import {
9
+ RowLink,
10
+ cleanAttrs,
11
+ columnLabel,
12
+ fromOutside,
13
+ logicalAlign,
14
+ resolveDataTableLabels,
15
+ splitCellProps,
16
+ } from "./data-table-shared";
9
17
  import { MobileFilters, MobileRow, MobileSort, StackedFields } from "./data-table-mobile";
10
18
  import { DataTableHead } from "./data-table-head";
11
19
  import { ColumnSettingsPanel } from "./data-table-column-settings";
@@ -20,7 +28,7 @@ import type { SwipeAction } from "./swipeable-row";
20
28
  import { FullBleedDialog } from "./full-bleed-dialog";
21
29
  import { usePhoneLayout } from "../hooks/use-breakpoint";
22
30
  import { useAnnounce } from "../hooks/use-announce";
23
- import { DEFAULT_DATA_TABLE_LABELS, resolveDataTableLabels, type DataTableLabels } from "./data-table-labels";
31
+ import { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from "./data-table-labels";
24
32
  import { useKitLabelOverrides, useKitLocale, useKitTableStorageKeyPrefix, type LabelOverride } from "../i18n/kit-labels";
25
33
  import { useEdgeFade } from "../lib/strip-fade";
26
34
  import { dirOf, type Direction } from "../lib/direction";
@@ -294,6 +302,16 @@ export interface DataTableProps<T> {
294
302
  onFiltersChange?: (next: FilterState) => void;
295
303
  sorts?: SortState[];
296
304
  onSortsChange?: (next: SortState[]) => void;
305
+ /**
306
+ * Controlled page size, as `sorts` above: the table pages by it and hands its pager's
307
+ * choice to `onPageSizeChange` (`Infinity` is "All"). For tables that page ALIKE —
308
+ * `TranslationReviewPanel`'s groups share one page size as they share one sort
309
+ * (0.35). Left out, the table keeps its own, from `defaultPageSize`, the URL and
310
+ * `storageKey`. A change of value sends a client-side table back to page 1. In
311
+ * `serverPagination` mode the page size is `serverPagination.pageSize`.
312
+ */
313
+ pageSize?: number;
314
+ onPageSizeChange?: (pageSize: number) => void;
297
315
  /**
298
316
  * Opt-in multi-row selection (desktop table only). When provided, a leading
299
317
  * checkbox column is rendered with a select-all box in the header; the owner
@@ -556,7 +574,7 @@ export interface DataTableProps<T> {
556
574
  * On a phone it heads the summary card, which is also named by it.
557
575
  *
558
576
  * There is no built-in default word: a new kit label key is a silent English string
559
- * in every translated app (see `missingDataTableLabels`), and the caller has the
577
+ * in every translated app (see `missingKitLabels`), and the caller has the
560
578
  * word in its own catalogue already. Without it the row is unlabelled on screen —
561
579
  * pass one.
562
580
  */
@@ -728,6 +746,8 @@ export function DataTable<T>({
728
746
  onFiltersChange,
729
747
  sorts: sortsProp,
730
748
  onSortsChange,
749
+ pageSize: pageSizeProp,
750
+ onPageSizeChange,
731
751
  selection,
732
752
  labels: labelsProp,
733
753
  locale: localeProp,
@@ -775,19 +795,7 @@ export function DataTable<T>({
775
795
  // prop to, `TranslationReviewPanel`'s among them.
776
796
  const providerStorageKeyPrefix = useKitTableStorageKeyPrefix();
777
797
  const storageKeyPrefix = storageKeyPrefixProp ?? providerStorageKeyPrefix ?? DEFAULT_PERSIST_PREFIX;
778
- const labels = useMemo(
779
- () =>
780
- resolveDataTableLabels(
781
- labelOverrides || labelsProp
782
- ? {
783
- ...labelOverrides,
784
- ...labelsProp,
785
- presets: { ...labelOverrides?.presets, ...labelsProp?.presets },
786
- }
787
- : undefined,
788
- ),
789
- [labelOverrides, labelsProp],
790
- );
798
+ const labels = useMemo(() => resolveDataTableLabels(labelOverrides, labelsProp), [labelOverrides, labelsProp]);
791
799
  const locale = useKitLocale(localeProp);
792
800
 
793
801
  // ---- Row actions ---- (data-table-row-actions.tsx)
@@ -830,6 +838,8 @@ export function DataTable<T>({
830
838
  defaultPageSize,
831
839
  sortsProp,
832
840
  filtersProp,
841
+ // Ignored in `serverPagination` mode, where the owner's is the page size.
842
+ pageSizeProp: isServer ? undefined : pageSizeProp,
833
843
  });
834
844
 
835
845
  // With the controls that change them switched off, persisted widths and hidden
@@ -1841,7 +1851,9 @@ export function DataTable<T>({
1841
1851
  isServer
1842
1852
  ? serverPagination!.onPageSizeChange
1843
1853
  : (n) => {
1844
- setPageSize(n);
1854
+ // To the owner when it holds the page size, as `commitSorts` does.
1855
+ if (onPageSizeChange) onPageSizeChange(n);
1856
+ else setPageSize(n);
1845
1857
  setPage(0);
1846
1858
  }
1847
1859
  }
@@ -23,7 +23,9 @@ export { DEFAULT_DIALOG_FRAME_LABELS };
23
23
  * (`children` is the body here, not the whole panel) and `title`, which is the dialog's
24
24
  * HEADING and a ReactNode rather than the browser's tooltip string — the collision
25
25
  * `PickerSheet` met first. Everything else, `size`, `draggable`, `fullBleed`,
26
- * `onKeyDown`, a `data-tour` anchor, still reaches the `Modal`.
26
+ * `onKeyDown`, a `data-tour` anchor, still reaches the `Modal` — and since 0.35 `ref`
27
+ * (round 0.35 R4): the panel, the `role="dialog"` element the frame's header, body and
28
+ * actions sit in.
27
29
  */
28
30
  export interface DialogFrameProps extends Omit<ModalProps, "labelledBy" | "children" | "title"> {
29
31
  /** The heading, and therefore the dialog's accessible name (`aria-labelledby`). */