@astryxdesign/core 0.1.8 → 0.1.9

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 (487) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +6 -2
  4. package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
  5. package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
  6. package/dist/Avatar/Avatar.d.ts +18 -2
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +63 -7
  9. package/dist/Avatar/AvatarStatusDot.d.ts +33 -5
  10. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  11. package/dist/Avatar/AvatarStatusDot.js +144 -19
  12. package/dist/Banner/Banner.d.ts +9 -1
  13. package/dist/Banner/Banner.d.ts.map +1 -1
  14. package/dist/Banner/Banner.js +31 -2
  15. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +31 -2
  16. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  17. package/dist/Breadcrumbs/BreadcrumbItem.js +208 -6
  18. package/dist/Breadcrumbs/index.d.ts +5 -0
  19. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  20. package/dist/Breadcrumbs/index.js +9 -1
  21. package/dist/Button/Button.d.ts +8 -2
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +31 -2
  24. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  25. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  26. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  27. package/dist/Calendar/Calendar.d.ts.map +1 -1
  28. package/dist/Calendar/Calendar.js +30 -2
  29. package/dist/Calendar/dayCellUtils.js +1 -1
  30. package/dist/Card/Card.d.ts +8 -2
  31. package/dist/Card/Card.d.ts.map +1 -1
  32. package/dist/Card/Card.js +28 -2
  33. package/dist/Carousel/Carousel.d.ts +4 -1
  34. package/dist/Carousel/Carousel.d.ts.map +1 -1
  35. package/dist/Carousel/Carousel.js +24 -4
  36. package/dist/Chat/ChatComposer.d.ts +8 -0
  37. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  38. package/dist/Chat/ChatComposer.js +46 -7
  39. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  40. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  41. package/dist/Chat/ChatComposerDrawer.js +11 -1
  42. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  43. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  44. package/dist/Chat/ChatComposerInput.js +35 -3
  45. package/dist/Chat/ChatContext.d.ts +33 -0
  46. package/dist/Chat/ChatContext.d.ts.map +1 -1
  47. package/dist/Chat/ChatContext.js +16 -2
  48. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  49. package/dist/Chat/ChatDictationButton.js +1 -1
  50. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  51. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  52. package/dist/Chat/ChatLayoutScrollButton.js +4 -4
  53. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  54. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  55. package/dist/Chat/ChatSendButton.js +7 -1
  56. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  57. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  58. package/dist/Chat/ChatToolCalls.js +18 -9
  59. package/dist/Chat/index.d.ts +2 -1
  60. package/dist/Chat/index.d.ts.map +1 -1
  61. package/dist/Chat/index.js +1 -1
  62. package/dist/Chat/useChatStreamScroll.d.ts +2 -1
  63. package/dist/Chat/useChatStreamScroll.d.ts.map +1 -1
  64. package/dist/Chat/useChatStreamScroll.js +22 -8
  65. package/dist/Chat/useSpeechRecognition.d.ts +0 -1
  66. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  67. package/dist/Chat/useSpeechRecognition.js +1 -1
  68. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  69. package/dist/CheckboxInput/CheckboxInput.js +1 -1
  70. package/dist/CheckboxList/CheckboxListItem.d.ts +20 -1
  71. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  72. package/dist/CheckboxList/CheckboxListItem.js +11 -1
  73. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  74. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  75. package/dist/ClickableCard/ClickableCard.js +5 -2
  76. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  77. package/dist/CodeBlock/CodeBlock.js +8 -1
  78. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  79. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  80. package/dist/CommandPalette/CommandPalette.js +3 -1
  81. package/dist/CommandPalette/CommandPaletteGroup.d.ts.map +1 -1
  82. package/dist/CommandPalette/CommandPaletteGroup.js +9 -2
  83. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  84. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  85. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  86. package/dist/DateInput/DateInput.d.ts +47 -1
  87. package/dist/DateInput/DateInput.d.ts.map +1 -1
  88. package/dist/DateInput/DateInput.js +26 -2
  89. package/dist/DateInput/index.d.ts +1 -1
  90. package/dist/DateInput/index.d.ts.map +1 -1
  91. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  92. package/dist/DateTimeInput/DateTimeInput.js +10 -1
  93. package/dist/Dialog/Dialog.d.ts +3 -0
  94. package/dist/Dialog/Dialog.d.ts.map +1 -1
  95. package/dist/Dialog/Dialog.js +57 -6
  96. package/dist/Dialog/DialogContext.d.ts +7 -0
  97. package/dist/Dialog/DialogContext.d.ts.map +1 -1
  98. package/dist/Dialog/DialogContext.js +2 -2
  99. package/dist/Dialog/DialogHeader.d.ts +5 -2
  100. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  101. package/dist/Dialog/DialogHeader.js +6 -1
  102. package/dist/Divider/Divider.js +1 -1
  103. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -2
  104. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  105. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +1 -2
  106. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -2
  107. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  108. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -2
  109. package/dist/Field/Field.d.ts.map +1 -1
  110. package/dist/Field/Field.js +2 -3
  111. package/dist/Field/FieldLabel.d.ts +1 -1
  112. package/dist/Field/FieldLabel.d.ts.map +1 -1
  113. package/dist/Field/FieldLabel.js +42 -15
  114. package/dist/FieldStatus/FieldStatus.d.ts +8 -0
  115. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  116. package/dist/FieldStatus/FieldStatus.js +22 -4
  117. package/dist/FileInput/FileInput.d.ts.map +1 -1
  118. package/dist/FileInput/FileInput.js +10 -2
  119. package/dist/HoverCard/HoverCard.d.ts +9 -1
  120. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  121. package/dist/HoverCard/HoverCard.js +2 -0
  122. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  123. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  124. package/dist/HoverCard/useHoverCard.js +7 -2
  125. package/dist/Icon/Icon.d.ts +7 -2
  126. package/dist/Icon/Icon.d.ts.map +1 -1
  127. package/dist/Item/Item.d.ts +6 -1
  128. package/dist/Item/Item.d.ts.map +1 -1
  129. package/dist/Item/Item.js +22 -1
  130. package/dist/Layer/useLayer.d.ts +6 -0
  131. package/dist/Layer/useLayer.d.ts.map +1 -1
  132. package/dist/Layer/useLayer.js +2 -0
  133. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  134. package/dist/MobileNav/MobileNav.js +6 -1
  135. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  136. package/dist/MobileNav/MobileNavToggle.js +4 -0
  137. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  138. package/dist/MultiSelector/MultiSelector.js +39 -12
  139. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  140. package/dist/MultiSelector/hooks.js +16 -0
  141. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  142. package/dist/Outline/Outline.d.ts +0 -1
  143. package/dist/Outline/Outline.d.ts.map +1 -1
  144. package/dist/Outline/Outline.js +0 -1
  145. package/dist/OverflowList/OverflowList.d.ts +20 -3
  146. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  147. package/dist/OverflowList/OverflowList.js +29 -4
  148. package/dist/Popover/Popover.d.ts.map +1 -1
  149. package/dist/Popover/Popover.js +3 -2
  150. package/dist/Popover/usePopover.d.ts.map +1 -1
  151. package/dist/Popover/usePopover.js +3 -9
  152. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  153. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  154. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  155. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  156. package/dist/SelectableCard/SelectableCard.js +16 -13
  157. package/dist/Selector/Selector.d.ts.map +1 -1
  158. package/dist/Selector/Selector.js +4 -2
  159. package/dist/Selector/hooks.d.ts.map +1 -1
  160. package/dist/Selector/hooks.js +16 -0
  161. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  162. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  163. package/dist/SideNav/SideNavHeading.js +21 -8
  164. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  165. package/dist/Slider/Slider.d.ts.map +1 -1
  166. package/dist/Slider/Slider.js +19 -2
  167. package/dist/Stack/stack.stylex.d.ts +9 -9
  168. package/dist/Table/BaseTable.d.ts.map +1 -1
  169. package/dist/Table/BaseTable.js +3 -2
  170. package/dist/Table/index.d.ts +2 -0
  171. package/dist/Table/index.d.ts.map +1 -1
  172. package/dist/Table/index.js +1 -0
  173. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  174. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  175. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  176. package/dist/Table/plugins/rowStatus/index.js +3 -0
  177. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  178. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  179. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  180. package/dist/Table/plugins/selection/useTableSelection.d.ts +13 -0
  181. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  182. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  183. package/dist/Table/plugins/tree/useTableTreeData.d.ts +16 -0
  184. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  185. package/dist/Table/plugins/tree/useTableTreeData.js +87 -0
  186. package/dist/Table/plugins/tree/useTableTreeState.d.ts +6 -0
  187. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -1
  188. package/dist/Table/plugins/tree/useTableTreeState.js +22 -1
  189. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  190. package/dist/Table/useBaseTablePlugins.js +5 -3
  191. package/dist/Thumbnail/Thumbnail.d.ts +26 -5
  192. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  193. package/dist/Thumbnail/Thumbnail.js +97 -54
  194. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +9 -0
  195. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +1 -0
  196. package/dist/Thumbnail/thumbnail.markers.stylex.js +14 -0
  197. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  198. package/dist/TimeInput/TimeInput.js +15 -4
  199. package/dist/Timestamp/Timestamp.d.ts +3 -1
  200. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  201. package/dist/Timestamp/Timestamp.js +8 -6
  202. package/dist/Toast/useToast.d.ts.map +1 -1
  203. package/dist/Toast/useToast.js +2 -5
  204. package/dist/ToggleButton/ToggleButtonGroup.d.ts +0 -1
  205. package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
  206. package/dist/ToggleButton/ToggleButtonGroup.js +1 -1
  207. package/dist/Tokenizer/Tokenizer.d.ts +1 -1
  208. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  209. package/dist/Tokenizer/Tokenizer.js +16 -4
  210. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  211. package/dist/TopNav/TopNavHeading.js +18 -8
  212. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  213. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +9 -1
  214. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
  215. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +8 -1
  216. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  217. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  218. package/dist/TopNav/TopNavMenu.js +68 -6
  219. package/dist/TreeList/TreeList.d.ts +12 -3
  220. package/dist/TreeList/TreeList.d.ts.map +1 -1
  221. package/dist/TreeList/TreeList.js +2 -0
  222. package/dist/TreeList/TreeListBranches.d.ts +5 -0
  223. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  224. package/dist/TreeList/TreeListBranches.js +10 -4
  225. package/dist/TreeList/TreeListItem.d.ts +8 -2
  226. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  227. package/dist/TreeList/TreeListItem.js +27 -7
  228. package/dist/TreeList/TreeListTypes.d.ts +25 -1
  229. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  230. package/dist/TreeList/index.d.ts +2 -2
  231. package/dist/TreeList/index.d.ts.map +1 -1
  232. package/dist/astryx.css +28 -18
  233. package/dist/astryx.umd.js +51 -51
  234. package/dist/astryx.umd.js.map +4 -4
  235. package/dist/authoring/doc.d.ts.map +1 -1
  236. package/dist/docs-types.d.ts +8 -0
  237. package/dist/docs-types.d.ts.map +1 -1
  238. package/dist/hooks/computeOverflow.d.ts +47 -0
  239. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  240. package/dist/hooks/computeOverflow.js +213 -0
  241. package/dist/hooks/index.d.ts +1 -0
  242. package/dist/hooks/index.d.ts.map +1 -1
  243. package/dist/hooks/index.js +2 -1
  244. package/dist/hooks/useDevWarning.d.ts +18 -0
  245. package/dist/hooks/useDevWarning.d.ts.map +1 -0
  246. package/dist/hooks/useDevWarning.js +49 -0
  247. package/dist/hooks/useOverflow.d.ts +19 -0
  248. package/dist/hooks/useOverflow.d.ts.map +1 -1
  249. package/dist/hooks/useOverflow.js +36 -25
  250. package/dist/i18n/resolve.d.ts.map +1 -1
  251. package/dist/i18n/resolve.js +7 -18
  252. package/dist/theme/Theme.d.ts.map +1 -1
  253. package/dist/theme/Theme.js +2 -7
  254. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  255. package/dist/theme/generateThemeRules.js +51 -0
  256. package/dist/theme/hct.d.ts +0 -1
  257. package/dist/theme/hct.d.ts.map +1 -1
  258. package/dist/theme/hct.js +1 -1
  259. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  260. package/dist/theme/syntax/defineSyntaxTheme.js +2 -1
  261. package/dist/utils/devWarning.d.ts +42 -0
  262. package/dist/utils/devWarning.d.ts.map +1 -0
  263. package/dist/utils/devWarning.js +88 -0
  264. package/dist/utils/index.d.ts +3 -1
  265. package/dist/utils/index.d.ts.map +1 -1
  266. package/dist/utils/index.js +3 -2
  267. package/dist/utils/plainDate.d.ts +30 -0
  268. package/dist/utils/plainDate.d.ts.map +1 -1
  269. package/dist/utils/plainDate.js +53 -0
  270. package/dist/utils/types.d.ts +13 -0
  271. package/dist/utils/types.d.ts.map +1 -1
  272. package/locales/en.json +24 -0
  273. package/locales/fr-FR.json +14 -0
  274. package/locales/pseudo.json +18 -0
  275. package/package.json +1 -1
  276. package/src/AppShell/AppShell.tsx +6 -0
  277. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  278. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  279. package/src/Avatar/Avatar.doc.mjs +15 -3
  280. package/src/Avatar/Avatar.test.tsx +156 -1
  281. package/src/Avatar/Avatar.tsx +96 -4
  282. package/src/Avatar/AvatarStatusDot.doc.mjs +23 -12
  283. package/src/Avatar/AvatarStatusDot.test.tsx +284 -0
  284. package/src/Avatar/AvatarStatusDot.tsx +178 -21
  285. package/src/Banner/Banner.doc.mjs +9 -0
  286. package/src/Banner/Banner.test.tsx +30 -0
  287. package/src/Banner/Banner.tsx +47 -3
  288. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +26 -0
  289. package/src/Breadcrumbs/BreadcrumbItem.tsx +333 -14
  290. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -0
  291. package/src/Breadcrumbs/Breadcrumbs.test.tsx +284 -2
  292. package/src/Breadcrumbs/index.ts +27 -0
  293. package/src/Button/Button.doc.mjs +9 -0
  294. package/src/Button/Button.test.tsx +49 -0
  295. package/src/Button/Button.tsx +25 -1
  296. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  297. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  298. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  299. package/src/Calendar/Calendar.doc.mjs +12 -1
  300. package/src/Calendar/Calendar.test.tsx +193 -0
  301. package/src/Calendar/Calendar.tsx +38 -16
  302. package/src/Calendar/dayCellUtils.test.ts +5 -0
  303. package/src/Calendar/dayCellUtils.ts +1 -1
  304. package/src/Card/Card.doc.mjs +9 -0
  305. package/src/Card/Card.test.tsx +21 -3
  306. package/src/Card/Card.tsx +35 -2
  307. package/src/Carousel/Carousel.doc.mjs +1 -0
  308. package/src/Carousel/Carousel.test.tsx +53 -0
  309. package/src/Carousel/Carousel.tsx +34 -4
  310. package/src/Chat/ChatComposer.doc.mjs +12 -3
  311. package/src/Chat/ChatComposer.test.tsx +115 -0
  312. package/src/Chat/ChatComposer.tsx +73 -9
  313. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  314. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  315. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  316. package/src/Chat/ChatComposerInput.test.tsx +103 -0
  317. package/src/Chat/ChatComposerInput.tsx +49 -3
  318. package/src/Chat/ChatContext.tsx +37 -10
  319. package/src/Chat/ChatDictationButton.tsx +4 -1
  320. package/src/Chat/ChatLayout.tsx +3 -1
  321. package/src/Chat/ChatLayoutScrollButton.tsx +7 -2
  322. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  323. package/src/Chat/ChatSendButton.tsx +11 -1
  324. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  325. package/src/Chat/ChatToolCalls.tsx +25 -4
  326. package/src/Chat/index.ts +5 -1
  327. package/src/Chat/useChatStreamScroll.test.tsx +49 -0
  328. package/src/Chat/useChatStreamScroll.ts +27 -10
  329. package/src/Chat/useSpeechRecognition.ts +1 -1
  330. package/src/Chat/useTriggerMenu.tsx +3 -1
  331. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  332. package/src/CheckboxInput/CheckboxInput.tsx +1 -1
  333. package/src/CheckboxList/CheckboxList.test.tsx +81 -3
  334. package/src/CheckboxList/CheckboxListItem.doc.mjs +33 -5
  335. package/src/CheckboxList/CheckboxListItem.tsx +41 -5
  336. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  337. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  338. package/src/ClickableCard/ClickableCard.tsx +13 -3
  339. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  340. package/src/CodeBlock/CodeBlock.tsx +9 -1
  341. package/src/CommandPalette/CommandPalette.doc.mjs +1 -0
  342. package/src/CommandPalette/CommandPalette.tsx +3 -1
  343. package/src/CommandPalette/CommandPaletteGroup.test.tsx +71 -0
  344. package/src/CommandPalette/CommandPaletteGroup.tsx +12 -1
  345. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  346. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  347. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  348. package/src/ContextMenu/ContextMenu.test.tsx +193 -0
  349. package/src/DateInput/DateInput.doc.mjs +16 -0
  350. package/src/DateInput/DateInput.test.tsx +108 -0
  351. package/src/DateInput/DateInput.tsx +68 -3
  352. package/src/DateInput/index.ts +1 -0
  353. package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
  354. package/src/DateTimeInput/DateTimeInput.tsx +14 -2
  355. package/src/Dialog/Dialog.doc.mjs +2 -2
  356. package/src/Dialog/Dialog.test.tsx +77 -4
  357. package/src/Dialog/Dialog.tsx +89 -5
  358. package/src/Dialog/DialogContext.ts +9 -2
  359. package/src/Dialog/DialogHeader.doc.mjs +2 -2
  360. package/src/Dialog/DialogHeader.tsx +9 -2
  361. package/src/Divider/Divider.tsx +2 -2
  362. package/src/DropdownMenu/DropdownMenu.test.tsx +169 -0
  363. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -2
  364. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +1 -2
  365. package/src/Field/Field.test.tsx +57 -12
  366. package/src/Field/Field.tsx +6 -5
  367. package/src/Field/FieldLabel.tsx +8 -0
  368. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  369. package/src/FieldStatus/FieldStatus.test.tsx +78 -40
  370. package/src/FieldStatus/FieldStatus.tsx +22 -4
  371. package/src/FileInput/FileInput.test.tsx +52 -3
  372. package/src/FileInput/FileInput.tsx +13 -2
  373. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  374. package/src/HoverCard/HoverCard.test.tsx +21 -2
  375. package/src/HoverCard/HoverCard.tsx +11 -0
  376. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  377. package/src/HoverCard/useHoverCard.tsx +24 -2
  378. package/src/Icon/Icon.doc.mjs +3 -2
  379. package/src/Icon/Icon.tsx +7 -2
  380. package/src/IconButton/IconButton.doc.mjs +8 -0
  381. package/src/IconButton/IconButton.test.tsx +15 -18
  382. package/src/Item/Item.test.tsx +40 -18
  383. package/src/Item/Item.tsx +38 -2
  384. package/src/Layer/useLayer.tsx +8 -0
  385. package/src/List/List.test.tsx +9 -8
  386. package/src/MobileNav/MobileNav.tsx +12 -1
  387. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  388. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  389. package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
  390. package/src/MultiSelector/MultiSelector.test.tsx +172 -3
  391. package/src/MultiSelector/MultiSelector.tsx +55 -12
  392. package/src/MultiSelector/hooks.ts +17 -0
  393. package/src/NumberInput/NumberInput.test.tsx +11 -7
  394. package/src/NumberInput/NumberInput.tsx +1 -2
  395. package/src/Outline/Outline.tsx +0 -1
  396. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  397. package/src/OverflowList/OverflowList.test.tsx +111 -0
  398. package/src/OverflowList/OverflowList.tsx +48 -8
  399. package/src/Popover/Popover.tsx +7 -4
  400. package/src/Popover/usePopover.tsx +9 -13
  401. package/src/PowerSearch/PowerSearch.doc.mjs +14 -0
  402. package/src/PowerSearch/PowerSearch.test.tsx +20 -0
  403. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  404. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  405. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  406. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  407. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  408. package/src/SelectableCard/SelectableCard.tsx +29 -16
  409. package/src/Selector/Selector.test.tsx +69 -0
  410. package/src/Selector/Selector.tsx +5 -1
  411. package/src/Selector/hooks.ts +17 -0
  412. package/src/SideNav/SideNav.test.tsx +119 -3
  413. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  414. package/src/SideNav/SideNavHeading.tsx +24 -8
  415. package/src/SideNav/SideNavItem.tsx +5 -1
  416. package/src/Slider/Slider.test.tsx +75 -2
  417. package/src/Slider/Slider.tsx +21 -5
  418. package/src/Switch/Switch.test.tsx +32 -1
  419. package/src/Table/BaseTable.tsx +3 -5
  420. package/src/Table/index.ts +5 -0
  421. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  422. package/src/Table/plugins/rowStatus/index.ts +7 -0
  423. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  424. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  425. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  426. package/src/Table/plugins/selection/useTableSelection.tsx +19 -1
  427. package/src/Table/plugins/tree/useTableTreeData.test.tsx +135 -3
  428. package/src/Table/plugins/tree/useTableTreeData.tsx +128 -1
  429. package/src/Table/plugins/tree/useTableTreeState.test.tsx +104 -0
  430. package/src/Table/plugins/tree/useTableTreeState.tsx +46 -2
  431. package/src/Table/useBaseTablePlugins.ts +10 -6
  432. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  433. package/src/Table/useTableSelection.doc.mjs +65 -14
  434. package/src/Table/useTableTreeData.doc.mjs +29 -0
  435. package/src/Table/useTableTreeState.doc.mjs +2 -2
  436. package/src/TextArea/TextArea.test.tsx +9 -1
  437. package/src/Thumbnail/Thumbnail.doc.mjs +19 -10
  438. package/src/Thumbnail/Thumbnail.test.tsx +98 -7
  439. package/src/Thumbnail/Thumbnail.tsx +141 -41
  440. package/src/Thumbnail/thumbnail.markers.stylex.ts +12 -0
  441. package/src/TimeInput/TimeInput.test.tsx +76 -1
  442. package/src/TimeInput/TimeInput.tsx +19 -6
  443. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  444. package/src/Timestamp/Timestamp.test.tsx +32 -0
  445. package/src/Timestamp/Timestamp.tsx +28 -8
  446. package/src/Toast/useToast.tsx +7 -8
  447. package/src/ToggleButton/ToggleButtonGroup.tsx +1 -1
  448. package/src/Tokenizer/Tokenizer.doc.mjs +15 -1
  449. package/src/Tokenizer/Tokenizer.test.tsx +183 -2
  450. package/src/Tokenizer/Tokenizer.tsx +16 -4
  451. package/src/TopNav/TopNav.test.tsx +125 -1
  452. package/src/TopNav/TopNavHeading.tsx +24 -8
  453. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  454. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  455. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +3 -3
  456. package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +19 -2
  457. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  458. package/src/TopNav/TopNavMenu.tsx +83 -5
  459. package/src/TreeList/TreeList.doc.mjs +22 -0
  460. package/src/TreeList/TreeList.test.tsx +232 -0
  461. package/src/TreeList/TreeList.tsx +18 -2
  462. package/src/TreeList/TreeListBranches.tsx +16 -2
  463. package/src/TreeList/TreeListItem.tsx +51 -11
  464. package/src/TreeList/TreeListTypes.ts +27 -1
  465. package/src/TreeList/index.ts +2 -2
  466. package/src/authoring/doc.ts +1 -3
  467. package/src/docs-types.ts +5 -0
  468. package/src/hooks/computeOverflow.test.ts +422 -0
  469. package/src/hooks/computeOverflow.ts +290 -0
  470. package/src/hooks/index.ts +2 -0
  471. package/src/hooks/useDevWarning.test.tsx +45 -0
  472. package/src/hooks/useDevWarning.ts +54 -0
  473. package/src/hooks/useOverflow.test.ts +96 -4
  474. package/src/hooks/useOverflow.ts +74 -33
  475. package/src/i18n/InternationalizationProvider.doc.mjs +99 -2
  476. package/src/i18n/resolve.ts +9 -21
  477. package/src/theme/Theme.tsx +11 -14
  478. package/src/theme/generateThemeRules.test.ts +65 -0
  479. package/src/theme/generateThemeRules.ts +56 -0
  480. package/src/theme/hct.ts +1 -1
  481. package/src/theme/syntax/defineSyntaxTheme.ts +5 -6
  482. package/src/utils/devWarning.test.ts +56 -0
  483. package/src/utils/devWarning.ts +102 -0
  484. package/src/utils/index.ts +6 -0
  485. package/src/utils/plainDate.test.ts +22 -0
  486. package/src/utils/plainDate.ts +61 -0
  487. package/src/utils/types.ts +14 -0
