@ahrowe/ui 0.30.0 → 0.32.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 (146) hide show
  1. package/dist/esm/common/actionButtons/actionButtons.mjs +1 -1
  2. package/dist/esm/common/actionButtons/actionButtons.mjs.map +1 -1
  3. package/dist/esm/common/alert/alert.mjs +1 -1
  4. package/dist/esm/common/alert/alert.mjs.map +1 -1
  5. package/dist/esm/common/alert/alert.types.mjs.map +1 -1
  6. package/dist/esm/common/breadcrumb/breadcrumb.mjs +1 -1
  7. package/dist/esm/common/breadcrumb/breadcrumb.mjs.map +1 -1
  8. package/dist/esm/common/carousel/carousel.mjs +1 -1
  9. package/dist/esm/common/carousel/carousel.mjs.map +1 -1
  10. package/dist/esm/common/chip/chip.mjs +1 -1
  11. package/dist/esm/common/chip/chip.mjs.map +1 -1
  12. package/dist/esm/common/chip/chip.types.mjs.map +1 -1
  13. package/dist/esm/common/colorPicker/colorPicker.mjs +1 -1
  14. package/dist/esm/common/colorPicker/colorPicker.mjs.map +1 -1
  15. package/dist/esm/common/configProvider/useLabels.mjs +2 -0
  16. package/dist/esm/common/configProvider/useLabels.mjs.map +1 -0
  17. package/dist/esm/common/confirmModal/confirmModal.mjs +1 -1
  18. package/dist/esm/common/confirmModal/confirmModal.mjs.map +1 -1
  19. package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.mjs.map +1 -1
  20. package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
  21. package/dist/esm/common/emptyState/emptyState.mjs +1 -1
  22. package/dist/esm/common/emptyState/emptyState.mjs.map +1 -1
  23. package/dist/esm/common/input/input.mjs +1 -1
  24. package/dist/esm/common/input/input.mjs.map +1 -1
  25. package/dist/esm/common/input/input.types.mjs.map +1 -1
  26. package/dist/esm/common/klipyPicker/klipyPicker.mjs +1 -1
  27. package/dist/esm/common/klipyPicker/klipyPicker.mjs.map +1 -1
  28. package/dist/esm/common/modal/modal.mjs +1 -1
  29. package/dist/esm/common/modal/modal.mjs.map +1 -1
  30. package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
  31. package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
  32. package/dist/esm/common/numberInput/numberInput.mjs +1 -1
  33. package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
  34. package/dist/esm/common/otpInput/otpInput.mjs +1 -1
  35. package/dist/esm/common/otpInput/otpInput.mjs.map +1 -1
  36. package/dist/esm/common/otpInput/otpInput.types.mjs.map +1 -1
  37. package/dist/esm/common/pagination/pagination.mjs +1 -1
  38. package/dist/esm/common/pagination/pagination.mjs.map +1 -1
  39. package/dist/esm/common/roomDrawer/roomDrawer.mjs +1 -1
  40. package/dist/esm/common/roomDrawer/roomDrawer.mjs.map +1 -1
  41. package/dist/esm/common/splitButton/splitButton.mjs +1 -1
  42. package/dist/esm/common/splitButton/splitButton.mjs.map +1 -1
  43. package/dist/esm/common/timer/timer.mjs +1 -1
  44. package/dist/esm/common/timer/timer.mjs.map +1 -1
  45. package/dist/esm/common/toast/toast.mjs +1 -1
  46. package/dist/esm/common/toast/toast.mjs.map +1 -1
  47. package/dist/esm/common/toast/toastProvider.mjs +1 -1
  48. package/dist/esm/common/toast/toastProvider.mjs.map +1 -1
  49. package/dist/esm/common/tree/tree.mjs +1 -1
  50. package/dist/esm/common/tree/tree.mjs.map +1 -1
  51. package/dist/esm/common/virtualList/useColumnResize.mjs +2 -0
  52. package/dist/esm/common/virtualList/useColumnResize.mjs.map +1 -0
  53. package/dist/esm/common/virtualList/useColumns.mjs +1 -1
  54. package/dist/esm/common/virtualList/useColumns.mjs.map +1 -1
  55. package/dist/esm/common/virtualList/useHeaderNavigation.mjs +2 -0
  56. package/dist/esm/common/virtualList/useHeaderNavigation.mjs.map +1 -0
  57. package/dist/esm/common/virtualList/useRowNavigation.mjs +2 -0
  58. package/dist/esm/common/virtualList/useRowNavigation.mjs.map +1 -0
  59. package/dist/esm/common/virtualList/useSorting.mjs +2 -0
  60. package/dist/esm/common/virtualList/useSorting.mjs.map +1 -0
  61. package/dist/esm/common/virtualList/useVirtualWindow.mjs +1 -1
  62. package/dist/esm/common/virtualList/useVirtualWindow.mjs.map +1 -1
  63. package/dist/esm/common/virtualList/virtualList.mjs +1 -1
  64. package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
  65. package/dist/esm/common/virtualList/virtualList.module.mjs +1 -1
  66. package/dist/esm/common/virtualList/virtualList.module.mjs.map +1 -1
  67. package/dist/esm/common/virtualList/virtualList.utils.mjs +1 -1
  68. package/dist/esm/common/virtualList/virtualList.utils.mjs.map +1 -1
  69. package/dist/esm/common/virtualList/virtualListHeader.mjs +2 -0
  70. package/dist/esm/common/virtualList/virtualListHeader.mjs.map +1 -0
  71. package/dist/esm/common/virtualList/virtualRow.mjs +1 -1
  72. package/dist/esm/common/virtualList/virtualRow.mjs.map +1 -1
  73. package/dist/esm/index.mjs +1 -1
  74. package/dist/index.cjs +3 -3
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/style.css +1 -1
  77. package/dist/types/common/actionButtons/actionButtons.types.d.ts +9 -2
  78. package/dist/types/common/alert/alert.types.d.ts +7 -0
  79. package/dist/types/common/breadcrumb/breadcrumb.types.d.ts +7 -0
  80. package/dist/types/common/carousel/carousel.types.d.ts +13 -0
  81. package/dist/types/common/chip/chip.types.d.ts +7 -1
  82. package/dist/types/common/colorPicker/colorPicker.d.ts +1 -1
  83. package/dist/types/common/colorPicker/colorPicker.types.d.ts +7 -0
  84. package/dist/types/common/configProvider/configProvider.types.d.ts +22 -1
  85. package/dist/types/common/configProvider/index.d.ts +1 -0
  86. package/dist/types/common/configProvider/useLabels.d.ts +22 -0
  87. package/dist/types/common/confirmModal/confirmModal.types.d.ts +9 -2
  88. package/dist/types/common/datePicker/components/datePickerMonth/datePickerMonth.d.ts +1 -13
  89. package/dist/types/common/datePicker/components/datePickerMonth/datePickerMonth.types.d.ts +20 -0
  90. package/dist/types/common/datePicker/datePicker.d.ts +1 -22
  91. package/dist/types/common/datePicker/datePicker.types.d.ts +51 -0
  92. package/dist/types/common/datePicker/index.d.ts +1 -0
  93. package/dist/types/common/emptyState/emptyState.d.ts +1 -1
  94. package/dist/types/common/emptyState/emptyState.types.d.ts +7 -0
  95. package/dist/types/common/input/input.types.d.ts +13 -0
  96. package/dist/types/common/klipyPicker/klipyPicker.d.ts +1 -1
  97. package/dist/types/common/klipyPicker/klipyPicker.types.d.ts +7 -0
  98. package/dist/types/common/modal/modal.types.d.ts +7 -0
  99. package/dist/types/common/multiSelect/multiSelect.types.d.ts +13 -6
  100. package/dist/types/common/numberInput/numberInput.types.d.ts +9 -2
  101. package/dist/types/common/otpInput/otpInput.types.d.ts +9 -0
  102. package/dist/types/common/pagination/pagination.types.d.ts +15 -0
  103. package/dist/types/common/roomDrawer/roomDrawer.types.d.ts +6 -0
  104. package/dist/types/common/splitButton/splitButton.types.d.ts +7 -0
  105. package/dist/types/common/timer/timer.types.d.ts +7 -0
  106. package/dist/types/common/toast/toast.d.ts +4 -2
  107. package/dist/types/common/toast/toast.types.d.ts +11 -1
  108. package/dist/types/common/toast/toastProvider.d.ts +1 -1
  109. package/dist/types/common/tree/tree.types.d.ts +7 -2
  110. package/dist/types/common/virtualList/useColumnResize.d.ts +59 -0
  111. package/dist/types/common/virtualList/useColumns.d.ts +12 -2
  112. package/dist/types/common/virtualList/useHeaderNavigation.d.ts +22 -0
  113. package/dist/types/common/virtualList/useRowNavigation.d.ts +50 -0
  114. package/dist/types/common/virtualList/useSorting.d.ts +26 -0
  115. package/dist/types/common/virtualList/useVirtualWindow.d.ts +1 -8
  116. package/dist/types/common/virtualList/virtualList.d.ts +1 -1
  117. package/dist/types/common/virtualList/virtualList.types.d.ts +91 -1
  118. package/dist/types/common/virtualList/virtualList.utils.d.ts +83 -1
  119. package/dist/types/common/virtualList/virtualListHeader.d.ts +35 -0
  120. package/dist/types/common/virtualList/virtualRow.d.ts +14 -1
  121. package/docs/ActionButtons.md +7 -6
  122. package/docs/Alert.md +2 -0
  123. package/docs/Breadcrumb.md +2 -0
  124. package/docs/Carousel.md +2 -0
  125. package/docs/Chip.md +3 -1
  126. package/docs/ColorPicker.md +2 -0
  127. package/docs/ConfigProvider.md +23 -0
  128. package/docs/ConfirmModal.md +7 -5
  129. package/docs/DatePicker.md +2 -1
  130. package/docs/EmptyState.md +2 -0
  131. package/docs/FormValidatorGroup.md +1 -1
  132. package/docs/Input.md +2 -0
  133. package/docs/KlipyPicker.md +2 -0
  134. package/docs/Modal.md +2 -0
  135. package/docs/MultiSelect.md +7 -5
  136. package/docs/NumberInput.md +4 -2
  137. package/docs/OtpInput.md +2 -0
  138. package/docs/Pagination.md +2 -0
  139. package/docs/RoomDrawer.md +2 -0
  140. package/docs/SplitButton.md +2 -0
  141. package/docs/Timer.md +2 -0
  142. package/docs/Toast.md +2 -0
  143. package/docs/Tree.md +3 -1
  144. package/docs/VirtualList.md +126 -7
  145. package/docs/Wizard.md +1 -2
  146. package/package.json +1 -1
