@ahrowe/ui 0.36.0 → 0.38.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 (176) hide show
  1. package/README.md +6 -2
  2. package/dist/cjs-types/common/commandPalette/commandPalette.d.ts +4 -0
  3. package/dist/cjs-types/common/commandPalette/commandPalette.types.d.ts +42 -0
  4. package/dist/cjs-types/common/commandPalette/filterCommands.d.ts +15 -0
  5. package/dist/cjs-types/common/commandPalette/index.d.ts +2 -0
  6. package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +6 -0
  7. package/dist/cjs-types/common/copyButton/copyButton.d.ts +4 -0
  8. package/dist/cjs-types/common/copyButton/copyButton.types.d.ts +36 -0
  9. package/dist/cjs-types/common/copyButton/index.d.ts +2 -0
  10. package/dist/cjs-types/common/errorBoundary/errorBoundary.d.ts +0 -3
  11. package/dist/cjs-types/common/errorBoundary/errorBoundary.types.d.ts +2 -3
  12. package/dist/cjs-types/common/hooks/index.d.ts +15 -0
  13. package/dist/cjs-types/common/hooks/useClipboard.d.ts +16 -0
  14. package/dist/cjs-types/common/hooks/useHotkeys.d.ts +45 -0
  15. package/dist/cjs-types/common/hooks/useSwipeDismiss.d.ts +6 -2
  16. package/dist/cjs-types/common/inputDropdown/inputDropdown.types.d.ts +7 -1
  17. package/dist/cjs-types/common/loading/index.d.ts +0 -1
  18. package/dist/cjs-types/common/menu/index.d.ts +1 -0
  19. package/dist/cjs-types/common/menu/menu.types.d.ts +11 -3
  20. package/dist/cjs-types/common/menu/menuHotkeys.d.ts +20 -0
  21. package/dist/cjs-types/common/popover/usePopoverPosition.d.ts +1 -0
  22. package/dist/cjs-types/common/sticky/sticky.d.ts +1 -1
  23. package/dist/cjs-types/common/sticky/sticky.types.d.ts +15 -0
  24. package/dist/cjs-types/common/sticky/stickyStack.d.ts +5 -0
  25. package/dist/cjs-types/common/themeProvider/themeProvider.d.ts +8 -7
  26. package/dist/cjs-types/common/utils/scrollAncestors.d.ts +10 -0
  27. package/dist/cjs-types/common/virtualList/scroller.d.ts +33 -0
  28. package/dist/cjs-types/common/virtualList/useRowDrag.d.ts +6 -1
  29. package/dist/cjs-types/common/virtualList/useRowNavigation.d.ts +11 -1
  30. package/dist/cjs-types/common/virtualList/useVirtualWindow.d.ts +22 -3
  31. package/dist/cjs-types/common/virtualList/virtualList.d.ts +1 -1
  32. package/dist/cjs-types/common/virtualList/virtualList.types.d.ts +43 -3
  33. package/dist/cjs-types/common/weatherIcon/index.d.ts +3 -0
  34. package/dist/cjs-types/common/weatherIcon/parts.d.ts +26 -0
  35. package/dist/cjs-types/common/weatherIcon/weatherIcon.d.ts +4 -0
  36. package/dist/cjs-types/common/weatherIcon/weatherIcon.types.d.ts +44 -0
  37. package/dist/cjs-types/index.d.ts +7 -0
  38. package/dist/esm/common/buttonGroup/buttonGroup.module.mjs.map +1 -1
  39. package/dist/esm/common/commandPalette/commandPalette.mjs +2 -0
  40. package/dist/esm/common/commandPalette/commandPalette.mjs.map +1 -0
  41. package/dist/esm/common/commandPalette/commandPalette.module.mjs +2 -0
  42. package/dist/esm/common/commandPalette/commandPalette.module.mjs.map +1 -0
  43. package/dist/esm/common/commandPalette/filterCommands.mjs +2 -0
  44. package/dist/esm/common/commandPalette/filterCommands.mjs.map +1 -0
  45. package/dist/esm/common/copyButton/copyButton.mjs +2 -0
  46. package/dist/esm/common/copyButton/copyButton.mjs.map +1 -0
  47. package/dist/esm/common/copyButton/copyButton.module.mjs +2 -0
  48. package/dist/esm/common/copyButton/copyButton.module.mjs.map +1 -0
  49. package/dist/esm/common/dropdown/dropdown.mjs +1 -1
  50. package/dist/esm/common/dropdown/dropdown.module.mjs.map +1 -1
  51. package/dist/esm/common/errorBoundary/errorBoundary.mjs +2 -2
  52. package/dist/esm/common/errorBoundary/errorBoundary.mjs.map +1 -1
  53. package/dist/esm/common/errorBoundary/errorBoundary.module.mjs +1 -1
  54. package/dist/esm/common/errorBoundary/errorBoundary.module.mjs.map +1 -1
  55. package/dist/esm/common/hooks/useClipboard.mjs +2 -0
  56. package/dist/esm/common/hooks/useClipboard.mjs.map +1 -0
  57. package/dist/esm/common/hooks/useHotkeys.mjs +2 -0
  58. package/dist/esm/common/hooks/useHotkeys.mjs.map +1 -0
  59. package/dist/esm/common/hooks/useSwipeDismiss.mjs +1 -1
  60. package/dist/esm/common/hooks/useSwipeDismiss.mjs.map +1 -1
  61. package/dist/esm/common/input/input.mjs +1 -1
  62. package/dist/esm/common/input/input.mjs.map +1 -1
  63. package/dist/esm/common/input/input.module.mjs +1 -1
  64. package/dist/esm/common/input/input.module.mjs.map +1 -1
  65. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  66. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  67. package/dist/esm/common/inputDropdown/inputDropdown.module.mjs +1 -1
  68. package/dist/esm/common/inputDropdown/inputDropdown.module.mjs.map +1 -1
  69. package/dist/esm/common/menu/menu.mjs +1 -1
  70. package/dist/esm/common/menu/menu.mjs.map +1 -1
  71. package/dist/esm/common/menu/menu.types.mjs.map +1 -1
  72. package/dist/esm/common/menu/menuHotkeys.mjs +2 -0
  73. package/dist/esm/common/menu/menuHotkeys.mjs.map +1 -0
  74. package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
  75. package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
  76. package/dist/esm/common/multiSelect/multiSelect.module.mjs +1 -1
  77. package/dist/esm/common/multiSelect/multiSelect.module.mjs.map +1 -1
  78. package/dist/esm/common/otpInput/otpInput.module.mjs.map +1 -1
  79. package/dist/esm/common/popover/usePopoverPosition.mjs +1 -1
  80. package/dist/esm/common/popover/usePopoverPosition.mjs.map +1 -1
  81. package/dist/esm/common/sticky/sticky.mjs +1 -1
  82. package/dist/esm/common/sticky/sticky.mjs.map +1 -1
  83. package/dist/esm/common/sticky/stickyStack.mjs +1 -1
  84. package/dist/esm/common/sticky/stickyStack.mjs.map +1 -1
  85. package/dist/esm/common/themeProvider/themeProvider.mjs +1 -1
  86. package/dist/esm/common/themeProvider/themeProvider.mjs.map +1 -1
  87. package/dist/esm/common/timer/timer.mjs +1 -1
  88. package/dist/esm/common/timer/timer.mjs.map +1 -1
  89. package/dist/esm/common/utils/scrollAncestors.mjs +1 -1
  90. package/dist/esm/common/utils/scrollAncestors.mjs.map +1 -1
  91. package/dist/esm/common/virtualList/scroller.mjs +2 -0
  92. package/dist/esm/common/virtualList/scroller.mjs.map +1 -0
  93. package/dist/esm/common/virtualList/useRowDrag.mjs +1 -1
  94. package/dist/esm/common/virtualList/useRowDrag.mjs.map +1 -1
  95. package/dist/esm/common/virtualList/useRowNavigation.mjs +1 -1
  96. package/dist/esm/common/virtualList/useRowNavigation.mjs.map +1 -1
  97. package/dist/esm/common/virtualList/useVirtualWindow.mjs +1 -1
  98. package/dist/esm/common/virtualList/useVirtualWindow.mjs.map +1 -1
  99. package/dist/esm/common/virtualList/virtualList.mjs +1 -1
  100. package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
  101. package/dist/esm/common/virtualList/virtualList.module.mjs +1 -1
  102. package/dist/esm/common/virtualList/virtualList.module.mjs.map +1 -1
  103. package/dist/esm/common/weatherIcon/parts.mjs +2 -0
  104. package/dist/esm/common/weatherIcon/parts.mjs.map +1 -0
  105. package/dist/esm/common/weatherIcon/weatherIcon.mjs +2 -0
  106. package/dist/esm/common/weatherIcon/weatherIcon.mjs.map +1 -0
  107. package/dist/esm/common/weatherIcon/weatherIcon.module.mjs +2 -0
  108. package/dist/esm/common/weatherIcon/weatherIcon.module.mjs.map +1 -0
  109. package/dist/esm/common/weatherIcon/weatherIcon.types.mjs +2 -0
  110. package/dist/esm/common/weatherIcon/weatherIcon.types.mjs.map +1 -0
  111. package/dist/esm/index.mjs +1 -1
  112. package/dist/index.cjs +3 -3
  113. package/dist/index.cjs.map +1 -1
  114. package/dist/style.css +1 -1
  115. package/dist/types/common/commandPalette/commandPalette.d.ts +4 -0
  116. package/dist/types/common/commandPalette/commandPalette.types.d.ts +42 -0
  117. package/dist/types/common/commandPalette/filterCommands.d.ts +15 -0
  118. package/dist/types/common/commandPalette/index.d.ts +2 -0
  119. package/dist/types/common/configProvider/configProvider.types.d.ts +6 -0
  120. package/dist/types/common/copyButton/copyButton.d.ts +4 -0
  121. package/dist/types/common/copyButton/copyButton.types.d.ts +36 -0
  122. package/dist/types/common/copyButton/index.d.ts +2 -0
  123. package/dist/types/common/errorBoundary/errorBoundary.d.ts +0 -3
  124. package/dist/types/common/errorBoundary/errorBoundary.types.d.ts +2 -3
  125. package/dist/types/common/hooks/index.d.ts +15 -0
  126. package/dist/types/common/hooks/useClipboard.d.ts +16 -0
  127. package/dist/types/common/hooks/useHotkeys.d.ts +45 -0
  128. package/dist/types/common/hooks/useSwipeDismiss.d.ts +6 -2
  129. package/dist/types/common/inputDropdown/inputDropdown.types.d.ts +7 -1
  130. package/dist/types/common/loading/index.d.ts +0 -1
  131. package/dist/types/common/menu/index.d.ts +1 -0
  132. package/dist/types/common/menu/menu.types.d.ts +11 -3
  133. package/dist/types/common/menu/menuHotkeys.d.ts +20 -0
  134. package/dist/types/common/popover/usePopoverPosition.d.ts +1 -0
  135. package/dist/types/common/sticky/sticky.d.ts +1 -1
  136. package/dist/types/common/sticky/sticky.types.d.ts +15 -0
  137. package/dist/types/common/sticky/stickyStack.d.ts +5 -0
  138. package/dist/types/common/themeProvider/themeProvider.d.ts +8 -7
  139. package/dist/types/common/utils/scrollAncestors.d.ts +10 -0
  140. package/dist/types/common/virtualList/scroller.d.ts +33 -0
  141. package/dist/types/common/virtualList/useRowDrag.d.ts +6 -1
  142. package/dist/types/common/virtualList/useRowNavigation.d.ts +11 -1
  143. package/dist/types/common/virtualList/useVirtualWindow.d.ts +22 -3
  144. package/dist/types/common/virtualList/virtualList.d.ts +1 -1
  145. package/dist/types/common/virtualList/virtualList.types.d.ts +43 -3
  146. package/dist/types/common/weatherIcon/index.d.ts +3 -0
  147. package/dist/types/common/weatherIcon/parts.d.ts +26 -0
  148. package/dist/types/common/weatherIcon/weatherIcon.d.ts +4 -0
  149. package/dist/types/common/weatherIcon/weatherIcon.types.d.ts +44 -0
  150. package/dist/types/index.d.ts +7 -0
  151. package/docs/ButtonGroup.md +2 -0
  152. package/docs/CLAUDE.md +6 -2
  153. package/docs/CommandPalette.md +84 -0
  154. package/docs/ConfigProvider.md +2 -2
  155. package/docs/CopyButton.md +45 -0
  156. package/docs/ErrorBoundary.md +4 -4
  157. package/docs/Fab.md +1 -2
  158. package/docs/Hooks.md +268 -0
  159. package/docs/InputDropdown.md +9 -4
  160. package/docs/Loading.md +56 -15
  161. package/docs/Menu.md +30 -6
  162. package/docs/Popover.md +2 -0
  163. package/docs/Sticky.md +22 -1
  164. package/docs/VirtualList.md +44 -4
  165. package/docs/WeatherIcon.md +49 -0
  166. package/package.json +1 -2
  167. package/dist/cjs-types/common/loading/iconLoading/iconLoading.d.ts +0 -5
  168. package/dist/cjs-types/common/loading/iconLoading/index.d.ts +0 -2
  169. package/dist/esm/common/loading/iconLoading/iconLoading.mjs +0 -2
  170. package/dist/esm/common/loading/iconLoading/iconLoading.mjs.map +0 -1
  171. package/dist/esm/common/loading/iconLoading/iconLoading.module.mjs +0 -2
  172. package/dist/esm/common/loading/iconLoading/iconLoading.module.mjs.map +0 -1
  173. package/dist/esm/common/loading/iconLoading/index.mjs +0 -2
  174. package/dist/esm/common/loading/iconLoading/index.mjs.map +0 -1
  175. package/dist/types/common/loading/iconLoading/iconLoading.d.ts +0 -5
  176. package/dist/types/common/loading/iconLoading/index.d.ts +0 -2