@@ -9,15 +9,27 @@
9
9
  * SYNC: When FieldStatus.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen} from '@testing-library/react';
12
+ import {describe, it, expect, vi, afterEach} from 'vitest';
13
+ import {render, screen, waitFor} from '@testing-library/react';
14
14
  import * as stylex from '@stylexjs/stylex';
15
15
  import {FieldStatus} from './FieldStatus';
16
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
16
17
 
17
18
  const testStyles = stylex.create({
18
19
  custom: {color: 'rebeccapurple'},
19
20
  });
20
21
 
22
+ function politeRegion(): HTMLElement | null {
23
+ return document.querySelector('[data-astryx-live-region="polite"]');
24
+ }
25
+ function assertiveRegion(): HTMLElement | null {
26
+ return document.querySelector('[data-astryx-live-region="assertive"]');
27
+ }
28
+
29
+ afterEach(() => {
30
+ __resetLiveRegionsForTest();
31
+ });
32
+
21
33
  describe('FieldStatus', () => {
22
34
  it('renders the message text', () => {
23
35
  render(<FieldStatus type="error" message="This field is required" />);
@@ -29,47 +41,75 @@ describe('FieldStatus', () => {
29
41
  expect(screen.getByTestId('fs').tagName).toBe('DIV');
30
42
  });
31
43
 
32
- describe('role semantics', () => {
33
- // Errors are urgent — they get role="alert" so assistive tech interrupts.
34
- it('uses role="alert" for error status', () => {
35
- render(<FieldStatus type="error" message="msg" />);
36
- expect(screen.getByRole('alert')).toHaveTextContent('msg');
44
+ describe('screen-reader announcements', () => {
45
+ // The rendered element is NOT itself a live region: FieldStatus is
46
+ // conditionally mounted by every caller, and live regions born together
47
+ // with their content are not reliably announced. Announcements go through
48
+ // the persistent useAnnounce singletons instead.
49
+ it('does not carry role or aria-live on the rendered element', () => {
50
+ render(<FieldStatus type="error" message="msg" data-testid="fs" />);
51
+ const el = screen.getByTestId('fs');
52
+ expect(el).not.toHaveAttribute('role');
53
+ expect(el).not.toHaveAttribute('aria-live');
37
54
  });
38
55
 
39
- // Non-error statuses are advisory — role="status" is a polite live region.
40
- it('uses role="status" for warning status', () => {
41
- render(<FieldStatus type="warning" message="msg" data-testid="fs" />);
42
- expect(screen.getByTestId('fs')).toHaveAttribute('role', 'status');
43
- expect(screen.queryByRole('alert')).not.toBeInTheDocument();
56
+ // Errors are urgent — they interrupt via the assertive channel.
57
+ it('announces error messages assertively, including on first mount', async () => {
58
+ render(<FieldStatus type="error" message="This field is required" />);
59
+ await waitFor(() => {
60
+ expect(assertiveRegion()).toHaveTextContent('This field is required');
61
+ });
62
+ expect(politeRegion()).toHaveTextContent('');
44
63
  });
45
64
 
46
- it('uses role="status" for success status', () => {
47
- render(<FieldStatus type="success" message="msg" data-testid="fs" />);
48
- expect(screen.getByTestId('fs')).toHaveAttribute('role', 'status');
65
+ it('announces warning messages politely', async () => {
66
+ render(<FieldStatus type="warning" message="Check this value" />);
67
+ await waitFor(() => {
68
+ expect(politeRegion()).toHaveTextContent('Check this value');
69
+ });
70
+ expect(assertiveRegion()).toHaveTextContent('');
49
71
  });
50
- });
51
72
 
52
- describe('aria-live politeness', () => {
53
- // Error → assertive so screen readers announce immediately.
54
- it('sets aria-live="assertive" for error', () => {
55
- render(<FieldStatus type="error" message="msg" data-testid="fs" />);
56
- expect(screen.getByTestId('fs')).toHaveAttribute(
57
- 'aria-live',
58
- 'assertive',
59
- );
73
+ it('announces success messages politely', async () => {
74
+ render(<FieldStatus type="success" message="Looks good" />);
75
+ await waitFor(() => {
76
+ expect(politeRegion()).toHaveTextContent('Looks good');
77
+ });
60
78
  });
61
79
 
62
- it('sets aria-live="polite" for warning', () => {
63
- render(<FieldStatus type="warning" message="msg" data-testid="fs" />);
64
- expect(screen.getByTestId('fs')).toHaveAttribute('aria-live', 'polite');
80
+ it('announces message changes', async () => {
81
+ const {rerender} = render(<FieldStatus type="error" message="First" />);
82
+ await waitFor(() => {
83
+ expect(assertiveRegion()).toHaveTextContent('First');
84
+ });
85
+ rerender(<FieldStatus type="error" message="Second" />);
86
+ await waitFor(() => {
87
+ expect(assertiveRegion()).toHaveTextContent('Second');
88
+ });
65
89
  });
66
90
 
67
- it('sets aria-live="polite" for success', () => {
68
- render(<FieldStatus type="success" message="msg" data-testid="fs" />);
69
- expect(screen.getByTestId('fs')).toHaveAttribute('aria-live', 'polite');
91
+ // Severity changes re-route the announcement to the matching channel.
92
+ it('re-routes to the polite channel when type changes from error', async () => {
93
+ const {rerender} = render(<FieldStatus type="error" message="msg" />);
94
+ await waitFor(() => {
95
+ expect(assertiveRegion()).toHaveTextContent('msg');
96
+ });
97
+ rerender(<FieldStatus type="success" message="msg" />);
98
+ await waitFor(() => {
99
+ expect(politeRegion()).toHaveTextContent('msg');
100
+ });
70
101
  });
71
102
 
72
- // The element is a live region, never hidden from assistive tech.
103
+ it('does not announce an empty message', () => {
104
+ render(<FieldStatus type="error" message="" />);
105
+ // The live regions are created lazily on first announce; an empty
106
+ // message must not trigger one.
107
+ expect(assertiveRegion()).toBeNull();
108
+ expect(politeRegion()).toBeNull();
109
+ });
110
+
111
+ // The visible message stays perceivable by assistive tech (it is the
112
+ // aria-describedby target for the input).
73
113
  it('does not mark itself aria-hidden', () => {
74
114
  render(<FieldStatus type="error" message="msg" data-testid="fs" />);
75
115
  expect(screen.getByTestId('fs')).not.toHaveAttribute('aria-hidden');
@@ -245,20 +285,18 @@ describe('FieldStatus', () => {
245
285
  expect(screen.getByTestId('fs')).toHaveTextContent('Second');
246
286
  });
247
287
 
248
- // Switching from error to a non-error type must relax both the role and
249
- // the live-region politeness in lockstep.
250
- it('relaxes role and aria-live when type changes from error', () => {
288
+ // The element must never regain live-region semantics when the type
289
+ // changes — announcements always flow through the persistent regions.
290
+ it('keeps the element role-free when type changes from error', () => {
251
291
  const {rerender} = render(
252
292
  <FieldStatus type="error" message="msg" data-testid="fs" />,
253
293
  );
254
- let el = screen.getByTestId('fs');
255
- expect(el).toHaveAttribute('role', 'alert');
256
- expect(el).toHaveAttribute('aria-live', 'assertive');
294
+ expect(screen.getByTestId('fs')).not.toHaveAttribute('role');
257
295
 
258
296
  rerender(<FieldStatus type="success" message="msg" data-testid="fs" />);
259
- el = screen.getByTestId('fs');
260
- expect(el).toHaveAttribute('role', 'status');
261
- expect(el).toHaveAttribute('aria-live', 'polite');
297
+ const el = screen.getByTestId('fs');
298
+ expect(el).not.toHaveAttribute('role');
299
+ expect(el).not.toHaveAttribute('aria-live');
262
300
  });
263
301
  });
264
302
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * @file FieldStatus.tsx
5
- * @input Uses React, stylex, theme tokens
5
+ * @input Uses React, stylex, theme tokens, useAnnounce
6
6
  * @output Exports FieldStatus component, FieldStatusProps
7
7
  * @position Core implementation; consumed by Field, Switch, CheckboxInput, and the FieldStatus entrypoint
8
8
  *
@@ -16,9 +16,10 @@
16
16
 
17
17
  'use client';
18
18
 
19
- import React from 'react';
19
+ import React, {useEffect} from 'react';
20
20
  import * as stylex from '@stylexjs/stylex';
21
21
  import type {BaseProps} from '../BaseProps';
22
+ import {useAnnounce} from '../hooks/useAnnounce';
22
23
  import {mergeProps} from '../utils';
23
24
  import {
24
25
  colorVars,
@@ -113,6 +114,14 @@ export interface FieldStatusProps extends BaseProps<HTMLDivElement> {
113
114
  /**
114
115
  * A status message component for form fields.
115
116
  *
117
+ * Screen-reader announcements go through the persistent `useAnnounce` live
118
+ * regions (assertive for errors, polite otherwise) rather than `role`/
119
+ * `aria-live` on the rendered element. Live regions that mount together with
120
+ * their content are not reliably announced by assistive technology, and
121
+ * FieldStatus is almost always conditionally rendered by its callers. The
122
+ * message is announced whenever it appears — including on first mount — and
123
+ * whenever it changes.
124
+ *
116
125
  * @example
117
126
  * ```
118
127
  * <FieldStatus
@@ -138,13 +147,22 @@ export function FieldStatus({
138
147
  ...rest
139
148
  }: FieldStatusProps) {
140
149
  const entryStyle = useEntryAnimation('slideDown');
150
+ const announce = useAnnounce();
151
+
152
+ // Announce the message through the persistently-mounted live regions.
153
+ // Announce-on-mount is intentional: callers conditionally mount FieldStatus
154
+ // when a status appears (and whole forms can mount with a server-side
155
+ // validation error already present), and both cases must be heard.
156
+ useEffect(() => {
157
+ if (message) {
158
+ announce(message, type === 'error' ? 'assertive' : 'polite');
159
+ }
160
+ }, [announce, message, type]);
141
161
 
142
162
  return (
143
163
  <div
144
164
  ref={ref}
145
165
  id={id}
146
- role={type === 'error' ? 'alert' : 'status'}
147
- aria-live={type === 'error' ? 'assertive' : 'polite'}
148
166
  {...rest}
149
167
  {...mergeProps(
150
168
  themeProps('field-status', {type, variant}),
@@ -348,9 +348,10 @@ describe('FileInput', () => {
348
348
  fireEvent.change(fileInputEl(), {
349
349
  target: {files: [createFile('note.txt', 100)]},
350
350
  });
351
- // A rejected selection creates no polite region (only the error goes to
352
- // the existing role="status" region).
353
- expect(politeRegion()).toBeNull();
351
+ // A rejected selection announces nothing politely. (The region pair may
352
+ // exist because the validation error itself is announced assertively via
353
+ // FieldStatus, but the polite channel must stay empty.)
354
+ expect(politeRegion()?.textContent ?? '').toBe('');
354
355
  });
355
356
  });
356
357
 
@@ -641,6 +642,54 @@ describe('FileInput', () => {
641
642
  });
642
643
  });
643
644
 
645
+ describe('trigger accessible name', () => {
646
+ it('includes the selected file name in the trigger name', () => {
647
+ const file = createFile('report.pdf', 1024, 'application/pdf');
648
+ render(<FileInput label="Document" value={file} onChange={() => {}} />);
649
+ expect(
650
+ screen.getByRole('button', {name: 'Document, report.pdf'}),
651
+ ).toBeInTheDocument();
652
+ });
653
+
654
+ it('includes all selected file names when multiple files are selected', () => {
655
+ const files = [createFile('a.txt', 100), createFile('b.txt', 200)];
656
+ render(
657
+ <FileInput
658
+ label="Files"
659
+ value={files}
660
+ onChange={() => {}}
661
+ isMultiple
662
+ />,
663
+ );
664
+ expect(
665
+ screen.getByRole('button', {name: 'Files, a.txt, b.txt'}),
666
+ ).toBeInTheDocument();
667
+ });
668
+
669
+ it('uses exactly the label when no files are selected', () => {
670
+ render(<FileInput label="Document" value={null} onChange={() => {}} />);
671
+ expect(screen.getByRole('button', {name: 'Document'})).toHaveAttribute(
672
+ 'aria-label',
673
+ 'Document',
674
+ );
675
+ });
676
+
677
+ it('includes the selected file name in dropzone mode', () => {
678
+ const file = createFile('doc.pdf', 100, 'application/pdf');
679
+ render(
680
+ <FileInput
681
+ label="Upload"
682
+ value={file}
683
+ onChange={() => {}}
684
+ mode="dropzone"
685
+ />,
686
+ );
687
+ expect(
688
+ screen.getByRole('button', {name: 'Upload, doc.pdf'}),
689
+ ).toBeInTheDocument();
690
+ });
691
+ });
692
+
644
693
  describe('data-testid', () => {
645
694
  it('passes data-testid to native input', () => {
646
695
  render(
@@ -745,7 +745,15 @@ export function FileInput({
745
745
  aria-disabled={showsDisabledMessage ? 'true' : undefined}
746
746
  onClick={handleClick}
747
747
  onKeyDown={handleKeyDown}
748
- aria-label={label}
748
+ // aria-label suppresses child content from the accessible name, so
749
+ // compose the selected filenames into it — otherwise a screen-reader
750
+ // user refocusing the control hears only the field label and cannot
751
+ // tell what (if anything) is attached.
752
+ aria-label={
753
+ hasFiles && fileNames
754
+ ? t('@astryx.fileInput.triggerWithFiles', {label, fileNames})
755
+ : label
756
+ }
749
757
  aria-busy={isLoading || undefined}
750
758
  // These describe the operable control, so they belong on the focusable
751
759
  // role="button" wrapper — not the hidden tabIndex={-1} file input the
@@ -785,7 +793,10 @@ export function FileInput({
785
793
  />
786
794
  {isDropzone ? renderDropzoneContent() : renderCompactContent()}
787
795
  {hasFiles && !isDisabled && !isLoading && (
788
- <InputClearButton label={t('@astryx.fileInput.clearLabel', {label})} onClick={handleClear} />
796
+ <InputClearButton
797
+ label={t('@astryx.fileInput.clearLabel', {label})}
798
+ onClick={handleClear}
799
+ />
789
800
  )}
790
801
  </div>
791
802
  <div
@@ -79,6 +79,12 @@ export const docs = {
79
79
  description: 'Enables or disables the hover and focus triggers.',
80
80
  default: 'true',
81
81
  },
82
+ {
83
+ name: 'label',
84
+ type: 'string',
85
+ description:
86
+ 'Accessible name for the hover card popup. When provided, the popup is exposed as a named role="dialog"; when omitted, it falls back to role="group" (a group may validly be unnamed).',
87
+ },
82
88
  {
83
89
  name: 'onOpenChange',
84
90
  type: '(isOpen: boolean) => void',
@@ -188,6 +194,12 @@ export const docsZh = {
188
194
  description: '启用或禁用悬停和聚焦触发器。',
189
195
  default: 'true',
190
196
  },
197
+ {
198
+ name: 'label',
199
+ type: 'string',
200
+ description:
201
+ '悬浮卡片弹出层的无障碍名称。提供时,弹出层以具名的 role="dialog" 暴露;省略时,回退为 role="group"(group 可以合法地没有名称)。',
202
+ },
191
203
  {
192
204
  name: 'onOpenChange',
193
205
  type: '(isOpen: boolean) => void',
@@ -252,6 +264,7 @@ export const docsDense = {
252
264
  hideDelay: 'Hide delay in ms.',
253
265
  focusTrigger: 'Controls when focus events trigger hover card.',
254
266
  isEnabled: 'Enable/disable hover + focus triggers.',
267
+ label: 'Accessible name for the popup. With label: named role="dialog"; without: role="group".',
255
268
  onOpenChange: 'Callback when visibility changes; true=shown, false=hidden.',
256
269
  hasHoverIndication: 'Dashed underline on trigger element.',
257
270
  isDefaultOpen: 'Show hover card on mount. Still dismissible.',
@@ -58,15 +58,34 @@ describe('HoverCard', () => {
58
58
  expect(screen.getByRole('button', {name: 'Trigger'})).toBeInTheDocument();
59
59
  });
60
60
 
61
- it('gives the floating layer role="dialog"', () => {
61
+ it('exposes the floating layer as role="group" when no label is provided', () => {
62
62
  render(
63
63
  <HoverCard content={<span>Card content</span>}>
64
64
  <button type="button">Trigger</button>
65
65
  </HoverCard>,
66
66
  );
67
- expect(screen.getByRole('dialog', {hidden: true})).toHaveTextContent(
67
+ // A group may validly be unnamed; an unnamed dialog may not. Without a
68
+ // label the layer must not claim the dialog role.
69
+ expect(screen.getByRole('group', {hidden: true})).toHaveTextContent(
68
70
  'Card content',
69
71
  );
72
+ expect(screen.queryByRole('dialog', {hidden: true})).toBeNull();
73
+ });
74
+
75
+ it('exposes the floating layer as a named dialog when label is provided', () => {
76
+ render(
77
+ <HoverCard content={<span>Card content</span>} label="Profile preview">
78
+ <button type="button">Trigger</button>
79
+ </HoverCard>,
80
+ );
81
+ // The layer is hidden while closed, so assert the accessible name via the
82
+ // aria-label attribute on the role-carrying element (same pattern as the
83
+ // Popover dialogLabel test) — accname computation returns '' for hidden
84
+ // elements.
85
+ const dialog = screen.getByRole('dialog', {hidden: true});
86
+ expect(dialog).toHaveAttribute('aria-label', 'Profile preview');
87
+ expect(dialog).toHaveTextContent('Card content');
88
+ expect(screen.queryByRole('group', {hidden: true})).toBeNull();
70
89
  });
71
90
 
72
91
  it('wraps element children in an inline-safe span', () => {
@@ -96,6 +96,15 @@ export interface HoverCardProps extends Pick<
96
96
  */
97
97
  isEnabled?: boolean;
98
98
 
99
+ /**
100
+ * Accessible name for the hover card popup.
101
+ *
102
+ * When provided, the popup is exposed to assistive technology as a named
103
+ * `role="dialog"`. When omitted, the popup falls back to `role="group"` —
104
+ * a group may validly be unnamed, an unnamed dialog may not.
105
+ */
106
+ label?: string;
107
+
99
108
  /**
100
109
  * Callback fired when hover card visibility changes.
101
110
  * Called with `true` when shown and `false` when hidden.
@@ -166,6 +175,7 @@ export function HoverCard({
166
175
  hideDelay = 200,
167
176
  focusTrigger = 'auto',
168
177
  isEnabled = true,
178
+ label,
169
179
  onOpenChange,
170
180
  hasHoverIndication = 'auto',
171
181
  isOpen,
@@ -197,6 +207,7 @@ export function HoverCard({
197
207
  hideDelay,
198
208
  focusTrigger,
199
209
  isEnabled,
210
+ label,
200
211
  isOpen,
201
212
  isDefaultOpen,
202
213
  onShow: handleShow,
@@ -13,6 +13,7 @@ export const docs = {
13
13
  {name: 'hideDelay', type: 'number', description: 'Delay before hiding after mouse or focus leaves, in milliseconds.', default: '200'},
14
14
  {name: 'focusTrigger', type: "'auto' | 'always' | 'never'", description: 'When focus should open the hover card. auto only attaches focus listeners to naturally focusable elements.', default: "'auto'"},
15
15
  {name: 'isEnabled', type: 'boolean', description: 'Whether hover and focus triggers are enabled.', default: 'true'},
16
+ {name: 'label', type: 'string', description: 'Accessible name for the hover card popup. When provided, the popup is exposed as a named role="dialog"; when omitted, it falls back to role="group" (a group may validly be unnamed).'},
16
17
  {name: 'isOpen', type: 'boolean', description: 'Controlled open state. true force-shows, false force-hides, undefined lets hover/focus manage visibility.'},
17
18
  {name: 'isDefaultOpen', type: 'boolean', description: 'Whether the hover card should be shown on mount.', default: 'false'},
18
19
  {name: 'onShow', type: '() => void', description: 'Callback fired when the hover card becomes visible.'},
@@ -52,6 +53,7 @@ export const docsDense = {
52
53
  hideDelay: 'hide delay after mouse/focus leave in ms.',
53
54
  focusTrigger: 'when focus opens hover card; auto = naturally focusable elements only.',
54
55
  isEnabled: 'whether hover/focus triggers are enabled.',
56
+ label: 'accessible name for popup. with label: named role="dialog"; without: role="group".',
55
57
  isOpen: 'controlled open state: true force-show, false force-hide, undefined unmanaged.',
56
58
  isDefaultOpen: 'show on mount.',
57
59
  onShow: 'fires when hover card becomes visible.',
@@ -119,6 +119,15 @@ export interface HoverCardOptions {
119
119
  */
120
120
  isEnabled?: boolean;
121
121
 
122
+ /**
123
+ * Accessible name for the hover card popup.
124
+ *
125
+ * When provided, the popup is exposed to assistive technology as a named
126
+ * `role="dialog"`. When omitted, the popup falls back to `role="group"` —
127
+ * a group may validly be unnamed, an unnamed dialog may not.
128
+ */
129
+ label?: string;
130
+
122
131
  /**
123
132
  * Controlled open state. When provided, overrides hover/focus triggers:
124
133
  * - `true`: force-show the hover card (hover/focus hide is suppressed)
@@ -250,6 +259,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
250
259
  hideDelay = 200,
251
260
  focusTrigger = 'auto',
252
261
  isEnabled = true,
262
+ label,
253
263
  isOpen,
254
264
  isDefaultOpen = false,
255
265
  onShow,
@@ -462,7 +472,11 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
462
472
  const renderProps = {
463
473
  placement: renderPlacement,
464
474
  alignment: props?.alignment ?? alignment,
465
- role: 'dialog',
475
+ // A named dialog when a label is provided; otherwise a group. A group
476
+ // may validly be unnamed, an unnamed dialog may not — and hover cards
477
+ // are non-modal, so group is honest semantics without a name.
478
+ role: label ? 'dialog' : 'group',
479
+ 'aria-label': label || undefined,
466
480
  xstyle: [popoverXstyle, layerAnimations[renderPlacement]],
467
481
  // Render the layer as inline-safe phrasing markup so HoverCard stays
468
482
  // valid (and hydration-stable) inside inline contexts like a `<p>`.
@@ -516,7 +530,15 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
516
530
  renderProps,
517
531
  );
518
532
  },
519
- [layer, placement, alignment, clearTimeouts, scheduleHide, popoverXstyle],
533
+ [
534
+ layer,
535
+ placement,
536
+ alignment,
537
+ label,
538
+ clearTimeouts,
539
+ scheduleHide,
540
+ popoverXstyle,
541
+ ],
520
542
  );
521
543
 
522
544
  return {
@@ -37,7 +37,7 @@ export const docs = {
37
37
  {
38
38
  name: 'label',
39
39
  type: 'string',
40
- description: 'Accessible name for a MEANINGFUL, standalone icon (a status glyph or icon-only indicator with no adjacent text). Setting it exposes the icon to screen readers as role="img" with this text as the accessible name (aria-label) and removes the default aria-hidden. Omit it (default) for decorative icons and the icon stays hidden from assistive tech (aria-hidden="true"). This is the accessible-name / alt-text prop for icons — one prop instead of manually setting aria-label + role + aria-hidden. An empty string is treated as decorative. Do not set it when an interactive parent (Button, IconButton, link) already names the control.',
40
+ description: 'Accessible name for a MEANINGFUL, standalone icon (a status glyph or icon-only indicator with no adjacent text). Setting it exposes the icon to screen readers as role="img" with this text as the accessible name (aria-label) and removes the default aria-hidden. Omit it (default) for decorative icons and the icon stays hidden from assistive tech (aria-hidden="true"). This is the accessible-name / alt-text prop for icons: one prop instead of manually setting aria-label + role + aria-hidden. An empty string is treated as decorative. Do not set it when an interactive parent (Button, IconButton, link) already names the control.',
41
41
  },
42
42
  ],
43
43
  theming: {
@@ -50,7 +50,7 @@ export const docs = {
50
50
  bestPractices: [
51
51
  { guidance: true, description: 'Use semantic icon names when available; they adapt to theme changes automatically.' },
52
52
  { guidance: true, description: 'Pair icons with text labels for accessibility; icon-only elements need an accessible label.' },
53
- { guidance: true, description: 'For a meaningful standalone icon (no adjacent text), give it an accessible name via the `label` prop — it sets role="img" + aria-label and unhides the icon.' },
53
+ { guidance: true, description: 'For a meaningful standalone icon (no adjacent text), give it an accessible name via the `label` prop: it sets role="img" + aria-label and unhides the icon.' },
54
54
  { guidance: true, description: 'Use color tokens for icon colors, not hardcoded hex values.' },
55
55
  { guidance: true, description: 'Be mindful of context; decorative icons in compact components can distract rather than help.' },
56
56
  { guidance: false, description: 'Use icons as the sole means of conveying meaning; always provide a text alternative.' },
@@ -121,6 +121,7 @@ export const docsDense = {
121
121
  bestPractices: [
122
122
  { guidance: true, description: 'Use semantic icon names when available; they adapt to theme changes automatically.' },
123
123
  { guidance: true, description: 'Pair icons with text labels for accessibility; icon-only elements need an accessible label.' },
124
+ { guidance: true, description: 'For a meaningful standalone icon (no adjacent text), set an accessible name via `label`: sets role="img" + aria-label, unhides icon.' },
124
125
  { guidance: true, description: 'Use color tokens for icon colors, not hardcoded hex values.' },
125
126
  { guidance: true, description: 'Be mindful of context; decorative icons in compact components can distract rather than help.' },
126
127
  { guidance: false, description: 'Use icons as the sole means of conveying meaning; always provide a text alternative.' },
package/src/Icon/Icon.tsx CHANGED
@@ -229,12 +229,17 @@ export interface IconProps extends Omit<
229
229
  * Don't set `label` when an interactive parent (Button, IconButton, link)
230
230
  * already names the control — that produces a duplicate announcement.
231
231
  *
232
+ * Meaningful, standalone icon: give it a label.
233
+ *
232
234
  * @example
233
235
  * ```
234
- * // Meaningful, standalone icon
235
236
  * <Icon icon="success" label="Completed" />
237
+ * ```
238
+ *
239
+ * Decorative icon (the default): omit label.
236
240
  *
237
- * // Decorative icon (default) — omit label
241
+ * @example
242
+ * ```
238
243
  * <Icon icon="search" />
239
244
  * ```
240
245
  */
@@ -36,6 +36,13 @@ export const docs = {
36
36
  description: 'Size variant.',
37
37
  default: "\'md\'",
38
38
  },
39
+ {
40
+ name: 'elevation',
41
+ type: "'none' | 'low' | 'med' | 'high'",
42
+ description:
43
+ 'Resting shadow depth. The most common FAB shape is an icon-only button, so raise it with `low`/`med`/`high` for a floating action button. `none` is the default flat button.',
44
+ default: "'none'",
45
+ },
39
46
  {
40
47
  name: 'isLoading',
41
48
  type: 'boolean',
@@ -95,6 +102,7 @@ export const docsDense = {
95
102
  icon: 'icon element rendered inside button',
96
103
  variant: 'visual style variant',
97
104
  size: 'size variant',
105
+ elevation: 'resting shadow depth: none|low|med|high; raise for a floating action button (FAB)',
98
106
  isLoading: 'shows loading spinner + disables interaction',
99
107
  isDisabled: 'disables button',
100
108
  tooltip: 'tooltip text shown on hover',
@@ -17,10 +17,7 @@ import {IconButton} from './IconButton';
17
17
  describe('IconButton', () => {
18
18
  it('renders as an icon-only button with aria-label', () => {
19
19
  render(
20
- <IconButton
21
- label="Settings"
22
- icon={<span data-testid="icon">⚙</span>}
23
- />,
20
+ <IconButton label="Settings" icon={<span data-testid="icon">⚙</span>} />,
24
21
  );
25
22
  const button = screen.getByRole('button', {name: 'Settings'});
26
23
  expect(button).toHaveAttribute('aria-label', 'Settings');
@@ -29,10 +26,7 @@ describe('IconButton', () => {
29
26
 
30
27
  it('does not render label as visible text', () => {
31
28
  render(
32
- <IconButton
33
- label="Settings"
34
- icon={<span data-testid="icon">⚙</span>}
35
- />,
29
+ <IconButton label="Settings" icon={<span data-testid="icon">⚙</span>} />,
36
30
  );
37
31
  const button = screen.getByRole('button');
38
32
  // The label text should not appear as visible content
@@ -42,11 +36,7 @@ describe('IconButton', () => {
42
36
 
43
37
  it('forwards variant prop', () => {
44
38
  render(
45
- <IconButton
46
- label="Delete"
47
- icon={<span>🗑</span>}
48
- variant="destructive"
49
- />,
39
+ <IconButton label="Delete" icon={<span>🗑</span>} variant="destructive" />,
50
40
  );
51
41
  expect(screen.getByRole('button', {name: 'Delete'})).toBeInTheDocument();
52
42
  });
@@ -60,11 +50,7 @@ describe('IconButton', () => {
60
50
  const user = userEvent.setup();
61
51
  const handleClick = vi.fn();
62
52
  render(
63
- <IconButton
64
- label="Close"
65
- icon={<span>✕</span>}
66
- onClick={handleClick}
67
- />,
53
+ <IconButton label="Close" icon={<span>✕</span>} onClick={handleClick} />,
68
54
  );
69
55
 
70
56
  await user.click(screen.getByRole('button'));
@@ -105,4 +91,15 @@ describe('IconButton', () => {
105
91
  it('has displayName set', () => {
106
92
  expect(IconButton.displayName).toBe('IconButton');
107
93
  });
94
+
95
+ it('forwards the elevation prop through to the underlying button', () => {
96
+ const classFor = (elevation: 'none' | 'med') => {
97
+ const {container} = render(
98
+ <IconButton label="Add" icon={<span>+</span>} elevation={elevation} />,
99
+ );
100
+ return container.querySelector('button')!.className;
101
+ };
102
+ // A raised FAB must render differently from the default flat icon button.
103
+ expect(classFor('med')).not.toBe(classFor('none'));
104
+ });
108
105
  });