@@ -106,3 +106,5 @@ function Example() {
106
106
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Breadcrumb: { separator: '/' } }}`. See [ConfigProvider.md](ConfigProvider.md).
107
107
 
108
108
  **Slots:** `root` `list` `item` `link` `icon` `separator` `current` `ellipsis` `hiddenList` `hiddenItem`
109
+
110
+ **Labels:** `showHidden` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `BreadcrumbLabels`.
package/docs/Carousel.md CHANGED
@@ -103,3 +103,5 @@ Slides announce themselves as the panel each dot controls, or as "slide 2 of 5"
103
103
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Carousel: { autoPlay: true } }}`. See [ConfigProvider.md](ConfigProvider.md).
104
104
 
105
105
  **Slots:** `root` `track` `slide` `prevButton` `nextButton` `dots` `dot`
106
+
107
+ **Labels:** `previous` `next` `slides` `goToSlide(slide)` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `CarouselLabels`.
package/docs/Chip.md CHANGED
@@ -56,7 +56,7 @@ import { Chip, ChipContainer, ChipStyleType } from '@ahrowe/ui';
56
56
  | `text` | `string` | Chip label |
57
57
  | `styleType` | `ChipStyleType` | Colour variant (default `Default`) |
58
58
  | `thumbnail` | `string` | Avatar image URL |
59
- | `removeLabel` | `string` | Accessible name for the remove button. Default: `'Remove'` |
59
+ | `labels.remove` | `string` | Accessible name for the remove button. Default: `'Remove'` |
60
60
  | `showRemoveIcon` | `boolean` | Show × icon |
61
61
  | `onRemove` | `() => void` | Remove button handler |
