@astryxdesign/core 0.1.8-canary.fe8657a → 0.1.9-canary.5eec8d9

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 (257) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/Avatar/Avatar.d.ts +18 -2
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +63 -7
  5. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +31 -2
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +208 -6
  8. package/dist/Breadcrumbs/index.d.ts +5 -0
  9. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  10. package/dist/Breadcrumbs/index.js +9 -1
  11. package/dist/Calendar/dayCellUtils.js +1 -1
  12. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  13. package/dist/Chat/ChatComposer.js +15 -1
  14. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  15. package/dist/Chat/ChatComposerInput.js +16 -0
  16. package/dist/Chat/ChatContext.d.ts +33 -0
  17. package/dist/Chat/ChatContext.d.ts.map +1 -1
  18. package/dist/Chat/ChatContext.js +16 -2
  19. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  20. package/dist/Chat/ChatDictationButton.js +1 -1
  21. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  22. package/dist/Chat/ChatLayoutScrollButton.js +4 -4
  23. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  24. package/dist/Chat/ChatSendButton.js +7 -1
  25. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  26. package/dist/Chat/ChatToolCalls.js +6 -6
  27. package/dist/Chat/index.d.ts +2 -1
  28. package/dist/Chat/index.d.ts.map +1 -1
  29. package/dist/Chat/index.js +1 -1
  30. package/dist/Chat/useSpeechRecognition.d.ts +0 -1
  31. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  32. package/dist/Chat/useSpeechRecognition.js +1 -1
  33. package/dist/CheckboxInput/CheckboxInput.js +1 -1
  34. package/dist/CheckboxList/CheckboxListItem.d.ts +20 -1
  35. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  36. package/dist/CheckboxList/CheckboxListItem.js +11 -1
  37. package/dist/ClickableCard/ClickableCard.js +2 -2
  38. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  39. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  40. package/dist/CommandPalette/CommandPalette.js +3 -1
  41. package/dist/CommandPalette/CommandPaletteGroup.d.ts.map +1 -1
  42. package/dist/CommandPalette/CommandPaletteGroup.js +9 -2
  43. package/dist/DateInput/DateInput.d.ts +47 -1
  44. package/dist/DateInput/DateInput.d.ts.map +1 -1
  45. package/dist/DateInput/DateInput.js +26 -2
  46. package/dist/DateInput/index.d.ts +1 -1
  47. package/dist/DateInput/index.d.ts.map +1 -1
  48. package/dist/Dialog/Dialog.d.ts +3 -0
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +55 -5
  51. package/dist/Dialog/DialogContext.d.ts +7 -0
  52. package/dist/Dialog/DialogContext.d.ts.map +1 -1
  53. package/dist/Dialog/DialogContext.js +2 -2
  54. package/dist/Dialog/DialogHeader.d.ts +5 -2
  55. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  56. package/dist/Dialog/DialogHeader.js +6 -1
  57. package/dist/Divider/Divider.js +1 -1
  58. package/dist/Field/Field.d.ts.map +1 -1
  59. package/dist/Field/Field.js +2 -3
  60. package/dist/Field/FieldLabel.d.ts +1 -1
  61. package/dist/Field/FieldLabel.d.ts.map +1 -1
  62. package/dist/Field/FieldLabel.js +42 -15
  63. package/dist/Icon/Icon.d.ts +7 -2
  64. package/dist/Icon/Icon.d.ts.map +1 -1
  65. package/dist/Item/Item.d.ts +6 -1
  66. package/dist/Item/Item.d.ts.map +1 -1
  67. package/dist/Item/Item.js +22 -1
  68. package/dist/Popover/Popover.d.ts.map +1 -1
  69. package/dist/Popover/Popover.js +3 -2
  70. package/dist/Popover/usePopover.d.ts.map +1 -1
  71. package/dist/Popover/usePopover.js +3 -9
  72. package/dist/SelectableCard/SelectableCard.js +2 -2
  73. package/dist/Slider/Slider.d.ts.map +1 -1
  74. package/dist/Slider/Slider.js +4 -1
  75. package/dist/Stack/stack.stylex.d.ts +9 -9
  76. package/dist/Table/BaseTable.d.ts.map +1 -1
  77. package/dist/Table/BaseTable.js +3 -2
  78. package/dist/Table/plugins/selection/useTableSelection.d.ts +2 -1
  79. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  80. package/dist/Table/plugins/tree/useTableTreeData.d.ts +16 -0
  81. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  82. package/dist/Table/plugins/tree/useTableTreeData.js +87 -0
  83. package/dist/Table/plugins/tree/useTableTreeState.d.ts +6 -0
  84. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -1
  85. package/dist/Table/plugins/tree/useTableTreeState.js +22 -1
  86. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  87. package/dist/Table/useBaseTablePlugins.js +5 -3
  88. package/dist/Thumbnail/Thumbnail.d.ts +24 -10
  89. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  90. package/dist/Thumbnail/Thumbnail.js +82 -77
  91. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +9 -0
  92. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +1 -0
  93. package/dist/Thumbnail/thumbnail.markers.stylex.js +14 -0
  94. package/dist/Timestamp/Timestamp.d.ts +3 -1
  95. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  96. package/dist/Timestamp/Timestamp.js +8 -6
  97. package/dist/Toast/useToast.d.ts.map +1 -1
  98. package/dist/Toast/useToast.js +2 -5
  99. package/dist/ToggleButton/ToggleButtonGroup.d.ts +0 -1
  100. package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
  101. package/dist/ToggleButton/ToggleButtonGroup.js +1 -1
  102. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +9 -1
  103. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
  104. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +8 -1
  105. package/dist/TreeList/TreeList.d.ts +12 -3
  106. package/dist/TreeList/TreeList.d.ts.map +1 -1
  107. package/dist/TreeList/TreeList.js +2 -0
  108. package/dist/TreeList/TreeListBranches.d.ts +5 -0
  109. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  110. package/dist/TreeList/TreeListBranches.js +10 -4
  111. package/dist/TreeList/TreeListItem.d.ts +8 -2
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +27 -7
  114. package/dist/TreeList/TreeListTypes.d.ts +25 -1
  115. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  116. package/dist/TreeList/index.d.ts +2 -2
  117. package/dist/TreeList/index.d.ts.map +1 -1
  118. package/dist/astryx.css +6 -10
  119. package/dist/astryx.umd.js +49 -49
  120. package/dist/astryx.umd.js.map +4 -4
  121. package/dist/docs-types.d.ts +8 -0
  122. package/dist/docs-types.d.ts.map +1 -1
  123. package/dist/hooks/index.d.ts +1 -0
  124. package/dist/hooks/index.d.ts.map +1 -1
  125. package/dist/hooks/index.js +2 -1
  126. package/dist/hooks/useDevWarning.d.ts +18 -0
  127. package/dist/hooks/useDevWarning.d.ts.map +1 -0
  128. package/dist/hooks/useDevWarning.js +49 -0
  129. package/dist/hooks/useOverflow.d.ts.map +1 -1
  130. package/dist/hooks/useOverflow.js +2 -3
  131. package/dist/i18n/resolve.d.ts.map +1 -1
  132. package/dist/i18n/resolve.js +7 -18
  133. package/dist/theme/Theme.d.ts.map +1 -1
  134. package/dist/theme/Theme.js +2 -7
  135. package/dist/theme/hct.d.ts +0 -1
  136. package/dist/theme/hct.d.ts.map +1 -1
  137. package/dist/theme/hct.js +1 -1
  138. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  139. package/dist/theme/syntax/defineSyntaxTheme.js +2 -1
  140. package/dist/utils/devWarning.d.ts +42 -0
  141. package/dist/utils/devWarning.d.ts.map +1 -0
  142. package/dist/utils/devWarning.js +88 -0
  143. package/dist/utils/index.d.ts +3 -1
  144. package/dist/utils/index.d.ts.map +1 -1
  145. package/dist/utils/index.js +3 -2
  146. package/dist/utils/plainDate.d.ts +30 -0
  147. package/dist/utils/plainDate.d.ts.map +1 -1
  148. package/dist/utils/plainDate.js +53 -0
  149. package/locales/en.json +12 -0
  150. package/locales/fr-FR.json +4 -0
  151. package/locales/pseudo.json +9 -0
  152. package/package.json +1 -1
  153. package/src/Avatar/Avatar.doc.mjs +10 -0
  154. package/src/Avatar/Avatar.test.tsx +156 -1
  155. package/src/Avatar/Avatar.tsx +96 -4
  156. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +26 -0
  157. package/src/Breadcrumbs/BreadcrumbItem.tsx +333 -14
  158. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -0
  159. package/src/Breadcrumbs/Breadcrumbs.test.tsx +284 -2
  160. package/src/Breadcrumbs/index.ts +27 -0
  161. package/src/Calendar/dayCellUtils.test.ts +5 -0
  162. package/src/Calendar/dayCellUtils.ts +1 -1
  163. package/src/Carousel/Carousel.doc.mjs +1 -0
  164. package/src/Chat/ChatComposer.doc.mjs +3 -3
  165. package/src/Chat/ChatComposer.test.tsx +82 -2
  166. package/src/Chat/ChatComposer.tsx +15 -0
  167. package/src/Chat/ChatComposerInput.test.tsx +16 -0
  168. package/src/Chat/ChatComposerInput.tsx +14 -0
  169. package/src/Chat/ChatContext.tsx +37 -10
  170. package/src/Chat/ChatDictationButton.tsx +4 -1
  171. package/src/Chat/ChatLayoutScrollButton.tsx +4 -1
  172. package/src/Chat/ChatSendButton.tsx +6 -0
  173. package/src/Chat/ChatToolCalls.tsx +12 -3
  174. package/src/Chat/index.ts +5 -1
  175. package/src/Chat/useSpeechRecognition.ts +1 -1
  176. package/src/CheckboxInput/CheckboxInput.tsx +1 -1
  177. package/src/CheckboxList/CheckboxList.test.tsx +72 -2
  178. package/src/CheckboxList/CheckboxListItem.doc.mjs +33 -5
  179. package/src/CheckboxList/CheckboxListItem.tsx +41 -5
  180. package/src/ClickableCard/ClickableCard.tsx +2 -2
  181. package/src/CommandPalette/CommandPalette.doc.mjs +1 -0
  182. package/src/CommandPalette/CommandPalette.tsx +3 -1
  183. package/src/CommandPalette/CommandPaletteGroup.test.tsx +71 -0
  184. package/src/CommandPalette/CommandPaletteGroup.tsx +12 -1
  185. package/src/ContextMenu/ContextMenu.test.tsx +193 -0
  186. package/src/DateInput/DateInput.doc.mjs +16 -0
  187. package/src/DateInput/DateInput.test.tsx +108 -0
  188. package/src/DateInput/DateInput.tsx +68 -3
  189. package/src/DateInput/index.ts +1 -0
  190. package/src/Dialog/Dialog.doc.mjs +2 -2
  191. package/src/Dialog/Dialog.test.tsx +77 -4
  192. package/src/Dialog/Dialog.tsx +77 -3
  193. package/src/Dialog/DialogContext.ts +9 -2
  194. package/src/Dialog/DialogHeader.doc.mjs +2 -2
  195. package/src/Dialog/DialogHeader.tsx +9 -2
  196. package/src/Divider/Divider.tsx +2 -2
  197. package/src/DropdownMenu/DropdownMenu.test.tsx +169 -0
  198. package/src/Field/Field.tsx +6 -5
  199. package/src/Field/FieldLabel.tsx +8 -0
  200. package/src/Icon/Icon.doc.mjs +3 -2
  201. package/src/Icon/Icon.tsx +7 -2
  202. package/src/Item/Item.test.tsx +40 -18
  203. package/src/Item/Item.tsx +38 -2
  204. package/src/List/List.test.tsx +9 -8
  205. package/src/MultiSelector/MultiSelector.test.tsx +10 -2
  206. package/src/Popover/Popover.tsx +7 -4
  207. package/src/Popover/usePopover.tsx +9 -13
  208. package/src/PowerSearch/PowerSearch.doc.mjs +14 -0
  209. package/src/PowerSearch/PowerSearch.test.tsx +20 -0
  210. package/src/SelectableCard/SelectableCard.tsx +2 -2
  211. package/src/Slider/Slider.test.tsx +13 -0
  212. package/src/Slider/Slider.tsx +6 -4
  213. package/src/Table/BaseTable.tsx +3 -5
  214. package/src/Table/plugins/selection/useTableSelection.tsx +2 -1
  215. package/src/Table/plugins/tree/useTableTreeData.test.tsx +135 -3
  216. package/src/Table/plugins/tree/useTableTreeData.tsx +128 -1
  217. package/src/Table/plugins/tree/useTableTreeState.test.tsx +104 -0
  218. package/src/Table/plugins/tree/useTableTreeState.tsx +46 -2
  219. package/src/Table/useBaseTablePlugins.ts +10 -6
  220. package/src/Table/useTableTreeData.doc.mjs +29 -0
  221. package/src/Table/useTableTreeState.doc.mjs +2 -2
  222. package/src/Thumbnail/Thumbnail.doc.mjs +16 -16
  223. package/src/Thumbnail/Thumbnail.test.tsx +82 -21
  224. package/src/Thumbnail/Thumbnail.tsx +139 -66
  225. package/src/Thumbnail/thumbnail.markers.stylex.ts +12 -0
  226. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  227. package/src/Timestamp/Timestamp.test.tsx +32 -0
  228. package/src/Timestamp/Timestamp.tsx +28 -8
  229. package/src/Toast/useToast.tsx +7 -8
  230. package/src/ToggleButton/ToggleButtonGroup.tsx +1 -1
  231. package/src/Tokenizer/Tokenizer.doc.mjs +14 -0
  232. package/src/Tokenizer/Tokenizer.test.tsx +46 -0
  233. package/src/TopNav/TopNav.test.tsx +45 -0
  234. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +3 -3
  235. package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +19 -2
  236. package/src/TreeList/TreeList.doc.mjs +22 -0
  237. package/src/TreeList/TreeList.test.tsx +232 -0
  238. package/src/TreeList/TreeList.tsx +18 -2
  239. package/src/TreeList/TreeListBranches.tsx +16 -2
  240. package/src/TreeList/TreeListItem.tsx +51 -11
  241. package/src/TreeList/TreeListTypes.ts +27 -1
  242. package/src/TreeList/index.ts +2 -2
  243. package/src/docs-types.ts +5 -0
  244. package/src/hooks/index.ts +2 -0
  245. package/src/hooks/useDevWarning.test.tsx +45 -0
  246. package/src/hooks/useDevWarning.ts +54 -0
  247. package/src/hooks/useOverflow.ts +8 -11
  248. package/src/i18n/InternationalizationProvider.doc.mjs +99 -2
  249. package/src/i18n/resolve.ts +9 -21
  250. package/src/theme/Theme.tsx +11 -14
  251. package/src/theme/hct.ts +1 -1
  252. package/src/theme/syntax/defineSyntaxTheme.ts +5 -6
  253. package/src/utils/devWarning.test.ts +56 -0
  254. package/src/utils/devWarning.ts +102 -0
  255. package/src/utils/index.ts +6 -0
  256. package/src/utils/plainDate.test.ts +22 -0
  257. package/src/utils/plainDate.ts +61 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # @xds/core
