@astryxdesign/core 0.1.8 → 0.1.9-canary.1887ee2

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 (610) 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 +53 -3
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +203 -49
  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/AvatarGroup/AvatarGroup.d.ts +7 -1
  13. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroup.js +58 -6
  15. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  16. package/dist/AvatarGroup/AvatarGroupOverflow.js +1 -0
  17. package/dist/Banner/Banner.d.ts +9 -1
  18. package/dist/Banner/Banner.d.ts.map +1 -1
  19. package/dist/Banner/Banner.js +33 -4
  20. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +31 -2
  21. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  22. package/dist/Breadcrumbs/BreadcrumbItem.js +208 -6
  23. package/dist/Breadcrumbs/index.d.ts +5 -0
  24. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  25. package/dist/Breadcrumbs/index.js +9 -1
  26. package/dist/Button/Button.d.ts +8 -2
  27. package/dist/Button/Button.d.ts.map +1 -1
  28. package/dist/Button/Button.js +33 -4
  29. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  30. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  31. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  32. package/dist/Calendar/Calendar.d.ts.map +1 -1
  33. package/dist/Calendar/Calendar.js +30 -2
  34. package/dist/Calendar/dayCellUtils.js +1 -1
  35. package/dist/Card/Card.d.ts +8 -2
  36. package/dist/Card/Card.d.ts.map +1 -1
  37. package/dist/Card/Card.js +28 -2
  38. package/dist/Carousel/Carousel.d.ts +4 -1
  39. package/dist/Carousel/Carousel.d.ts.map +1 -1
  40. package/dist/Carousel/Carousel.js +24 -4
  41. package/dist/Chat/ChatComposer.d.ts +8 -0
  42. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposer.js +46 -7
  44. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  45. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  46. package/dist/Chat/ChatComposerDrawer.js +11 -1
  47. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  48. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  49. package/dist/Chat/ChatComposerInput.js +36 -4
  50. package/dist/Chat/ChatContext.d.ts +33 -0
  51. package/dist/Chat/ChatContext.d.ts.map +1 -1
  52. package/dist/Chat/ChatContext.js +16 -2
  53. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  54. package/dist/Chat/ChatDictationButton.js +1 -1
  55. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  56. package/dist/Chat/ChatLayout.js +4 -4
  57. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  58. package/dist/Chat/ChatLayoutScrollButton.js +4 -4
  59. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  60. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  61. package/dist/Chat/ChatSendButton.js +7 -1
  62. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  63. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  64. package/dist/Chat/ChatToolCalls.js +18 -9
  65. package/dist/Chat/index.d.ts +2 -1
  66. package/dist/Chat/index.d.ts.map +1 -1
  67. package/dist/Chat/index.js +1 -1
  68. package/dist/Chat/useChatStreamScroll.d.ts +2 -1
  69. package/dist/Chat/useChatStreamScroll.d.ts.map +1 -1
  70. package/dist/Chat/useChatStreamScroll.js +22 -8
  71. package/dist/Chat/useSpeechRecognition.d.ts +0 -1
  72. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  73. package/dist/Chat/useSpeechRecognition.js +1 -1
  74. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  75. package/dist/Chat/useTriggerMenu.js +2 -2
  76. package/dist/CheckboxInput/CheckboxInput.js +1 -1
  77. package/dist/CheckboxList/CheckboxListItem.d.ts +20 -1
  78. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  79. package/dist/CheckboxList/CheckboxListItem.js +11 -1
  80. package/dist/Citation/Citation.d.ts +22 -1
  81. package/dist/Citation/Citation.d.ts.map +1 -1
  82. package/dist/Citation/Citation.js +20 -6
  83. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  84. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  85. package/dist/ClickableCard/ClickableCard.js +5 -2
  86. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  87. package/dist/CodeBlock/CodeBlock.js +9 -2
  88. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  89. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  90. package/dist/CommandPalette/CommandPalette.js +3 -1
  91. package/dist/CommandPalette/CommandPaletteGroup.d.ts.map +1 -1
  92. package/dist/CommandPalette/CommandPaletteGroup.js +9 -2
  93. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  94. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  95. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  96. package/dist/CommandPalette/CommandPaletteItem.js +1 -1
  97. package/dist/DateInput/DateInput.d.ts +55 -2
  98. package/dist/DateInput/DateInput.d.ts.map +1 -1
  99. package/dist/DateInput/DateInput.js +28 -2
  100. package/dist/DateInput/index.d.ts +1 -1
  101. package/dist/DateInput/index.d.ts.map +1 -1
  102. package/dist/DateRangeInput/DateRangeInput.d.ts +9 -2
  103. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  104. package/dist/DateRangeInput/DateRangeInput.js +3 -1
  105. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  106. package/dist/DateTimeInput/DateTimeInput.js +18 -3
  107. package/dist/Dialog/Dialog.d.ts +3 -0
  108. package/dist/Dialog/Dialog.d.ts.map +1 -1
  109. package/dist/Dialog/Dialog.js +57 -6
  110. package/dist/Dialog/DialogContext.d.ts +7 -0
  111. package/dist/Dialog/DialogContext.d.ts.map +1 -1
  112. package/dist/Dialog/DialogContext.js +2 -2
  113. package/dist/Dialog/DialogHeader.d.ts +5 -2
  114. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  115. package/dist/Dialog/DialogHeader.js +6 -1
  116. package/dist/Divider/Divider.js +1 -1
  117. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -2
  118. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  119. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +1 -2
  120. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  121. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -2
  122. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  123. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -2
  124. package/dist/Field/Field.d.ts.map +1 -1
  125. package/dist/Field/Field.js +2 -3
  126. package/dist/Field/FieldLabel.d.ts +1 -1
  127. package/dist/Field/FieldLabel.d.ts.map +1 -1
  128. package/dist/Field/FieldLabel.js +43 -16
  129. package/dist/FieldStatus/FieldStatus.d.ts +15 -0
  130. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  131. package/dist/FieldStatus/FieldStatus.js +58 -6
  132. package/dist/FileInput/FileInput.d.ts +9 -2
  133. package/dist/FileInput/FileInput.d.ts.map +1 -1
  134. package/dist/FileInput/FileInput.js +42 -20
  135. package/dist/HoverCard/HoverCard.d.ts +9 -1
  136. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  137. package/dist/HoverCard/HoverCard.js +2 -0
  138. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  139. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  140. package/dist/HoverCard/useHoverCard.js +7 -2
  141. package/dist/Icon/Icon.d.ts +7 -2
  142. package/dist/Icon/Icon.d.ts.map +1 -1
  143. package/dist/Item/Item.d.ts +6 -1
  144. package/dist/Item/Item.d.ts.map +1 -1
  145. package/dist/Item/Item.js +22 -1
  146. package/dist/Layer/useLayer.d.ts +6 -0
  147. package/dist/Layer/useLayer.d.ts.map +1 -1
  148. package/dist/Layer/useLayer.js +2 -0
  149. package/dist/Lightbox/Lightbox.js +4 -4
  150. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  151. package/dist/MobileNav/MobileNav.js +6 -1
  152. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  153. package/dist/MobileNav/MobileNavToggle.js +4 -0
  154. package/dist/MultiSelector/MultiSelector.d.ts +9 -1
  155. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  156. package/dist/MultiSelector/MultiSelector.js +41 -12
  157. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  158. package/dist/MultiSelector/hooks.js +16 -0
  159. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  160. package/dist/NumberInput/NumberInput.d.ts +9 -2
  161. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  162. package/dist/NumberInput/NumberInput.js +8 -1
  163. package/dist/Outline/Outline.d.ts +0 -1
  164. package/dist/Outline/Outline.d.ts.map +1 -1
  165. package/dist/Outline/Outline.js +0 -1
  166. package/dist/OverflowList/OverflowList.d.ts +20 -3
  167. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  168. package/dist/OverflowList/OverflowList.js +29 -4
  169. package/dist/Pagination/Pagination.d.ts.map +1 -1
  170. package/dist/Pagination/Pagination.js +11 -3
  171. package/dist/Popover/Popover.d.ts.map +1 -1
  172. package/dist/Popover/Popover.js +3 -2
  173. package/dist/Popover/usePopover.d.ts.map +1 -1
  174. package/dist/Popover/usePopover.js +3 -9
  175. package/dist/PowerSearch/PowerSearch.d.ts +9 -2
  176. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  177. package/dist/PowerSearch/PowerSearch.js +2 -0
  178. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  179. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  180. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  181. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  182. package/dist/SelectableCard/SelectableCard.js +16 -13
  183. package/dist/Selector/Selector.d.ts +8 -0
  184. package/dist/Selector/Selector.d.ts.map +1 -1
  185. package/dist/Selector/Selector.js +7 -3
  186. package/dist/Selector/hooks.d.ts.map +1 -1
  187. package/dist/Selector/hooks.js +16 -0
  188. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  189. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  190. package/dist/SideNav/SideNavHeading.js +21 -8
  191. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  192. package/dist/Slider/Slider.d.ts.map +1 -1
  193. package/dist/Slider/Slider.js +19 -2
  194. package/dist/Stack/stack.stylex.d.ts +9 -9
  195. package/dist/TabList/Tab.js +2 -2
  196. package/dist/TabList/TabMenu.js +1 -1
  197. package/dist/Table/BaseTable.d.ts.map +1 -1
  198. package/dist/Table/BaseTable.js +3 -2
  199. package/dist/Table/TableCell.js +3 -3
  200. package/dist/Table/TableHeaderCell.js +3 -3
  201. package/dist/Table/index.d.ts +2 -0
  202. package/dist/Table/index.d.ts.map +1 -1
  203. package/dist/Table/index.js +1 -0
  204. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  205. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  206. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  207. package/dist/Table/plugins/rowStatus/index.js +3 -0
  208. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  209. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  210. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  211. package/dist/Table/plugins/selection/useTableSelection.d.ts +13 -0
  212. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  213. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  214. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +1 -1
  215. package/dist/Table/plugins/tree/useTableTreeData.d.ts +16 -0
  216. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  217. package/dist/Table/plugins/tree/useTableTreeData.js +87 -0
  218. package/dist/Table/plugins/tree/useTableTreeState.d.ts +6 -0
  219. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -1
  220. package/dist/Table/plugins/tree/useTableTreeState.js +22 -1
  221. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  222. package/dist/Table/useBaseTablePlugins.js +5 -3
  223. package/dist/TextArea/TextArea.d.ts +9 -1
  224. package/dist/TextArea/TextArea.d.ts.map +1 -1
  225. package/dist/TextArea/TextArea.js +2 -0
  226. package/dist/TextInput/TextInput.d.ts +9 -2
  227. package/dist/TextInput/TextInput.d.ts.map +1 -1
  228. package/dist/TextInput/TextInput.js +6 -1
  229. package/dist/Thumbnail/Thumbnail.d.ts +26 -5
  230. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  231. package/dist/Thumbnail/Thumbnail.js +97 -54
  232. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +9 -0
  233. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +1 -0
  234. package/dist/Thumbnail/thumbnail.markers.stylex.js +14 -0
  235. package/dist/TimeInput/TimeInput.d.ts +9 -2
  236. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  237. package/dist/TimeInput/TimeInput.js +17 -4
  238. package/dist/Timestamp/Timestamp.d.ts +3 -1
  239. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  240. package/dist/Timestamp/Timestamp.js +8 -6
  241. package/dist/Toast/useToast.d.ts.map +1 -1
  242. package/dist/Toast/useToast.js +2 -5
  243. package/dist/ToggleButton/ToggleButtonGroup.d.ts +0 -1
  244. package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
  245. package/dist/ToggleButton/ToggleButtonGroup.js +1 -1
  246. package/dist/Tokenizer/Tokenizer.d.ts +10 -3
  247. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  248. package/dist/Tokenizer/Tokenizer.js +18 -4
  249. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  250. package/dist/TopNav/TopNavHeading.js +18 -8
  251. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  252. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +9 -1
  253. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
  254. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +8 -1
  255. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  256. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  257. package/dist/TopNav/TopNavMenu.js +68 -6
  258. package/dist/TreeList/TreeList.d.ts +12 -3
  259. package/dist/TreeList/TreeList.d.ts.map +1 -1
  260. package/dist/TreeList/TreeList.js +2 -0
  261. package/dist/TreeList/TreeListBranches.d.ts +5 -0
  262. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  263. package/dist/TreeList/TreeListBranches.js +10 -4
  264. package/dist/TreeList/TreeListItem.d.ts +8 -2
  265. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  266. package/dist/TreeList/TreeListItem.js +28 -8
  267. package/dist/TreeList/TreeListTypes.d.ts +25 -1
  268. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  269. package/dist/TreeList/index.d.ts +2 -2
  270. package/dist/TreeList/index.d.ts.map +1 -1
  271. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  272. package/dist/Typeahead/BaseTypeahead.js +20 -11
  273. package/dist/Typeahead/Typeahead.d.ts +9 -2
  274. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  275. package/dist/Typeahead/Typeahead.js +2 -0
  276. package/dist/astryx.css +41 -31
  277. package/dist/astryx.umd.js +51 -51
  278. package/dist/astryx.umd.js.map +4 -4
  279. package/dist/authoring/doc.d.ts.map +1 -1
  280. package/dist/docs-types.d.ts +8 -0
  281. package/dist/docs-types.d.ts.map +1 -1
  282. package/dist/hooks/computeOverflow.d.ts +47 -0
  283. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  284. package/dist/hooks/computeOverflow.js +213 -0
  285. package/dist/hooks/index.d.ts +1 -0
  286. package/dist/hooks/index.d.ts.map +1 -1
  287. package/dist/hooks/index.js +2 -1
  288. package/dist/hooks/isRtlElement.d.ts +18 -0
  289. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  290. package/dist/hooks/isRtlElement.js +27 -0
  291. package/dist/hooks/useDevWarning.d.ts +18 -0
  292. package/dist/hooks/useDevWarning.d.ts.map +1 -0
  293. package/dist/hooks/useDevWarning.js +49 -0
  294. package/dist/hooks/useGridFocus.d.ts +6 -1
  295. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  296. package/dist/hooks/useGridFocus.js +12 -9
  297. package/dist/hooks/useListFocus.d.ts +6 -1
  298. package/dist/hooks/useListFocus.d.ts.map +1 -1
  299. package/dist/hooks/useListFocus.js +12 -4
  300. package/dist/hooks/useOverflow.d.ts +19 -0
  301. package/dist/hooks/useOverflow.d.ts.map +1 -1
  302. package/dist/hooks/useOverflow.js +36 -25
  303. package/dist/i18n/InternationalizationContext.d.ts +1 -0
  304. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  305. package/dist/i18n/InternationalizationContext.js +3 -0
  306. package/dist/i18n/InternationalizationProvider.d.ts +8 -1
  307. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  308. package/dist/i18n/InternationalizationProvider.js +5 -1
  309. package/dist/i18n/getLocaleDirection.d.ts +18 -0
  310. package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
  311. package/dist/i18n/getLocaleDirection.js +29 -0
  312. package/dist/i18n/index.d.ts +6 -0
  313. package/dist/i18n/index.d.ts.map +1 -1
  314. package/dist/i18n/index.js +7 -1
  315. package/dist/i18n/resolve.d.ts.map +1 -1
  316. package/dist/i18n/resolve.js +7 -18
  317. package/dist/i18n/useDirection.d.ts +2 -0
  318. package/dist/i18n/useDirection.d.ts.map +1 -0
  319. package/dist/i18n/useDirection.js +27 -0
  320. package/dist/theme/Theme.d.ts.map +1 -1
  321. package/dist/theme/Theme.js +2 -7
  322. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  323. package/dist/theme/derivedVarRegistry.js +13 -0
  324. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  325. package/dist/theme/generateThemeRules.js +51 -0
  326. package/dist/theme/hct.d.ts +0 -1
  327. package/dist/theme/hct.d.ts.map +1 -1
  328. package/dist/theme/hct.js +1 -1
  329. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  330. package/dist/theme/syntax/defineSyntaxTheme.js +2 -1
  331. package/dist/utils/devWarning.d.ts +42 -0
  332. package/dist/utils/devWarning.d.ts.map +1 -0
  333. package/dist/utils/devWarning.js +88 -0
  334. package/dist/utils/index.d.ts +3 -1
  335. package/dist/utils/index.d.ts.map +1 -1
  336. package/dist/utils/index.js +3 -2
  337. package/dist/utils/plainDate.d.ts +30 -0
  338. package/dist/utils/plainDate.d.ts.map +1 -1
  339. package/dist/utils/plainDate.js +53 -0
  340. package/dist/utils/types.d.ts +13 -0
  341. package/dist/utils/types.d.ts.map +1 -1
  342. package/locales/en.json +32 -0
  343. package/locales/fr-FR.json +14 -0
  344. package/locales/pseudo.json +24 -0
  345. package/package.json +1 -1
  346. package/src/AppShell/AppShell.tsx +6 -0
  347. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  348. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  349. package/src/Avatar/Avatar.doc.mjs +60 -3
  350. package/src/Avatar/Avatar.test.tsx +281 -1
  351. package/src/Avatar/Avatar.tsx +324 -64
  352. package/src/Avatar/AvatarStatusDot.doc.mjs +23 -12
  353. package/src/Avatar/AvatarStatusDot.test.tsx +284 -0
  354. package/src/Avatar/AvatarStatusDot.tsx +178 -21
  355. package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
  356. package/src/AvatarGroup/AvatarGroup.test.tsx +125 -0
  357. package/src/AvatarGroup/AvatarGroup.tsx +58 -3
  358. package/src/AvatarGroup/AvatarGroupOverflow.tsx +1 -0
  359. package/src/Banner/Banner.doc.mjs +9 -0
  360. package/src/Banner/Banner.test.tsx +30 -0
  361. package/src/Banner/Banner.tsx +53 -9
  362. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +26 -0
  363. package/src/Breadcrumbs/BreadcrumbItem.tsx +333 -14
  364. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -0
  365. package/src/Breadcrumbs/Breadcrumbs.test.tsx +284 -2
  366. package/src/Breadcrumbs/index.ts +27 -0
  367. package/src/Button/Button.doc.mjs +9 -0
  368. package/src/Button/Button.test.tsx +49 -0
  369. package/src/Button/Button.tsx +27 -3
  370. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  371. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  372. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  373. package/src/Calendar/Calendar.doc.mjs +12 -1
  374. package/src/Calendar/Calendar.test.tsx +193 -0
  375. package/src/Calendar/Calendar.tsx +38 -16
  376. package/src/Calendar/dayCellUtils.test.ts +5 -0
  377. package/src/Calendar/dayCellUtils.ts +1 -1
  378. package/src/Card/Card.doc.mjs +9 -0
  379. package/src/Card/Card.test.tsx +21 -3
  380. package/src/Card/Card.tsx +35 -2
  381. package/src/Carousel/Carousel.doc.mjs +1 -0
  382. package/src/Carousel/Carousel.test.tsx +53 -0
  383. package/src/Carousel/Carousel.tsx +34 -4
  384. package/src/Chat/ChatComposer.doc.mjs +12 -3
  385. package/src/Chat/ChatComposer.test.tsx +115 -0
  386. package/src/Chat/ChatComposer.tsx +73 -9
  387. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  388. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  389. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  390. package/src/Chat/ChatComposerInput.test.tsx +103 -0
  391. package/src/Chat/ChatComposerInput.tsx +51 -5
  392. package/src/Chat/ChatContext.tsx +37 -10
  393. package/src/Chat/ChatDictationButton.tsx +4 -1
  394. package/src/Chat/ChatLayout.tsx +7 -5
  395. package/src/Chat/ChatLayoutScrollButton.tsx +7 -2
  396. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  397. package/src/Chat/ChatSendButton.tsx +11 -1
  398. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  399. package/src/Chat/ChatToolCalls.tsx +25 -4
  400. package/src/Chat/index.ts +5 -1
  401. package/src/Chat/useChatStreamScroll.test.tsx +49 -0
  402. package/src/Chat/useChatStreamScroll.ts +27 -10
  403. package/src/Chat/useSpeechRecognition.ts +1 -1
  404. package/src/Chat/useTriggerMenu.tsx +4 -2
  405. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  406. package/src/CheckboxInput/CheckboxInput.tsx +1 -1
  407. package/src/CheckboxList/CheckboxList.test.tsx +81 -3
  408. package/src/CheckboxList/CheckboxListItem.doc.mjs +33 -5
  409. package/src/CheckboxList/CheckboxListItem.tsx +41 -5
  410. package/src/Citation/Citation.doc.mjs +4 -3
  411. package/src/Citation/Citation.test.tsx +97 -0
  412. package/src/Citation/Citation.tsx +43 -11
  413. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  414. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  415. package/src/ClickableCard/ClickableCard.tsx +13 -3
  416. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  417. package/src/CodeBlock/CodeBlock.tsx +10 -2
  418. package/src/CommandPalette/CommandPalette.doc.mjs +1 -0
  419. package/src/CommandPalette/CommandPalette.tsx +3 -1
  420. package/src/CommandPalette/CommandPaletteGroup.test.tsx +71 -0
  421. package/src/CommandPalette/CommandPaletteGroup.tsx +12 -1
  422. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  423. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  424. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  425. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  426. package/src/ContextMenu/ContextMenu.test.tsx +193 -0
  427. package/src/DateInput/DateInput.doc.mjs +31 -0
  428. package/src/DateInput/DateInput.test.tsx +131 -0
  429. package/src/DateInput/DateInput.tsx +78 -3
  430. package/src/DateInput/index.ts +1 -0
  431. package/src/DateRangeInput/DateRangeInput.doc.mjs +8 -0
  432. package/src/DateRangeInput/DateRangeInput.test.tsx +23 -0
  433. package/src/DateRangeInput/DateRangeInput.tsx +13 -3
  434. package/src/DateTimeInput/DateTimeInput.doc.mjs +16 -1
  435. package/src/DateTimeInput/DateTimeInput.test.tsx +147 -0
  436. package/src/DateTimeInput/DateTimeInput.tsx +42 -18
  437. package/src/Dialog/Dialog.doc.mjs +2 -2
  438. package/src/Dialog/Dialog.test.tsx +77 -4
  439. package/src/Dialog/Dialog.tsx +89 -5
  440. package/src/Dialog/DialogContext.ts +9 -2
  441. package/src/Dialog/DialogHeader.doc.mjs +2 -2
  442. package/src/Dialog/DialogHeader.tsx +9 -2
  443. package/src/Divider/Divider.tsx +2 -2
  444. package/src/DropdownMenu/DropdownMenu.test.tsx +169 -0
  445. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -2
  446. package/src/DropdownMenu/DropdownMenuItem.tsx +1 -1
  447. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +1 -2
  448. package/src/Field/Field.test.tsx +57 -12
  449. package/src/Field/Field.tsx +6 -5
  450. package/src/Field/FieldLabel.tsx +9 -1
  451. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  452. package/src/FieldStatus/FieldStatus.test.tsx +150 -40
  453. package/src/FieldStatus/FieldStatus.tsx +63 -5
  454. package/src/FileInput/FileInput.doc.mjs +17 -2
  455. package/src/FileInput/FileInput.test.tsx +135 -12
  456. package/src/FileInput/FileInput.tsx +53 -28
  457. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  458. package/src/HoverCard/HoverCard.test.tsx +21 -2
  459. package/src/HoverCard/HoverCard.tsx +11 -0
  460. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  461. package/src/HoverCard/useHoverCard.tsx +24 -2
  462. package/src/Icon/Icon.doc.mjs +3 -2
  463. package/src/Icon/Icon.tsx +7 -2
  464. package/src/IconButton/IconButton.doc.mjs +8 -0
  465. package/src/IconButton/IconButton.test.tsx +15 -18
  466. package/src/Item/Item.test.tsx +40 -18
  467. package/src/Item/Item.tsx +38 -2
  468. package/src/Layer/useLayer.tsx +8 -0
  469. package/src/Lightbox/Lightbox.tsx +4 -4
  470. package/src/List/List.test.tsx +9 -8
  471. package/src/MobileNav/MobileNav.tsx +12 -1
  472. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  473. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  474. package/src/MultiSelector/MultiSelector.doc.mjs +13 -2
  475. package/src/MultiSelector/MultiSelector.test.tsx +195 -3
  476. package/src/MultiSelector/MultiSelector.tsx +65 -12
  477. package/src/MultiSelector/hooks.ts +17 -0
  478. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  479. package/src/NumberInput/NumberInput.doc.mjs +15 -0
  480. package/src/NumberInput/NumberInput.test.tsx +82 -7
  481. package/src/NumberInput/NumberInput.tsx +21 -4
  482. package/src/Outline/Outline.tsx +0 -1
  483. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  484. package/src/OverflowList/OverflowList.test.tsx +111 -0
  485. package/src/OverflowList/OverflowList.tsx +48 -8
  486. package/src/Pagination/Pagination.tsx +11 -3
  487. package/src/Popover/Popover.tsx +7 -4
  488. package/src/Popover/usePopover.tsx +9 -13
  489. package/src/PowerSearch/PowerSearch.doc.mjs +29 -0
  490. package/src/PowerSearch/PowerSearch.test.tsx +43 -0
  491. package/src/PowerSearch/PowerSearch.tsx +10 -1
  492. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  493. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  494. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  495. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  496. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  497. package/src/SelectableCard/SelectableCard.tsx +29 -16
  498. package/src/Selector/Selector.doc.mjs +7 -0
  499. package/src/Selector/Selector.test.tsx +92 -0
  500. package/src/Selector/Selector.tsx +16 -2
  501. package/src/Selector/hooks.ts +17 -0
  502. package/src/SideNav/SideNav.test.tsx +119 -3
  503. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  504. package/src/SideNav/SideNavHeading.tsx +24 -8
  505. package/src/SideNav/SideNavItem.tsx +5 -1
  506. package/src/Slider/Slider.test.tsx +75 -2
  507. package/src/Slider/Slider.tsx +21 -5
  508. package/src/Switch/Switch.test.tsx +32 -1
  509. package/src/TabList/Tab.tsx +2 -2
  510. package/src/TabList/TabMenu.tsx +2 -2
  511. package/src/Table/BaseTable.tsx +3 -5
  512. package/src/Table/TableCell.tsx +3 -3
  513. package/src/Table/TableHeaderCell.tsx +3 -3
  514. package/src/Table/index.ts +5 -0
  515. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  516. package/src/Table/plugins/rowStatus/index.ts +7 -0
  517. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  518. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  519. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  520. package/src/Table/plugins/selection/useTableSelection.tsx +19 -1
  521. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -1
  522. package/src/Table/plugins/tree/useTableTreeData.test.tsx +135 -3
  523. package/src/Table/plugins/tree/useTableTreeData.tsx +128 -1
  524. package/src/Table/plugins/tree/useTableTreeState.test.tsx +104 -0
  525. package/src/Table/plugins/tree/useTableTreeState.tsx +46 -2
  526. package/src/Table/useBaseTablePlugins.ts +10 -6
  527. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  528. package/src/Table/useTableSelection.doc.mjs +65 -14
  529. package/src/Table/useTableTreeData.doc.mjs +29 -0
  530. package/src/Table/useTableTreeState.doc.mjs +2 -2
  531. package/src/TextArea/TextArea.doc.mjs +15 -0
  532. package/src/TextArea/TextArea.test.tsx +32 -1
  533. package/src/TextArea/TextArea.tsx +10 -0
  534. package/src/TextInput/TextInput.doc.mjs +15 -0
  535. package/src/TextInput/TextInput.test.tsx +44 -0
  536. package/src/TextInput/TextInput.tsx +13 -1
  537. package/src/Thumbnail/Thumbnail.doc.mjs +19 -10
  538. package/src/Thumbnail/Thumbnail.test.tsx +98 -7
  539. package/src/Thumbnail/Thumbnail.tsx +142 -42
  540. package/src/Thumbnail/thumbnail.markers.stylex.ts +12 -0
  541. package/src/TimeInput/TimeInput.doc.mjs +15 -0
  542. package/src/TimeInput/TimeInput.test.tsx +99 -1
  543. package/src/TimeInput/TimeInput.tsx +29 -6
  544. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  545. package/src/Timestamp/Timestamp.test.tsx +32 -0
  546. package/src/Timestamp/Timestamp.tsx +28 -8
  547. package/src/Toast/useToast.tsx +7 -8
  548. package/src/ToggleButton/ToggleButtonGroup.tsx +1 -1
  549. package/src/Tokenizer/Tokenizer.doc.mjs +30 -1
  550. package/src/Tokenizer/Tokenizer.test.tsx +206 -2
  551. package/src/Tokenizer/Tokenizer.tsx +26 -4
  552. package/src/TopNav/TopNav.test.tsx +125 -1
  553. package/src/TopNav/TopNavHeading.tsx +24 -8
  554. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  555. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  556. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +3 -3
  557. package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +19 -2
  558. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  559. package/src/TopNav/TopNavMenu.tsx +83 -5
  560. package/src/TreeList/TreeList.doc.mjs +22 -0
  561. package/src/TreeList/TreeList.test.tsx +232 -0
  562. package/src/TreeList/TreeList.tsx +18 -2
  563. package/src/TreeList/TreeListBranches.tsx +16 -2
  564. package/src/TreeList/TreeListItem.tsx +52 -12
  565. package/src/TreeList/TreeListTypes.ts +27 -1
  566. package/src/TreeList/index.ts +2 -2
  567. package/src/Typeahead/BaseTypeahead.tsx +30 -15
  568. package/src/Typeahead/Typeahead.doc.mjs +7 -0
  569. package/src/Typeahead/Typeahead.test.tsx +50 -0
  570. package/src/Typeahead/Typeahead.tsx +10 -0
  571. package/src/authoring/doc.ts +1 -3
  572. package/src/docs-types.ts +5 -0
  573. package/src/hooks/computeOverflow.test.ts +422 -0
  574. package/src/hooks/computeOverflow.ts +290 -0
  575. package/src/hooks/index.ts +2 -0
  576. package/src/hooks/isRtlElement.ts +27 -0
  577. package/src/hooks/useDevWarning.test.tsx +45 -0
  578. package/src/hooks/useDevWarning.ts +54 -0
  579. package/src/hooks/useGridFocus.doc.mjs +3 -3
  580. package/src/hooks/useGridFocus.test.tsx +41 -0
  581. package/src/hooks/useGridFocus.ts +21 -10
  582. package/src/hooks/useListFocus.doc.mjs +3 -3
  583. package/src/hooks/useListFocus.test.tsx +67 -1
  584. package/src/hooks/useListFocus.ts +21 -5
  585. package/src/hooks/useOverflow.test.ts +96 -4
  586. package/src/hooks/useOverflow.ts +74 -33
  587. package/src/i18n/InternationalizationContext.ts +4 -0
  588. package/src/i18n/InternationalizationProvider.doc.mjs +99 -2
  589. package/src/i18n/InternationalizationProvider.tsx +12 -2
  590. package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
  591. package/src/i18n/__tests__/useDirection.test.tsx +66 -0
  592. package/src/i18n/getLocaleDirection.ts +29 -0
  593. package/src/i18n/index.ts +6 -0
  594. package/src/i18n/resolve.ts +9 -21
  595. package/src/i18n/useDirection.ts +29 -0
  596. package/src/reset.css +0 -26
  597. package/src/reset.test.ts +60 -0
  598. package/src/theme/Theme.tsx +11 -14
  599. package/src/theme/derivedVarRegistry.test.ts +1 -0
  600. package/src/theme/derivedVarRegistry.ts +6 -0
  601. package/src/theme/generateThemeRules.test.ts +107 -0
  602. package/src/theme/generateThemeRules.ts +56 -0
  603. package/src/theme/hct.ts +1 -1
  604. package/src/theme/syntax/defineSyntaxTheme.ts +5 -6
  605. package/src/utils/devWarning.test.ts +56 -0
  606. package/src/utils/devWarning.ts +102 -0
  607. package/src/utils/index.ts +6 -0
  608. package/src/utils/plainDate.test.ts +22 -0
  609. package/src/utils/plainDate.ts +61 -0
  610. package/src/utils/types.ts +14 -0