62
62
  | `onClick` | `() => void` | Makes chip clickable |
@@ -66,3 +66,5 @@ import { Chip, ChipContainer, ChipStyleType } from '@ahrowe/ui';
66
66
  | Prop | Type | Description |
67
67
  |------|------|-------------|
68
68
  | `inline` | `boolean` | Inline layout (default wraps) |
69
+
70
+ **Labels:** `remove` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `ChipLabels`.
@@ -50,3 +50,5 @@ Parsing uses the browser's own colour engine, which means it needs a DOM with a
50
50
  The inline swatch tracks the theme's text size, since it sits beside a label: `--swatch-size` falls back to `2em`, i.e. 32px at a 16px base, and the gap, the hex overlay and the transparency checkerboard scale with it.
51
51
 
52
52
  The picker panel deliberately does not. It is a floating control surface with its own fixed layout — a saturation area, sliders and a hex field — and it holds together at a large base font rather than needing to grow with it.
53
+
54
+ **Labels:** `pickFromScreen` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `ColorPickerLabels`.
@@ -114,3 +114,26 @@ Each entry is a `Partial<...Props>`, so any of that component's props can be def
114
114
  `Textarea`, `NumberInput`, and `DatePicker` are built on top of `Input`, so they **also** inherit `Input` defaults for the props they forward to it. Their own component-keyed defaults take precedence for props they handle themselves. Example: with `Input: { alwaysFloatLabel: true }` set, every `Textarea` / `NumberInput` / `DatePicker` floats its label too, without needing a separate entry.
115
115
 
116
116
  The `ComponentDefaults` type is the source of truth for which keys are currently supported — more components adopt it over time.
117
+
118
+ ## Translating the components
119
+
120
+ Every string a component renders of its own is set through its `labels` prop, and a `ConfigProvider` sets them for the whole app at once:
121
+
122
+ ```tsx
123
+ <ConfigProvider
124
+ defaultProps={{
125
+ Alert: { labels: { dismiss: 'Schließen' } },
126
+ Modal: { labels: { close: 'Schließen' } },
127
+ Pagination: { labels: { goToPage: (page) => `Zu Seite ${page}` } },
128
+ VirtualList: { labels: { selectRow: 'Zeile auswählen', selectAllRows: 'Alle Zeilen auswählen' } },
129
+ }}
130
+ >
131
+ <App />
132
+ </ConfigProvider>
133
+ ```
134
+
135
+ **Labels merge per key**, unlike every other default here. An instance that overrides one string keeps the provider's translations for the rest, so `<Alert labels={{ dismiss: 'Weg damit' }} />` under the provider above does not lose anything else Alert might have been given. Anything the component does not produce itself, a `title`, a column `label`, a button's children, is already yours and never appears in `labels`.
136
+
137
+ `ColorPicker` and `VirtualList` appear in `ComponentDefaults` with their labels only: the first is configured elsewhere, the second has generic props that a per-key default map cannot express. `Toast` is not there at all, because `ToastProvider` is already its app-wide configuration and carries `labels` itself.
138
+
139
+ Each component's doc lists its own keys under **Labels:**.
@@ -38,9 +38,9 @@ const [isOpen, setIsOpen] = useState(false);
38
38
  | `content` | `ReactNode` | Dialog body |
39
39
  | `onConfirm` | `() => void \| Promise<void>` | Called when user confirms |
40
40
  | `onClose` | `(didSubmit: boolean) => void` | Called on both confirm and cancel |
41
- | `submitLabel` | `string` | Confirm button label (default `'Confirm'`) |
41
+ | `labels.submit` | `string` | Confirm button label (default `'Confirm'`) |
42
42
  | `isDelete` | `boolean` | Render the confirm button with the destructive (delete) style |
43
- | `actionButtonsProps` | `ActionButtonsProps` | Overrides passed through to the underlying `ActionButtons` (wins over `submitLabel` / `isDelete`) |
43
+ | `actionButtonsProps` | `ActionButtonsProps` | Overrides passed through to the underlying `ActionButtons` (wins over `labels.submit` / `isDelete`) |
44
44
  | `children` | `ReactNode` | Alternative to `content` prop |
45
45
 
46
46
  **Destructive action:**
@@ -51,16 +51,18 @@ const [isOpen, setIsOpen] = useState(false);
51
51
  title="Delete item?"
52
52
  content="This action cannot be undone."
53
53
  isDelete
54
- submitLabel="Delete"
54
+ labels={{ submit: 'Delete' }}
55
55
  onConfirm={async () => { await deleteItem(); }}
56
56
  onClose={() => setIsOpen(false)}
57
57
  />
58
58
  ```
59
59
 
60
- **Global defaults:** `ConfirmModal` supports `ConfigProvider` — set a default `submitLabel` (or any other prop) app-wide:
60
+ **Global defaults:** `ConfirmModal` supports `ConfigProvider` — set a default `labels.submit` (or any other prop) app-wide:
61
61
 
62
62
  ```tsx
63
- <ConfigProvider defaultProps={{ ConfirmModal: { submitLabel: 'OK' } }}>
63
+ <ConfigProvider defaultProps={{ ConfirmModal: { labels: { submit: 'OK' } } }}>
64
64
  <App />
65
65
  </ConfigProvider>
66
66
  ```
67
+
68
+ **Labels:** `submit` `cancel` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `ConfirmModalLabels`.
@@ -6,7 +6,8 @@
6
6
 
7
7
  **Keyboard:** the day grid uses a roving tab stop (the selected day, or today, or the 1st — one Tab reaches it, not every day individually); arrow keys move by day (`←`/`→`) or by week (`↑`/`↓`), crossing into the adjacent month automatically when they run past the edge, and Enter/Space selects the focused day.
8
8
 
9
- **Import:** `import { DatePicker } from '@ahrowe/ui'`
9
+ **Import:** `import { DatePicker } from '@ahrowe/ui'`
10
+ **Types:** `import type { DatePickerProps } from '@ahrowe/ui'`
10
11
 
11
12
  ```tsx
12
13
  import { DatePicker, FormValidator } from '@ahrowe/ui';
@@ -28,3 +28,5 @@ import { faInbox } from '@fortawesome/free-solid-svg-icons';
28
28
  | `icon` | `IconDefinition \| ReactElement` | Icon shown above the message — FontAwesome icon or any React element |