2
2
 
3
+ # 0.1.9
4
+
5
+ #### New Features
6
+
7
+ - Avatar: add a `tooltip?: string | boolean` prop for a name-on-hover tooltip. Omitting it (or `true`) shows the avatar's `name` on hover and keyboard focus; a string shows that text instead (no need to wrap in `Tooltip`); `false` disables it. Avatar owns the tooltip via the existing Tooltip hook, so there's no extra wrapper DOM. Because this adds a default tooltip to every existing named Avatar, set `tooltip={false}` when you supply your own `Tooltip`/`HoverCard` overlay. The root `aria-label` (`alt || name`) is unchanged; the default name tooltip is visual-only (no `aria-describedby` double-announce), while a custom string tooltip is exposed as a description. Decorative avatars (no `name`/`alt`) get no tooltip. (#4164)
8
+ - BreadcrumbItem gains a `menu` prop that turns a crumb into a menu trigger for switching between sibling destinations. It accepts the same item API as DropdownMenu/MoreMenu/ContextMenu (a `DropdownMenuOption[]` array or composed item children), so existing menu-item definitions drop into a breadcrumb with no rewrite. The item components are also re-exported under `Breadcrumb*` aliases.
9
+ - Calendar: make the today/selected day-cell ring precisely themeable. The day cell now reflects a compound `marker` state (`today-only` / `today-in-range`) that maps 1:1 to the treatment actually drawn, so `defineTheme({components: {'calendar-day': {'marker:today-only': {...}}}})` targets exactly those states without over-matching or needing a `:not()` exclusion. Default rendering is unchanged.
10
+ - Calendar: add a dedicated `astryx-calendar-nav` theme target for the prev/next month-nav buttons, so consumers can theme the nav controls (color, radius, per-direction, disabled edge) without reaching every Button via the global `astryx-button` handle. Reflects `nav` (prev/next) and the `disabled` state as data attributes.
11
+ - ChatComposer: make custom inputs first-class. `useChatComposerContext()` and its types are now public, so any input in the `input` slot can read `value`/`onChange`/`onSubmit`/`canSend`/`placeholder`/`isDisabled` and drive the shell's send button. Inputs can register a focus control on `inputControlRef` so click-to-focus works for any input shape (not just `contenteditable`/`textarea`); the shell keeps a DOM-query fallback for uninstrumented inputs.
12
+ - ChatComposerInput: add an `onKeyDown` seam so consumers can host platform- or app-specific key handling — e.g. `preventDefault()` Enter to insert a newline on a touch keyboard, or submit on Cmd/Ctrl+Enter. Enter also no longer submits mid-IME-composition.
13
+ - CommandPalette: add a dedicated `astryx-command-palette-group-heading` theme target on the group heading, so consumers can theme just the heading (e.g. its padding or typography) via `defineTheme` instead of a fragile structural selector. The group root keeps its own `astryx-command-palette-group` target.
14
+ - DateInput gains a `format` prop for the committed date value, reusing Timestamp's `format` vocabulary so the same literal renders the same date shape in both components. Named values are `date_long` (the default, "March 21, 2026"), `date` ("Mar 21, 2026"), `date_weekday` ("Wed, Mar 21, 2026"), and `system_date` ("2026-03-21"); a `(value) => string` function is also accepted for custom output. The `date_long` default is byte-identical to DateInput's previous long-month rendering, so existing usage is unchanged. This also extends Timestamp with two new shared members, `date_long` and `date_weekday`, giving the two components full value parity on the date-only formats. Formatting applies only to the committed value, never to text being typed.
15
+ - Add an `elevation` prop to configurable surfaces — Card, ClickableCard, SelectableCard, Button, IconButton, ButtonGroup, and Banner take the full `'none' | 'low' | 'med' | 'high'` scale; ChatComposer takes `'none' | 'low'`. Defaults preserve today's appearance (`none` everywhere except ChatComposer's `low`), so nothing changes unless you opt in. (#4146)
16
+ - OverflowList: add `maxVisibleItems` to cap the number of visible items (the ceiling partner to `minVisibleItems`) and `maxRows` for bounded multi-row wrapping — items wrap onto up to N rows, then collapse into the overflow indicator. Both props are optional and default to off, so single-line behavior is unchanged. See #4176.
17
+ - Add `useTableRowStatus`, a plugin that prepends a narrow column
18
+ signaling per-row status.
19
+ - Thumbnail: add `showRemoveOn` prop — `'hover'` (default) reveals the remove button on hover or keyboard focus and keeps it visible on touch; `'always'` shows it at rest.
20
+ - Table tree: add an optional expand-all/collapse-all header control (#4142)
21
+ `useTableTreeState` now returns an aggregate `isAllExpanded` state (`true` / `false` / `'indeterminate'`) and threads `expandAll` / `collapseAll` into `treeConfig`. `useTableTreeData` gains a `hasExpandAllControl` prop: when set, it renders an expand-all/collapse-all toggle in the tree column header, wired to that state, so consumers no longer need to hand-roll external buttons. Flat data stays a full no-op. This is the first affordance folded in from `useTableRowExpansion` as part of converging the two tree plugins.
22
+ - TreeList: add a dedicated `astryx-tree-list-chevron` theme target for the expand/collapse toggle, so consumers can theme the chevron (color, per open/closed state) via `defineTheme` instead of reaching it through the functional `[data-tree-toggle]` attribute. Reflects the open/closed state as a `data-state` attribute (`expanded`/`collapsed`); the functional `data-tree-toggle` hook is unchanged.
23
+ - TreeList: add a stable `astryx-tree-list-guide` theme target on the hierarchy guide (connector) line elements, so consumers can recolor or hide the guides through `defineTheme` (e.g. `backgroundColor`, or `display: 'none'` to hide them) instead of hiding the built-in connectors and reimplementing them with unlayered CSS.
24
+ - TreeList: add a dedicated `astryx-tree-list-item-label` theme target on the item's label text, so consumers can theme just the label (e.g. bold the selected item's label) via `defineTheme` instead of a fragile `button:not([data-tree-toggle]) > span` structural selector. Reflects the row's `selected` state as a `data-selected` attribute on the label.
25
+
26
+ #### Fixes
27
+
28
+ - AvatarStatusDot: pair each variant with a distinct built-in shape — success stays a filled dot, neutral renders as a ring, error gets a minus bar — so status no longer relies on colour alone (WCAG 2.1 SC 1.4.1, #4143). A rendered `icon` replaces the shape glyph at sizes where icons fit; themes can target the new stable `astryx-avatar-status-dot-glyph` class and its `data-shape` attribute — a stroked inline `<svg>` painted from the dot's `currentColor`.
29
+ - Button: link-rendered buttons (`href`) now expose `aria-busy` while loading, matching the `<button>` branch. Previously an interruptible loading link showed the spinner and announced "Loading" but carried no machine-readable busy state.
30
+ - Calendar only marks in-month date cells as today, preventing duplicate today indicators in multi-month views.
31
+ - Carousel: slides now expose APG slide semantics (role=group, aria-roledescription="slide", "Slide N of M" labels) instead of anonymous divs.
32
+ - Honor `prefers-reduced-motion` in ChatToolCalls (chevron rotation, expand/collapse), ChatLayoutScrollButton (pill show/hide), and ChatDictationButton (equalizer bars).
33
+ - Chat/useChatStreamScroll: the scroll-follow spring now respects `prefers-reduced-motion` — locked following, `scrollToBottom()`, and `lock()` fall back to the existing instant jump, so the transcript still tracks the bottom without animated travel. Follow-up promised in #3800.
34
+ - Chat: the composer drawer toggle now references its disclosed content via aria-controls, so assistive tech can navigate from the toggle to the drawer.
35
+ - ChatSendButton now forwards `className`, `style`, and pass-through attributes (`data-*`, `aria-*`, and other rest props) to the rendered button. Previously these were silently dropped. (#4190)
36
+ - Chat: tool-call error details are now exposed to screen readers and keyboard users instead of living only in a hover-only title attribute.
37
+ - CheckboxInput: the indeterminate mark now uses the `--radius-full` token instead of a hardcoded radius, for token consistency. No visual change.
38
+ - CheckboxList: items with rich (non-string) labels can now provide an accessibleLabel so their checkbox no longer announces as the literal "Checkbox".
39
+ - CodeBlock: collapsed code regions are now inert, so keyboard focus can no longer land on the invisible scroll container while collapsed.
40
+ - CommandPalette: forward BaseProps pass-through attributes (className, style, xstyle, data-_, aria-_) to the underlying Dialog. Previously these were silently dropped.
41
+ - CommandPalette: the search input (role=combobox) now has an accessible name by default, from the new label prop or the visible placeholder. Previously screen readers announced a nameless combobox.
42
+ - DateTimeInput: ArrowDown (and Alt+ArrowDown) in the date field now opens the calendar popover from the keyboard, matching DateInput and the advertised combobox pattern.
43
+ - Dialog: modals are now automatically labelled by their DialogHeader title via aria-labelledby, matching AlertDialog. Unnamed open dialogs warn in development.
44
+ - Dialog: the entry animation is disabled under prefers-reduced-motion, matching the Layer animation guards.
45
+ - Fix Divider rest-prop spread order so consumer-passed HTML attributes cannot overwrite the component's `role="separator"` or `aria-orientation`.
46
+ - FieldLabel now forwards className, style, xstyle, and pass-through attributes (data-_, aria-_, event handlers) to the rendered element. Previously these were accepted by the type but silently dropped.
47
+ - Field/FieldStatus: status and error messages are now announced through persistent live regions, so they are reliably read by screen readers regardless of when they appear.
48
+ - FileInput: the trigger's accessible name now includes the selected filenames, so screen-reader users can review what is attached when refocusing the control.
49
+ - Prevent PowerSearch edit popover from closing when selecting multi-select options via Enter (#4245)
50
+ - Selector/MultiSelector: PageUp/PageDown now jump the active option to the first/last match while searching, complementing Home/End which stay on text-caret movement.
51
+ - HoverCard: popups now expose a named dialog when the new label prop is set, and honest group semantics otherwise. Previously every hover card announced as an unnamed dialog.
52
+ - Item: aria-selected is now emitted only when the item's role permits it (option, tab, treeitem, grid cells), removing invalid ARIA from plain list items.
53
+ - MobileNav: the toggle button now exposes `aria-expanded` and references the nav drawer via `aria-controls`, so screen-reader users can tell whether the drawer is open and what the button controls. (#3721)
54
+ - MultiSelector: searching within the options popover now announces the number of matching results ("3 results" / "No results found") to screen readers, mirroring Selector and Typeahead. Previously filtering happened silently.
55
+ - SideNav/TopNav: collapsed heading popovers no longer wrap their menus in a modal dialog, and the heading button is no longer an invalid child of the menu.
56
+ - Slider now clamps controlled values to its minimum and maximum before positioning the thumb and exposing ARIA values.
57
+ - Slider: required sliders now convey the required state to screen readers through the thumb's accessible description (aria-required is invalid on the slider role).
58
+ - Make the CLI's `.mjs` sources fully strict-typecheckable (checkJs + JSDoc)
59
+ Annotated the entire CLI package so `tsconfig.strict.json` (full `strict` `checkJs` over `src`, `bin`, `scripts`, `docs`, and the emitted `templates`) reports zero errors — down from 1717. Fixes are JSDoc-only: no runtime logic changed, `.mjs` stays `.mjs`. Strict checking also surfaced and corrected several type-contract drifts: the `upgrade.run` response type (declared a `depsUpdated` field the command never emits, and omitted the real `integrations`/`filesChanged`/`transformsApplied`/`errors`), registered the emitted `theme.list`/`theme.add`/`layout.*` response types in the `--json` envelope union, and added `category?` to `ReferenceSection` in core's docs types (reference docs already emit it).
60
+ - Table: the selection plugin accepts getRowLabel so row checkboxes announce which row they select, instead of an undifferentiated "Select row".
61
+ - Text: an explicit `size` now overrides the font-size of a themed `type`. The `size` class lived in a lower cascade layer (`astryx-base`) than a theme's per-type font-size rule (`astryx-theme`), so `<Text type="supporting" size="xsm">` silently kept the type's size. Themes now re-emit the size classes in the theme layer so `size` wins as documented.
62
+ - Thumbnail: replace the hover box-shadow on interactive tiles with the same `::after` overlay treatment ClickableCard and SelectableCard use. All three now tint on hover with `--color-overlay-hover` (and `--color-overlay-pressed` on press), so interactive feedback is consistent across the card family.
63
+ - Thumbnail/TopNavMegaMenuFeaturedCard: images without alt text are now explicitly decorative instead of silently empty-alt, matching Avatar's handling.
64
+ - Thumbnail: the overlaid remove button now uses a fixed `--color-overlay` scrim with an `--color-on-dark` icon instead of adapting to the image's luminance, so it has reliable contrast on any image.
65
+ - Tokenizer: adding and removing tokens (including Backspace on an empty input) is now announced to screen readers. Previously tokens appeared and disappeared silently.
66
+ - TopNav: TopNavMegaMenu triggers now expose aria-controls, and the panel is a labeled group instead of an invalid modal-dialog-wrapped menu.
67
+ - TopNav: TopNavMenu popups now expose proper menu semantics (no modal dialog wrapper) with the full APG keyboard pattern (roving tabindex, arrow keys, typeahead).
68
+
69
+ #### Documentation
70
+
71
+ - docs(Tokenizer, PowerSearch): document and test the existing startIcon prop
72
+ Both components have shipped a working `startIcon?: ReactNode | IconType` prop for a while (`PowerSearch` forwards it verbatim to the internal `Tokenizer`, and it's already exercised in Storybook), but neither's `.doc.mjs` documented it and neither had test coverage — so it was invisible to `astryx component <Name> --dense`, the docsite props table, and anyone (human or AI) relying on those as the source of truth. No behavior change; adds the missing props-table entries (en/zh/dense) and colocated tests confirming the icon renders and forwards correctly.
73
+
74
+ #### Other Changes
75
+
76
+ - Compact per-row status signal (error, warning, unread, etc.) without a
77
+ dedicated status column: a colored status dot by default, or an icon when provided.
78
+ - `getStatus(item)` maps a row to `{color, icon?, label?}`, or `null` for no
79
+ indicator. `color` accepts a semantic status name (`success`/`error`/`warning`/`accent`/`red`/`green`/etc.) mapped to a theme token, or a raw CSS color as an escape hatch.
80
+ - `icon` renders the status as a shape signifier instead of the dot, which is
81
+ more accessible than color alone when multiple statuses coexist. `label` supplies the accessible name.
82
+ - Memoize `getStatus` with `useCallback` for a stable plugin identity.
83
+
84
+ #### Contributors
85
+
86
+ Thanks to everyone who contributed to this release:
87
+
88
+ - @AKnassa
89
+ - @bhamodi
90
+ - @cixzhang
91
+ - @ejc3
92
+ - @freddymeta
93
+ - @HelloOjasMutreja
94
+ - @humbertovirtudes
95
+ - @josephfarina
96
+ - @kentonquatman
97
+ - @Kevinjohn
98
+ - @saadpocalypse
99
+ - @yyq1025
100
+
101
+ ---
102
+
3
103
  # 0.1.8
4
104
 
5
105
  #### Breaking Changes
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @file Avatar.tsx
3
- * @input Uses React, HTMLAttributes, ReactNode, useState
3
+ * @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
4
+ * (Tooltip hook) for the optional name-on-hover tooltip
4
5
  * @output Exports Avatar component, AvatarProps, AvatarSize types
5
6
  * @position Core implementation; consumed by index.ts
6
7
  *
@@ -70,6 +71,19 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
70
71
  * Typically used for status indicators or badges.
71
72
  */
72
73
  status?: ReactNode;
74
+ /**
75
+ * Tooltip shown on hover (and keyboard focus).
76
+ * - omitted / `true`: show the avatar's `name`
77
+ * - a string: show that text instead
78
+ * - `false`: no tooltip
79
+ *
80
+ * The avatar owns this tooltip. It is NOT auto-disabled when wrapped in your
81
+ * own Tooltip/HoverCard — set `tooltip={false}` if you provide your own
82
+ * overlay. No tooltip is shown if `tooltip` is `true`/omitted and there is
83
+ * no (non-whitespace) `name`.
84
+ * @default true
85
+ */
86
+ tooltip?: string | boolean;
73
87
  }
74
88
  /**
75
89
  * Avatar component for displaying user profile pictures.
@@ -82,9 +96,11 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
82
96
  * <Avatar src="/user.jpg" name="John Doe" />
83
97
  * <Avatar name="Jane Smith" size="xl" />
84
98
  * <Avatar src="/user.jpg" status={<OnlineIndicator />} />
99
+ * <Avatar name="jsmith" tooltip="Jane Smith, Staff Engineer" />
100
+ * <Avatar name="Jane" tooltip={false} />
85
101
  * ```
86
102
  */
87
- export declare function Avatar({ alt, 'data-testid': testId, fallbackSrc, name, size, src, status, xstyle, className, style, ref, ...props }: AvatarProps): import("react").JSX.Element;
103
+ export declare function Avatar({ alt, 'data-testid': testId, fallbackSrc, name, size, src, status, tooltip, xstyle, className, style, ref, ...props }: AvatarProps): import("react").JSX.Element;
88
104
  export declare namespace Avatar {
89
105
  var displayName: string;
90
106
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../src/Avatar/Avatar.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AACxD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAsC5C;;;;;;GAMG;AACH,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;GAEG;AACH,KAAK,iBAAiB,GACpB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAgBpD;AAqFD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAsCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,EACrB,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,WAAW,EACX,IAAI,EACJ,IAAW,EACX,GAAG,EACH,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAwFb;yBArGe,MAAM"}
1
+ {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../src/Avatar/Avatar.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AACxD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAuC5C;;;;;;GAMG;AACH,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;GAEG;AACH,KAAK,iBAAiB,GACpB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAgBpD;AAkGD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAsCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,EACrB,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,WAAW,EACX,IAAI,EACJ,IAAW,EACX,GAAG,EACH,MAAM,EACN,OAAc,EACd,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAqJb;yBAnKe,MAAM"}
@@ -4,7 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file Avatar.tsx
7
- * @input Uses React, HTMLAttributes, ReactNode, useState
7
+ * @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
8
+ * (Tooltip hook) for the optional name-on-hover tooltip
8
9
  * @output Exports Avatar component, AvatarProps, AvatarSize types
9
10
  * @position Core implementation; consumed by index.ts
10
11
  *
@@ -20,8 +21,9 @@ import "../theme/tokens.stylex.js";
20
21
  import { colorVars, typographyVars, fontWeightVars, radiusVars } from "../theme/tokens.stylex.js";
21
22
  import { AvatarSizeContext } from "./AvatarSizeContext.js";
22
23
  import { useAvatarGroup } from "../AvatarGroup/AvatarGroupContext.js";
23
- import { mergeProps } from "../utils/index.js";
24
+ import { mergeProps, mergeRefs } from "../utils/index.js";
24
25
  import { themeProps } from "../utils/themeProps.js";
26
+ import { useTooltip } from "../Tooltip/useTooltip.js";
25
27
 
26
28
  /**
27
29
  * The offset ratio for positioning elements on a circle's edge at 45°.
@@ -35,7 +37,7 @@ import { themeProps } from "../utils/themeProps.js";
35
37
  *
36
38
  * This constant represents that ratio: (1 - 1/√2) / 2 ≈ 0.146
37
39
  */
38
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
40
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
39
41
  const CIRCLE_EDGE_OFFSET_RATIO = (1 - 1 / Math.SQRT2) / 2;
40
42
 
41
43
  /**
@@ -122,6 +124,11 @@ const styles = {
122
124
  status: {
123
125
  kVAEAm: "x10l6tqk",
124
126
  $$css: true
127
+ },
128
+ focusable: {
129
+ kI3sdo: "x17nn4n9",
130
+ kInvED: "x1wfwxd8 x7s97pk",
131
+ $$css: true
125
132
  }
126
133
  };
127
134
 
@@ -229,6 +236,8 @@ function DefaultIcon({
229
236
  * <Avatar src="/user.jpg" name="John Doe" />
230
237
  * <Avatar name="Jane Smith" size="xl" />
231
238
  * <Avatar src="/user.jpg" status={<OnlineIndicator />} />
239
+ * <Avatar name="jsmith" tooltip="Jane Smith, Staff Engineer" />
240
+ * <Avatar name="Jane" tooltip={false} />
232
241
  * ```
233
242
  */
234
243
  export function Avatar({
@@ -239,6 +248,7 @@ export function Avatar({
239
248
  size = 'md',
240
249
  src,
241
250
  status,
251
+ tooltip = true,
242
252
  xstyle,
243
253
  className,
244
254
  style,
@@ -262,18 +272,54 @@ export function Avatar({
262
272
  const avatarGroup = useAvatarGroup();
263
273
  const resolvedSize = avatarGroup?.size ?? size;
264
274
  const numericSize = useMemo(() => resolveSize(resolvedSize), [resolvedSize]);
265
- return /*#__PURE__*/_jsx(AvatarSizeContext, {
275
+
276
+ // Resolve the tooltip content:
277
+ // - `false` → no tooltip
278
+ // - a string → that string
279
+ // - `true` / omitted → the `name` (a whitespace-only name yields nothing)
280
+ // Note: the *visible* tooltip prefers `name` (not `alt`); the *accessible
281
+ // name* on the root still uses `alt || name` above, independent of this.
282
+ const tooltipContent = tooltip === false ? undefined : typeof tooltip === 'string' ? tooltip : name;
283
+ const trimmedTooltip = tooltipContent?.trim();
284
+ const showTooltip = trimmedTooltip != null && trimmedTooltip !== '';
285
+ // Whether the tooltip text is a consumer-authored override (a custom string)
286
+ // rather than the default name. A custom description is worth wiring to
287
+ // `aria-describedby` (it adds information, matching Button); the default name
288
+ // tooltip is visual-only — its text duplicates the root `aria-label`, so
289
+ // describing it too would double-announce the same name (OQ-4).
290
+ const isCustomTooltip = typeof tooltip === 'string';
291
+
292
+ // Own the name tooltip via the Tooltip hook (the Button pattern), which
293
+ // returns `describedBy` as a value we choose whether to apply — the only way
294
+ // to satisfy the per-case aria-describedby rule (default name: none; custom
295
+ // string: describe) without editing Tooltip. `focusTrigger: 'auto'` shows the
296
+ // tooltip on keyboard focus once the root is made focusable (below).
297
+ const tooltipHook = useTooltip({
298
+ placement: 'above',
299
+ isEnabled: showTooltip
300
+ });
301
+ const rootRef = mergeRefs(ref, showTooltip ? tooltipHook.ref : undefined);
302
+ const describedByProp = showTooltip && isCustomTooltip ? {
303
+ 'aria-describedby': [props['aria-describedby'], tooltipHook.describedBy].filter(Boolean).join(' ') || undefined
304
+ } : null;
305
+ const avatarElement = /*#__PURE__*/_jsx(AvatarSizeContext, {
266
306
  value: numericSize,
267
307
  children: /*#__PURE__*/_jsxs("div", {
268
308
  ...props,
269
- ref: ref,
309
+ ref: rootRef,
270
310
  role: isDecorative ? 'presentation' : 'img',
271
311
  "aria-label": isDecorative ? undefined : accessibleName,
272
- "aria-hidden": isDecorative || undefined,
312
+ "aria-hidden": isDecorative || undefined
313
+ // The root is a div[role="img"], not natively focusable. When a name
314
+ // tooltip is active, add a tab stop so keyboard users can reveal it
315
+ // (WCAG 1.4.13 / 2.1.1) — matching Timestamp/Button. Only while active.
316
+ ,
317
+ tabIndex: showTooltip ? 0 : undefined,
273
318
  "data-testid": testId,
319
+ ...describedByProp,
274
320
  ...mergeProps(themeProps('avatar', {
275
321
  size: resolvedSize
276
- }), stylex.props(styles.wrapper, avatarGroup && groupStyles.ring, avatarGroup && groupStyles.overlap, avatarGroup && groupDynamicStyles.overlap(-avatarGroup.overlap), xstyle), className, style),
322
+ }), stylex.props(styles.wrapper, showTooltip && styles.focusable, avatarGroup && groupStyles.ring, avatarGroup && groupStyles.overlap, avatarGroup && groupDynamicStyles.overlap(-avatarGroup.overlap), xstyle), className, style),
277
323
  children: [/*#__PURE__*/_jsxs("div", {
278
324
  ...stylex.props(styles.content, dynamicStyles.size(numericSize)),
279
325
  children: [showImage && /*#__PURE__*/_jsx("img", {
@@ -307,5 +353,15 @@ export function Avatar({
307
353
  })]
308
354
  })
309
355
  });
356
+ if (!showTooltip) {
357
+ return avatarElement;
358
+ }
359
+
360
+ // Render the tooltip as a sibling (no wrapper DOM) — the hook's ref is already
361
+ // attached to the root via `rootRef` above. `trimmedTooltip` is guaranteed
362
+ // non-empty here.
363
+ return /*#__PURE__*/_jsxs(_Fragment, {
364
+ children: [avatarElement, tooltipHook.renderTooltip(trimmedTooltip)]
365
+ });
310
366
  }
311
367
  Avatar.displayName = 'Avatar';
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * @file BreadcrumbItem.tsx
3
- * @input Uses React use/useRef/useEffect, stylex, theme tokens, BreadcrumbContext
3
+ * @input Uses React use/useRef/useEffect, stylex, theme tokens, BreadcrumbContext,
4
+ * and (for the menu trigger) usePopover + the shared DropdownMenu item API
5
+ * (renderDropdownItems, DropdownMenuContext, MENU_ITEM_SELECTOR, useListFocus)
4
6
  * @output Exports BreadcrumbItem component and BreadcrumbItemProps
5
7
  * @position Individual breadcrumb item; used inside Breadcrumbs
6
8
  *
9
+ * A `menu` prop turns the item's link-styled button into a menu trigger whose
10
+ * popover reuses DropdownMenu's item pipeline — the same wiring ContextMenu
11
+ * uses — so menu-item definitions are portable across the menu family.
12
+ *
7
13
  * SYNC: When modified, update these files to stay in sync:
8
14
  * - /packages/core/src/Breadcrumbs/Breadcrumbs.doc.mjs
9
15
  * - /packages/core/src/Breadcrumbs/Breadcrumbs.test.tsx
@@ -14,6 +20,8 @@
14
20
  import React, { type ReactNode, type MouseEvent } from 'react';
15
21
  import type { LinkComponentType } from '../Link/types';
16
22
  import type { BaseProps } from '../BaseProps';
23
+ import { type DropdownMenuSize } from '../DropdownMenu/DropdownMenuContext';
24
+ import type { DropdownMenuOption } from '../DropdownMenu/DropdownMenu';
17
25
  export interface BreadcrumbItemProps extends Omit<BaseProps<HTMLLIElement>, 'onClick'> {
18
26
  ref?: React.Ref<HTMLLIElement>;
19
27
  /**
@@ -44,6 +52,27 @@ export interface BreadcrumbItemProps extends Omit<BaseProps<HTMLLIElement>, 'onC
44
52
  * Optional icon rendered before the label.
45
53
  */
46
54
  startIcon?: ReactNode;
55
+ /**
56
+ * Menu opened when the item is activated. Accepts the SAME item API as
57
+ * DropdownMenu / MoreMenu / ContextMenu, so a consumer's existing menu-item
58
+ * definitions are portable into a breadcrumb with no rewrite:
59
+ * - a `DropdownMenuOption[]` data array (items, sections, dividers), or
60
+ * - composed `DropdownMenuItem` / `DropdownMenuCheckboxItem` /
61
+ * `DropdownMenuRadioGroup` children.
62
+ *
63
+ * When set, the item renders as a link-styled menu trigger (button + a
64
+ * trailing chevron, `aria-haspopup="menu"` / `aria-expanded`) whose popover
65
+ * is a `role="menu"` container that provides `DropdownMenuContext` and runs
66
+ * `useListFocus`. Takes precedence over `href` / `onClick` (which are
67
+ * ignored when `menu` is set — a dev-time warning is logged).
68
+ */
69
+ menu?: DropdownMenuOption[] | ReactNode;
70
+ /**
71
+ * Size passed to the menu items via `DropdownMenuContext` (item
72
+ * padding/typography). Defaults from the breadcrumb variant:
73
+ * `'supporting'` → `'sm'`, otherwise `'md'`.
74
+ */
75
+ menuSize?: DropdownMenuSize;
47
76
  }
48
77
  /**
49
78
  * An individual breadcrumb item. Renders as a link (`<a>`) or a span
@@ -59,7 +88,7 @@ export interface BreadcrumbItemProps extends Omit<BaseProps<HTMLLIElement>, 'onC
59
88
  * <BreadcrumbItem isCurrent>My Project</BreadcrumbItem>
60
89
  * ```
61
90
  */
62
- export declare function BreadcrumbItem({ ref, as, children, href, onClick, isCurrent: isCurrentProp, startIcon, xstyle, className, style, 'data-testid': testId, ...rest }: BreadcrumbItemProps): React.JSX.Element;
91
+ export declare function BreadcrumbItem({ ref, as, children, href, onClick, isCurrent: isCurrentProp, startIcon, menu, menuSize, xstyle, className, style, 'data-testid': testId, ...rest }: BreadcrumbItemProps): React.JSX.Element;
63
92
  export declare namespace BreadcrumbItem {
64
93
  var displayName: string;
65
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BreadcrumbItem.d.ts","sourceRoot":"","sources":["../../src/Breadcrumbs/BreadcrumbItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAIZ,KAAK,SAAS,EACd,KAAK,UAAU,EAChB,MAAM,OAAO,CAAC;AAKf,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAO5C,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAC/C,SAAS,CAAC,aAAa,CAAC,EACxB,SAAS,CACV;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/B;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAC/C;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAoFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,SAAS,EAAE,aAAa,EACxB,SAAS,EACT,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,qBAuJrB;yBApKe,cAAc"}
1
+ {"version":3,"file":"BreadcrumbItem.d.ts","sourceRoot":"","sources":["../../src/Breadcrumbs/BreadcrumbItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACd,KAAK,UAAU,EAChB,MAAM,OAAO,CAAC;AAUf,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAQ5C,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,qCAAqC,CAAC;AAK7C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,8BAA8B,CAAC;AAMrE,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAC/C,SAAS,CAAC,aAAa,CAAC,EACxB,SAAS,CACV;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/B;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAC/C;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,kBAAkB,EAAE,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AA8GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,SAAS,EAAE,aAAa,EACxB,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,qBAoMrB;yBAnNe,cAAc"}
@@ -4,10 +4,16 @@
4
4
 
5
5
  /**
6
6
  * @file BreadcrumbItem.tsx
7
- * @input Uses React use/useRef/useEffect, stylex, theme tokens, BreadcrumbContext
7
+ * @input Uses React use/useRef/useEffect, stylex, theme tokens, BreadcrumbContext,
8
+ * and (for the menu trigger) usePopover + the shared DropdownMenu item API
9
+ * (renderDropdownItems, DropdownMenuContext, MENU_ITEM_SELECTOR, useListFocus)
8
10
  * @output Exports BreadcrumbItem component and BreadcrumbItemProps
9
11
  * @position Individual breadcrumb item; used inside Breadcrumbs
10
12
  *
13
+ * A `menu` prop turns the item's link-styled button into a menu trigger whose
14
+ * popover reuses DropdownMenu's item pipeline — the same wiring ContextMenu
15
+ * uses — so menu-item definitions are portable across the menu family.
16
+ *
11
17
  * SYNC: When modified, update these files to stay in sync:
12
18
  * - /packages/core/src/Breadcrumbs/Breadcrumbs.doc.mjs
13
19
  * - /packages/core/src/Breadcrumbs/Breadcrumbs.test.tsx
@@ -15,14 +21,22 @@
15
21
  * - /apps/storybook/stories/Breadcrumbs.stories.tsx
16
22
  * - /packages/cli/templates/blocks/components/Breadcrumbs/ (showcase blocks)
17
23
  */
18
- import React, { use, useRef, useEffect } from 'react';
24
+ import React, { use, useCallback, useId, useMemo, useRef, useEffect } from 'react';
19
25
  import * as stylex from '@stylexjs/stylex';
20
26
  import "../theme/tokens.stylex.js";
21
- import { colorVars, spacingVars, typeScaleVars } from "../theme/tokens.stylex.js";
27
+ import { colorVars, spacingVars, typeScaleVars, radiusVars } from "../theme/tokens.stylex.js";
22
28
  import { BreadcrumbContext } from "./Breadcrumbs.js";
23
29
  import { useLinkComponent } from "../Link/useLinkComponent.js";
24
30
  import { mergeProps, mergeRefs } from "../utils/index.js";
25
31
  import { themeProps } from "../utils/themeProps.js";
32
+ import { getIcon } from "../Icon/globalIconRegistry.js";
33
+ import { usePopover } from "../Popover/usePopover.js";
34
+ import { useListFocus } from "../hooks/useListFocus.js";
35
+ import { useTypeahead } from "../hooks/useTypeahead.js";
36
+ import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
37
+ import { renderDropdownItems } from "../DropdownMenu/renderDropdownItems.js";
38
+ import { DropdownMenuContext } from "../DropdownMenu/DropdownMenuContext.js";
39
+ import { MENU_ITEM_ROLES, MENU_ITEM_SELECTOR } from "../DropdownMenu/menuItemRoles.js";
26
40
 
27
41
  // =============================================================================
28
42
  // Props
@@ -52,6 +66,13 @@ const itemStyles = {
52
66
  $$css: true
53
67
  }
54
68
  };
69
+ const menuStyles = {
70
+ popover: {
71
+ k7Eaqz: "x5w4yej",
72
+ kqGvvJ: "xsq74q5",
73
+ $$css: true
74
+ }
75
+ };
55
76
 
56
77
  // =============================================================================
57
78
  // Component
@@ -79,6 +100,8 @@ export function BreadcrumbItem({
79
100
  onClick,
80
101
  isCurrent: isCurrentProp,
81
102
  startIcon,
103
+ menu,
104
+ menuSize,
82
105
  xstyle,
83
106
  className,
84
107
  style,
@@ -95,6 +118,21 @@ export function BreadcrumbItem({
95
118
  const contentRef = useRef(null);
96
119
  const isCurrent = isCurrentProp === true;
97
120
  const isAutoCandidate = isCurrentProp == null;
121
+ const hasMenu = menu != null;
122
+ const resolvedMenuSize = menuSize ?? (isSupporting ? 'sm' : 'md');
123
+
124
+ // `menu` owns the click interaction; a label can't both navigate/act and
125
+ // open a menu on the same activation. Warn once so the (ignored) href/onClick
126
+ // don't silently disappear on the consumer.
127
+ useEffect(() => {
128
+ if (process.env.NODE_ENV !== 'production' && hasMenu) {
129
+ if (href != null) {
130
+ console.warn('BreadcrumbItem: `menu` and `href` are mutually exclusive; `menu` ' + 'takes precedence and `href` is ignored.');
131
+ } else if (onClick != null) {
132
+ console.warn('BreadcrumbItem: `menu` and `onClick` are mutually exclusive; ' + '`menu` takes precedence and `onClick` is ignored.');
133
+ }
134
+ }
135
+ }, [hasMenu, href, onClick]);
98
136
 
99
137
  // Auto-detect: if no sibling has aria-current="page" and this is the last
100
138
  // non-separator item, set aria-current on our content element.
@@ -152,7 +190,19 @@ export function BreadcrumbItem({
152
190
  className: "x11ke7fs x6s0dn4 xv1l7n4 xu0wf1k x87ps6o"
153
191
  },
154
192
  children: ctx.separator
155
- }), /*#__PURE__*/_jsx("span", {
193
+ }), hasMenu ?
194
+ /*#__PURE__*/
195
+ // A current crumb can also open a sibling menu — the trigger keeps
196
+ // both aria-current="page" and aria-haspopup="menu".
197
+ _jsx(BreadcrumbMenuTrigger, {
198
+ ref: contentRef,
199
+ menu: menu,
200
+ menuSize: resolvedMenuSize,
201
+ isSupporting: isSupporting,
202
+ isCurrent: true,
203
+ label: children,
204
+ children: content
205
+ }) : /*#__PURE__*/_jsx("span", {
156
206
  ...{
157
207
  0: {
158
208
  className: "x78zum5 x6s0dn4 xzye2dw x1pd3egz x1tgivj0"
@@ -180,7 +230,14 @@ export function BreadcrumbItem({
180
230
  className: "x11ke7fs x6s0dn4 xv1l7n4 xu0wf1k x87ps6o"
181
231
  },
182
232
  children: ctx.separator
183
- }), href != null ? /*#__PURE__*/_jsx(LinkComponent, {
233
+ }), hasMenu ? /*#__PURE__*/_jsx(BreadcrumbMenuTrigger, {
234
+ ref: contentRef,
235
+ menu: menu,
236
+ menuSize: resolvedMenuSize,
237
+ isSupporting: isSupporting,
238
+ label: children,
239
+ children: content
240
+ }) : href != null ? /*#__PURE__*/_jsx(LinkComponent, {
184
241
  ref: contentRef,
185
242
  href: href,
186
243
  onClick: onClick,
@@ -220,4 +277,149 @@ export function BreadcrumbItem({
220
277
  })]
221
278
  });
222
279
  }
223
- BreadcrumbItem.displayName = 'BreadcrumbItem';
280
+ BreadcrumbItem.displayName = 'BreadcrumbItem';
281
+
282
+ // =============================================================================
283
+ // Menu trigger
284
+ // =============================================================================
285
+
286
+ /**
287
+ * The link-styled `<button>` trigger + its `role="menu"` popover. Reuses the
288
+ * DropdownMenu item pipeline (`renderDropdownItems` for arrays, the item
289
+ * components for children) inside a `useListFocus` container that provides
290
+ * `DropdownMenuContext` — the same wiring ContextMenu uses — so menu-item
291
+ * definitions are portable into a breadcrumb with no rewrite.
292
+ */
293
+ function BreadcrumbMenuTrigger({
294
+ ref,
295
+ children,
296
+ label,
297
+ menu,
298
+ menuSize,
299
+ isSupporting,
300
+ isCurrent = false
301
+ }) {
302
+ const menuId = useId();
303
+ const buttonRef = useRef(null);
304
+ const popover = usePopover({
305
+ onHide: useCallback(() => {
306
+ buttonRef.current?.focus();
307
+ }, []),
308
+ hasLightDismiss: true,
309
+ hasCloseButton: false,
310
+ hasAutoFocus: false,
311
+ // The popup's own role="menu" is the exposed semantics; a modal dialog
312
+ // wrapper would announce an unnamed dialog around the menu.
313
+ role: 'none'
314
+ });
315
+ const closeMenu = useCallback(() => {
316
+ popover.hide();
317
+ }, [popover]);
318
+ const {
319
+ listRef,
320
+ handleKeyDown: listNavKeyDown,
321
+ focusFirst,
322
+ focusItem
323
+ } = useListFocus({
324
+ itemSelector: MENU_ITEM_SELECTOR,
325
+ wrap: false,
326
+ onEscape: closeMenu
327
+ });
328
+ const getMenuItems = useCallback(() => listRef.current ? Array.from(listRef.current.querySelectorAll(MENU_ITEM_SELECTOR)) : [], [listRef]);
329
+ const typeahead = useTypeahead({
330
+ getItemLabels: () => getMenuItems().map(el => el.textContent),
331
+ onMatch: focusItem,
332
+ getCurrentIndex: () => getMenuItems().findIndex(el => el === document.activeElement || el.contains(document.activeElement))
333
+ });
334
+ const listKeyDown = useCallback(e => {
335
+ if (e.key === 'Enter' || e.key === ' ') {
336
+ e.preventDefault();
337
+ const focused = document.activeElement;
338
+ if (focused && MENU_ITEM_ROLES.has(focused.getAttribute('role') ?? '')) {
339
+ focused.click();
340
+ }
341
+ return;
342
+ }
343
+ // APG menu-button pattern: Tab closes the menu (items are tabIndex={-1}).
344
+ if (e.key === 'Tab') {
345
+ closeMenu();
346
+ return;
347
+ }
348
+ if (typeahead.onKeyDown(e)) {
349
+ e.preventDefault();
350
+ return;
351
+ }
352
+ listNavKeyDown(e);
353
+ }, [listNavKeyDown, closeMenu, typeahead]);
354
+ const openAndFocus = useCallback(() => {
355
+ popover.show();
356
+ requestAnimationFrame(() => focusFirst());
357
+ }, [popover, focusFirst]);
358
+ const handleClick = useCallback(() => {
359
+ if (popover.isOpen) {
360
+ popover.hide();
361
+ } else {
362
+ openAndFocus();
363
+ }
364
+ }, [popover, openAndFocus]);
365
+ const handleKeyDown = useCallback(e => {
366
+ if (!popover.isOpen) {
367
+ if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
368
+ e.preventDefault();
369
+ openAndFocus();
370
+ }
371
+ }
372
+ }, [popover.isOpen, openAndFocus]);
373
+ const contextValue = useMemo(() => ({
374
+ closeMenu,
375
+ menuSize
376
+ }), [closeMenu, menuSize]);
377
+ const menuContent = Array.isArray(menu) ? renderDropdownItems(menu) : menu;
378
+ return /*#__PURE__*/_jsxs(_Fragment, {
379
+ children: [/*#__PURE__*/_jsxs("button", {
380
+ ref: mergeRefs(ref, buttonRef, popover.triggerRef),
381
+ type: "button",
382
+ onClick: handleClick,
383
+ onKeyDown: handleKeyDown,
384
+ ...mergeProps(themeProps('breadcrumb-item-menu-trigger'), {
385
+ ...popover.triggerProps,
386
+ 'aria-haspopup': 'menu',
387
+ 'aria-controls': menuId,
388
+ 'aria-current': isCurrent ? 'page' : undefined
389
+ }),
390
+ ...{
391
+ 0: {
392
+ className: "x78zum5 x6s0dn4 xzye2dw xu0wf1k x1hl2dhg x4ohgrr x1ypdohk x1717udv x1ghz6dp xln7xf2 xv1l7n4"
393
+ },
394
+ 1: {
395
+ className: "x78zum5 x6s0dn4 xzye2dw xu0wf1k x1hl2dhg x4ohgrr x1ypdohk x1717udv x1ghz6dp xln7xf2 xv1l7n4"
396
+ }
397
+ }[!!isSupporting << 0],
398
+ children: [children, /*#__PURE__*/_jsx("span", {
399
+ "aria-hidden": "true",
400
+ ...{
401
+ className: "x78zum5 x6s0dn4 x2lah0s x141an7d"
402
+ },
403
+ children: getIcon('chevronDown')
404
+ })]
405
+ }), popover.render(/*#__PURE__*/_jsx("div", {
406
+ ref: listRef,
407
+ id: menuId,
408
+ role: "menu",
409
+ "aria-label": typeof label === 'string' ? label : undefined,
410
+ onKeyDown: listKeyDown,
411
+ ...mergeProps(themeProps('breadcrumb-menu'), {
412
+ className: "x9f619 x78zum5 xdt5ytf x1lsbc85 xuyqlj2 x1odjw0f x1fcsqxe xgory14 x9epnlk x1n97fys x87ps6o"
413
+ }),
414
+ children: /*#__PURE__*/_jsx(DropdownMenuContext, {
415
+ value: contextValue,
416
+ children: menuContent
417
+ })
418
+ }), {
419
+ placement: 'below',
420
+ alignment: 'start',
421
+ xstyle: [menuStyles.popover, layerAnimations.below]
422
+ })]
423
+ });
424
+ }
425
+ BreadcrumbMenuTrigger.displayName = 'BreadcrumbMenuTrigger';
@@ -5,4 +5,9 @@ export { Breadcrumbs } from './Breadcrumbs';
5
5
  export type { BreadcrumbsProps, BreadcrumbsVariant, BreadcrumbsVariantMap, } from './Breadcrumbs';
6
6
  export { BreadcrumbItem } from './BreadcrumbItem';
7
7
  export type { BreadcrumbItemProps } from './BreadcrumbItem';
8
+ export { DropdownMenuItem as BreadcrumbMenuItem, type DropdownMenuItemProps as BreadcrumbMenuItemProps, } from '../DropdownMenu/DropdownMenuItem';
9
+ export { DropdownMenuCheckboxItem as BreadcrumbMenuCheckboxItem, type DropdownMenuCheckboxItemProps as BreadcrumbMenuCheckboxItemProps, } from '../DropdownMenu/DropdownMenuCheckboxItem';
10
+ export { DropdownMenuRadioGroup as BreadcrumbMenuRadioGroup, type DropdownMenuRadioGroupProps as BreadcrumbMenuRadioGroupProps, } from '../DropdownMenu/DropdownMenuRadioGroup';
11
+ export { DropdownMenuRadioItem as BreadcrumbMenuRadioItem, type DropdownMenuRadioItemProps as BreadcrumbMenuRadioItemProps, } from '../DropdownMenu/DropdownMenuRadioItem';
12
+ export type { DropdownMenuOption as BreadcrumbMenuOption, DropdownMenuItemData as BreadcrumbMenuItemData, DropdownMenuDivider as BreadcrumbMenuDivider, DropdownMenuSection as BreadcrumbMenuSection, } from '../DropdownMenu/DropdownMenu';
8
13
  //# sourceMappingURL=index.d.ts.map