@@ -74,6 +74,13 @@ export interface VirtualListColumn<T> {
74
74
  */
75
75
  sortable?: boolean;
76
76
  }
77
+ /**
78
+ * What scrolls, when the list is not to scroll itself. `'nearest'` is the first
79
+ * ancestor set to scroll, the same one `Sticky` pins to; `'window'` is the page;
80
+ * an element (or a getter for one, for a ref that fills after the first render)
81
+ * names it outright.
82
+ */
83
+ export type VirtualListScrollParent = 'nearest' | 'window' | HTMLElement | (() => HTMLElement | null);
77
84
  export interface VirtualListScrollOptions {
78
85
  /** Where the target row lands in the viewport. Default: 'start'. */
79
86
  align?: 'start' | 'center' | 'end';
@@ -88,8 +95,18 @@ export interface VirtualListHandle {
88
95
  scrollToKey: (key: string | number, options?: VirtualListScrollOptions) => void;
89
96
  /** Scroll back to the top. */
90
97
  scrollToTop: (options?: Pick<VirtualListScrollOptions, 'behavior'>) => void;
91
- /** The underlying scroll container element, for advanced use. */
92
- getScrollElement: () => HTMLDivElement | null;
98
+ /**
99
+ * The element that scrolls: the list itself, or the `scrollParent` it was
100
+ * given. `null` for `scrollParent='window'`, where the page scrolls.
101
+ */
102
+ getScrollElement: () => HTMLElement | null;
103
+ /**
104
+ * Re-read where the list sits in its `scrollParent`. Only needed in that mode,
105
+ * and only when something *above* the list changed height without the list
106
+ * re-rendering: an element moving fires no event of its own, and the list
107
+ * would keep rendering the window it had before.
108
+ */
109
+ remeasure: () => void;
93
110
  /**
94
111
  * Re-measure `fit` columns on the next layout. Call this after an out-of-band
95
112
  * content change that doesn't flow through a new `items` array or `columns`
@@ -143,8 +160,22 @@ export interface VirtualListProps<T> extends HtmlProps {
143
160
  container: string;
144
161
  row: string;
145
162
  };
146
- /** Height of the scroll viewport. Defaults to '100%' to fill the parent container. */
163
+ /** Height of the scroll viewport. Defaults to '100%' to fill the parent container. Ignored with `scrollParent`. */
147
164
  height?: number | string;
165
+ /**
166
+ * Drops the list's own scrollbar: it renders at its full height in the page
167
+ * flow and virtualizes against whatever is given here, so a long list at the
168
+ * end of a scrolling page doesn't put a second scrollbar inside the first.
169
+ * `height` no longer applies. `'nearest'` covers most cases: it walks up to
170
+ * the first ancestor whose `overflow-y` is set to scroll, whether or not it
171
+ * currently does, and falls back to the page.
172
+ *
173
+ * The header still sticks, now to the top of that scrollport; give the list
174
+ * `--vl-header-top` to clear a fixed app bar. Rows of uneven height move the
175
+ * page's own scrollbar as they are measured, so set `estimatedRowHeight` to
176
+ * the real height where the rows have one.
177
+ */
178
+ scrollParent?: VirtualListScrollParent;
148
179
  /** Estimated row height before measurement. Default: 40. */
149
180
  estimatedRowHeight?: number;
150
181
  /** Extra rows to render above/below the visible window. Default: 3. */
@@ -187,6 +218,15 @@ export interface VirtualListProps<T> extends HtmlProps {
187
218
  * `Tree` does.
188
219
  */
189
220
  keyboardNavigation?: boolean;
221
+ /**
222
+ * Lets Tab carry on into the next row instead of leaving the list at the end
223
+ * of the current one, for a grid of inputs a user fills in field by field.
224
+ * Shift+Tab walks back the same way, and Tab out of the last row (or back out
225
+ * of the first) leaves the list as it always did. Default: false, because for
226
+ * a list that is read rather than filled in, one tab stop and the arrow keys
227
+ * are the expected behaviour, and 5000 rows of tab stops are a trap.
228
+ */
229
+ tabAcrossRows?: boolean;
190
230
  /** Controlled visible column keys. When provided, internal state is ignored. */
191
231
  visibleColumnKeys?: string[];
192
232
  /** Called when the user toggles a column in the popover. */
@@ -0,0 +1,3 @@
1
+ export { default } from './weatherIcon.js';
2
+ export { WeatherType } from './weatherIcon.types.js';
3
+ export type { WeatherIconLabels, WeatherIconProps } from './weatherIcon.types.js';
@@ -0,0 +1,26 @@
1
+ import { ReactElement } from 'react';
2
+ import { WeatherPartProps } from './weatherIcon.types.js';
3
+ /** Exported so a scene can clip what sits behind the cloud to this same silhouette. */
4
+ export declare const CLOUD_PATH = "M 26.3 62.9 C 15.4 65.0 15.0 48.5 27.8 49.8 C 23.9 43.9 34.2 34.0 43.0 39.6 C 46.0 26.2 73.9 32.0 68.9 46.3 C 85.0 44.3 84.6 64.2 72.4 62.5 Z";
5
+ export declare function Cloud({ className, delayed }: {
6
+ className?: string;
7
+ delayed?: boolean;
8
+ }): ReactElement;
9
+ export declare function Sun({ className }: {
10
+ className?: string;
11
+ }): ReactElement;
12
+ export declare function Moon({ className }: {
13
+ className?: string;
14
+ }): ReactElement;
15
+ export declare enum RainIntensity {
16
+ Light = "light",
17
+ Medium = "medium",
18
+ Heavy = "heavy"
19
+ }
20
+ export declare function Rain({ animated, intensity, }: WeatherPartProps & {
21
+ intensity?: RainIntensity;
22
+ }): ReactElement;
23
+ export declare function Snow({ animated }: WeatherPartProps): ReactElement;
24
+ export declare function Hail({ animated }: WeatherPartProps): ReactElement;
25
+ export declare function Fog(): ReactElement;
26
+ export declare function Lightning(): ReactElement;
@@ -0,0 +1,4 @@
1
+ import { ReactElement } from 'react';
2
+ import { WeatherIconProps } from './weatherIcon.types.js';
3
+ declare function WeatherIcon(props: WeatherIconProps): ReactElement;
4
+ export default WeatherIcon;
@@ -0,0 +1,44 @@
1
+ import { CSSProperties, SVGAttributes } from 'react';
2
+ import { DataProps } from '../types/slots.types.js';
3
+ export declare enum WeatherType {
4
+ Sunny = "sunny",
5
+ ClearNight = "clearNight",
6
+ Cloudy = "cloudy",
7
+ PartlyCloudy = "partlyCloudy",
8
+ PartlyCloudyNight = "partlyCloudyNight",
9
+ RainyLight = "rainyLight",
10
+ Rainy = "rainy",
11
+ RainyHeavy = "rainyHeavy",
12
+ Snowy = "snowy",
13
+ Hail = "hail",
14
+ Foggy = "foggy",
15
+ Thunderstorm = "thunderstorm"
16
+ }
17
+ /** The accessible name of each weather, read out by screen readers. */
18
+ export type WeatherIconLabels = Partial<Record<`${WeatherType}`, string>>;
19
+ /**
20
+ * The root is an `<svg>`, so the native attributes come from `SVGAttributes` rather than
21
+ * `HtmlProps`. Its `type` is the attribute of `<script>` and `<style>`, not of an `<svg>`.
22
+ */
23
+ export interface WeatherIconProps extends DataProps, Omit<SVGAttributes<SVGSVGElement>, 'type'> {
24
+ /**
25
+ * The weather to draw. Takes the enum or its string value, so an API's own enum with the same
26
+ * values passes as it is.
27
+ */
28
+ type: WeatherType | `${WeatherType}`;
29
+ /** Width and height, any CSS size. Default `64`. */
30
+ size?: number | string;
31
+ /**
32
+ * Default `true`. `false` draws a still frame, for small or repeated icons where the motion is
33
+ * too small to see but costs the same. Always still under `prefers-reduced-motion`.
34
+ */
35
+ animated?: boolean;
36
+ /** Text this component renders of its own. */
37
+ labels?: WeatherIconLabels;
38
+ className?: string;
39
+ style?: CSSProperties;
40
+ }
41
+ /** What a falling part needs to know to pick its still frame. */
42
+ export interface WeatherPartProps {
43
+ animated: boolean;
44
+ }
@@ -31,10 +31,14 @@ export { default as Chip } from './common/chip/index.js';
31
31
  export * from './common/chip/index.js';
32
32
  export { default as ColorPicker } from './common/colorPicker/index.js';
33
33
  export * from './common/colorPicker/index.js';
34
+ export { default as CommandPalette } from './common/commandPalette/index.js';
35
+ export * from './common/commandPalette/index.js';
34
36
  export { default as ConfigProvider } from './common/configProvider/index.js';
35
37
  export * from './common/configProvider/index.js';
36
38
  export { default as ConfirmModal } from './common/confirmModal/index.js';
37
39
  export * from './common/confirmModal/index.js';
40
+ export { default as CopyButton } from './common/copyButton/index.js';
41
+ export * from './common/copyButton/index.js';
38
42
  export { default as DatePicker } from './common/datePicker/index.js';
39
43
  export * from './common/datePicker/index.js';
40
44
  export { default as Divider } from './common/divider/index.js';
@@ -146,8 +150,11 @@ export { default as Tree } from './common/tree/index.js';
146
150
  export * from './common/tree/index.js';
147
151
  export { default as VirtualList } from './common/virtualList/index.js';
148
152
  export * from './common/virtualList/index.js';
153
+ export { default as WeatherIcon } from './common/weatherIcon/index.js';
154
+ export * from './common/weatherIcon/index.js';
149
155
  export { default as Wizard } from './common/wizard/index.js';
150
156
  export * from './common/wizard/index.js';
157
+ export * from './common/hooks/index.js';
151
158
  export { FormValidator, FormValidatorGroup, Validators, ValidatableComponent, useFormValidator, useFormValidatorGroup, } from './services/formValidation/index.js';
152
159
  export type { ValidationError, ValidatorFunction, ErrorParser, FormValidatorGroupOptions, InferGroupValues, MergedValues, } from './services/formValidation/index.js';
153
160
  export { mapLocale, setLocale, getLocale } from './services/localization.js';
@@ -77,6 +77,8 @@ import { Input } from '@ahrowe/ui';
77
77
 
78
78
  **How it works:** the first and last children keep rounded outer corners; every child in between is flattened on both sides. Adjacent borders are collapsed with a `-1px` margin so two 1px borders don't stack into a visible double line, and whichever child is hovered/focused gets a `z-index` bump so its border/focus ring isn't clipped by its neighbors.
79
79
 
80
+ **One border per seam:** the overlap alone keeps both borders, and at a fractional scale (browser zoom, a 125% or 150% display) they round to different device pixels, so one seam reads 2px next to a 1px one. Each child therefore clips away the 1px it shares with the next one, and the later child's border is the one you see, as it always was. A focused child keeps its whole border, and the next child clips its shared 1px instead. This is a `clip-path` on the children, so a `clip-path` of your own on a grouped child is overridden.
81
+
80
82
  Flattening reaches a child's visible border even when that border doesn't live on the child's own root element — the case for `Input`, `Dropdown`, and everything built on either of them (`NumberInput`, `DatePicker`, `TimeInput`, `InputDropdown`, `Textarea`), whose actual border sits on a `fieldset` nested inside a `<label>`, as well as `ColorPicker`'s swatch and `Checkbox`'s checkmark box. Alongside the direct `border-radius` reset it applies to each child's root, `ButtonGroup` also sets `--group-radius-tl`/`-tr`/`-br`/`-bl` custom properties per position. Unlike `border-radius` itself, custom properties inherit through any nesting depth, so a descendant whose own `border-radius` reads `var(--group-radius-tl, var(--default-border-radius))` (etc.) picks up the right corner overrides no matter how deep it's nested — no consumer-facing prop needed.
81
83
 
82
84
  **Separators:** any child with `role="separator"` (what `Divider` sets) is excluded from that border-collapsing margin, on both sides — it stays fully visible between its neighbors instead of being partially hidden under one of them. Use a vertical `Divider` (see [Divider.md](Divider.md)) between two buttons when their style has no visible border of its own to merge (`Primary`, `Delete`), so there's still a clear seam between them.
package/docs/CLAUDE.md CHANGED
@@ -59,8 +59,10 @@ enums and slot names. Do not infer props from a component's name.
59
59
  | Checkbox.md | Checkbox | Boolean toggle input — settings, multi-select lists, accept terms, feature flags |
60
60
  | Chip.md | Chip, ChipContainer, ChipStyleType | Compact tag, label, or filter token — selected items, applied filters, category badges, user avatars with remove |
61
61
  | ColorPicker.md | ColorPicker | Hex colour selection — theme editors, annotation tools, customisation panels |
62
+ | CommandPalette.md | CommandPalette | A search-first dialog for running any command or jumping anywhere in the app from the keyboard, usually opened with ⌘K / Ctrl+K |
62
63
  | ConfigProvider.md | ConfigProvider | Set global **default props** for library components once, near the root of your app, instead of repeating the same prop on every instance |
63
64
  | ConfirmModal.md | ConfirmModal | Confirmation dialog before destructive or irreversible actions — delete, archive, send, overwrite |
65
+ | CopyButton.md | CopyButton | Icon-only button that copies a value to the clipboard: an invoice number, an ID in a table row, an API key or share link inside an `Input` |
64
66
  | DatePicker.md | DatePicker | Date selection input — booking forms, birth date fields, date range pickers, deadline selectors |
65
67
  | Divider.md | Divider, DividerTextPosition, DividerOrientation | A rule that separates sections of content — horizontal by default, filling the width of its parent |
66
68
  | Drawer.md | Drawer | Inline horizontal panel that collapses and expands by animating its width — side panels, navigation rails, detail/filter panes that slide open beside the main content |
@@ -73,6 +75,7 @@ enums and slot names. Do not infer props from a component's name.
73
75
  | FloorPlan.md | emptyFloorPlan, addWall, derivePlan | The shared data model behind `RoomDrawer` and `RoomViewer` |
74
76
  | FormValidator.md | FormValidator, Validators, useFormValidator | Track and validate a single form field's value, dirty/touched state, and errors |
75
77
  | FormValidatorGroup.md | useFormValidatorGroup, FormValidatorGroup, FormValidator, Validators | Manage a whole form — multiple fields, a single submit handler, validate-on-submit, dirty/touched tracking across the form, and mapping server-side errors back onto the right fields |
78
+ | Hooks.md | useHotkeys, formatHotkey, useMediaQuery, useCoarsePointer, useVisualViewportSize, useScrollEdges, useSwipeDismiss, useOverlay, useClipboard, tabbablesIn | Standalone React hooks the library uses internally and publishes for the same jobs in your own code: keyboard shortcuts, media queries, the mobile keyboard's viewport, scroll-edge detection, swipe-to-dismiss, copying to the clipboard, and the Escape / focus-trap / scroll-lock behaviour behind a custom overlay |
76
79
  | IconPicker.md | IconPicker | Pick one item from a visual icon/image grid — category selectors, emoji-style pickers, avatar selectors, anything where each option is best represented by an icon or image rather than text alone |
77
80
  | IdleManager.md | IdleManager | Show something only once the user has stopped interacting — a screen saver, a session-timeout warning, an "are you still there?" prompt, an idle dashboard view |
78
81
  | Input.md | Input, InputType | Text input with floating label, validation state, and type variants |
@@ -80,8 +83,8 @@ enums and slot names. Do not infer props from a component's name.
80
83
  | InteractableDiv.md | InteractableDiv | Accessible `<div>` that behaves like a button — click and keyboard (Enter/Space) events, with proper ARIA semantics |
81
84
  | KanbanBoard.md | KanbanBoard, KanbanColumn | Drag-and-drop board with columns — task management, workflow stages, status boards (e.g. Todo / In Progress / Done) |
82
85
  | KlipyPicker.md | KlipyPicker | GIF picker backed by the Klipy API — chat reactions, message composers, content authoring tools |
83
- | Loading.md | IconLoading, SpinnerLoading | Loading indicators for async operations — page loads, data fetching, button actions (when not using Button's built-in `isLoading`) |
84
- | Menu.md | Menu | A list of actions hanging off a trigger — a row's "more" button, a toolbar overflow, a context menu on a card |
86
+ | Loading.md | SpinnerLoading | A loading indicator for async operations: page loads, data fetching, or covering a region while it refreshes |
87
+ | Menu.md | Menu, menuHotkeys | A list of actions hanging off a trigger — a row's "more" button, a toolbar overflow, a context menu on a card |
85
88
  | Modal.md | Modal | Full overlay dialog — forms, detail views, confirmations, wizards that need to block the rest of the UI |
86
89
  | MultiSelect.md | MultiSelect | Pick several options from a list |
87
90
  | NumberInput.md | NumberInput | Numeric input with formatting — prices, quantities, percentages, phone numbers with separators |
@@ -118,6 +121,7 @@ enums and slot names. Do not infer props from a component's name.
118
121
  | Tooltip.md | Tooltip | Contextual help, info icons, field explanations, error pointers |
119
122
  | Tree.md | Tree, flattenTree | A hierarchical list the user can expand, collapse and pick from: categories, nested locations, folder structures, org charts |
120
123
  | VirtualList.md | VirtualList | Efficiently render large lists or tables — thousands of rows rendered with a virtualised scroll window so only visible rows are in the DOM |
124
+ | WeatherIcon.md | WeatherIcon, WeatherType | An animated weather symbol for a forecast, a dashboard widget or a day selector, one per condition a forecast reports |
121
125
  | Wizard.md | Wizard | Animated step container for multi-step forms and flows |
122
126
 
123
127
  <!-- components:end -->
@@ -0,0 +1,84 @@
1
+ # CommandPalette
2
+
3
+ **When to use:** A search-first dialog for running any command or jumping anywhere in the app from the keyboard, usually opened with ⌘K / Ctrl+K. It takes the same `MenuEntry[]` as [Menu](Menu.md), so one list can feed the menus, the palette and the shortcuts. For picking a value in a form, use [InputDropdown](InputDropdown.md).
4
+
5
+ **Keywords:** cmd+k, ctrl+k, command menu, quick open, launcher, spotlight, go to anything, jump to, omnibox
6
+
7
+ **Import:** `import { CommandPalette } from '@ahrowe/ui'`
8
+ **Types:** `import type { CommandPaletteProps, CommandPaletteLabels } from '@ahrowe/ui'`
9
+
10
+ **Requires:** `<div id="bodyEnd"></div>` in your app: the palette is a [Modal](Modal.md) and portals there.
11
+
12
+ ```tsx
13
+ import { CommandPalette, menuHotkeys, useHotkeys } from '@ahrowe/ui';
14
+ import type { MenuEntry } from '@ahrowe/ui';
15
+
16
+ const commands: MenuEntry[] = [
17
+ { heading: 'Invoice' },
18
+ { id: 'new-invoice', label: 'New invoice', icon: faPlus, shortcut: 'mod+n', onClick: createInvoice },
19
+ { id: 'export', label: 'Export as PDF', icon: faFilePdf, keywords: ['download', 'print'], onClick: exportPdf },
20
+ { heading: 'Go to' },
21
+ { id: 'customers', label: 'Customers', onClick: () => navigate('/customers') },
22
+ ];
23
+
24
+ function App() {
25
+ const [isOpen, setOpen] = useState(false);
26
+ useHotkeys({ ...menuHotkeys(commands), 'mod+k': () => setOpen(true) });
27
+
28
+ return <CommandPalette items={commands} isOpen={isOpen} onClose={() => setOpen(false)} />;
29
+ }
30
+ ```
31
+
32
+ **Opening it is yours.** The palette binds no shortcut of its own, for the same reason `Menu` does not: if it listened for ⌘K and your app did too, it would open twice. `menuHotkeys` binds each command's `shortcut`, so the hint the palette draws and the key that fires stay one spelling.
33
+
34
+ **Filtering** matches every typed word, in any order, against a string `label` and the command's `keywords`, ignoring case and accents: `uber` finds `Übersicht`. Within each group, a label starting with the query comes first, then a label word or a keyword starting with it, then any other match, and the group holding the best match moves to the top. A heading moves with its group and stays above it while any of it matches; separators are dropped while a query is typed. Typing highlights the best match, so Enter runs it. A `label` that is not a string cannot be read, so give such a command `keywords`.
35
+
36
+ **Server results.** Set `filter={false}` and the palette shows `items` exactly as given, so they can be the results of your own search:
37
+
38
+ ```tsx
39
+ const [results, setResults] = useState<MenuEntry[]>(commands);
40
+ const [isLoading, setLoading] = useState(false);
41
+
42
+ <CommandPalette
43
+ items={results}
44
+ filter={false}
45
+ isLoading={isLoading}
46
+ onQueryChange={(query) => {
47
+ if (!query) return setResults(commands);
48
+ setLoading(true);
49
+ searchCustomers(query).then((hits) => {
50
+ setResults(hits.map((c) => ({ id: c.id, label: c.name, onClick: () => navigate(`/customers/${c.id}`) })));
51
+ setLoading(false);
52
+ });
53
+ }}
54
+ isOpen={isOpen}
55
+ onClose={() => setOpen(false)}
56
+ />;
57
+ ```
58
+
59
+ `onQueryChange` fires on every keystroke and with `''` each time the palette opens, so debounce the request and drop a response that arrives after a newer query. Between keystrokes the highlight follows the command's `id`, so it stays put while results arrive and are replaced around it.
60
+
61
+ **Keyboard:** the focus stays in the search field. ↑ and ↓ move the highlight, wrapping and skipping disabled commands, Enter runs it, and Escape closes. Running a command calls its own `onClick`, then `onAction`, then `onClose`, the same order `Menu` uses. The number of matches is announced to screen readers as it changes.
62
+
63
+ On a small touch screen it can present as a bottom sheet like every `Modal`: pass `presentation={Presentation.Auto}`, or set it app-wide through `ConfigProvider`.
64
+
65
+ **Key props:**
66
+
67
+ | Prop | Type | Description |
68
+ |------|------|-------------|
69
+ | `items` | `MenuEntry[]` | The commands. Headings group them; separators split groups (required) |
70
+ | `isOpen` | `boolean` | Whether the palette is open (required) |
71
+ | `onClose` | `() => void` | Called on Escape, a backdrop click, and after a command runs (required) |
72
+ | `onAction` | `(id: string) => void` | Called with the id of the command that ran, after its own `onClick` |
73
+ | `onQueryChange` | `(query: string) => void` | Called on every keystroke, and with `''` on open |
74
+ | `filter` | `boolean` | Filter `items` by the query (default `true`). `false` shows them as given |
75
+ | `isLoading` | `boolean` | Shows the `loading` label instead of `empty` while nothing matches |
76
+ | `presentation` | `Presentation` | Centred dialog (default), bottom sheet, or a sheet only on a small touch screen |
77
+
78
+ A command is a `MenuAction`: `id`, `label`, `icon`, `shortcut`, `keywords`, `disabled`, `danger` and `onClick`. See [Menu.md](Menu.md).
79
+
80
+ **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ CommandPalette: { presentation: Presentation.Auto } }}`. See [ConfigProvider.md](ConfigProvider.md).
81
+
82
+ **Slots:** `root` `search` `input` `list` `item` `itemIcon` `itemLabel` `itemShortcut` `heading` `separator` `empty`
83
+
84
+ **Labels:** `label` `placeholder` `empty` `loading` `results(count)`: 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: `CommandPaletteLabels`.
@@ -92,8 +92,8 @@ A nested provider inherits it unless it sets its own, the same way `defaultProps
92
92
  Each entry is a `Partial<...Props>`, so any of that component's props can be defaulted:
93
93
 
94
94
  - **Form inputs:** `Input` · `Textarea` · `NumberInput` · `Dropdown` · `InputDropdown` · `Checkbox` · `Switch` · `RadioGroup` · `DatePicker` · `OptionPicker` · `OtpInput`
95
- - **Display:** `Button` · `ActionButtons` · `Badge` · `Chip` · `Card` · `SectionHeader` · `Skeleton` · `Accordion` · `Divider` · `Timer`
96
- - **Overlays:** `ConfirmModal` · `Modal` · `Popover` · `Popover`
95
+ - **Display:** `Button` · `CopyButton` · `ActionButtons` · `Badge` · `Chip` · `Card` · `SectionHeader` · `Skeleton` · `Accordion` · `Divider` · `Timer` · `WeatherIcon`
96
+ - **Overlays:** `ConfirmModal` · `Modal` · `CommandPalette` · `Popover`
97
97
  - **Editors:** `RoomDrawer`, `RoomViewer`
98
98
 
99
99
  ```tsx
@@ -0,0 +1,45 @@
1
+ # CopyButton
2
+
3
+ **When to use:** Icon-only button that copies a value to the clipboard: an invoice number, an ID in a table row, an API key or share link inside an `Input`. For a labelled "Copy link" button, use `Button` with the `useClipboard` hook instead (see [Hooks.md](Hooks.md)).
4
+
5
+ **Keywords:** token, secret, reference number, snippet
6
+
7
+ **Import:** `import { CopyButton } from '@ahrowe/ui'`
8
+ **Types:** `import type { CopyButtonProps, CopyButtonLabels } from '@ahrowe/ui'`
9
+
10
+ ```tsx
11
+ import { CopyButton, Input } from '@ahrowe/ui';
12
+
13
+ <span>
14
+ {invoice.number} <CopyButton value={invoice.number} />
15
+ </span>
16
+
17
+ // inside a field
18
+ <Input label='API key' value={apiKey} readOnly suffix={<CopyButton value={apiKey} />} />
19
+
20
+ // a toast on success, where the button is not where the user is looking
21
+ <CopyButton value={url} onCopy={() => showToast('Link copied', { type: 'success' })} />
22
+ ```
23
+
24
+ After a click the icon rotates into a check (through `AnimatedIcon`, held still under `prefers-reduced-motion`) for `timeout` ms, the accessible name and tooltip read "Copied", and a polite live region announces it, because a swapped icon or a changed `aria-label` alone is not read out. A refused write (an insecure context, a blocked iframe) announces "Could not copy" and calls `onError`; the icon stays as it was. `CopyButton` shows no toast of its own: pass one through `onCopy` / `onError` if you want it.
25
+
26
+ The live region is rendered as a sibling of the button, so `CopyButton` returns two elements. It is visually hidden and takes no space.
27
+
28
+ **Key props:**
29
+
30
+ | Prop | Type | Description |
31
+ |------|------|-------------|
32
+ | `value` | `string` | Text written to the clipboard on click (required) |
33
+ | `timeout` | `number` | How long the copied state shows, in ms (default `2000`) |
34
+ | `icon` | `IconDefinition \| ReactElement` | Icon before copying (default `faCopy`). The swap animates only when both icons are FontAwesome definitions |
35
+ | `copiedIcon` | `IconDefinition \| ReactElement` | Icon while copied (default `faCheck`) |
36
+ | `onCopy` | `(value: string) => void` | Called after a successful copy |
37
+ | `onError` | `() => void` | Called when the clipboard refuses the write. `useClipboard` returns the reason |
38
+ | `onClick` | `(event: MouseEvent) => void` | Called on every click, before the copy |
39
+ | `ref` | `Ref<HTMLDivElement>` | Ref to the button |
40
+
41
+ **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ CopyButton: { timeout: 1500 } }}`. See [ConfigProvider.md](ConfigProvider.md).
42
+
43
+ **Slots:** `root` `icon`
44
+
45
+ **Labels:** `copy` `copied` `failed`: 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: `CopyButtonLabels`.
@@ -65,7 +65,7 @@ import { ErrorBoundary } from '@ahrowe/ui';
65
65
  </ErrorBoundary>
66
66
  ```
67
67
 
68
- The default fallback is a centered card with a tinted icon badge, an `Accordion` for the collapsible stack trace (with a labeled copy `Button` that fires a `Toast` confirmation if `ToastProvider` is mounted), and a `Button` for "Try again".
68
+ The default fallback is a centered card with a tinted icon badge, an `Accordion` for the collapsible stack trace (with a `CopyButton` that also fires a `Toast` confirmation if `ToastProvider` is mounted), and a `Button` for "Try again".
69
69
 
70
70
  **Key props:**
71
71
 
@@ -74,7 +74,7 @@ The default fallback is a centered card with a tinted icon badge, an `Accordion`
74
74
  | `children` | `ReactNode` | The subtree to protect |
75
75
  | `title` | `ReactNode` | Heading in the default fallback (default `"Something went wrong"`) |
76
76
  | `description` | `ReactNode` | Overrides the message shown under the title. Defaults to `error.message` |
77
- | `labels` | `ErrorBoundaryLabels` | Overrides for every other built-in string (stack trace label, copy/reset button text, copy toasts) — pass a translated set for non-English apps. See below |
77
+ | `labels` | `ErrorBoundaryLabels` | Overrides for every other built-in string (stack trace label, copy button tooltip, reset button text, copy toasts) — pass a translated set for non-English apps. See below |
78
78
  | `fallback` | `ReactNode \| (props: ErrorBoundaryFallbackProps) => ReactNode` | Replaces the built-in fallback entirely. The render-function form receives `{ error, errorInfo, resetErrorBoundary }` |
79
79
  | `onError` | `(error: Error, errorInfo: ErrorInfo) => void` | Called once per catch — the place to report to Sentry/Datadog/etc. |
80
80
  | `onReset` | `() => void` | Called after the boundary is reset, whether via the built-in "Try again" button, a custom fallback calling `resetErrorBoundary`, or a `resetKeys` change. Use it to re-arm whatever state caused the crash |
@@ -86,8 +86,8 @@ The default fallback is a centered card with a tinted icon badge, an `Accordion`
86
86
  | Field | Default | Description |
87
87
  |-------|---------|--------------|
88
88
  | `stackTrace` | `"Stack trace"` | Accordion header for the stack trace section |
89
- | `copy` | `"Copy"` | Copy button label before copying |
90
- | `copied` | `"Copied"` | Copy button label right after a successful copy |
89
+ | `copy` | `"Copy"` | Copy button tooltip and accessible name before copying |
90
+ | `copied` | `"Copied"` | Copy button tooltip and accessible name right after a successful copy |
91
91
  | `reset` | `"Try again"` | Reset button label |
92
92
  | `copySuccessToast` | `"Stack trace copied to clipboard"` | Toast shown after a successful copy (only visible if `ToastProvider` is mounted) |
93
93
  | `copyErrorToast` | `"Could not copy stack trace"` | Toast shown if the copy fails |
package/docs/Fab.md CHANGED
@@ -22,7 +22,6 @@ import { faPlus, faPen, faCamera } from '@fortawesome/free-solid-svg-icons';
22
22
  { id: 'photo', icon: faCamera, label: 'Photo' },
23
23
  ]}
24
24
  onMenuEntryClicked={(id) => handleAction(id)}
25
- closeMenuOnEntryClicked
26
25
  />
27
26
 
28
27
  // Mini size
@@ -38,7 +37,7 @@ import { faPlus, faPen, faCamera } from '@fortawesome/free-solid-svg-icons';
38
37
  | `size` | `'default' \| 'mini'` | Button size |
39
38
  | `menuEntries` | `FabMenuEntry[]` | `{ id, icon, label? }` — enables speed dial |
40
39
  | `onMenuEntryClicked` | `(id: string) => void` | Sub-menu entry handler |
41
- | `closeMenuOnEntryClicked` | `boolean` | Auto-close menu after selection |
40
+ | `closeMenuOnEntryClicked` | `boolean` | Close the menu after an entry is picked (default `true`) |
42
41
  | `onClick` | `() => void` | Simple click handler (no speed dial) |
43
42
  | `children` | `ReactNode` | FAB icon content |
44
43