29
29
  | `children` | `ReactNode` | Message body (defaults to `"No entries available"`) |
30
30
  | `className` | `string` | Root element class |
31
+
32
+ **Labels:** `empty` — the text this component renders of its own. Pass `labels` to override it, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `EmptyStateLabels`.
@@ -38,7 +38,7 @@ function ProfileForm({ user, onSaved }) {
38
38
  <Input label="Name" formValidator={form.group.name} />
39
39
  <Input label="Email" type={InputType.Email} formValidator={form.group.email} />
40
40
  <ActionButtons
41
- submitLabel="Save"
41
+ labels={{ submit: 'Save' }}
42
42
  onSubmit={form.submit}
43
43
  isSubmitButtonLoading={form.isSubmitting}
44
44
  isSubmitButtonDisabled={!form.isDirty}
package/docs/Input.md CHANGED
@@ -125,6 +125,8 @@ The stored value is the formatted one, spaces included, so strip them before a l
125
125
 
126
126
  **Slots:** `label` `container` `input` `suffix` `upperRightLabel` `fieldset`
127
127
 
128
+ **Labels:** `clear` `clearSearch` `showPassword` `hidePassword` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `InputLabels`.
129
+
128
130
  ## Browser autofill
129
131
 
130
132
  `autoComplete` is the plain HTML attribute and is passed straight through — use `'one-time-code'`, `'new-password'`, `'street-address'` and the rest as you would on a bare `<input>`. Input does **not** set it: autofill and password managers are usability and accessibility features, and a plain field has no suggestion list of its own for them to collide with.
@@ -41,3 +41,5 @@ import { KlipyPicker } from '@ahrowe/ui';
41
41
  | `selected` | `string \| null` | Currently selected GIF id (for visual highlight) |
42
42
  | `onSelect` | `(preview: KlipyResult \| null) => void \| Promise<void>` | Fires when a GIF is picked; `null` when cleared |
43
43
  | `className` | `string` | Root element class |
44
+
45
+ **Labels:** `search` — the text this component renders of its own. Pass `labels` to override it, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `KlipyPickerLabels`.
package/docs/Modal.md CHANGED
@@ -71,3 +71,5 @@ A sheet-presented Modal takes 90% of the screen height rather than the 70% a men
71
71
  The panel also takes the full native `div` attribute set (`id`, `role`, `aria-*`, `data-*`, …). Pass `aria-label` when using `hideHeader`, since there is then no title to label the dialog with.
72
72
 
73
73
  **Slots:** `container` `backdrop` `modal` `header` `title` `closeButton` `body`
74
+
75
+ **Labels:** `close` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `ModalLabels`.
@@ -28,10 +28,10 @@ const [value, setValue] = useState<MultiSelectValue[]>([]);
28
28
  <MultiSelect items={items} defaultValue={['apple']} onChange={(value) => console.log(value)} />
29
29
 
30
30
  // Searchable, for a list too long to scan
31
- <MultiSelect items={countries} searchable searchPlaceholder="Filter countries…" />
31
+ <MultiSelect items={countries} searchable labels={{ searchPlaceholder: 'Filter countries…' }} />
32
32
 
33
33
  // Tag entry: type a value that isn't in `items` and pick the "add" row
34
- <MultiSelect items={commonTags} allowCustomValues createLabel={(value) => `Neu: ${value}`} />
34
+ <MultiSelect items={commonTags} allowCustomValues labels={{ create: (value) => `Neu: ${value }}`} />
35
35
 
36
36
  // Cap the selection — unselected options disable once the cap is reached
37
37
  <MultiSelect items={items} maxSelected={3} />
@@ -72,9 +72,9 @@ import { Presentation } from '@ahrowe/ui';
72
72
  | `placeholder` | `string` | Shown while nothing is selected. With a `label`, it fades in only after the label has finished floating clear |
73
73
  | `searchable` | `boolean` | Show a search box in the panel (default `false`) |
74
74
  | `allowCustomValues` | `boolean` | Let the user add a value not in `items`. Implies `searchable` |
75
- | `createLabel` | `(value: string) => string` | Label for the add row (default `Add "…"`) |
76
- | `searchPlaceholder` | `string` | Placeholder for that box (default `'Search…'`) |
77
- | `emptyLabel` | `string` | Shown when nothing matches (default `'No matches'`) |
75
+ | `labels.create` | `(value: string) => string` | Label for the add row (default `Add "…"`) |
76
+ | `labels.searchPlaceholder` | `string` | Placeholder for that box (default `'Search…'`) |
77
+ | `labels.empty` | `string` | Shown when nothing matches (default `'No matches'`) |
78
78
  | `maxSelected` | `number` | Cap the selection; unselected options disable at the cap |
79
79
  | `clearable` | `boolean` | Show the clear-all button (default `true`) |
80
80
  | `disabled` | `boolean` | Dims the field and blocks interaction |
@@ -98,3 +98,5 @@ import { Presentation } from '@ahrowe/ui';
98
98
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ MultiSelect: { searchable: true } }}`. See [ConfigProvider.md](ConfigProvider.md).
99
99
 
100
100
  **Slots:** `root` `label` `fieldset` `control` `values` `chip` `placeholder` `clearButton` `icon` `panel` `search` `options` `option` `optionCheck` `optionLabel` `empty`
101
+
102
+ **Labels:** `clearSelection` `create(value)` `searchPlaceholder` `empty` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `MultiSelectLabels`.
@@ -74,8 +74,8 @@ const priceValidator = new FormValidator<number | undefined>(undefined, [Validat
74
74
  | `min` | `number` | Lower bound — see the note below |
75
75
  | `max` | `number` | Upper bound — see the note below |
76
76
  | `arrowsLayout` | `'auto' \| 'stacked' \| 'split'` | Stepper layout. Default: `'auto'` |
77
- | `increaseLabel` | `string` | Accessible name for the increase button. Default: `'Increase'` |
78
- | `decreaseLabel` | `string` | Accessible name for the decrease button. Default: `'Decrease'` |
77
+ | `labels.increase` | `string` | Accessible name for the increase button. Default: `'Increase'` |
78
+ | `labels.decrease` | `string` | Accessible name for the decrease button. Default: `'Decrease'` |
79
79
 
80
80
  **Steppers (`showArrows`):** two layouts, picked by `arrowsLayout`:
81
81
 
@@ -124,3 +124,5 @@ const qty = new FormValidator<number | undefined>(undefined, [inRange(10, 100)])
124
124
  ```
