@ahrowe/ui 0.32.0 → 0.34.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 (172) hide show
  1. package/dist/esm/common/actionIcon/actionIcon.module.mjs.map +1 -1
  2. package/dist/esm/common/breadcrumb/breadcrumb.mjs +1 -1
  3. package/dist/esm/common/breadcrumb/breadcrumb.mjs.map +1 -1
  4. package/dist/esm/common/breadcrumb/breadcrumb.module.mjs +1 -1
  5. package/dist/esm/common/breadcrumb/breadcrumb.module.mjs.map +1 -1
  6. package/dist/esm/common/card/Card.mjs +1 -1
  7. package/dist/esm/common/card/Card.mjs.map +1 -1
  8. package/dist/esm/common/card/card.types.mjs.map +1 -1
  9. package/dist/esm/common/chip/chip.mjs +1 -1
  10. package/dist/esm/common/chip/chip.mjs.map +1 -1
  11. package/dist/esm/common/chip/chip.types.mjs.map +1 -1
  12. package/dist/esm/common/colorPicker/colorPicker.mjs +1 -1
  13. package/dist/esm/common/colorPicker/colorPicker.mjs.map +1 -1
  14. package/dist/esm/common/colorPicker/colorPicker.module.mjs.map +1 -1
  15. package/dist/esm/common/configProvider/configProvider.mjs.map +1 -1
  16. package/dist/esm/common/configProvider/presentation.types.mjs +2 -0
  17. package/dist/esm/common/configProvider/presentation.types.mjs.map +1 -0
  18. package/dist/esm/common/configProvider/usePresentationDefault.mjs.map +1 -1
  19. package/dist/esm/common/confirmModal/confirmModal.mjs +1 -1
  20. package/dist/esm/common/confirmModal/confirmModal.mjs.map +1 -1
  21. package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.mjs +1 -1
  22. package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.mjs.map +1 -1
  23. package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.module.mjs.map +1 -1
  24. package/dist/esm/common/datePicker/datePicker.mjs +1 -1
  25. package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
  26. package/dist/esm/common/drawer/drawer.mjs +1 -1
  27. package/dist/esm/common/drawer/drawer.mjs.map +1 -1
  28. package/dist/esm/common/dropdown/dropdown.mjs +1 -1
  29. package/dist/esm/common/dropdown/dropdown.mjs.map +1 -1
  30. package/dist/esm/common/dropdown/dropdown.module.mjs +1 -1
  31. package/dist/esm/common/dropdown/dropdown.module.mjs.map +1 -1
  32. package/dist/esm/common/emptyState/emptyState.mjs +1 -1
  33. package/dist/esm/common/emptyState/emptyState.mjs.map +1 -1
  34. package/dist/esm/common/fab/components/fabBase/fabBase.mjs +1 -1
  35. package/dist/esm/common/fab/components/fabBase/fabBase.mjs.map +1 -1
  36. package/dist/esm/common/fab/fab.mjs +1 -1
  37. package/dist/esm/common/fab/fab.mjs.map +1 -1
  38. package/dist/esm/common/hooks/useAnchorTracking.mjs.map +1 -1
  39. package/dist/esm/common/hooks/useFocusBoundary.mjs +1 -1
  40. package/dist/esm/common/hooks/useFocusBoundary.mjs.map +1 -1
  41. package/dist/esm/common/hooks/useSheet.mjs +1 -1
  42. package/dist/esm/common/hooks/useSheet.mjs.map +1 -1
  43. package/dist/esm/common/infiniteBlock/infiniteBlock.mjs +1 -1
  44. package/dist/esm/common/infiniteBlock/infiniteBlock.mjs.map +1 -1
  45. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  46. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  47. package/dist/esm/common/inputDropdown/inputDropdown.module.mjs +1 -1
  48. package/dist/esm/common/inputDropdown/inputDropdown.module.mjs.map +1 -1
  49. package/dist/esm/common/interactableDiv/interactableDiv.mjs +1 -1
  50. package/dist/esm/common/interactableDiv/interactableDiv.mjs.map +1 -1
  51. package/dist/esm/common/klipyPicker/klipyPicker.mjs +1 -1
  52. package/dist/esm/common/klipyPicker/klipyPicker.mjs.map +1 -1
  53. package/dist/esm/common/menu/menu.mjs +2 -0
  54. package/dist/esm/common/menu/menu.mjs.map +1 -0
  55. package/dist/esm/common/menu/menu.module.mjs +2 -0
  56. package/dist/esm/common/menu/menu.module.mjs.map +1 -0
  57. package/dist/esm/common/menu/menu.types.mjs +2 -0
  58. package/dist/esm/common/menu/menu.types.mjs.map +1 -0
  59. package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
  60. package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
  61. package/dist/esm/common/multiSelect/multiSelect.module.mjs +1 -1
  62. package/dist/esm/common/multiSelect/multiSelect.module.mjs.map +1 -1
  63. package/dist/esm/common/numberInput/numberInput.mjs +1 -1
  64. package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
  65. package/dist/esm/common/popover/popover.mjs +1 -1
  66. package/dist/esm/common/popover/popover.mjs.map +1 -1
  67. package/dist/esm/common/popover/popover.module.mjs +1 -1
  68. package/dist/esm/common/popover/popover.module.mjs.map +1 -1
  69. package/dist/esm/common/popover/popover.types.mjs.map +1 -1
  70. package/dist/esm/common/popover/usePopoverPosition.mjs +1 -1
  71. package/dist/esm/common/popover/usePopoverPosition.mjs.map +1 -1
  72. package/dist/esm/common/progressBar/progressBar.mjs +1 -1
  73. package/dist/esm/common/progressBar/progressBar.mjs.map +1 -1
  74. package/dist/esm/common/searchInput/searchInput.mjs +1 -1
  75. package/dist/esm/common/searchInput/searchInput.mjs.map +1 -1
  76. package/dist/esm/common/splitButton/splitButton.mjs +1 -1
  77. package/dist/esm/common/splitButton/splitButton.mjs.map +1 -1
  78. package/dist/esm/common/splitButton/splitButton.module.mjs +1 -1
  79. package/dist/esm/common/splitButton/splitButton.module.mjs.map +1 -1
  80. package/dist/esm/common/timeInput/timeInput.mjs +1 -1
  81. package/dist/esm/common/timeInput/timeInput.mjs.map +1 -1
  82. package/dist/esm/common/toast/toast.module.mjs.map +1 -1
  83. package/dist/esm/common/tooltip/tooltip.mjs +1 -1
  84. package/dist/esm/common/tooltip/tooltip.mjs.map +1 -1
  85. package/dist/esm/common/tooltip/tooltip.module.mjs +1 -1
  86. package/dist/esm/common/tooltip/tooltip.module.mjs.map +1 -1
  87. package/dist/esm/common/virtualList/virtualList.module.mjs +1 -1
  88. package/dist/esm/common/virtualList/virtualList.module.mjs.map +1 -1
  89. package/dist/esm/common/virtualList/virtualListHeader.mjs +1 -1
  90. package/dist/esm/common/virtualList/virtualListHeader.mjs.map +1 -1
  91. package/dist/esm/index.mjs +1 -1
  92. package/dist/index.cjs +3 -3
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/style.css +1 -1
  95. package/dist/types/common/card/card.types.d.ts +6 -1
  96. package/dist/types/common/chip/chip.types.d.ts +6 -1
  97. package/dist/types/common/colorPicker/colorPicker.d.ts +1 -1
  98. package/dist/types/common/colorPicker/colorPicker.types.d.ts +6 -1
  99. package/dist/types/common/configProvider/configProvider.d.ts +1 -1
  100. package/dist/types/common/configProvider/configProvider.types.d.ts +6 -3
  101. package/dist/types/common/configProvider/index.d.ts +1 -0
  102. package/dist/types/common/configProvider/presentation.types.d.ts +14 -0
  103. package/dist/types/common/configProvider/usePresentationDefault.d.ts +1 -1
  104. package/dist/types/common/confirmModal/confirmModal.types.d.ts +4 -2
  105. package/dist/types/common/datePicker/components/datePickerMonth/datePickerMonth.d.ts +1 -1
  106. package/dist/types/common/datePicker/components/datePickerMonth/datePickerMonth.types.d.ts +3 -0
  107. package/dist/types/common/datePicker/datePicker.types.d.ts +11 -2
  108. package/dist/types/common/drawer/drawer.d.ts +1 -1
  109. package/dist/types/common/drawer/drawer.types.d.ts +7 -1
  110. package/dist/types/common/dropdown/dropdown.types.d.ts +7 -7
  111. package/dist/types/common/emptyState/emptyState.d.ts +1 -1
  112. package/dist/types/common/emptyState/emptyState.types.d.ts +6 -1
  113. package/dist/types/common/fab/components/fabBase/fabBase.d.ts +1 -1
  114. package/dist/types/common/fab/components/fabBase/fabBase.types.d.ts +3 -1
  115. package/dist/types/common/fab/fab.d.ts +1 -1
  116. package/dist/types/common/fab/fab.types.d.ts +6 -1
  117. package/dist/types/common/hooks/useAnchorTracking.d.ts +1 -1
  118. package/dist/types/common/hooks/useFocusBoundary.d.ts +3 -3
  119. package/dist/types/common/hooks/useSheet.d.ts +1 -1
  120. package/dist/types/common/infiniteBlock/infiniteBlock.d.ts +1 -1
  121. package/dist/types/common/infiniteBlock/infiniteBlock.types.d.ts +2 -0
  122. package/dist/types/common/menu/index.d.ts +2 -0
  123. package/dist/types/common/menu/menu.d.ts +14 -0
  124. package/dist/types/common/menu/menu.types.d.ts +55 -0
  125. package/dist/types/common/modal/modal.types.d.ts +1 -1
  126. package/dist/types/common/multiSelect/multiSelect.types.d.ts +1 -1
  127. package/dist/types/common/numberInput/numberInput.types.d.ts +6 -0
  128. package/dist/types/common/popover/popover.types.d.ts +34 -3
  129. package/dist/types/common/popover/usePopoverPosition.d.ts +20 -1
  130. package/dist/types/common/progressBar/progressBar.d.ts +1 -1
  131. package/dist/types/common/progressBar/progressBar.types.d.ts +6 -1
  132. package/dist/types/common/searchInput/searchInput.d.ts +1 -1
  133. package/dist/types/common/searchInput/searchInput.types.d.ts +13 -1
  134. package/dist/types/common/splitButton/splitButton.types.d.ts +3 -3
  135. package/dist/types/index.d.ts +2 -2
  136. package/docs/Breadcrumb.md +2 -2
  137. package/docs/ButtonGroup.md +1 -1
  138. package/docs/CLAUDE.md +1 -1
  139. package/docs/Card.md +3 -1
  140. package/docs/Chip.md +2 -0
  141. package/docs/ColorPicker.md +4 -0
  142. package/docs/ConfigProvider.md +2 -2
  143. package/docs/DatePicker.md +4 -0
  144. package/docs/Drawer.md +2 -0
  145. package/docs/Dropdown.md +3 -1
  146. package/docs/EmptyState.md +2 -0
  147. package/docs/Fab.md +2 -0
  148. package/docs/InputDropdown.md +1 -1
  149. package/docs/InteractableDiv.md +2 -0
  150. package/docs/Menu.md +107 -0
  151. package/docs/Modal.md +1 -1
  152. package/docs/NumberInput.md +4 -0
  153. package/docs/Popover.md +11 -5
  154. package/docs/ProgressBar.md +2 -0
  155. package/docs/SearchInput.md +4 -0
  156. package/docs/SplitButton.md +8 -4
  157. package/docs/ThemeProvider.md +1 -1
  158. package/docs/VirtualList.md +2 -2
  159. package/package.json +1 -1
  160. package/dist/esm/common/floatingMenu/floatingMenu.mjs +0 -2
  161. package/dist/esm/common/floatingMenu/floatingMenu.mjs.map +0 -1
  162. package/dist/esm/common/floatingMenu/floatingMenu.module.mjs +0 -2
  163. package/dist/esm/common/floatingMenu/floatingMenu.module.mjs.map +0 -1
  164. package/dist/esm/common/floatingMenu/floatingMenu.types.mjs +0 -2
  165. package/dist/esm/common/floatingMenu/floatingMenu.types.mjs.map +0 -1
  166. package/dist/esm/common/floatingMenu/useFloatingPosition.mjs +0 -2
  167. package/dist/esm/common/floatingMenu/useFloatingPosition.mjs.map +0 -1
  168. package/dist/types/common/floatingMenu/floatingMenu.d.ts +0 -3
  169. package/dist/types/common/floatingMenu/floatingMenu.types.d.ts +0 -32
  170. package/dist/types/common/floatingMenu/index.d.ts +0 -2
  171. package/dist/types/common/floatingMenu/useFloatingPosition.d.ts +0 -27
  172. package/docs/FloatingMenu.md +0 -90
