@ahrowe/ui 0.51.0 → 0.53.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 (106) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -0
  3. package/dist/cjs-types/common/actionIcon/actionIcon.types.d.ts +10 -6
  4. package/dist/cjs-types/common/ahroweProvider/ahroweProvider.d.ts +1 -1
  5. package/dist/cjs-types/common/button/button.types.d.ts +25 -8
  6. package/dist/cjs-types/common/configProvider/configProvider.d.ts +2 -1
  7. package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +8 -0
  8. package/dist/cjs-types/common/copyButton/copyButton.types.d.ts +4 -1
  9. package/dist/cjs-types/common/listItem/index.d.ts +2 -0
  10. package/dist/cjs-types/common/listItem/listItem.d.ts +4 -0
  11. package/dist/cjs-types/common/listItem/listItem.types.d.ts +33 -0
  12. package/dist/cjs-types/common/menu/menu.types.d.ts +2 -2
  13. package/dist/cjs-types/common/menu/menuItem.d.ts +5 -2
  14. package/dist/cjs-types/common/otpInput/otpInput.types.d.ts +1 -1
  15. package/dist/cjs-types/common/themeProvider/theme.types.d.ts +4 -0
  16. package/dist/cjs-types/common/utils/navigation.d.ts +12 -0
  17. package/dist/cjs-types/index.d.ts +3 -1
  18. package/dist/cjs-types/services/formValidation/index.d.ts +1 -1
  19. package/dist/cjs-types/services/formValidation/useFormValidatorGroup.d.ts +1 -1
  20. package/dist/esm/common/ahroweProvider/ahroweProvider.mjs +1 -1
  21. package/dist/esm/common/ahroweProvider/ahroweProvider.mjs.map +1 -1
  22. package/dist/esm/common/breadcrumb/breadcrumb.mjs +1 -1
  23. package/dist/esm/common/breadcrumb/breadcrumb.mjs.map +1 -1
  24. package/dist/esm/common/button/button.mjs +1 -1
  25. package/dist/esm/common/button/button.mjs.map +1 -1
  26. package/dist/esm/common/button/button.module.mjs +1 -1
  27. package/dist/esm/common/button/button.module.mjs.map +1 -1
  28. package/dist/esm/common/button/button.types.mjs.map +1 -1
  29. package/dist/esm/common/commandPalette/commandPalette.mjs +1 -1
  30. package/dist/esm/common/commandPalette/commandPalette.mjs.map +1 -1
  31. package/dist/esm/common/configProvider/configProvider.mjs +1 -1
  32. package/dist/esm/common/configProvider/configProvider.mjs.map +1 -1
  33. package/dist/esm/common/datePicker/datePicker.mjs +1 -1
  34. package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
  35. package/dist/esm/common/dropdown/dropdown.mjs +1 -1
  36. package/dist/esm/common/dropdown/dropdown.mjs.map +1 -1
  37. package/dist/esm/common/iconPicker/iconPicker.mjs +1 -1
  38. package/dist/esm/common/iconPicker/iconPicker.mjs.map +1 -1
  39. package/dist/esm/common/input/input.mjs +1 -1
  40. package/dist/esm/common/input/input.mjs.map +1 -1
  41. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  42. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  43. package/dist/esm/common/listItem/listItem.mjs +2 -0
  44. package/dist/esm/common/listItem/listItem.mjs.map +1 -0
  45. package/dist/esm/common/listItem/listItem.module.mjs +2 -0
  46. package/dist/esm/common/listItem/listItem.module.mjs.map +1 -0
  47. package/dist/esm/common/menu/menu.mjs +1 -1
  48. package/dist/esm/common/menu/menu.mjs.map +1 -1
  49. package/dist/esm/common/menu/menu.types.mjs.map +1 -1
  50. package/dist/esm/common/menu/menuItem.mjs +1 -1
  51. package/dist/esm/common/menu/menuItem.mjs.map +1 -1
  52. package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
  53. package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
  54. package/dist/esm/common/numberInput/numberInput.mjs +1 -1
  55. package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
  56. package/dist/esm/common/otpInput/otpInput.mjs +1 -1
  57. package/dist/esm/common/otpInput/otpInput.mjs.map +1 -1
  58. package/dist/esm/common/otpInput/otpInput.types.mjs.map +1 -1
  59. package/dist/esm/common/radioGroup/radioGroup.mjs +1 -1
  60. package/dist/esm/common/radioGroup/radioGroup.mjs.map +1 -1
  61. package/dist/esm/common/timeInput/timeInput.mjs +1 -1
  62. package/dist/esm/common/timeInput/timeInput.mjs.map +1 -1
  63. package/dist/esm/common/utils/navigation.mjs +2 -0
  64. package/dist/esm/common/utils/navigation.mjs.map +1 -0
  65. package/dist/esm/index.mjs +1 -1
  66. package/dist/esm/services/formValidation/useFormValidatorGroup.mjs +1 -1
  67. package/dist/esm/services/formValidation/useFormValidatorGroup.mjs.map +1 -1
  68. package/dist/esm/services/formValidation/validatableComponent.mjs +1 -1
  69. package/dist/esm/services/formValidation/validatableComponent.mjs.map +1 -1
  70. package/dist/index.cjs +5 -5
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/style.css +1 -1
  73. package/dist/types/common/actionIcon/actionIcon.types.d.ts +10 -6
  74. package/dist/types/common/ahroweProvider/ahroweProvider.d.ts +1 -1
  75. package/dist/types/common/button/button.types.d.ts +25 -8
  76. package/dist/types/common/configProvider/configProvider.d.ts +2 -1
  77. package/dist/types/common/configProvider/configProvider.types.d.ts +8 -0
  78. package/dist/types/common/copyButton/copyButton.types.d.ts +4 -1
  79. package/dist/types/common/listItem/index.d.ts +2 -0
  80. package/dist/types/common/listItem/listItem.d.ts +4 -0
  81. package/dist/types/common/listItem/listItem.types.d.ts +33 -0
  82. package/dist/types/common/menu/menu.types.d.ts +2 -2
  83. package/dist/types/common/menu/menuItem.d.ts +5 -2
  84. package/dist/types/common/otpInput/otpInput.types.d.ts +1 -1
  85. package/dist/types/common/themeProvider/theme.types.d.ts +4 -0
  86. package/dist/types/common/utils/navigation.d.ts +12 -0
  87. package/dist/types/index.d.ts +3 -1
  88. package/dist/types/services/formValidation/index.d.ts +1 -1
  89. package/dist/types/services/formValidation/useFormValidatorGroup.d.ts +1 -1
  90. package/docs/ActionIcon.md +7 -3
  91. package/docs/AhroweProvider.md +2 -1
  92. package/docs/Breadcrumb.md +4 -24
  93. package/docs/Button.md +44 -9
  94. package/docs/CLAUDE.md +4 -0
  95. package/docs/CommandPalette.md +1 -1
  96. package/docs/ConfigProvider.md +34 -1
  97. package/docs/FormValidator.md +23 -1
  98. package/docs/FormValidatorGroup.md +5 -2
  99. package/docs/InputDropdown.md +1 -1
  100. package/docs/ListItem.md +97 -0
  101. package/docs/Menu.md +2 -2
  102. package/docs/MultiSelect.md +1 -1
  103. package/docs/OtpInput.md +1 -1
  104. package/docs/RadioGroup.md +1 -1
  105. package/docs/ScrollbarProvider.md +1 -1
  106. package/package.json +2 -2