125
125
 
126
126
  **Mobile keyboard layout:** shows the numeric on-screen keyboard automatically (`inputMode="numeric"` when `decimalScale={0}`, `"decimal"` otherwise, so a decimal separator key is available whenever fractional values are allowed). Pass `inputMode` directly to override.
127
+
128
+ **Labels:** `increase` `decrease` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `NumberInputLabels`.
package/docs/OtpInput.md CHANGED
@@ -91,6 +91,8 @@ const codeValidator = new FormValidator('', [Validators.required(), Validators.m
91
91
 
92
92
  **Slots:** `root` `label` `inputs` `input` `separator`
93
93
 
94
+ **Labels:** `field` `digit(position, length)` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `OtpInputLabels`.
95
+
94
96
  ## Sizing
95
97
 
96
98
  The code boxes, the digit inside them, the gaps and the separator all scale with the theme's text, so the field stays in proportion with its label.
@@ -70,3 +70,5 @@ const [page, setPage] = useState(1);
70
70
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Pagination: { showEdges: true } }}`. See [ConfigProvider.md](ConfigProvider.md).
71
71
 
72
72
  **Slots:** `root` `item` `control` `ellipsis`
73
+
74
+ **Labels:** `goToPage(page)` `first` `previous` `next` `last` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `PaginationLabels`.
@@ -373,3 +373,5 @@ The previous `RoomDrawer` had no data contract at all: `RoomDrawerProps` was `{
373
373
  - Removing a corner was a double-click, which was undiscoverable and impossible on touch. Select it and press `Delete` instead.
374
374
 
375
375
  **Slots:** `root` `toolbar` `toolButton` `toolIcon` `viewControls` `viewButton` `zoomLabel` `canvasWrapper` `canvas` `grid` `rooms` `roomFill` `roomLabel` `walls` `wallBody` `openings` `opening` `overlay` `measureLine` `floorBar` `floorTab` `inspector` `inspectorLabel` `inspectorField` `hint`
376
+
377
+ **Labels:** every string the drawer's own chrome renders, including `tools`, `storeyHeightHint` and `sillHeightHint`. Type: `RoomDrawerLabels`.
@@ -79,3 +79,5 @@ import { ButtonStyleType } from '@ahrowe/ui';
79
79
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ SplitButton: { styleType: ButtonStyleType.Default } }}`. See [ConfigProvider.md](ConfigProvider.md).
80
80
 
81
81
  **Slots:** `root` `primaryButton` `triggerButton` `menu` `menuItem`
82
+
83
+ **Labels:** `moreActions` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `SplitButtonLabels`.
package/docs/Timer.md CHANGED
@@ -140,6 +140,8 @@ function Example() {
140
140
 
141
141
  **Slots:** `root` `ringWrapper` `ringSvg` `ringTrack` `ringProgress` `ripples` `ripple` `display` `time` `label` `controls` `addButton` `removeButton` `pauseButton` `cancelButton`
142
142
 
143
+ **Labels:** `cancel` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `TimerLabels`.
144
+
143
145
  ## Sizing
144
146
 
145
147
  The ring follows the theme's text size, because the readout inside it does: `--timer-size` falls back to `10em`, i.e. 160px at a 16px base, and the gaps and padding around it are `em` too. The `size` prop still wins where it is given.
package/docs/Toast.md CHANGED
@@ -75,6 +75,8 @@ showToast('Done', {
75
75
 
76
76
  **Slots:** `root` `icon` `content` `closeButton` `progressBar`
77
77
 
78
+ **Labels:** `close` — the dismiss button's screen-reader name. Set it on `ToastProvider` for the whole app (`labels={{ close: 'Schließen' }}`), or per toast through the `showToast` options. Type: `ToastLabels`.
79
+
78
80
  ## Sizing
79
81
 
80
82
  A toast is sized and spaced by its font: the padding, the gap, the body copy, the icon, the close button, the variant accent bar and the progress bar all scale with the theme's text, as does the gap between stacked toasts.
package/docs/Tree.md CHANGED
@@ -62,7 +62,7 @@ import { faWarehouse } from '@fortawesome/free-solid-svg-icons';
62
62
  | `toggleIcon` | `IconDefinition \| ReactElement` | Toggle icon, rotated 90° when open (default a right chevron) |
63
63
  | `height` | `number \| string` | Windows the rows through `VirtualList` at this viewport height |
64
64
  | `estimatedRowHeight` | `number` | Row height for the windowed renderer (default `32`) |
65
- | `emptyLabel` | `string` | Shown when `nodes` is empty (default `'Nothing here'`) |
65
+ | `labels.empty` | `string` | Shown when `nodes` is empty (default `'Nothing here'`) |
66
66
  | `disabled` | `boolean` | Dims the tree and blocks interaction |
67
67
  | `aria-label` | `string` | Accessible name for the tree (default `'Tree'`) |
68
68
 
@@ -93,3 +93,5 @@ Two things change in that mode. Rows must be a uniform height, so size them with
93
93
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Tree: { indent: 30 } }}`. See [ConfigProvider.md](ConfigProvider.md).
94
94
 
95
95
  **Slots:** `root` `node` `toggle` `icon` `label` `empty`
96
+
97
+ **Labels:** `empty` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `TreeLabels`.
@@ -1,11 +1,11 @@
1
1
  # VirtualList
2
2
 
3
- **When to use:** Efficiently render large lists or tables — thousands of rows rendered with a virtualised scroll window so only visible rows are in the DOM. Supports column definitions, row selection, multi-select, column visibility toggling, and infinite scroll.
3
+ **When to use:** Efficiently render large lists or tables — thousands of rows rendered with a virtualised scroll window so only visible rows are in the DOM. Supports column definitions, row selection, multi-select, column sorting, column resizing, column visibility toggling, and infinite scroll.
4
4
 
5
- **Keywords:** data table, virtualized table
5
+ **Keywords:** data table, virtualized table, sort, sorting, order by, ascending, descending, column header click, resize, resizable, drag column edge, column width, autofit
6
6
 