@@ -1,13 +1,18 @@
1
- import { HtmlProps } from '../types/slots.types';
1
+ import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
2
2
  export interface Progress {
3
3
  amount: number;
4
4
  label?: string;
5
5
  color?: string;
6
6
  class?: string;
7
7
  }
8
+ export type ProgressBarSlots = 'root' | 'fill' | 'label';
8
9
  export interface ProgressBarProps extends HtmlProps {
9
10
  className?: string | undefined;
10
11
  total?: number;
11
12
  progress?: number | Progress | Progress[];
12
13
  style?: React.CSSProperties | undefined;
14
+ /** Classes for the inner elements. See `ProgressBarSlots`. */
15
+ classNames?: SlotClassNames<ProgressBarSlots>;
16
+ /** Inline styles for the inner elements. See `ProgressBarSlots`. */
17
+ styles?: SlotStyles<ProgressBarSlots>;
13
18
  }
@@ -1,3 +1,3 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { SearchInputProps } from './searchInput.types';
3
- export default function SearchInput({ value, onChange, onDebounce, placeholder, label, debounceMs, totalAmount, showAmount, className, width, ...rest }: SearchInputProps): ReactElement;
3
+ export default function SearchInput({ value, onChange, onDebounce, placeholder, label, debounceMs, totalAmount, showAmount, className, width, style, classNames, styles: slotStyles, labels, ...rest }: SearchInputProps): ReactElement;
@@ -1,4 +1,10 @@
1
- import { HtmlProps } from '../types/slots.types';
1
+ import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
2
+ export type SearchInputSlots = 'root' | 'input' | 'resultCount';
3
+ /** Text SearchInput renders of its own, for translating it. */
4
+ export interface SearchInputLabels {
5
+ /** The result count, given the number (default `12 results`). */
6
+ results?: (count: number) => string;
7
+ }
2
8
  export interface SearchInputProps extends Omit<HtmlProps, 'onChange'> {
3
9
  value: string;
4
10
  onChange: (value: string) => void;
@@ -10,4 +16,10 @@ export interface SearchInputProps extends Omit<HtmlProps, 'onChange'> {
10
16
  showAmount?: boolean;
11
17
  className?: string;
12
18
  width?: number | string;
19
+ /** Classes for the inner elements. See `SearchInputSlots`. */
20
+ classNames?: SlotClassNames<SearchInputSlots>;
21
+ /** Inline styles for the inner elements. See `SearchInputSlots`. */
22
+ styles?: SlotStyles<SearchInputSlots>;
23
+ /** Overrides the text SearchInput renders of its own. Merged per key. */
24
+ labels?: SearchInputLabels;
13
25
  }
@@ -1,7 +1,7 @@
1
+ import { PopoverAlign } from '../popover/popover.types';
1
2
  import { CSSProperties, ReactNode } from 'react';
2
3
  import { IconContent } from '../types/icon.types';
3
4
  import { ButtonStyleType, ButtonSize } from '../button/button.types';
4
- import { Align } from '../floatingMenu/floatingMenu.types';
5
5
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
6
6
  export interface SplitButtonAction {
7
7
  id: string;
@@ -24,8 +24,8 @@ export interface SplitButtonProps extends HtmlProps {
24
24
  size?: ButtonSize;
25
25
  disabled?: boolean;
26
26
  isLoading?: boolean;
27
- /** Alignment of the actions menu relative to the trigger button (default `Align.Right`) */
28
- align?: Align;
27
+ /** Alignment of the actions menu relative to the trigger button (default `PopoverAlign.End`) */
28
+ align?: PopoverAlign;
29
29
  className?: string;
30
30
  style?: CSSProperties;
31
31
  classNames?: SlotClassNames<SplitButtonSlots>;
@@ -53,8 +53,6 @@ export { default as Fab } from './common/fab';
53
53
  export * from './common/fab';
54
54
  export { default as Flip } from './common/flip';
55
55
  export * from './common/flip';
56
- export { default as FloatingMenu } from './common/floatingMenu';
57
- export * from './common/floatingMenu';
58
56
  export * from './common/floorPlan';
59
57
  export { default as IconPicker } from './common/iconPicker';
60
58
  export * from './common/iconPicker';
@@ -73,6 +71,8 @@ export * from './common/kanbanBoard';
73
71
  export { default as KlipyPicker } from './common/klipyPicker';
74
72
  export * from './common/klipyPicker';
75
73
  export * from './common/loading';
74
+ export { default as Menu } from './common/menu';
75
+ export * from './common/menu';
76
76
  export { default as Modal } from './common/modal';
77
77
  export * from './common/modal';
78
78
  export { default as MultiSelect } from './common/multiSelect';
@@ -68,7 +68,7 @@ function Example() {
68
68
  // Long trails: collapse the middle behind a clickable ellipsis once items.length
69
69
  // exceeds maxItems. The ellipsis counts as one of the visible slots, so maxItems={3}
70
70
  // on a 4-item trail shows: Home / ... / Settings. Clicking the ellipsis opens a menu
71
- // (via FloatingMenu) listing the hidden items.
71
+ // (via Popover) listing the hidden items.
72
72
  <Breadcrumb
73
73
  items={[
74
74
  { label: 'Home', href: '/' },
@@ -80,7 +80,7 @@ function Example() {
80
80
  />
81
81
  ```
82
82
 
83
- **Requires:** `<div id="bodyEnd"></div>` in your HTML when `maxItems` is used and the trail actually collapses (the hidden-items menu is a `FloatingMenu`, which renders via portal).
83
+ **Requires:** `<div id="bodyEnd"></div>` in your HTML when `maxItems` is used and the trail actually collapses (the hidden-items menu is a `Popover`, which renders via portal).
84
84
 
85
85
  **BreadcrumbItem:**
86
86
 
@@ -81,6 +81,6 @@ Flattening reaches a child's visible border even when that border doesn't live o
81
81
 
82
82
  **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.
83
83
 
84
- **Note:** the corner-flattening rules key off DOM position (`:first-child`/`:last-child`), so they work for direct `Button`/`Input`/`Dropdown`-family children out of the box. A child that renders as a wrapper around its own button (e.g. a component that portals its real content elsewhere) won't get the flattening automatically. `SplitButton` is exactly that case (its second child is a `FloatingMenu`-wrapped button), which is why it sets its own corner overrides directly rather than relying on `ButtonGroup`'s automatic ones. See [SplitButton.md](SplitButton.md). A fully custom component whose CSS doesn't reference the `--group-radius-*` variables can still be flattened manually the same way, via its own `classNames`/`styles` slots if it exposes the right one.
84
+ **Note:** the corner-flattening rules key off DOM position (`:first-child`/`:last-child`), so they work for direct `Button`/`Input`/`Dropdown`-family children out of the box. A child that renders as a wrapper around its own button (e.g. a component that portals its real content elsewhere) won't get the flattening automatically. `SplitButton` is exactly that case (its second child is a `Popover`-wrapped button), which is why it sets its own corner overrides directly rather than relying on `ButtonGroup`'s automatic ones. See [SplitButton.md](SplitButton.md). A fully custom component whose CSS doesn't reference the `--group-radius-*` variables can still be flattened manually the same way, via its own `classNames`/`styles` slots if it exposes the right one.
85
85
 
86
86
  **Slots:** none — `ButtonGroup` has no named inner elements, only `className`/`style` on the root.
package/docs/CLAUDE.md CHANGED
@@ -142,7 +142,6 @@ FontAwesome internals, so they still take an `IconDefinition` only.
142
142
  @ErrorBoundary.md
143
143
  @Fab.md
144
144
  @Flip.md
145
- @FloatingMenu.md
146
145
  @FloorPlan.md
147
146
  @FormValidator.md
148
147
  @FormValidatorGroup.md
@@ -155,6 +154,7 @@ FontAwesome internals, so they still take an `IconDefinition` only.
155
154
  @KanbanBoard.md
156
155
  @KlipyPicker.md
157
156
  @Loading.md
157
+ @Menu.md
158
158
  @Modal.md
159
159
  @MultiSelect.md
160
160
  @NumberInput.md
package/docs/Card.md CHANGED
@@ -60,7 +60,7 @@ All sub-components are optional — use only what you need. Stacked sub-componen
60
60
  | `hoverEffect` | `boolean` | Adds hover elevation |
61
61
  | `tabIndex` | `number \| null` | For keyboard navigation |
62
62
  | `autoGap` | `boolean` | Space stacked sub-components apart with a shared gap (default `true`); set `false` to space them yourself |
63
- | `clip` | `boolean` | Clip content to the rounded corners (`overflow: hidden`, default `false`). Set `true` for full-bleed `CardMedia` or a clipped hover/ripple effect. Safe to enable even when the card holds a `Dropdown`/`FloatingMenu`/`Tooltip` — those popovers are portaled and always escape the card regardless of this setting |
63
+ | `clip` | `boolean` | Clip content to the rounded corners (`overflow: hidden`, default `false`). Set `true` for full-bleed `CardMedia` or a clipped hover/ripple effect. Safe to enable even when the card holds a `Dropdown`/`Popover`/`Tooltip` — those popovers are portaled and always escape the card regardless of this setting |
64
64
 
65
65
  **CardHeaderProps:**
66
66
 
@@ -82,3 +82,5 @@ All sub-components are optional — use only what you need. Stacked sub-componen
82
82
  Card is spaced by its font: the padding, the header's gap and thumbnail, the gap between title and subtitle, and the action row all scale with the theme's text size. The title is `1.125em` — 18px at a 16px base — rather than a fixed size, so it stays proportional to the body text beneath it.
83
83
 
84
84
  `CardMedia` bleeds to the card's edges with a negative margin that has to stay equal to the padding, so change the two together.
85
+
86
+ **Slots:** `root` `content`
package/docs/Chip.md CHANGED
@@ -68,3 +68,5 @@ import { Chip, ChipContainer, ChipStyleType } from '@ahrowe/ui';
68
68
  | `inline` | `boolean` | Inline layout (default wraps) |
69
69
 
70
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`.
71
+
72
+ **Slots:** `root` `inner` `thumbnail` `text` `removeIcon`
@@ -52,3 +52,7 @@ The inline swatch tracks the theme's text size, since it sits beside a label: `-
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
53
 
54
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`.
55
+
56
+ **Keyboard:** the swatch is a button, so `Enter` or `Space` opens the panel and puts the focus on the hue slider, the arrow keys move it, `Tab` walks the rest of the panel, and `Escape` closes and hands the focus back. The saturation and value square is a pointer control with no keyboard equivalent; the hex field covers the same ground for anyone not using a mouse.
57
+
58
+ **Slots:** `root` `swatch` `panel` `gradient` `hueSlider` `alphaSlider` `hexInput` `eyeDropper`
@@ -59,7 +59,7 @@ So a global default only fills in props a given instance left out — any instan
59
59
 
60
60
  ## App-wide `presentation`
61
61
 
62
- `FloatingMenu`, `Dropdown`, `Popover` and `Modal` can each present as a bottom sheet instead of anchored to their trigger (centred, for `Modal`). They're unrelated components, so setting that through `defaultProps` would mean four entries, and missing one would leave sheets in half an app. `presentation` is the single switch:
62
+ `Popover`, `Menu`, `Dropdown`, `MultiSelect` and `Modal` can each present as a bottom sheet instead of anchored to their trigger (centred, for `Modal`). They're unrelated components, so setting that through `defaultProps` would mean five entries, and missing one would leave sheets in half an app. `presentation` is the single switch:
63
63
 
64
64
  ```tsx
65
65
  // Bottom sheets on small touch screens, everywhere, in one line
@@ -93,7 +93,7 @@ Each entry is a `Partial<...Props>`, so any of that component's props can be def
93
93
 
94
94
  - **Form inputs:** `Input` · `Textarea` · `NumberInput` · `Dropdown` · `InputDropdown` · `Checkbox` · `Switch` · `RadioGroup` · `DatePicker` · `OptionPicker` · `OtpInput`
95
95
  - **Display:** `Button` · `ActionButtons` · `Badge` · `Chip` · `Card` · `SectionHeader` · `Skeleton` · `Accordion` · `Divider` · `Timer`
96
- - **Overlays:** `ConfirmModal` · `Modal` · `FloatingMenu` · `Popover`
96
+ - **Overlays:** `ConfirmModal` · `Modal` · `Popover` · `Popover`
97
97
  - **Editors:** `RoomDrawer`, `RoomViewer`
98
98
 
99
99
  ```tsx
@@ -80,3 +80,7 @@ const dateValidator = new FormValidator<Date | null>(null);
80
80
  | `showClearButton` | `boolean` | Show a clear/reset button |
81
81
  | `startDate` | `Date` | Range highlight start |
82
82
  | `endDate` | `Date` | Range highlight end |
83
+
84
+ **Slots:** `root` `input` `header` `monthDropdown` `yearDropdown` `calendar` `clearButton`
85
+
86
+ The root also takes the full native attribute set (`id`, `data-*`, `aria-*`, `title`, event handlers). `onChange` and `onSelect` report a `Date`, so the native handlers of those names are not available.
package/docs/Drawer.md CHANGED
@@ -62,3 +62,5 @@ function Example() {
62
62
  - It's an **inline** element with `height: 100%` and a `width` transition — place it in a flex/grid layout next to your main content; it doesn't overlay.
63
63
  - When `openedWidth` is omitted, the open width tracks the content's `scrollWidth` via a `ResizeObserver`, so it grows/shrinks with dynamic content.
64
64
  - For a full-screen overlay dialog instead, use `Modal`; for a collapsing scroll header, see `Overscroll`.
65
+
66
+ **Slots:** `root` `content` `inner`
package/docs/Dropdown.md CHANGED
@@ -62,6 +62,8 @@ const statusValidator = new FormValidator('', [Validators.required()]);
62
62
  | `presentation` | `Presentation` | Where the list opens: anchored to the input (`Default`), always a bottom sheet, or a sheet only on a small touch screen. Imported from the same package: `import { Presentation } from '@ahrowe/ui'` |
63
63
  | `alwaysFloatLabel` | `boolean` | Keep the material label floated in the border notch even when nothing is selected, so an empty dropdown reads as blank instead of showing the label as if it were the selected value. Only applies with `useMatLabelStyle` (default) |
64
64
 
65
- The list is rendered through a portal and tracks the trigger across scroll, so it always escapes clipping ancestors (cards, scroll containers, virtualized lists). It flips to open upward automatically when there's no room below, and shifts horizontally to stay within the viewport when the trigger sits near the left or right edge of the screen. It hides (without closing) if the trigger itself scrolls behind a clipping ancestor, reappearing once it's back in view. It also re-measures when its own content changes size while open, so a list that shrinks (e.g. filtered down to fewer entries) stays anchored to the trigger instead of hanging above it.
65
+ The list is rendered through a portal and tracks the trigger across scroll, so it always escapes clipping ancestors (cards, scroll containers, virtualized lists). The panel itself is a [Popover](Popover.md), so it flips, clamps and presents as a bottom sheet the same way every other anchored surface in the library does. It flips to open upward automatically when there's no room below, and shifts horizontally to stay within the viewport when the trigger sits near the left or right edge of the screen. It hides (without closing) if the trigger itself scrolls behind a clipping ancestor, reappearing once it's back in view. It also re-measures when its own content changes size while open, so a list that shrinks (e.g. filtered down to fewer entries) stays anchored to the trigger instead of hanging above it.
66
66
 
67
67
  **Bottom sheet:** `presentation={Presentation.Sheet}` drops the anchoring entirely and pins the list to the bottom of the screen instead, full width up to 560px, behind a backdrop, scrolling internally past 70% of the viewport height and lifting itself above an open on-screen keyboard, shrinking to fit the space that leaves rather than running off the top of the screen. Tapping the backdrop closes the list without also pressing whatever sits behind it. It is the same list with the same keyboard handling, so selection, highlighting and infinite scroll are unchanged. `Presentation.Auto` does that only when the pointer is coarse *and* the screen is under 768px wide. The default is `Presentation.Default`. A whole app can opt in with `<ConfigProvider defaultProps={{ Dropdown: { presentation: Presentation.Auto } }}>`, or for every sheet-capable component at once with `<ConfigProvider presentation={Presentation.Auto}>` — see [ConfigProvider.md](ConfigProvider.md).
68
+
69
+ **Slots:** `root` `label` `field` `control` `icon` `placeholder` `list` `option`
@@ -30,3 +30,5 @@ import { faInbox } from '@fortawesome/free-solid-svg-icons';
30
30
  | `className` | `string` | Root element class |
31
31
 
32
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`.
33
+
34
+ **Slots:** `root` `icon` `text`
package/docs/Fab.md CHANGED
@@ -57,3 +57,5 @@ import { faPlus, faPen, faCamera } from '@fortawesome/free-solid-svg-icons';
57
57
  ```
58
58
 
59
59
  `--fab-icon-size` is independent of `--fab-size`, so enlarging the button does not enlarge the glyph; set both. It applies to the speed-dial entries as well as the main button.
60
+
61
+ **Slots:** `root` `backdrop` `button` `icon` `menu` `menuEntry` `menuEntryLabel`
@@ -71,4 +71,4 @@ interface InputDropdownItem {
71
71
 
72
72
  Long lists are handled by capping how many matches are rendered (`maxItems`, default `100`) rather than by virtualizing: only about six rows fit in the scroll box, so anything past the cap is DOM the user never sees. Raise `maxItems` (or set it to `0`) if a list is genuinely meant to be scrolled end to end. For lists too large to pass as `items` at all, fetch on the server, keep the results in your own state and feed them in as `items`.
73
73
 
74
- The list is rendered through a portal and tracks the trigger across scroll, so it always escapes clipping ancestors (cards, scroll containers, virtualized lists). It flips to open upward automatically when there's no room below, and shifts horizontally to stay within the viewport when the trigger sits near the left or right edge of the screen. It hides (without closing) if the trigger itself scrolls behind a clipping ancestor, reappearing once it's back in view. It also re-measures when its own content changes size while open, so a list that shrinks (e.g. filtered down to fewer entries) stays anchored to the trigger instead of hanging above it.
74
+ The list is rendered through a portal and tracks the trigger across scroll, so it always escapes clipping ancestors (cards, scroll containers, virtualized lists). The panel itself is a [Popover](Popover.md), so it flips, clamps and presents as a bottom sheet the same way every other anchored surface in the library does. It flips to open upward automatically when there's no room below, and shifts horizontally to stay within the viewport when the trigger sits near the left or right edge of the screen. It hides (without closing) if the trigger itself scrolls behind a clipping ancestor, reappearing once it's back in view. It also re-measures when its own content changes size while open, so a list that shrinks (e.g. filtered down to fewer entries) stays anchored to the trigger instead of hanging above it.
@@ -2,6 +2,8 @@
2
2
 
3
3
  **When to use:** Accessible `<div>` that behaves like a button — click and keyboard (Enter/Space) events, with proper ARIA semantics. Use when you need a custom-styled clickable container that isn't a `<button>`.
4
4
 
5
+ Enter and Space dispatch a real click, the way a native button does, so `onClick` always receives a `MouseEvent` and a handler on an element above this one sees the activation too.
6
+
5
7
  **Keywords:** custom button div
6
8
 
7
9
  **Import:** `import { InteractableDiv } from '@ahrowe/ui'`
package/docs/Menu.md ADDED
@@ -0,0 +1,107 @@
1
+ # Menu
2
+
3
+ **When to use:** A list of actions hanging off a trigger — a row's "more" button, a toolbar overflow, a context menu on a card. Use [Popover](Popover.md) directly when the panel holds something other than a list of actions, and [Dropdown](Dropdown.md) when the user is picking a value rather than doing something.
4
+
5
+ **Keywords:** context menu, action menu, overflow menu, kebab menu, three dots, popup menu, right click, anchored list
6
+
7
+ **Import:** `import { Menu } from '@ahrowe/ui'`
8
+ **Types:** `import type { MenuProps, MenuEntry, MenuAction } from '@ahrowe/ui'`
9
+
10
+ **Requires:** `<div id="bodyEnd"></div>` in your app: the menu is portalled there, like every other anchored panel in the library.
11
+
12
+ ```tsx
13
+ import { Menu } from '@ahrowe/ui';
14
+ import type { MenuEntry } from '@ahrowe/ui';
15
+ import { faPen, faCopy, faTrash } from '@fortawesome/free-solid-svg-icons';
16
+
17
+ const items: MenuEntry[] = [
18
+ { id: 'edit', label: 'Edit', icon: faPen, shortcut: '⌘E' },
19
+ { id: 'duplicate', label: 'Duplicate', icon: faCopy },
20
+ { separator: true },
21
+ { id: 'delete', label: 'Delete', icon: faTrash, danger: true },
22
+ ];
23
+
24
+ <Menu items={items} onAction={(id) => run(id)}>
25
+ <Button>Actions</Button>
26
+ </Menu>;
27
+ ```
28
+
29
+ **Entries** are one of three shapes, and the list is read in the order you give it:
30
+
31
+ ```ts
32
+ { id: 'edit', label: 'Edit', icon, shortcut, disabled, danger, onClick } // an action
33
+ { separator: true } // a line
34
+ { heading: 'This document' } // a label over what follows
35
+ ```
36
+
37
+ 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.
38
+
39
+ ```tsx
40
+ // Grouped, with a heading over each group
41
+ <Menu
42
+ items={[
43
+ { heading: 'This document' },
44
+ { id: 'rename', label: 'Rename' },
45
+ { separator: true },
46
+ { heading: 'Elsewhere' },
47
+ { id: 'open', label: 'Open in a new tab' },
48
+ ]}
49
+ >
50
+ <ActionIcon icon={faEllipsisVertical} aria-label='More actions' />
51
+ </Menu>
52
+
53
+ // A sheet on a small touch screen, anchored everywhere else
54
+ <Menu items={items} presentation={Presentation.Auto}>
55
+ <Button>Open</Button>
56
+ </Menu>
57
+ ```
58
+
59
+ **Keyboard:** the menu takes one tab stop, and follows the menu-button pattern.
60
+
61
+ | Key | Does |
62
+ |-----|------|
63
+ | `↓` on the closed trigger | Open, focus the first entry |
64
+ | `↑` on the closed trigger | Open, focus the last entry |
65
+ | `↓` `↑` on the trigger of a menu opened by mouse | Move into the list, at the first or last entry |
66
+ | `↑` `↓` | Move between entries, wrapping, skipping disabled ones |
67
+ | `Home` `End` | First or last entry |
68
+ | a letter | Jump to the next entry that starts with it; keep typing to narrow, or repeat one letter to walk the matches |
69
+ | `Enter` `Space` | Run the focused entry |
70
+ | `Escape` | Close and hand focus back to the trigger |
71
+ | `Tab` | Close, and carry on to whatever follows the trigger, from any entry |
72
+ | `Shift+Tab` | Close, and hand focus back to the trigger |
73
+
74
+ A click opens the menu without moving the focus, so no entry is marked until you point at one or press an arrow key: a highlight that does not follow the mouse reads as a selection. Opening with the keyboard, including `Enter` and `Space`, puts the focus on an entry straight away.
75
+
76
+ Separators and headings are never focused. The trigger is given `aria-haspopup`, `aria-expanded` and `aria-controls` unless it sets them itself, so a screen reader announces it as a menu button.
77
+
78
+ **Key props:**
79
+
80
+ | Prop | Type | Description |
81
+ |------|------|-------------|
82
+ | `items` | `MenuEntry[]` | What the menu offers (required) |
83
+ | `children` | `ReactNode` | The trigger |
84
+ | `onAction` | `(id: string) => void` | Called with the id of the entry that was picked |
85
+ | `isOpen` | `boolean` | Controlled open state; leave it out to let the menu manage its own |
86
+ | `onOpenChange` | `(isOpen: boolean) => void` | Called when the menu wants to open or close |
87
+ | `placement` | `PopoverPlacement` | Which side of the trigger it opens on (default `Bottom`) |
88
+ | `align` | `PopoverAlign` | Which edge it lines up with (default `Start`) |
89
+ | `presentation` | `Presentation` | Anchored, a bottom sheet, or a sheet only on touch |
90
+
91
+ **Entry fields (`MenuAction`):**
92
+
93
+ | Field | Type | Description |
94
+ |-------|------|-------------|
95
+ | `id` | `string` | Stable id, also what `onAction` reports |
96
+ | `label` | `ReactNode` | What the entry says |
97
+ | `icon` | `IconContent` | Leading icon |
98
+ | `shortcut` | `ReactNode` | Hint on the trailing edge, e.g. `⌘K`. Display only: binding it is yours |
99
+ | `disabled` | `boolean` | Not clickable, and skipped by the arrow keys |
100
+ | `danger` | `boolean` | Renders in the destructive colour |
101
+ | `onClick` | `() => void` | Runs before `onAction` |
102
+
103
+ **Slots:** `root` `trigger` `menu` `item` `itemIcon` `itemLabel` `itemShortcut` `separator` `heading`
104
+
105
+ `root` and `trigger` generate no box, so your trigger keeps the place it would have had without a Menu around it. A `className` or `style` on those two cannot paint; style the trigger you pass in instead.
106
+
107
+ **No submenus yet.** An entry is a single action. Nesting brings its own interaction model — hover intent, a second layer of focus management, and a different shape again as a sheet on touch — so it is a separate piece of work rather than a field on `MenuAction`.
package/docs/Modal.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  The panel itself never scrolls: the header is fixed and the children scroll inside the `body` slot, so the title and close button stay visible no matter how long the content is.
12
12
 
13
- While open, the modal traps focus (focus moves into the panel and returns to whatever was focused before on close), locks scrolling on the page behind it, and closes on Escape. Modals nest correctly: Escape closes only the topmost one, and the page stays locked until the last one closes. The same applies to anything opened from inside a modal — a `FloatingMenu`, `Dropdown` or `Popover` counts as the layer above it, so Escape closes that first, and the modal's focus trap leaves focus alone while it's open (all three render through the same portal, so their DOM sits beside the modal's panel rather than inside it).
13
+ While open, the modal traps focus (focus moves into the panel and returns to whatever was focused before on close), locks scrolling on the page behind it, and closes on Escape. Modals nest correctly: Escape closes only the topmost one, and the page stays locked until the last one closes. The same applies to anything opened from inside a modal — a `Menu`, `Dropdown` or `Popover` counts as the layer above it, so Escape closes that first, and the modal's focus trap leaves focus alone while it's open (all three render through the same portal, so their DOM sits beside the modal's panel rather than inside it).
14
14
 
15
15
  ```tsx
16
16
  import { Modal, ActionButtons } from '@ahrowe/ui';
@@ -126,3 +126,7 @@ const qty = new FormValidator<number | undefined>(undefined, [inRange(10, 100)])
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
127
 
128
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`.
129
+
130
+ **Slots:** `root` `input` `arrows` `increaseButton` `decreaseButton`
131
+
132
+ With `arrowsLayout='split'` the root is the group around the two buttons and the field; otherwise it is the field itself.
package/docs/Popover.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Popover
2
2
 
3
- **When to use:** A floating panel anchored to a trigger — profile cards, quick forms, action menus, help panels, filter dropdowns. Reach for `Tooltip` for plain text hints, `FloatingMenu` for a menu that mirrors the trigger width; use `Popover` when you need free content on any side with an arrow.
3
+ **When to use:** Any panel anchored to a trigger: profile cards, quick forms, action menus, pickers, filter dropdowns. It is the one anchored surface in the library, and it absorbed `FloatingMenu`, which used to be the other one. Reach for `Tooltip` instead when all you need is a plain text hint on hover.
4
4
 
5
- **Keywords:** hover card, info panel, bottom sheet, action sheet, mobile, touch
5
+ **Keywords:** hover card, info panel, bottom sheet, action sheet, mobile, touch, anchored panel, dropdown surface
6
6
 
7
7
  **Import:** `import { Popover } from '@ahrowe/ui'`
8
8
 
@@ -49,7 +49,7 @@ import { Popover, PopoverPlacement, PopoverAlign, PopoverTrigger } from '@ahrowe
49
49
  | `trigger` | `PopoverTrigger` | `Click` | What opens the popover |
50
50
  | `isOpen` | `boolean` | — | Controlled open state (omit for uncontrolled) |
51
51
  | `defaultOpen` | `boolean` | `false` | Initial open state when uncontrolled |
52
- | `onOpenChange` | `(isOpen: boolean) => void` | — | Open-state change callback |
52
+ | `onOpenChange` | `(isOpen, { fromKeyboard }) => void` | — | Open-state change callback. `fromKeyboard` says whether a key caused it, which is what a menu uses to decide between putting the focus on an entry and leaving it on the trigger |
53
53
  | `offset` | `number` | `10` | Gap in px between trigger and panel |
54
54
  | `withArrow` | `boolean` | `true` | Render the arrow pointing at the trigger |
55
55
  | `closeOnOutsideClick` | `boolean` | `true` | Close on click outside (ignored for hover trigger) |
@@ -58,15 +58,21 @@ import { Popover, PopoverPlacement, PopoverAlign, PopoverTrigger } from '@ahrowe
58
58
  | `closeDelay` | `number` | `120` | Hover close delay in ms |
59
59
  | `disabled` | `boolean` | `false` | Prevent opening |
60
60
  | `presentation` | `Presentation` | `Default` | Where the panel opens: anchored to the trigger, always a bottom sheet, or a sheet only on a small touch screen |
61
+ | `focusOnOpen` | `boolean \| 'keyboard'` | `'keyboard'` | Move focus into the panel on open, and claim `role="dialog"` with it. `'keyboard'` does it only when the open came from the keyboard, which is the only way into a panel portaled to the end of the document, while a click leaves the focus where the pointer already put it. `true` always, `false` never |
62
+ | `closeOnTriggerClick` | `boolean` | `true` | Whether clicking the trigger while open closes it. Off for a field that opens a picker, where a click to place the caret must not take the panel away |
63
+ | `width` | `'auto' \| 'trigger'` | `'auto'` | `'trigger'` gives the panel at least the trigger's width, for a list that lines up with its field |
64
+ | `constrainWidth` | `boolean` | `true` | Cap the panel at a readable measure. Off for a panel with its own width, a calendar or a media grid |
61
65
 
62
66
  **Slots:** `root` `trigger` `floating` `panel` `arrow` `backdrop` (`backdrop` only exists while presenting as a sheet)
63
67
 
68
+ `root` and `trigger` generate no box of their own, so your trigger sits in your layout exactly as it would without Popover around it: it is the flex item, the grid item, the block-level child, and a row that stretches its children reaches it. The trade is that a `className` or `style` on those two cannot paint a background, padding or a border. Style the trigger you pass in instead.
69
+
64
70
  **Bottom sheet:** `presentation={Presentation.Sheet}` drops the anchoring and the arrow, and pins the panel to the bottom of the screen instead — full width up to 560px, behind a backdrop, scrolling internally past 70% of the viewport height, lifted above an open on-screen keyboard, and shrunk to fit the space that leaves rather than running off the top of the screen. Scrolling on the page behind it is locked while it's open, and tapping the backdrop closes it without pressing whatever sits behind it (`closeOnOutsideClick={false}` leaves the backdrop inert but still blocking). `Presentation.Auto` applies that only when the pointer is coarse *and* the screen is under 768px wide.
65
71
 
66
72
  Only a `Click` trigger presents as a sheet. `Hover` and `Focus` open something small next to whatever caused them, which is the opposite of a screen-owning sheet, so they stay anchored regardless of `presentation`.
67
73
 
68
74
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Popover: { presentation: Presentation.Auto } }}`, or `presentation={Presentation.Auto}` on the provider itself to set every sheet-capable component at once. See [ConfigProvider.md](ConfigProvider.md).
69
75
 
70
- Requires a `<div id="bodyEnd"></div>` at the app root — the panel is portaled there, same as `Tooltip` and `FloatingMenu`.
76
+ Requires a `<div id="bodyEnd"></div>` at the app root — the panel is portaled there, same as `Tooltip` and `Menu`.
71
77
 
72
- **Keyboard & focus (click trigger):** the panel is portaled to the end of the DOM, so Tab can't reach it naturally. When the panel has focusable content, opening it (click trigger only) moves focus to the first focusable element and the panel gets `role="dialog"`. Tabbing past the last element closes the popover and moves focus to the element after the trigger; Shift+Tab past the first element, and Escape, close it and return focus to the trigger. This is non-modal — focus is not trapped, and hover/focus triggers don't steal focus. Escape closes only the innermost open layer, so a popover inside a `Modal` closes on the first press and leaves the modal open. Popovers with only static content don't manage focus or claim the dialog role.
78
+ **Keyboard & focus (click trigger):** the panel is portaled to the end of the DOM, so Tab can't reach it naturally. When the panel has focusable content and the keyboard opened it (Enter or Space on the trigger, or ArrowDown/ArrowUp), focus moves to the first focusable element and the panel gets `role="dialog"`; a pointer click leaves the focus where it already is. `focusOnOpen` forces either. Tabbing past the last element closes the popover and moves focus to the element after the trigger; Shift+Tab past the first element, and Escape, close it and return focus to the trigger. This is non-modal: focus is not trapped, and hover/focus triggers don't steal focus. Escape closes only the innermost open layer, so a popover inside a `Modal` closes on the first press and leaves the modal open. Popovers with only static content don't manage focus or claim the dialog role.
@@ -58,3 +58,5 @@ const theme: Theme = {
58
58
  // Or one-off via style
59
59
  <ProgressBar progress={60} style={{ '--progress-bar-track-color': '#2a2a2a' } as React.CSSProperties} />
60
60
  ```
61
+
62
+ **Slots:** `root` `fill` `label`
@@ -55,3 +55,7 @@ import { SearchInput } from '@ahrowe/ui';
55
55
  | `showAmount` | `boolean` | Show the result count line (default `true`) |
56
56
  | `width` | `number \| string` | Fixed width for the wrapper |
57
57
  | `className` | `string` | Root wrapper class |
58
+
59
+ **Slots:** `root` `input` `resultCount`
60
+
61
+ **Labels:** `results(count)` — the line under the field (default `12 results`). Pass `labels` to override it, on the component or app-wide through `ConfigProvider`. Type: `SearchInputLabels`.
@@ -1,13 +1,14 @@
1
1
  # SplitButton
2
2
 
3
- **When to use:** A primary action button with a small attached dropdown for closely related secondary actions, e.g. "Save ▾" opening "Save as draft" / "Save and close". Built from `Button`, `ButtonGroup`, and `FloatingMenu` internally, rather than a mechanism of its own.
3
+ **When to use:** A primary action button with a small attached dropdown for closely related secondary actions, e.g. "Save ▾" opening "Save as draft" / "Save and close". Built from `Button`, `ButtonGroup` and [Menu](Menu.md) internally, rather than a mechanism of its own. Reach for `Menu` on its own when there is no primary action to weld the list to.
4
4
 
5
5
  **Keywords:** dropdown button, button with menu
6
6
 
7
7
  **Import:** `import { SplitButton } from '@ahrowe/ui'`
8
- **Types:** `import type { SplitButtonAction, SplitButtonProps } from '@ahrowe/ui'`
8
+ **Types:** `import type { SplitButtonAction, SplitButtonProps } from '@ahrowe/ui'`
9
+ **Enums:** `import { PopoverAlign } from '@ahrowe/ui'` for `align`.
9
10
 
10
- **Requires:** `<div id="bodyEnd"></div>` in your HTML (the actions menu renders via `FloatingMenu`, which portals).
11
+ **Requires:** `<div id="bodyEnd"></div>` in your HTML (the actions menu is a `Menu`, which portals).
11
12
 
12
13
  ```tsx
13
14
  import { SplitButton } from '@ahrowe/ui';
@@ -73,11 +74,14 @@ import { ButtonStyleType } from '@ahrowe/ui';
73
74
  | `size` | `ButtonSize` | Size variant, shared by both buttons |
74
75
  | `disabled` | `boolean` | Disables both the primary and trigger button |
75
76
  | `isLoading` | `boolean` | Shows a spinner on the primary button (same as `Button`'s own `isLoading`) |
77
+ | `align` | `PopoverAlign` | Which edge of the group the menu lines up with (default `End`, the chevron's side). The menu is a [Menu](Menu.md), hence `Popover`'s enum |
76
78
 
77
79
  **`isLoading` vs. `disabled`:** `isLoading` only affects the primary button — the trigger and its menu stay usable while the primary action is in flight, e.g. to cancel via a menu action. To lock the whole control during the async action instead, pass both: `isLoading={isSaving} disabled={isSaving}`. Setting `disabled` also closes the menu if it happened to be open already, and blocks any in-flight click on a menu action.
78
80
 
79
81
  **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ SplitButton: { styleType: ButtonStyleType.Default } }}`. See [ConfigProvider.md](ConfigProvider.md).
80
82
 
81
- **Slots:** `root` `primaryButton` `triggerButton` `menu` `menuItem`
83
+ **Keyboard:** the list is a [Menu](Menu.md), so it behaves like one: `↓` `↑` move between actions and skip disabled ones, `Home` and `End` jump to either end, typing a letter jumps to the action that starts with it, `Enter` and `Space` run the focused action, and `Escape` closes and hands the focus back. A click opens the menu without taking the focus, so nothing is marked until you point at an action or press an arrow key; opening with the keyboard focuses the first action straight away. The trigger announces itself as a menu button.
84
+
85
+ **Slots:** `root` `primaryButton` `triggerButton` `menu` `menuItem` (`menu` is Menu's list, `menuItem` one of its rows)
82
86
 
83
87
  **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`.
@@ -175,5 +175,5 @@ through its own variable (`--slider-range-color`, `--pagination-active-backgroun
175
175
 
176
176
  Providers nest: an inner one overrides the variables it declares and inherits the rest, so a differently-themed panel inside a themed app works without restating the whole theme.
177
177
 
178
- A portal breaks that by default, because theme variables travel by CSS inheritance and a portal escapes the subtree. Anything rendered through `BodyEnd` — `Tooltip`, `Dropdown`, `InputDropdown`, `ColorPicker`, `FloatingMenu`, `Popover`, `Modal`, `Toast` — therefore carries the nearest provider's variables with it, so an open panel matches the panel that opened it rather than whatever wraps `#bodyEnd`.
178
+ A portal breaks that by default, because theme variables travel by CSS inheritance and a portal escapes the subtree. Anything rendered through `BodyEnd` — `Tooltip`, `Dropdown`, `InputDropdown`, `ColorPicker`, `Menu`, `Popover`, `Modal`, `Toast` — therefore carries the nearest provider's variables with it, so an open panel matches the panel that opened it rather than whatever wraps `#bodyEnd`.
179
179
 
@@ -7,7 +7,7 @@
7
7
  **Import:** `import { VirtualList } from '@ahrowe/ui'`
8
8
  **Types:** `import type { VirtualListProps, VirtualListColumn, VirtualListHandle, VirtualListSort } from '@ahrowe/ui'`
9
9
 
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.
10
+ **Requires:** `<div id="bodyEnd"></div>` in your app **only if** you use the column-visibility toggle (the gear menu) — its popover is a `Popover` that renders via portal. Lists without columns, or with `showColumnToggle={false}`, don't need it.
11
11
 
12
12
  ```tsx
13
13
  import { VirtualList } from '@ahrowe/ui';
@@ -367,4 +367,4 @@ This needs the list to have a **bounded height** (the default `height="100%"` in
367
367
 
368
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`.
369
369
 
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.
370
+ **In-row popovers:** Rows support inline popover content that overflows the row's bottom edge — e.g. a `Dropdown`, `InputDropdown`, `Tooltip`, or `Popover` 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/ui",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "A React UI component library with theming, CSS Modules, and TypeScript support",
5
5
  "keywords": [
6
6
  "components",
@@ -1,2 +0,0 @@
1
- import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import n from"../bodyEnd/bodyEnd.mjs";import r from"./floatingMenu.module.mjs";import i from"../styles/sheet.module.mjs";import a from"../sheetBackdrop/sheetBackdrop.mjs";import{useFloatingPosition as o}from"./useFloatingPosition.mjs";import{tabbablesIn as s,useFocusBoundary as c}from"../hooks/useFocusBoundary.mjs";import{Align as l,Presentation as u}from"./floatingMenu.types.mjs";import d from"../hooks/useSheet.mjs";import{useOverlay as f}from"../hooks/useOverlay.mjs";import{useEffect as p,useRef as m,useState as h}from"react";import g from"classnames";import{jsx as _,jsxs as v}from"react/jsx-runtime";function y(e){return e.charAt(0).toUpperCase()+e.slice(1)}function b(b){let{align:x=l.Left,children:S=null,content:C=null,isOpen:w=!1,onOpenChange:T=()=>{},dontCloseOnChildClick:E=!1,presentation:D,className:O,style:k,classNames:A,styles:j,...M}=e(`FloatingMenu`,b),N=m(null),P=m(null),F=m(null),I=m(T),[L,R]=h(10),z=m(!1);function B(){z.current=!0}p(()=>{I.current=T});function V(){if(w){E||T(!1);return}let e=F.current?.getBoundingClientRect();R(e?.width??10),T(!0)}let H=t(),U=D??H??u.Default,{isSheet:W,sheetStyle:G}=d(U);o({isOpen:w&&!W,triggerRef:F,anchorRef:P,panelRef:N,flipClassName:r.floatingMenuAnimFlip}),p(()=>{if(!w)return;function e(e){let t=N.current,n=F.current;if(!t||!n)return;let r=e.target;if(n===r||n.contains(r))return;let i=z.current;z.current=!1,(!(i||t===r||t.contains(r))||!E)&&I.current(!1)}return window.addEventListener(`mouseup`,e),()=>window.removeEventListener(`mouseup`,e)},[w,E]);let{restoreFocusToTrigger:K,focusAfterTrigger:q}=c({triggerRef:F,contentRef:N});f({isActive:w,containerRef:N,onEscape:()=>{N.current?.contains(document.activeElement)&&K(),I.current(!1)},trapFocus:!1,lockScroll:W});function J(e){if(e.key!==`Tab`)return;let t=s(N.current);if(t.length===0)return;let n=document.activeElement;!e.shiftKey&&n===t[t.length-1]?(e.preventDefault(),q(),I.current(!1)):e.shiftKey&&n===t[0]&&(e.preventDefault(),K(),I.current(!1))}return v(`div`,{...M,className:g(r.floatingMenu,O,A?.root),style:{...k,...j?.root},children:[_(`div`,{className:g(r.floatingMenuContent,A?.trigger),style:j?.trigger,ref:F,onClick:V,children:S}),w&&v(n,{children:[W&&_(a,{onClose:()=>I.current(!1),className:A?.backdrop,style:j?.backdrop}),v(`div`,{ref:P,className:g(W?i.sheet:r.floatingMenuAnim,A?.menu),style:{...W?G:{width:`${L}px`},...j?.menu},children:[!W&&_(`div`,{className:g(r.floatingMenuContainerFlag,r[`floatingMenuContainerFlagAlign${y(x)}`])}),_(`div`,{ref:N,className:g(r.floatingMenuContainer,W?i.sheetPanel:[r.floatingMenuContainerAnchored,r[`floatingMenuContainerAlign${y(x)}`]],A?.menuContainer),style:j?.menuContainer,onMouseUpCapture:B,onKeyDown:J,children:C})]})]})]})}export{b as default};
2
- //# sourceMappingURL=floatingMenu.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"floatingMenu.mjs","names":[],"sources":["../../../../package/common/floatingMenu/floatingMenu.tsx"],"sourcesContent":["import { useRef, useEffect, useState } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './floatingMenu.module.pcss';\nimport sheetStyles from '../styles/sheet.module.pcss';\nimport SheetBackdrop from '../sheetBackdrop';\nimport { useFloatingPosition } from './useFloatingPosition';\nimport { useFocusBoundary, tabbablesIn } from '../hooks/useFocusBoundary';\nimport { useSheet } from '../hooks/useSheet';\nimport { useOverlay } from '../hooks/useOverlay';\nimport { useComponentDefaults, usePresentationDefault } from '../configProvider';\nimport { Align, Presentation, FloatingMenuProps } from './floatingMenu.types';\n\nfunction capitalize(s: string) {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nfunction FloatingMenu(props: FloatingMenuProps) {\n const {\n align = Align.Left,\n children = null,\n content = null,\n isOpen = false,\n onOpenChange = () => {},\n dontCloseOnChildClick = false,\n presentation: presentationProp,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('FloatingMenu', props);\n const menuContainerRef = useRef<HTMLDivElement>(null);\n const menuAnimRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const onOpenChangeRef = useRef(onOpenChange);\n const [triggerWidth, setTriggerWidth] = useState(10);\n\n const contentInteractedRef = useRef(false);\n function handleContentMouseUp() {\n contentInteractedRef.current = true;\n }\n\n useEffect(() => {\n onOpenChangeRef.current = onOpenChange;\n });\n\n function handleTriggerClick() {\n if (isOpen) {\n if (!dontCloseOnChildClick) onOpenChange(false);\n return;\n }\n const rect = triggerRef.current?.getBoundingClientRect();\n setTriggerWidth(rect?.width ?? 10);\n onOpenChange(true);\n }\n\n // Explicit prop → this component's global default → the app-wide one → anchored.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation ?? Presentation.Default;\n const { isSheet, sheetStyle } = useSheet(presentation);\n\n // A sheet isn't anchored to anything, so there's nothing to track, flip or clamp.\n useFloatingPosition({\n isOpen: isOpen && !isSheet,\n triggerRef,\n anchorRef: menuAnimRef,\n panelRef: menuContainerRef,\n flipClassName: styles.floatingMenuAnimFlip,\n });\n\n useEffect(() => {\n if (!isOpen) return;\n\n function onMouseUp(event: MouseEvent) {\n const menuElem = menuContainerRef.current;\n const triggerElem = triggerRef.current;\n if (!menuElem || !triggerElem) return;\n\n const target = event.target as Node;\n if (triggerElem === target || triggerElem.contains(target)) return;\n\n const contentInteracted = contentInteractedRef.current;\n contentInteractedRef.current = false;\n const clickedInMenu = contentInteracted || menuElem === target || menuElem.contains(target);\n\n if (!clickedInMenu || !dontCloseOnChildClick) {\n onOpenChangeRef.current(false);\n }\n }\n\n window.addEventListener('mouseup', onMouseUp);\n return () => window.removeEventListener('mouseup', onMouseUp);\n }, [isOpen, dontCloseOnChildClick]);\n\n const { restoreFocusToTrigger, focusAfterTrigger } = useFocusBoundary({\n triggerRef,\n contentRef: menuContainerRef,\n });\n\n // Registered for as long as the menu is open, not just as a sheet: this is what puts it on the\n // overlay stack, so an enclosing Modal knows something sits above it. Without that, the modal\n // stays topmost and its focus trap drags focus out of this menu — which lives outside the\n // modal's panel, in the portal — and Escape closes the modal instead of just this menu.\n // Focus is deliberately not trapped: the menu doesn't take focus on open (see\n // `handleMenuKeyDown`), so there'd be nothing inside to trap.\n useOverlay({\n isActive: isOpen,\n containerRef: menuContainerRef,\n onEscape: () => {\n if (menuContainerRef.current?.contains(document.activeElement)) restoreFocusToTrigger();\n onOpenChangeRef.current(false);\n },\n trapFocus: false,\n lockScroll: isSheet,\n });\n\n // The menu is portaled to the end of the DOM, so Tab never flows into it naturally. When\n // `content` has real focusable elements, close on Tab out of either end and continue focus as\n // if the menu sat right after the trigger. Escape is handled by `useOverlay` above instead, so\n // that it works from the trigger too and closes only the innermost open layer.\n // Unlike Popover, this doesn't also auto-focus the\n // first element on open, content here ranges from menus to live controls like ColorPicker's\n // hue slider, where grabbing focus on open would let a stray arrow-key press nudge a value the\n // user never interacted with. Auto-focus-on-open is still worth doing per-consumer (e.g.\n // SplitButton focuses its first enabled action), just not assumed generically here.\n function handleMenuKeyDown(event: React.KeyboardEvent) {\n if (event.key !== 'Tab') return;\n const focusables = tabbablesIn(menuContainerRef.current);\n if (focusables.length === 0) return;\n const active = document.activeElement;\n if (!event.shiftKey && active === focusables[focusables.length - 1]) {\n event.preventDefault();\n focusAfterTrigger();\n onOpenChangeRef.current(false);\n } else if (event.shiftKey && active === focusables[0]) {\n event.preventDefault();\n restoreFocusToTrigger();\n onOpenChangeRef.current(false);\n }\n }\n\n return (\n <div\n {...rest}\n className={cx(styles.floatingMenu, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <div\n className={cx(styles.floatingMenuContent, classNames?.trigger)}\n style={slotStyles?.trigger}\n ref={triggerRef}\n onClick={handleTriggerClick}\n >\n {children}\n </div>\n {isOpen && (\n <BodyEnd>\n {isSheet && (\n <SheetBackdrop\n onClose={() => onOpenChangeRef.current(false)}\n className={classNames?.backdrop}\n style={slotStyles?.backdrop}\n />\n )}\n <div\n ref={menuAnimRef}\n className={cx(isSheet ? sheetStyles.sheet : styles.floatingMenuAnim, classNames?.menu)}\n style={{\n // A sheet spans the screen; only an anchored menu takes the trigger's width.\n ...(isSheet ? sheetStyle : { width: `${triggerWidth}px` }),\n ...slotStyles?.menu,\n }}\n >\n {!isSheet && (\n <div\n className={cx(\n styles.floatingMenuContainerFlag,\n styles[`floatingMenuContainerFlagAlign${capitalize(align)}`],\n )}\n />\n )}\n <div\n ref={menuContainerRef}\n className={cx(\n styles.floatingMenuContainer,\n isSheet\n ? sheetStyles.sheetPanel\n : [styles.floatingMenuContainerAnchored, styles[`floatingMenuContainerAlign${capitalize(align)}`]],\n classNames?.menuContainer,\n )}\n style={slotStyles?.menuContainer}\n onMouseUpCapture={handleContentMouseUp}\n onKeyDown={handleMenuKeyDown}\n >\n {content}\n </div>\n </div>\n </BodyEnd>\n )}\n </div>\n );\n}\n\nexport default FloatingMenu;\n"],"mappings":"0wBAcA,SAAS,EAAW,EAAW,CAC7B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,EAAI,EAAE,MAAM,CAAC,CAC9C,CAEA,SAAS,EAAa,EAA0B,CAC9C,GAAM,CACJ,QAAQ,EAAM,KACd,WAAW,KACX,UAAU,KACV,SAAS,GACT,mBAAqB,CAAC,EACtB,wBAAwB,GACxB,aAAc,EACd,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,eAAgB,CAAK,EACxC,EAAmB,EAAuB,IAAI,EAC9C,EAAc,EAAuB,IAAI,EACzC,EAAa,EAAuB,IAAI,EACxC,EAAkB,EAAO,CAAY,EACrC,CAAC,EAAc,GAAmB,EAAS,EAAE,EAE7C,EAAuB,EAAO,EAAK,EACzC,SAAS,GAAuB,CAC9B,EAAqB,QAAU,EACjC,CAEA,MAAgB,CACd,EAAgB,QAAU,CAC5B,CAAC,EAED,SAAS,GAAqB,CAC5B,GAAI,EAAQ,CACL,GAAuB,EAAa,EAAK,EAC9C,MACF,CACA,IAAM,EAAO,EAAW,SAAS,sBAAsB,EACvD,EAAgB,GAAM,OAAS,EAAE,EACjC,EAAa,EAAI,CACnB,CAGA,IAAM,EAAkB,EAAuB,EACzC,EAAe,GAAoB,GAAmB,EAAa,QACnE,CAAE,UAAS,cAAe,EAAS,CAAY,EAGrD,EAAoB,CAClB,OAAQ,GAAU,CAAC,EACnB,aACA,UAAW,EACX,SAAU,EACV,cAAe,EAAO,oBACxB,CAAC,EAED,MAAgB,CACd,GAAI,CAAC,EAAQ,OAEb,SAAS,EAAU,EAAmB,CACpC,IAAM,EAAW,EAAiB,QAC5B,EAAc,EAAW,QAC/B,GAAI,CAAC,GAAY,CAAC,EAAa,OAE/B,IAAM,EAAS,EAAM,OACrB,GAAI,IAAgB,GAAU,EAAY,SAAS,CAAM,EAAG,OAE5D,IAAM,EAAoB,EAAqB,QAC/C,EAAqB,QAAU,IAG3B,EAFkB,GAAqB,IAAa,GAAU,EAAS,SAAS,CAAM,IAEpE,CAAC,IACrB,EAAgB,QAAQ,EAAK,CAEjC,CAGA,OADA,OAAO,iBAAiB,UAAW,CAAS,MAC/B,OAAO,oBAAoB,UAAW,CAAS,CAC9D,EAAG,CAAC,EAAQ,CAAqB,CAAC,EAElC,GAAM,CAAE,wBAAuB,qBAAsB,EAAiB,CACpE,aACA,WAAY,CACd,CAAC,EAQD,EAAW,CACT,SAAU,EACV,aAAc,EACd,aAAgB,CACV,EAAiB,SAAS,SAAS,SAAS,aAAa,GAAG,EAAsB,EACtF,EAAgB,QAAQ,EAAK,CAC/B,EACA,UAAW,GACX,WAAY,CACd,CAAC,EAWD,SAAS,EAAkB,EAA4B,CACrD,GAAI,EAAM,MAAQ,MAAO,OACzB,IAAM,EAAa,EAAY,EAAiB,OAAO,EACvD,GAAI,EAAW,SAAW,EAAG,OAC7B,IAAM,EAAS,SAAS,cACpB,CAAC,EAAM,UAAY,IAAW,EAAW,EAAW,OAAS,IAC/D,EAAM,eAAe,EACrB,EAAkB,EAClB,EAAgB,QAAQ,EAAK,GACpB,EAAM,UAAY,IAAW,EAAW,KACjD,EAAM,eAAe,EACrB,EAAsB,EACtB,EAAgB,QAAQ,EAAK,EAEjC,CAEA,OACE,EAAC,MAAD,CACE,GAAI,EACJ,UAAW,EAAG,EAAO,aAAc,EAAW,GAAY,IAAI,EAC9D,MAAO,CAAE,GAAG,EAAO,GAAG,GAAY,IAAK,EAHzC,SAAA,CAKE,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,oBAAqB,GAAY,OAAO,EAC7D,MAAO,GAAY,QACnB,IAAK,EACL,QAAS,EAER,UACE,CAAA,EACJ,GACC,EAAC,EAAD,CAAA,SAAA,CACG,GACC,EAAC,EAAD,CACE,YAAe,EAAgB,QAAQ,EAAK,EAC5C,UAAW,GAAY,SACvB,MAAO,GAAY,QACpB,CAAA,EAEH,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EAAG,EAAU,EAAY,MAAQ,EAAO,iBAAkB,GAAY,IAAI,EACrF,MAAO,CAEL,GAAI,EAAU,EAAa,CAAE,MAAO,GAAG,EAAa,GAAI,EACxD,GAAG,GAAY,IACjB,EAPF,SAAA,CASG,CAAC,GACA,EAAC,MAAD,CACE,UAAW,EACT,EAAO,0BACP,EAAO,iCAAiC,EAAW,CAAK,IAC1D,CACD,CAAA,EAEH,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EACT,EAAO,sBACP,EACI,EAAY,WACZ,CAAC,EAAO,8BAA+B,EAAO,6BAA6B,EAAW,CAAK,IAAI,EACnG,GAAY,aACd,EACA,MAAO,GAAY,cACnB,iBAAkB,EAClB,UAAW,EAEV,SAAA,CACE,CAAA,CACF,CACE,CAAA,CAAA,CAAA,CAAA,CAER,GAET"}
@@ -1,2 +0,0 @@
1
- var e=`floatingMenu_E1XCy`,t=`floatingMenu-content_AmxkA`,n=`floatingMenu-container_w7k-Y`,r=`floatingMenu-container-anchored_-8CHY`,i=`floatingMenu-anim-flip_a4-Ri`,a=`floatingMenu-container-align-left_InKnZ`,o=`floatingMenu-container-align-right_a9OoS`,s=`floatingMenu-container-align-center_-8bGT`,c=`floatingMenu-container-flag_VdKHr`,l=`floatingMenu-container-flag-align-right_vxrRN`,u=`floatingMenu-container-flag-align-left_a2Vv-`,d=`floatingMenu-container-flag-align-center_SmaaY`,f=`floatingMenu-anim_5KB-f`,p=`showMenu_AprCh`,m={floatingMenu:e,"floatingMenu-content":`floatingMenu-content_AmxkA`,floatingMenuContent:t,"floatingMenu-container":`floatingMenu-container_w7k-Y`,floatingMenuContainer:n,"floatingMenu-container-anchored":`floatingMenu-container-anchored_-8CHY`,floatingMenuContainerAnchored:r,"floatingMenu-anim-flip":`floatingMenu-anim-flip_a4-Ri`,floatingMenuAnimFlip:i,"floatingMenu-container-align-left":`floatingMenu-container-align-left_InKnZ`,floatingMenuContainerAlignLeft:a,"floatingMenu-container-align-right":`floatingMenu-container-align-right_a9OoS`,floatingMenuContainerAlignRight:o,"floatingMenu-container-align-center":`floatingMenu-container-align-center_-8bGT`,floatingMenuContainerAlignCenter:s,"floatingMenu-container-flag":`floatingMenu-container-flag_VdKHr`,floatingMenuContainerFlag:c,"floatingMenu-container-flag-align-right":`floatingMenu-container-flag-align-right_vxrRN`,floatingMenuContainerFlagAlignRight:l,"floatingMenu-container-flag-align-left":`floatingMenu-container-flag-align-left_a2Vv-`,floatingMenuContainerFlagAlignLeft:u,"floatingMenu-container-flag-align-center":`floatingMenu-container-flag-align-center_SmaaY`,floatingMenuContainerFlagAlignCenter:d,"floatingMenu-anim":`floatingMenu-anim_5KB-f`,floatingMenuAnim:f,showMenu:p};export{m as default,e as floatingMenu,f as floatingMenuAnim,i as floatingMenuAnimFlip,n as floatingMenuContainer,s as floatingMenuContainerAlignCenter,a as floatingMenuContainerAlignLeft,o as floatingMenuContainerAlignRight,r as floatingMenuContainerAnchored,c as floatingMenuContainerFlag,d as floatingMenuContainerFlagAlignCenter,u as floatingMenuContainerFlagAlignLeft,l as floatingMenuContainerFlagAlignRight,t as floatingMenuContent,p as showMenu};
2
- //# sourceMappingURL=floatingMenu.module.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"floatingMenu.module.mjs","names":[],"sources":["../../../../package/common/floatingMenu/floatingMenu.module.pcss"],"sourcesContent":[".floatingMenu {\n position: relative;\n\n &-content {\n display: flex;\n align-items: stretch;\n height: 100%;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n }\n\n &-container {\n color: var(--text-color);\n font-family: var(--default-font);\n font-size: var(--default-font-size);\n box-shadow: var(--card-shadow);\n background: var(--background-modal);\n padding: 8px;\n border-radius: calc(var(--default-border-radius) + 4px);\n\n /* Positioning lives here rather than on `&-container` itself, so that sheet presentation\n can leave it off instead of having to override it. */\n &-anchored {\n position: absolute;\n margin-top: 10px;\n\n .floatingMenu-anim-flip & {\n top: auto;\n bottom: 0;\n margin-top: 0;\n margin-bottom: 10px;\n }\n }\n\n &-align {\n &-left {\n left: 0;\n }\n\n &-right {\n right: 0;\n }\n\n &-center {\n left: 50%;\n transform: translateX(-50%);\n }\n }\n\n &-flag {\n box-shadow: var(--card-shadow);\n background: var(--background-modal);\n height: 10px;\n width: 10px;\n position: absolute;\n margin-top: 5px;\n transform: rotate(45deg);\n\n .floatingMenu-anim-flip & {\n top: auto;\n bottom: 0;\n margin-top: 0;\n margin-bottom: 5px;\n }\n\n &-align-right {\n right: 8px;\n }\n\n &-align-left {\n left: 8px;\n }\n\n &-align-center {\n left: 50%;\n transform: translateX(-50%) rotate(45deg);\n }\n }\n }\n\n &-anim {\n transform: scale(0.7);\n opacity: 0.7;\n animation: showMenu 0.1s forwards;\n position: fixed;\n z-index: 1;\n }\n\n @keyframes showMenu {\n to {\n transform: scale(1);\n opacity: 1;\n }\n }\n}\n"],"mappings":""}
@@ -1,2 +0,0 @@
1
- var e=function(e){return e.Default=`default`,e.Sheet=`sheet`,e.Auto=`auto`,e}({}),t=function(e){return e.Left=`left`,e.Right=`right`,e.Center=`center`,e}({});export{t as Align,e as Presentation};
2
- //# sourceMappingURL=floatingMenu.types.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"floatingMenu.types.mjs","names":[],"sources":["../../../../package/common/floatingMenu/floatingMenu.types.ts"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport type { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';\n\n/** How a surface is presented on screen. */\nexport enum Presentation {\n /** Wherever the component normally puts it: anchored to its trigger, or centred for a Modal. */\n Default = 'default',\n /** Pinned to the bottom of the screen, full width up to a maximum. */\n Sheet = 'sheet',\n /** A sheet on touch devices with a small screen, anchored everywhere else. */\n Auto = 'auto',\n}\n\nexport enum Align {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\nexport type FloatingMenuSlots = 'root' | 'trigger' | 'menu' | 'menuContainer' | 'backdrop';\n\nexport interface FloatingMenuProps extends Omit<HtmlProps, 'content'> {\n align?: Align;\n children?: ReactNode;\n content?: ReactNode;\n isOpen?: boolean;\n onOpenChange?: (isOpen: boolean) => void;\n dontCloseOnChildClick?: boolean;\n /** Where the menu opens: anchored to the trigger (`Default`), pinned to the bottom of the\n * screen, or the latter only on a small touch screen. */\n presentation?: Presentation;\n className?: string;\n style?: CSSProperties;\n classNames?: SlotClassNames<FloatingMenuSlots>;\n styles?: SlotStyles<FloatingMenuSlots>;\n}\n"],"mappings":"AAIA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,QAAA,UAEA,EAAA,MAAA,QAEA,EAAA,KAAA,QACF,EAAA,CAAA,CAAA,EAEY,EAAL,SAAA,EAAA,OACL,GAAA,KAAA,OACA,EAAA,MAAA,QACA,EAAA,OAAA,UACF,EAAA,CAAA,CAAA"}
@@ -1,2 +0,0 @@
1
- import{isHiddenByAnyAncestor as e}from"../utils/scrollAncestors.mjs";import{getVisualViewportRect as t}from"../utils/visualViewport.mjs";import{useAnchorTracking as n}from"../hooks/useAnchorTracking.mjs";var r=8;function i({isOpen:i,triggerRef:a,anchorRef:o,panelRef:s,flipClassName:c,placement:l=`bottom`}){n({isOpen:i,triggerRef:a,elementRefs:[o],resizeRefs:[o,s],deps:[o,s,c,l],update:(n,i)=>{let a=o.current;if(!a)return;let u=n.getBoundingClientRect(),d=s.current?.offsetHeight??0,f=t(),p=f.top+f.height-u.bottom,m=u.top-f.top,h=l===`top`?m:p,g=h<d&&(l===`top`?p:m)>h,_=l===`top`?!g:g;a.classList.toggle(c,g),a.style.top=`${_?u.top-a.offsetHeight:u.bottom}px`,a.style.left=`${u.left}px`;let v=s.current;if(v){let e=v===a?0:v.offsetLeft,t=u.left+e,n=t+v.offsetWidth-(f.left+f.width-r),i=f.left+r-t;n>0?a.style.left=`${u.left-n}px`:i>0&&(a.style.left=`${u.left+i}px`)}a.style.visibility=e(u,i)?`hidden`:``},teardown:()=>{let e=o.current;e&&(e.style.top=``,e.style.left=``,e.style.visibility=``,e.classList.remove(c))}})}export{i as useFloatingPosition};
2
- //# sourceMappingURL=useFloatingPosition.mjs.map