@@ -64,7 +64,7 @@ interface InputDropdownItem {
64
64
  | `placeholder` | `string` | |
65
65
  | `readOnly` | `boolean` | Make the field read-only — typing is blocked, the list won't open, and keyboard navigation/selection is disabled. Default `false` |
66
66
  | `labels` | `InputDropdownLabels` | Text the component renders of its own, see **Labels** below |
67
- | `formValidator` | `FormValidator` | Connects to validation — shows the error state/message once the field has been touched (blurred), and the required mark (`*`) when the validator has a `required` validator |
67
+ | `formValidator` | `FormValidator` | Connects to validation — shows the error state/message once the validator's `shouldShowError` is true (by default once the field has been blurred, see FormValidator.md), and the required mark (`*`) when the validator has a `required` validator |
68
68
  | `error` | `string` | Manual error message (used when no `formValidator` is given) |
69
69
  | `isRequired` | `boolean` | Shows the required mark (`*`) next to the label. Also inferred automatically when `formValidator` has a `required` validator. Default `false` |
70
70
 
@@ -0,0 +1,97 @@
1
+ # ListItem
2
+
3
+ **When to use:** One row of a list that stands for a record: a customer, an invoice, a delivery note. It shows a title, a second line and a status, can hold actions of its own, and can open the record from the whole row. For hundreds of rows use [VirtualList](VirtualList.md); for actions hanging off a trigger, [Menu](Menu.md).
4
+
5
+ **Keywords:** row, list row, cell, entry, record, item, drill-down, detail link, settings row
6
+
7
+ **Import:** `import { ListItem } from '@ahrowe/ui'`
8
+ **Types:** `import type { ListItemProps } from '@ahrowe/ui'`
9
+
10
+ ```tsx
11
+ import { ActionIcon, Chip, ListItem, ThemeColor } from '@ahrowe/ui';
12
+ import { faFileInvoice, faPen } from '@fortawesome/free-solid-svg-icons';
13
+
14
+ // Shows a record
15
+ <ListItem title="Created" endContent="12 Oct 2026" />
16
+
17
+ // Opens it: the whole row is the link, with a chevron
18
+ <ListItem
19
+ icon={faFileInvoice}
20
+ title="Invoice 2026-0042"
21
+ subtitle="Müller GmbH, due 1 Nov"
22
+ endContent={<Chip text="Open" color={ThemeColor.Warn} />}
23
+ href="/invoices/42"
24
+ />
25
+
26
+ // Opens it and has an action: the edit button stays its own
27
+ <ListItem
28
+ title="Müller GmbH"
29
+ subtitle="3 projects"
30
+ href="/clients/7"
31
+ endContent={<ActionIcon icon={faPen} aria-label="Edit" onClick={editClient} />}
32
+ />
33
+
34
+ // Opens it from a button instead of a link: no chevron, which promises a page
35
+ <ListItem title="Add a contact" onClick={openContactModal} />
36
+
37
+ // The one that is open, beside its detail view
38
+ <ListItem title="Müller GmbH" href="/clients/7" selected={clientId === 7} />
39
+ ```
40
+
41
+ ## Opening a record
42
+
43
+ With `href` the title is a link, and with `onClick` a button. Either one stretches over the whole
44
+ row, so a click anywhere on it opens the record, and with a link a middle click and a long press
45
+ too. Controls in the row stay their own: a button, a link, an input or anything with a `tabindex`
46
+ sits above the stretched area. Text around them still opens the row, so a click on a status `Chip`
47
+ opens the record.
48
+
49
+ A plain click on a page of the app goes through `ConfigProvider`'s `navigate` when it is set, see
50
+ [ConfigProvider.md](ConfigProvider.md). A screen reader hears the title as the link's name and the
51
+ subtitle, and a text `endContent`, as its description, rather than the whole row run together.
52
+
53
+ Because the link covers the row, its text cannot be selected with the mouse. A row that is only
54
+ read, with neither `href` nor `onClick`, selects as usual.
55
+
56
+ The rows sit in whatever holds them, a `Card` or a `<ul>` of your own. ListItem draws no dividers
57
+ and no list semantics of its own: it is a `<div>`, so in a `<ul>` give each one an `<li>`.
58
+
59
+ **Dividers.** Between groups of rows, a [Divider](Divider.md): it has no margin, so it adds the line
60
+ and nothing else. Between every row, a border from the container instead, because a screen reader
61
+ announces each `Divider` as a separator. The same rule works on `<div>`s and on `<li>`s:
62
+
63
+ ```css
64
+ .clients > * + * {
65
+ border-top: 1px solid var(--border-color);
66
+ }
67
+ ```
68
+
69
+ **Key props:**
70
+
71
+ | Prop | Type | Description |
72
+ |------|------|-------------|
73
+ | `title` | `ReactNode` | The main line, cut off with an ellipsis where it does not fit (required) |
74
+ | `subtitle` | `ReactNode` | A smaller line under it, cut off after two lines |
75
+ | `icon` | `IconDefinition \| ReactElement` | Before the title: a FontAwesome icon or any element, such as an `Avatar` |
76
+ | `endContent` | `ReactNode` | The trailing edge: a status, a date, an amount, and controls of its own |
77
+ | `href` | `string` | Opens the record from the whole row, as a link. `target`, `rel` and `download` apply to it |
78
+ | `onClick` | `MouseEventHandler<HTMLElement>` | Opens the record from the whole row, as a button. With `href` too, runs first, and `preventDefault()` keeps the link from being followed |
79
+ | `selected` | `boolean` | The row whose record is open: tinted in `--list-item-selected-background`, and `aria-current="page"` on its link |
80
+ | `chevron` | `boolean` | The chevron at the end. On by default for a row with an `href`, off for one with only `onClick`, which opens a dialog or runs an action rather than going somewhere |
81
+
82
+ Every native attribute passes through to the root `<div>`. A long title shrinks before
83
+ `endContent` and the chevron do, so they never leave the row. `endContent` itself never shrinks:
84
+ a value longer than a date or an amount belongs in the `subtitle`.
85
+
86
+ **Theming:**
87
+
88
+ | Variable | Default |
89
+ |----------|---------|
90
+ | `--list-item-min-height` | `48px`, the smallest comfortable touch target |
91
+ | `--list-item-selected-background` | a 12% tint of `--primary-color` |
92
+
93
+ Set it once in the theme rather than on each row: `'--list-item-min-height': '64px'`. Hover and
94
+ press tint the row in its own text colour, so they show on any surface; the keyboard focus ring
95
+ goes around the whole row, inside its edge, in `--focus-border-color`.
96
+
97
+ **Slots:** `root` `icon` `labels` `trigger` (the link or button, which draws the hover and focus ring in its `::after`) `title` `subtitle` `endContent` `chevron`
package/docs/Menu.md CHANGED
@@ -54,7 +54,7 @@ useHotkeys(menuHotkeys(items, run));
54
54
  useHotkeys(menuHotkeys(items, run), { enabled: isOpen });
55
55
  ```
56
56
 
57
- It runs each entry the way clicking it does, its own `onClick` then `onAction` then its `href`, and leaves out a `disabled` entry and any entry with no `shortcut`. Bind them yourself instead if the actions already live in a keymap of your own; the menu does not mind either way.
57
+ It runs each entry the way clicking it does, its own `onClick` then `onAction` then its `href`, and leaves out a `disabled` entry and any entry with no `shortcut`. It runs outside React, so it cannot reach `ConfigProvider`'s `navigate`: an `href` run from a shortcut loads the page. Bind them yourself instead if the actions already live in a keymap of your own; the menu does not mind either way.
58
58
 
59
59
  An action runs its own `onClick` first, then `onAction` on the menu, then the menu closes. Use whichever of the two fits: a handler per entry when they are unrelated, `onAction` when they funnel into one place.
60
60
 
@@ -120,7 +120,7 @@ Separators and headings are never focused. The trigger is given `aria-haspopup`,
120
120
  | `shortcut` | `string` | A combo in `useHotkeys` spelling, e.g. `'mod+k'`. Shown on the trailing edge, written for the platform. Binding it is yours, see `menuHotkeys` |
121
121
  | `disabled` | `boolean` | Not clickable, and skipped by the arrow keys |
122
122
  | `danger` | `boolean` | Renders in the destructive colour |
123
- | `href` | `string` | Makes the entry a real `<a>`, so Ctrl/⌘-click and a middle click open it in a new tab. A click runs `onClick` and `onAction` first, a middle click neither. The browser loads the page, so with a client-side router navigate in `onClick` instead. A `disabled` entry stays a button |
123
+ | `href` | `string` | Makes the entry a real `<a>`, so Ctrl/⌘-click and a middle click open it in a new tab. A click runs `onClick` and `onAction` first, a middle click neither, then follows the link through `ConfigProvider`'s `navigate` when it is set, see [ConfigProvider.md](ConfigProvider.md). A `disabled` entry stays a button |
124
124
  | `onClick` | `() => void` | Runs before `onAction` |
125
125
 
126
126
  **Slots:** `root` `trigger` `menu` `item` `itemIcon` `itemLabel` `itemShortcut` `separator` `heading`
@@ -49,7 +49,7 @@ import { Presentation } from '@ahrowe/ui';
49
49
 
50
50
  **The label floats like `Input`'s.** It rests vertically centred over the control and rises into a notch cut out of the border once the field is focused, open, or has a selection, using the same `fieldset`/`legend` construction and the same `--input-notch-height` geometry as `Input`. A `MultiSelect` sitting next to an `Input` in a form animates with it rather than against it. Set `alwaysFloatLabel` to pin it up, which is what you want when a `placeholder` should always be readable: with a label present the placeholder is otherwise faded in on a delay matching the label's animation, so the two never sit on top of each other. Without a label there is nothing to wait for and it fades in at once.
51
51
 
52
- **Errors:** with a `formValidator` the field stays quiet until it has been touched (the first time the panel closes), then shows the validator's message while the field is hovered or focused, and drops it as soon as the validator is happy again. Without a validator, `isValid` and `errorMessage` are plain props and nothing clears them for you: derive them from your own state, as in `isValid={picked.length > 0}`.
52
+ **Errors:** with a `formValidator` the field stays quiet until the validator's `showErrors` lets the error through (by default once touched: the first time the panel closes, see FormValidator.md), then shows the validator's message while the field is hovered or focused, and drops it as soon as the validator is happy again. Without a validator, `isValid` and `errorMessage` are plain props and nothing clears them for you: derive them from your own state, as in `isValid={picked.length > 0}`.
53
53
 
54
54
  **Height:** a MultiSelect with one row of chips is exactly as tall as an `Input` or `Dropdown` beside it, so a form row lines up. It grows only when the chips wrap onto a second row. The chips render at `0.8em` to fit: a standalone `Chip` is nearly as tall as the whole field, because its remove button carries `ActionIcon`'s padding around a `1em` icon.
55
55
 
package/docs/OtpInput.md CHANGED
@@ -60,7 +60,7 @@ const codeValidator = new FormValidator('', [Validators.required(), Validators.m
60
60
 
61
61
  **Interaction:** typing a character fills the focused box and jumps to the next one; on the last box, it blurs instead. Backspace on a filled box just clears it; on an already-empty box it clears and focuses the previous one. Left/Right arrow keys move focus without changing values. Pasting (or an SMS autofill suggestion landing in one box) distributes the pasted text across boxes starting from wherever it lands. Focusing a box selects its content, so typing immediately overwrites it rather than requiring a manual clear first.
62
62
 
63
- **Error display:** like `Input`/`RadioGroup`, the boxes get a red border once errored, and the message itself appears in an error `Tooltip` on focus or hover rather than sitting inline under the boxes all the time. With a `formValidator`, this only shows once the field has been *touched* (focus has left the group) **and** has an error — so a required-field error doesn't flash before the user has even had a chance to type. With manual `isValid`/`errorMessage`, there's no touched-gating (the consumer already controls when to set `isValid={false}`, e.g. only after a failed verification call). Either way, the error persists across renders until whatever's driving it — `isValid`, or the `formValidator`'s own error state — actually changes; `OtpInput` never silently clears it on its own. See `clearOnError` below for the one built-in exception.
63
+ **Error display:** like `Input`/`RadioGroup`, the boxes get a red border once errored, and the message itself appears in an error `Tooltip` on focus or hover rather than sitting inline under the boxes all the time. With a `formValidator`, this shows once the validator's `shouldShowError` is true: by default once the field has been *touched* (focus has left the group) **and** has an error, so a required-field error doesn't flash before the user has even had a chance to type (`showErrors`, see FormValidator.md). With manual `isValid`/`errorMessage`, there's no touched-gating (the consumer already controls when to set `isValid={false}`, e.g. only after a failed verification call). Either way, the error persists across renders until whatever's driving it — `isValid`, or the `formValidator`'s own error state — actually changes; `OtpInput` never silently clears it on its own. See `clearOnError` below for the one built-in exception.
64
64
 
65
65
  **`onComplete`:** fires once per genuinely new completion — i.e. the code going from fewer than `length` characters to exactly `length`. Editing individual boxes of an already-complete code (retyping a digit that was wrong) does **not** refire it on every keystroke, since the code never drops below `length` in between; it fires again only after the code is cleared (manually, or via `clearOnError` below) and refilled.
66
66
 
@@ -71,7 +71,7 @@ interface RadioOption {
71
71
  | `errorMessage` | `string` | Manual error message (when not using a `formValidator`) |
72
72
  | `isValid` | `boolean` | Manual valid state (default `true`) |
73
73
 
74
- **Validation:** like `Input`, an errored group shows an error `Tooltip` and turns its unselected circles red. With a `formValidator` the error surfaces only after the field is *touched* (focus leaves the group), then on focus or hover.
74
+ **Validation:** like `Input`, an errored group shows an error `Tooltip` and turns its unselected circles red. With a `formValidator` the error surfaces once the validator's `showErrors` allows it (by default after the field is *touched*: focus leaves the group, see FormValidator.md), then on focus or hover.
75
75
 
76
76
  **Global defaults:** adopts `ConfigProvider` — e.g. `defaultProps={{ RadioGroup: { orientation: RadioGroupOrientation.Horizontal } }}`. See [ConfigProvider.md](ConfigProvider.md).
77
77
 
@@ -10,7 +10,7 @@
10
10
  import { ScrollbarProvider } from '@ahrowe/ui';
11
11
 
12
12
  <ScrollbarProvider className="my-list">
13
- {items.map(item => <ListItem key={item.id} item={item} />)}
13
+ {items.map(item => <Row key={item.id} item={item} />)}
14
14
  </ScrollbarProvider>
15
15
  ```
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/ui",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "A React UI component library with theming, CSS Modules, and TypeScript support",
5
5
  "keywords": [
6
6
  "agent",
@@ -106,7 +106,7 @@
106
106
  "prepare": "husky"
107
107
  },
108
108
  "dependencies": {
109
- "@ahrowe/form-validation": "^0.3.0",
109
+ "@ahrowe/form-validation": "^0.4.0",
110
110
  "@dnd-kit/core": "^6.3.1",
111
111
  "@dnd-kit/sortable": "^10.0.0",
112
112
  "@dnd-kit/utilities": "^3.2.2",