7
7
  **Import:** `import { VirtualList } from '@ahrowe/ui'`
8
- **Types:** `import type { VirtualListProps, VirtualListColumn, VirtualListHandle } from '@ahrowe/ui'`
8
+ **Types:** `import type { VirtualListProps, VirtualListColumn, VirtualListHandle, VirtualListSort } from '@ahrowe/ui'`
9
9
 
10
10
  **Requires:** `<div id="bodyEnd"></div>` in your app **only if** you use the column-visibility toggle (the gear menu) — its popover is a `FloatingMenu` that renders via portal. Lists without columns, or with `showColumnToggle={false}`, don't need it.
11
11
 
@@ -96,6 +96,11 @@ const columns: VirtualListColumn<User>[] = [
96
96
  | `toggleLabel` | `ReactNode` | Text shown for this column in the gear menu, overriding `label`. Use for columns with an empty header (e.g. an actions column) so their menu entry isn't blank |
97
97
  | `lockVisible` | `boolean` | Column can't be hidden — always visible (overrides `defaultHidden` and any controlled/persisted state), shown checked and disabled in the gear menu |
98
98
  | `hideFromToggle` | `boolean` | Omit the column from the gear menu entirely (no entry). Visibility otherwise follows `defaultHidden`/controlled/persisted state; combine with `lockVisible` to keep it always visible and out of the menu. The gear button hides automatically when no column is left to toggle |
99
+ | `sortValue` | `(item) => string \| number \| boolean \| Date \| null \| undefined` | Makes the column sortable **and** lets the list sort `items` itself by this value |
100
+ | `compare` | `(a, b) => number` | Ascending comparator for an order `sortValue` can't express. Also makes the column sortable, and wins over `sortValue` |
101
+ | `sortable` | `boolean` | Makes the header clickable without the list reordering anything: the sort is only reported through `onSortChange`. For server-side / paginated sorting. Ignored when `sortValue` or `compare` is set |
102
+ | `minWidth` | `number` | Floor this column can be dragged to, in px (default `48`) |
103
+ | `resizable` | `boolean` | Set `false` to opt this column out while the list has `resizableColumns` |
99
104
 
100
105
  ```tsx
101
106
  // An actions column: no header, always visible, labelled "Actions" in the gear menu
@@ -120,7 +125,7 @@ const columns: VirtualListColumn<User>[] = [
120
125
  | `renderRow` | `(item, index) => ReactNode` | Row render fn — list mode (no columns) |
121
126
  | `columns` | `VirtualListColumn<T>[]` | Column definitions — enables table/header mode |
122
127
  | `height` | `number \| string` | Scroll viewport height (default `'100%'`) |
123
- | `estimatedRowHeight` | `number` | Estimated row height before measurement (default `40`) |
128
+ | `estimatedRowHeight` | `number` | Row height assumed before anything has been measured (default `40`). Once rows have been measured, their average is used for the ones that haven't, so this only has to be roughly right |
124
129
  | `overscan` | `number` | Extra rows rendered outside the viewport (default `3`) |
125
130
  | `rowGap` | `number \| string` | Gap between rows — number = px; string = any CSS length (`'1em'`, `'var(--spacing-s)'`). Default `0` |
126
131
  | `rowPadding` | `number \| string` | Padding on every row — number = px; string = any CSS padding value (`'8px 12px'`, `'var(--spacing-s)'`) |
@@ -128,10 +133,17 @@ const columns: VirtualListColumn<User>[] = [
128
133
  | `rowHover` | `boolean` | Highlight rows on hover (default `true`). Set `false` when the row content brings its own hover affordance (e.g. a `Card` per row) so the two don't fight |
129
134
  | `getItemKey` | `(item, index) => string \| number` | Stable key per item |
130
135
  | `selectedKey` | `string \| number \| null` | Controlled single-select key |
131
- | `onRowClick` | `(item, index) => void` | Row click handler |
136
+ | `onRowClick` | `(item, index) => void` | Row click handler, also fired by `Enter` on the active row |
137
+ | `keyboardNavigation` | `boolean` | Arrow-key navigation over the rows (default `true`). See **Keyboard** |
132
138
  | `multiSelect` | `boolean` | Enable checkbox multi-select column |
133
139
  | `selectedKeys` | `Set<string \| number>` | Controlled multi-select keys |
134
140
  | `onSelectionChange` | `(keys) => void` | Multi-select change callback |
141
+ | `sort` | `VirtualListSort \| null` | Controlled sort. `null` is source order |
142
+ | `defaultSort` | `VirtualListSort \| null` | Initial sort, uncontrolled (default `null`) |
143
+ | `onSortChange` | `(sort) => void` | Fires on every header click with the next step of the cycle |
144
+ | `resizableColumns` | `boolean` | Let the user drag column edges to resize (see **Column resizing**) |
145
+ | `columnWidths` | `Record<string, number>` | Controlled column widths in px, keyed by column key |
146
+ | `onColumnWidthsChange` | `(widths) => void` | Called with the full width map after every resize or auto-fit |
135
147
  | `showColumnToggle` | `boolean` | Show gear button to show/hide columns (default `true` when columns present) |
136
148
  | `visibleColumnKeys` | `string[]` | Controlled visible column keys |
137
149
  | `onVisibleColumnsChange` | `(keys) => void` | Column visibility change callback |
@@ -143,6 +155,108 @@ const columns: VirtualListColumn<User>[] = [
143
155
 
144
156
  Drag-and-drop reordering adds `reorderable`, `treeReorder`, `dragHandle`, and their callbacks — see **Row reordering** below.
145
157
 
158
+ **Sorting:** a column becomes sortable as soon as it carries `sortValue`, `compare` or `sortable`. Its header turns into a button that cycles ascending, descending, unsorted, and carries `aria-sort` for screen readers.
159
+
160
+ Who reorders the data is the column's decision:
161
+
162
+ - **`sortValue` / `compare`**: the list sorts `items` itself. For data you hold in full, in memory.
163
+ - **`sortable`**: the list only reports the click through `onSortChange` and never touches `items`. Use this whenever the rows are paginated or fetched per sort (`onLoadMore`), where sorting the rows loaded so far would silently order part of the data.
164
+
165
+ ```tsx
166
+ // Sorted by the list
167
+ const columns: VirtualListColumn<User>[] = [
168
+ { key: 'name', label: 'Name', width: { type: 'flex' }, renderCell: (u) => u.name, sortValue: (u) => u.name },
169
+ { key: 'joined', label: 'Joined', width: { type: 'fit' }, renderCell: (u) => u.joined, sortValue: (u) => new Date(u.joined) },
170
+ ];
171
+
172
+ <VirtualList items={users} columns={columns} defaultSort={{ key: 'name', direction: 'asc' }} height={500} />
173
+
174
+ // Sorted by the server: the header only reports, the query does the work
175
+ const serverColumns: VirtualListColumn<User>[] = [
176
+ { key: 'name', label: 'Name', width: { type: 'flex' }, renderCell: (u) => u.name, sortable: true },
177
+ ];
178
+
179
+ const [sort, setSort] = useState<VirtualListSort | null>(null);
180
+
181
+ <VirtualList
182
+ items={page}
183
+ columns={serverColumns}
184
+ sort={sort}
185
+ onSortChange={(next) => { setSort(next); refetch({ sort: next }); }}
186
+ height={500}
187
+ />
188
+ ```
189
+
190
+ `null` and `undefined` (and `NaN`) `sortValue`s always sort last, in both directions, so empty cells stay at the bottom of the table. Strings are compared with a locale collator using numeric ordering, so `Order 2` comes before `Order 10`, and case and accents don't split otherwise-equal values. Rows that compare equal keep their original order. A `compare` function is used as given and negated for descending, so the empty-values rule doesn't apply to it.
191
+
192
+ **Sorting and row reordering are mutually exclusive.** While a sort is active, `reorderable` and `treeReorder` are switched off (with a console warning), because a drop position would be overwritten by the next sort pass. Clear the sort to drag rows again.
193
+
194
+ **Column resizing:** set `resizableColumns` and every column gets a grab strip on its trailing edge.
195
+
196
+ - **Drag** it to resize. The column follows the pointer exactly, and `Escape` mid-drag cancels back to the width it started at.
197
+ - **Double-click** it to fit the column to its widest cell. Only rendered rows can be measured, so this fits what is on screen, not the whole data set.
198
+ - **Keyboard**: `Alt` plus the arrow keys on the focused header cell, 10px a press (`Alt+Shift` for 1px). `Alt+Home` goes to the column's minimum, `Alt+End` auto-fits. The strip itself is a mouse affordance and is not a tab stop.
199
+ - **Undo it**: once anything has been resized, the gear menu grows a **Reset column widths** entry, and the same thing is on the imperative handle as `resetColumnWidths()`. Both also clear the saved widths.
200
+
201
+ Its text, like every other string the table renders of its own, is set through `labels` (see below): `labels={{ resetColumnWidths: 'Spaltenbreiten zurücksetzen' }}`.
202
+
203
+ ```tsx
204
+ <VirtualList
205
+ items={rows}
206
+ columns={columns}
207
+ resizableColumns
208
+ persistColumnsKey="invoices" // widths are saved with the column visibility
209
+ height={500}
210
+ />
211
+ ```
212
+
213
+ **The first resize freezes the layout.** Every visible column is pinned to the width it already has, so nothing moves at that moment, and from then on the table is exact pixels rather than `flex`/`fit` tracks. This is what makes a drag follow the cursor one to one: with `fr` tracks the other columns would absorb the difference, and the dragged edge would stop moving the moment they hit their minimum. The table may then be wider than its container, in which case it scrolls horizontally.
214
+
215
+ Two consequences worth knowing:
216
+
217
+ - A `fit` column stops re-measuring once the layout is frozen. Its width is the user's from then on, which is the point: measurement only ever grows a column, so it would undo every drag that made one narrower.
218
+ - A column revealed from the gear menu after the freeze starts at 160px, since there is nothing measured to go on. Double-click its handle to fit it.
219
+
220
+ Widths ride along with `persistColumnsKey`, in the same `localStorage` entry as the visible columns. Entries saved before this existed still load. For full control, drive `columnWidths` and `onColumnWidthsChange` yourself: as with any controlled prop, a width the consumer doesn't accept snaps back once the drag ends.
221
+
222
+ Resizing is a pointer and keyboard interaction. There is no touch gesture for it, because a horizontal drag at a row edge belongs to scrolling; double-click auto-fit works everywhere.
223
+
224
+ **Keyboard:** the table takes **two tab stops** in total, whatever its size: the rows, then the header. (The rows come first because the element carrying their stop is the header's ancestor, and a focusable ancestor always precedes its descendants.)
225
+
226
+ Rows, with the list focused:
227
+
228
+ | Key | Does |
229
+ |-----|------|
230
+ | `↑` `↓` | Move the active row, scrolling it into view only if it isn't already |
231
+ | `Home` `End` | First or last row |
232
+ | `PageUp` `PageDown` | A viewport at a time |
233
+ | `Enter` | Fire `onRowClick` for the active row, or step into it when the list has no `onRowClick` |
234
+ | `Space` | Toggle the active row's selection (`multiSelect` only) |
235
+ | `F2` | Step into the active row: focus its first control |
236
+ | `Escape` | Step back out of a row to the list, making that row the active one |
237
+
238
+ The list keeps the focus and points at the active row with `aria-activedescendant`, rather than moving focus onto the row: a virtualized row unmounts when it scrolls out of the window, which would strand the focus. For the same reason the per-row checkboxes are not tab stops.
239
+
240
+ Keys are ignored while the focus is inside a cell, so an input, a `Dropdown` or a button in a row keeps its own. `F2` and `Escape` are the way in and back out; `Escape` is left alone when the cell's own content already handled it, so closing a `Dropdown` does not also leave the row.
241
+
242
+ **Controls inside rows are not tab stops.** The list holds them at `tabindex="-1"` and puts back the ones of the row being used, so `Tab` walks that row and then leaves the list. Without that, every control in every *rendered* row is a stop, the set changes as the list scrolls, and tabbing through a long list drops out of it at an arbitrary row, because the next one isn't in the DOM yet when the browser looks for the next stop. Entering a row happens with `F2`, with `Enter` when the list has no `onRowClick`, or simply by clicking a control. A `tabindex` you set yourself is restored exactly as it was, and an element you already parked at `-1` stays parked.
243
+
244
+ The row being used also stays mounted while it holds the focus, even once it scrolls out of the window, so scrolling can't drop the focus to the document body. Set `keyboardNavigation={false}` for a list whose rows bring their own key handling, as [Tree](Tree.md) does.
245
+
246
+ Because `role="list"` is not a composite role, the active row is only announced in table mode. In list mode, pass `ariaRoles={{ container: 'listbox', row: 'option' }}` (or another composite pair) to have it announced there too.
247
+
248
+ The header is the second stop, and one stop however many columns it has.
249
+
250
+ | Key | Does |
251
+ |-----|------|
252
+ | `←` `→` | Move between the select-all box, the column headers and the column menu |
253
+ | `Home` `End` | Jump to the first or last of those |
254
+ | `Enter` `Space` | Sort the focused column, or toggle select-all on its cell |
255
+ | `Alt` + `←` `→` | Resize the focused column by 10px (`Alt+Shift` by 1px) |
256
+ | `Alt` + `Home` `End` | Take the focused column to its minimum width, or fit it to its content |
257
+
258
+ Focus sits on the header cell rather than on the sort button inside it, so every key a column understands is in one place and a column costs one stop instead of three.
259
+
146
260
  **Imperative handle (`ref`):**
147
261
 
148
262
  Pass a `ref` typed as `VirtualListHandle` to control the list imperatively:
@@ -159,12 +273,15 @@ const listRef = useRef<VirtualListHandle>(null);
159
273
  listRef.current?.scrollToKey(user.id, { align: 'center' });
160
274
  ```
161
275
 
276
+ **Reach for `scrollToKey` unless you actually mean a position.** A key survives sorting, filtering and reloading, because it belongs to the row rather than to where the row happens to sit. `scrollToIndex` is for the cases where there is no row yet: a jump box, "row 500", a scrollbar of your own. Its index is zero-based and counts the **rendered** order, so on a sorted list it is not the index in your `items` array.
277
+
162
278
  | Method | Signature | Description |
163
279
  |--------|-----------|-------------|
164
- | `scrollToIndex` | `(index, options?) => void` | Scroll so the row at `index` is visible. `options`: `{ align?: 'start' \| 'center' \| 'end'; behavior?: ScrollBehavior }` |
280
+ | `scrollToIndex` | `(index, options?) => void` | Scroll so the row at `index` is visible. `options`: `{ align?: 'start' \| 'center' \| 'end'; behavior?: ScrollBehavior }`. A jump into rows that have never been rendered re-aims itself as their real heights are measured, so it lands on the row you asked for rather than on estimated arithmetic. Input of your own during the jump cancels it |
165
281
  | `scrollToKey` | `(key, options?) => void` | Scroll to the row whose `getItemKey` matches `key` (no-op if not found) |
166
282
  | `scrollToTop` | `(options?) => void` | Scroll back to the top |
167
283
  | `getScrollElement` | `() => HTMLDivElement \| null` | The underlying scroll container, for advanced use |
284
+ | `resetColumnWidths` | `() => void` | Drop every user-set column width, returning the table to the widths its columns declare, and clear them from `persistColumnsKey` storage |
168
285
  | `recalculateColumns` | `() => void` | Re-measure `fit` columns on the next layout. Only needed after an out-of-band content change that doesn't flow through a new `items` array or `columns` set — those re-measure automatically, as does content first appearing in a cell |
169
286
 
170
287
  **Row reordering:** two modes, both drag-and-drop, both mouse and touch. Set exactly one:
@@ -246,6 +363,8 @@ interface TreeDropTarget {
246
363
 
247
364
  This needs the list to have a **bounded height** (the default `height="100%"` inside a sized parent, or an explicit `height`). With a height that grows with its content, there is no empty space to detect and only the scroll trigger applies. Nothing fires while `isLoading` is set, and only one load runs at a time.
248
365
 
249
- **Slots:** `root` `header` `headerCell` `headerToggle` `togglePopover` `toggleItem` `body` `row` `cell` `selectCell` `loadingIndicator` `dropIndicator` `columnChip`
366
+ **Slots:** `root` `header` `headerCell` `headerSort` `sortIcon` `resizeHandle` `headerToggle` `togglePopover` `toggleItem` `toggleReset` `body` `row` `cell` `selectCell` `activeRow` `loadingIndicator` `dropIndicator` `columnChip`
367
+
368
+ **Labels:** `selectRow` `selectAllRows` `toggleColumns` `columnsHidden(count)` `resetColumnWidths` — the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `VirtualListLabels`.
250
369
 
251
370
  **In-row popovers:** Rows support inline popover content that overflows the row's bottom edge — e.g. a `Dropdown`, `InputDropdown`, `Tooltip`, or `FloatingMenu` whose list opens below its trigger. All four render their popover through a `BodyEnd` portal and track their trigger's position directly, so they always escape row/cell clipping and paint above the rows below, regardless of focus — no special CSS is needed on the row or cell for this. Cells still clip their own content by default (ellipsis / neighbour-spill guard); that's unaffected. This works in both list mode (`renderRow`) and table mode (`columns`). Stop the row's click handler from firing when interacting with the control (`onClick={(e) => e.stopPropagation()}` on the cell) if the row is also selectable.
package/docs/Wizard.md CHANGED
@@ -26,8 +26,7 @@ const [step, setStep] = useState(0);
26
26
  />
27
27
 
28
28
  <ActionButtons
29
- submitLabel={step < STEPS.length - 1 ? 'Next' : 'Finish'}
30
- cancelLabel={step > 0 ? 'Back' : 'Cancel'}
29
+ labels={{ submit: step < STEPS.length - 1 ? 'Next' : 'Finish', cancel: step > 0 ? 'Back' : 'Cancel' }}
31
30
  onSubmit={() => step < STEPS.length - 1 ? setStep(step + 1) : handleFinish()}
32
31
  onClose={() => step > 0 ? setStep(step - 1) : handleCancel()}
33
32
  />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/ui",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "A React UI component library with theming, CSS Modules, and TypeScript support",
5
5
  "keywords": [
6
6
  "components",