@@ -147,16 +147,33 @@ describe('FileInput', () => {
147
147
  expect(screen.getByText('Upload')).toBeInTheDocument();
148
148
  });
149
149
 
150
- it('sets aria-required when isRequired is true', () => {
150
+ it('conveys required state through the accessible description', () => {
151
151
  render(
152
152
  <FileInput label="Resume" isRequired value={null} onChange={() => {}} />,
153
153
  );
154
- // aria-required lives on the focusable role="button" wrapper, not the
154
+ const trigger = screen.getByRole('button', {name: 'Resume'});
155
+ // Required is conveyed via a visually hidden "Required" node referenced
156
+ // from aria-describedby on the focusable role="button" trigger — not the
155
157
  // hidden file input (forms-6).
156
- expect(screen.getByRole('button', {name: 'Resume'})).toHaveAttribute(
157
- 'aria-required',
158
- 'true',
158
+ expect(trigger).toHaveAccessibleDescription(/Required/);
159
+ // aria-required is not a supported property of role="button" in
160
+ // WAI-ARIA 1.2 (AT does not announce it), so it must never appear.
161
+ expect(trigger).not.toHaveAttribute('aria-required');
162
+ });
163
+
164
+ it('combines required with the description in the accessible description', () => {
165
+ render(
166
+ <FileInput
167
+ label="Resume"
168
+ description="PDF only"
169
+ isRequired
170
+ value={null}
171
+ onChange={() => {}}
172
+ />,
159
173
  );
174
+ const trigger = screen.getByRole('button', {name: 'Resume'});
175
+ expect(trigger).toHaveAccessibleDescription(/PDF only/);
176
+ expect(trigger).toHaveAccessibleDescription(/Required/);
160
177
  });
161
178
 
162
179
  it('places aria-describedby on the focusable button, not the hidden input (forms-6)', () => {
@@ -176,11 +193,11 @@ describe('FileInput', () => {
176
193
  expect(input).toHaveAttribute('aria-hidden', 'true');
177
194
  });
178
195
 
179
- it('does not set aria-required by default', () => {
196
+ it('does not mention required without isRequired', () => {
180
197
  render(<FileInput label="Resume" value={null} onChange={() => {}} />);
181
- expect(screen.getByRole('button', {name: 'Resume'})).not.toHaveAttribute(
182
- 'aria-required',
183
- );
198
+ const trigger = screen.getByRole('button', {name: 'Resume'});
199
+ expect(trigger).not.toHaveAccessibleDescription(/Required/);
200
+ expect(trigger).not.toHaveAttribute('aria-required');
184
201
  });
185
202
 
186
203
  it('sets disabled attribute when isDisabled is true', () => {
@@ -348,9 +365,44 @@ describe('FileInput', () => {
348
365
  fireEvent.change(fileInputEl(), {
349
366
  target: {files: [createFile('note.txt', 100)]},
350
367
  });
351
- // A rejected selection creates no polite region (only the error goes to
352
- // the existing role="status" region).
353
- expect(politeRegion()).toBeNull();
368
+ // A rejected selection announces nothing politely. (The region pair may
369
+ // exist because the validation error itself is announced assertively via
370
+ // FieldStatus, but the polite channel must stay empty.)
371
+ expect(politeRegion()?.textContent ?? '').toBe('');
372
+ });
373
+
374
+ it('announces a validation error exactly once across live regions', async () => {
375
+ render(
376
+ <FileInput
377
+ label="Upload"
378
+ value={null}
379
+ onChange={() => {}}
380
+ accept=".pdf"
381
+ />,
382
+ );
383
+ fireEvent.change(fileInputEl(), {
384
+ target: {files: [createFile('note.txt', 100)]},
385
+ });
386
+ const errorText = '"note.txt" is not an accepted file type';
387
+ // The error lands in the assertive announce region (via the FieldStatus
388
+ // the derived error status mounts)…
389
+ await waitFor(() => {
390
+ expect(
391
+ document.querySelector('[data-astryx-live-region="assertive"]'),
392
+ ).toHaveTextContent(errorText);
393
+ });
394
+ // …and in exactly one live region overall. FileInput's own role="status"
395
+ // region used to duplicate the FieldStatus announcement, so the same
396
+ // error was read twice (assertively, then politely).
397
+ const liveRegions = Array.from(
398
+ document.querySelectorAll(
399
+ '[role="status"], [role="alert"], [aria-live]',
400
+ ),
401
+ );
402
+ const regionsWithError = liveRegions.filter(el =>
403
+ (el.textContent ?? '').includes(errorText),
404
+ );
405
+ expect(regionsWithError).toHaveLength(1);
354
406
  });
355
407
  });
356
408
 
@@ -641,6 +693,54 @@ describe('FileInput', () => {
641
693
  });
642
694
  });
643
695
 
696
+ describe('trigger accessible name', () => {
697
+ it('includes the selected file name in the trigger name', () => {
698
+ const file = createFile('report.pdf', 1024, 'application/pdf');
699
+ render(<FileInput label="Document" value={file} onChange={() => {}} />);
700
+ expect(
701
+ screen.getByRole('button', {name: 'Document, report.pdf'}),
702
+ ).toBeInTheDocument();
703
+ });
704
+
705
+ it('includes all selected file names when multiple files are selected', () => {
706
+ const files = [createFile('a.txt', 100), createFile('b.txt', 200)];
707
+ render(
708
+ <FileInput
709
+ label="Files"
710
+ value={files}
711
+ onChange={() => {}}
712
+ isMultiple
713
+ />,
714
+ );
715
+ expect(
716
+ screen.getByRole('button', {name: 'Files, a.txt, b.txt'}),
717
+ ).toBeInTheDocument();
718
+ });
719
+
720
+ it('uses exactly the label when no files are selected', () => {
721
+ render(<FileInput label="Document" value={null} onChange={() => {}} />);
722
+ expect(screen.getByRole('button', {name: 'Document'})).toHaveAttribute(
723
+ 'aria-label',
724
+ 'Document',
725
+ );
726
+ });
727
+
728
+ it('includes the selected file name in dropzone mode', () => {
729
+ const file = createFile('doc.pdf', 100, 'application/pdf');
730
+ render(
731
+ <FileInput
732
+ label="Upload"
733
+ value={file}
734
+ onChange={() => {}}
735
+ mode="dropzone"
736
+ />,
737
+ );
738
+ expect(
739
+ screen.getByRole('button', {name: 'Upload, doc.pdf'}),
740
+ ).toBeInTheDocument();
741
+ });
742
+ });
743
+
644
744
  describe('data-testid', () => {
645
745
  it('passes data-testid to native input', () => {
646
746
  render(
@@ -800,3 +900,26 @@ describe('FileInput', () => {
800
900
  });
801
901
  });
802
902
  });
903
+
904
+
905
+ describe('FileInput statusVariant forwarding', () => {
906
+ it('defaults to attached (status renders with data-variant="attached")', () => {
907
+ const {container} = render(
908
+ <FileInput label="Upload" value={null} onChange={() => {}} status={{type: 'error', message: 'Something went wrong'}} />,
909
+ );
910
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
911
+ 'data-variant',
912
+ 'attached',
913
+ );
914
+ });
915
+
916
+ it('forwards statusVariant="detached" to the underlying Field status', () => {
917
+ const {container} = render(
918
+ <FileInput label="Upload" value={null} onChange={() => {}} status={{type: 'error', message: 'Something went wrong'}} statusVariant="detached" />,
919
+ );
920
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
921
+ 'data-variant',
922
+ 'detached',
923
+ );
924
+ });
925
+ });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file FileInput.tsx
7
- * @input Uses React, useId, Field, Icon, Spinner
7
+ * @input Uses React, useId, Field, Icon, Spinner, VisuallyHidden
8
8
  * @output Exports FileInput component, FileInputProps, FileInputStatus
9
9
  * @position Core implementation; consumed by index.ts, tested by FileInput.test.tsx
10
10
  *
@@ -41,9 +41,11 @@ import {
41
41
  InputClearButton,
42
42
  type InputStatus,
43
43
  type InputStatusType,
44
+ type FieldStatusVariant,
44
45
  } from '../Field';
45
46
  import {Icon, type IconName} from '../Icon';
46
47
  import {Spinner} from '../Spinner';
48
+ import {VisuallyHidden} from '../VisuallyHidden';
47
49
  import {useAnnounce} from '../hooks/useAnnounce';
48
50
  import {useTooltip} from '../Tooltip';
49
51
 
@@ -246,17 +248,6 @@ const styles = stylex.create({
246
248
  flex: 1,
247
249
  minWidth: 0,
248
250
  },
249
- liveRegion: {
250
- position: 'absolute',
251
- width: 1,
252
- height: 1,
253
- padding: 0,
254
- margin: -1,
255
- overflow: 'hidden',
256
- clip: 'rect(0, 0, 0, 0)',
257
- whiteSpace: 'nowrap',
258
- borderWidth: 0,
259
- },
260
251
  });
261
252
 
262
253
  const statusBorderStyles = stylex.create({
@@ -358,6 +349,13 @@ export interface FileInputProps extends Omit<
358
349
  * Validation status for the input.
359
350
  */
360
351
  status?: InputStatus;
352
+ /**
353
+ * How the status message is placed relative to the input.
354
+ * - 'attached': message overlaps directly below the input (bordered treatment)
355
+ * - 'detached': message floats below as a separate element with spacing
356
+ * @default 'attached'
357
+ */
358
+ statusVariant?: FieldStatusVariant;
361
359
  /**
362
360
  * Description text displayed below the label.
363
361
  */
@@ -414,6 +412,7 @@ export function FileInput({
414
412
  isRequired = false,
415
413
  isLoading = false,
416
414
  status: statusProp,
415
+ statusVariant = 'attached',
417
416
  description,
418
417
  placeholder,
419
418
  mode = 'input',
@@ -430,15 +429,16 @@ export function FileInput({
430
429
  const id = useId();
431
430
  const descriptionID = useId();
432
431
  const statusMessageID = useId();
433
- const liveRegionID = useId();
432
+ const requiredID = useId();
434
433
  const inputRef = useRef<HTMLInputElement | null>(null);
435
434
  const [isDragOver, setIsDragOver] = useState(false);
436
435
  const [validationError, setValidationError] = useState<string | null>(null);
437
436
  const [, startTransition] = useTransition();
438
437
 
439
438
  // Announce successful file selection to screen readers via a persistent
440
- // live region (forms-17). The component's own role="status" region only
441
- // carries validation errors, so a successful attach was previously silent.
439
+ // live region (forms-17). Validation errors are announced (assertively) by
440
+ // the FieldStatus that the derived error status mounts, so only the
441
+ // successful attach needs an explicit announcement here.
442
442
  const announce = useAnnounce();
443
443
 
444
444
  // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
@@ -474,10 +474,18 @@ export function FileInput({
474
474
  success: 'success',
475
475
  };
476
476
 
477
+ // Required state. `aria-required` is not a supported property of
478
+ // role="button" in WAI-ARIA 1.2 (AT does not announce it there), so the
479
+ // trigger instead points its aria-describedby at a visually hidden
480
+ // "Required" span — mirroring the Field label's visible indicator (where
481
+ // isOptional takes precedence) and the pattern Slider uses for its thumbs.
482
+ const conveysRequired = isRequired && !isOptional;
483
+
477
484
  const ariaDescribedBy =
478
485
  [
479
486
  description ? descriptionID : null,
480
487
  status?.message ? statusMessageID : null,
488
+ conveysRequired ? requiredID : null,
481
489
  showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
482
490
  ]
483
491
  .filter(Boolean)
@@ -515,8 +523,9 @@ export function FileInput({
515
523
  onChange(result);
516
524
 
517
525
  // Announce the successful selection politely. Validation errors are
518
- // handled by the role="status" region below, so only announce the
519
- // attach here (do not double-announce errors).
526
+ // announced assertively by the FieldStatus that the derived error
527
+ // status mounts, so only announce the attach here (do not
528
+ // double-announce errors).
520
529
  if (errors.length === 0) {
521
530
  announce(
522
531
  valid.length === 1
@@ -728,6 +737,7 @@ export function FileInput({
728
737
  }
729
738
  : undefined
730
739
  }
740
+ statusVariant={statusVariant}
731
741
  labelTooltip={labelTooltip}
732
742
  width={width}>
733
743
  <div
@@ -745,13 +755,27 @@ export function FileInput({
745
755
  aria-disabled={showsDisabledMessage ? 'true' : undefined}
746
756
  onClick={handleClick}
747
757
  onKeyDown={handleKeyDown}
748
- aria-label={label}
758
+ // aria-label suppresses child content from the accessible name, so
759
+ // compose the selected filenames into it — otherwise a screen-reader
760
+ // user refocusing the control hears only the field label and cannot
761
+ // tell what (if anything) is attached.
762
+ aria-label={
763
+ hasFiles && fileNames
764
+ ? t('@astryx.fileInput.triggerWithFiles', {label, fileNames})
765
+ : label
766
+ }
749
767
  aria-busy={isLoading || undefined}
750
768
  // These describe the operable control, so they belong on the focusable
751
769
  // role="button" wrapper — not the hidden tabIndex={-1} file input the
752
- // user never focuses (forms-6).
770
+ // user never focuses (forms-6). Required state is conveyed through
771
+ // the visually hidden "Required" node in aria-describedby, not
772
+ // aria-required, which role="button" does not support in ARIA 1.2.
753
773
  aria-describedby={ariaDescribedBy}
754
- aria-required={isRequired ? 'true' : undefined}
774
+ // aria-invalid is likewise unsupported on role="button", but it stays:
775
+ // a message-less error status (`status={{type: 'error'}}`) has no
776
+ // described-by replacement, and some AT still honor the attribute, so
777
+ // removing it would not be strictly better. Errors *with* a message
778
+ // are described via statusMessageID above.
755
779
  aria-invalid={status?.type === 'error' ? 'true' : undefined}
756
780
  {...dragDropProps}
757
781
  {...mergeProps(
@@ -785,16 +809,17 @@ export function FileInput({
785
809
  />
786
810
  {isDropzone ? renderDropzoneContent() : renderCompactContent()}
787
811
  {hasFiles && !isDisabled && !isLoading && (
788
- <InputClearButton label={t('@astryx.fileInput.clearLabel', {label})} onClick={handleClear} />
812
+ <InputClearButton
813
+ label={t('@astryx.fileInput.clearLabel', {label})}
814
+ onClick={handleClear}
815
+ />
789
816
  )}
790
817
  </div>
791
- <div
792
- id={liveRegionID}
793
- role="status"
794
- aria-live="polite"
795
- {...stylex.props(styles.liveRegion)}>
796
- {validationError}
797
- </div>
818
+ {conveysRequired && (
819
+ <VisuallyHidden id={requiredID}>
820
+ {t('@astryx.fileInput.required')}
821
+ </VisuallyHidden>
822
+ )}
798
823
  {showsDisabledMessage &&
799
824
  disabledMessageTooltip.renderTooltip(disabledMessage)}
800
825
  </Field>
@@ -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
  });