@astryxdesign/core 0.1.7 → 0.1.8-canary.0c4d8d7

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 (428) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +30 -19
  3. package/dist/AppShell/AppShell.d.ts.map +1 -1
  4. package/dist/AppShell/AppShell.js +6 -2
  5. package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
  6. package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
  7. package/dist/Avatar/Avatar.d.ts +10 -5
  8. package/dist/Avatar/Avatar.d.ts.map +1 -1
  9. package/dist/Avatar/Avatar.js +12 -8
  10. package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
  11. package/dist/Avatar/AvatarSizeContext.js +1 -1
  12. package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
  13. package/dist/Avatar/AvatarStatusDot.js +2 -2
  14. package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
  15. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  16. package/dist/AvatarGroup/AvatarGroup.js +2 -2
  17. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  18. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  19. package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
  20. package/dist/Banner/Banner.d.ts +9 -1
  21. package/dist/Banner/Banner.d.ts.map +1 -1
  22. package/dist/Banner/Banner.js +31 -2
  23. package/dist/Button/Button.d.ts +8 -2
  24. package/dist/Button/Button.d.ts.map +1 -1
  25. package/dist/Button/Button.js +31 -2
  26. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  27. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  28. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  29. package/dist/Card/Card.d.ts +8 -2
  30. package/dist/Card/Card.d.ts.map +1 -1
  31. package/dist/Card/Card.js +28 -2
  32. package/dist/Carousel/Carousel.d.ts +6 -2
  33. package/dist/Carousel/Carousel.d.ts.map +1 -1
  34. package/dist/Carousel/Carousel.js +55 -5
  35. package/dist/Chat/ChatComposer.d.ts +8 -0
  36. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  37. package/dist/Chat/ChatComposer.js +31 -6
  38. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  39. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  40. package/dist/Chat/ChatComposerDrawer.js +11 -1
  41. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  42. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposerInput.js +19 -3
  44. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  45. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  46. package/dist/Chat/ChatMessage.d.ts +1 -1
  47. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  48. package/dist/Chat/ChatMessage.js +1 -1
  49. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  50. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  51. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  52. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  53. package/dist/Chat/ChatToolCalls.js +12 -3
  54. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  55. package/dist/Citation/Citation.d.ts.map +1 -1
  56. package/dist/Citation/Citation.js +10 -4
  57. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  58. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  59. package/dist/ClickableCard/ClickableCard.js +3 -0
  60. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  61. package/dist/CodeBlock/CodeBlock.js +8 -1
  62. package/dist/Collapsible/Collapsible.d.ts +11 -1
  63. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  64. package/dist/Collapsible/Collapsible.js +36 -4
  65. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  66. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  67. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  68. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  69. package/dist/ContextMenu/ContextMenu.js +4 -3
  70. package/dist/ContextMenu/index.d.ts +3 -0
  71. package/dist/ContextMenu/index.d.ts.map +1 -1
  72. package/dist/ContextMenu/index.js +7 -1
  73. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  74. package/dist/DateTimeInput/DateTimeInput.js +10 -1
  75. package/dist/Dialog/Dialog.d.ts.map +1 -1
  76. package/dist/Dialog/Dialog.js +2 -1
  77. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenu.js +8 -4
  79. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +88 -0
  80. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
  81. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +169 -0
  82. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
  83. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
  85. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +68 -0
  86. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
  87. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +69 -0
  88. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
  89. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
  90. package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
  91. package/dist/DropdownMenu/index.d.ts +5 -1
  92. package/dist/DropdownMenu/index.d.ts.map +1 -1
  93. package/dist/DropdownMenu/index.js +10 -1
  94. package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
  95. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
  96. package/dist/DropdownMenu/menuItemRoles.js +15 -0
  97. package/dist/FieldStatus/FieldStatus.d.ts +8 -0
  98. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  99. package/dist/FieldStatus/FieldStatus.js +22 -4
  100. package/dist/FileInput/FileInput.d.ts.map +1 -1
  101. package/dist/FileInput/FileInput.js +10 -2
  102. package/dist/HoverCard/HoverCard.d.ts +9 -1
  103. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  104. package/dist/HoverCard/HoverCard.js +2 -0
  105. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  106. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  107. package/dist/HoverCard/useHoverCard.js +7 -2
  108. package/dist/Icon/Icon.d.ts +43 -13
  109. package/dist/Icon/Icon.d.ts.map +1 -1
  110. package/dist/Icon/Icon.js +62 -27
  111. package/dist/Layer/useLayer.d.ts +6 -0
  112. package/dist/Layer/useLayer.d.ts.map +1 -1
  113. package/dist/Layer/useLayer.js +2 -0
  114. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  115. package/dist/MobileNav/MobileNav.js +6 -1
  116. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  117. package/dist/MobileNav/MobileNavToggle.js +4 -0
  118. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  119. package/dist/MultiSelector/MultiSelector.js +39 -12
  120. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  121. package/dist/MultiSelector/hooks.js +16 -0
  122. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  123. package/dist/Outline/Outline.d.ts +68 -3
  124. package/dist/Outline/Outline.d.ts.map +1 -1
  125. package/dist/Outline/Outline.js +128 -26
  126. package/dist/Outline/useScrollSpy.d.ts +27 -7
  127. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  128. package/dist/Outline/useScrollSpy.js +202 -70
  129. package/dist/OverflowList/OverflowList.d.ts +20 -3
  130. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  131. package/dist/OverflowList/OverflowList.js +29 -4
  132. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  133. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  134. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  135. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  136. package/dist/SelectableCard/SelectableCard.js +14 -11
  137. package/dist/Selector/Selector.d.ts.map +1 -1
  138. package/dist/Selector/Selector.js +4 -2
  139. package/dist/Selector/hooks.d.ts.map +1 -1
  140. package/dist/Selector/hooks.js +16 -0
  141. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  142. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  143. package/dist/SideNav/SideNavHeading.js +21 -8
  144. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  145. package/dist/Slider/Slider.d.ts.map +1 -1
  146. package/dist/Slider/Slider.js +15 -1
  147. package/dist/TabList/Tab.d.ts.map +1 -1
  148. package/dist/TabList/Tab.js +2 -2
  149. package/dist/TabList/TabList.d.ts.map +1 -1
  150. package/dist/TabList/TabList.js +2 -0
  151. package/dist/TabList/TabMenu.d.ts.map +1 -1
  152. package/dist/TabList/TabMenu.js +1 -1
  153. package/dist/Table/Table.d.ts +1 -1
  154. package/dist/Table/Table.js +1 -1
  155. package/dist/Table/index.d.ts +4 -0
  156. package/dist/Table/index.d.ts.map +1 -1
  157. package/dist/Table/index.js +2 -0
  158. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  159. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  160. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
  161. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  162. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  163. package/dist/Table/plugins/rowStatus/index.js +3 -0
  164. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  165. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  166. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  167. package/dist/Table/plugins/selection/useTableSelection.d.ts +12 -0
  168. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  169. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  170. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  171. package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
  172. package/dist/Table/plugins/tree/index.d.ts +5 -0
  173. package/dist/Table/plugins/tree/index.d.ts.map +1 -0
  174. package/dist/Table/plugins/tree/index.js +4 -0
  175. package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
  176. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
  177. package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
  178. package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
  179. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
  180. package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
  181. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  182. package/dist/Table/useBaseTablePlugins.js +6 -3
  183. package/dist/Thumbnail/Thumbnail.d.ts +8 -1
  184. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  185. package/dist/Thumbnail/Thumbnail.js +46 -8
  186. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  187. package/dist/TimeInput/TimeInput.js +15 -4
  188. package/dist/Tokenizer/Tokenizer.d.ts +1 -1
  189. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  190. package/dist/Tokenizer/Tokenizer.js +16 -4
  191. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  192. package/dist/Toolbar/Toolbar.js +8 -1
  193. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  194. package/dist/TopNav/TopNavHeading.js +18 -8
  195. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  196. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  197. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  198. package/dist/TopNav/TopNavMenu.js +68 -6
  199. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  200. package/dist/TreeList/TreeListItem.js +5 -14
  201. package/dist/astryx.css +51 -16
  202. package/dist/astryx.umd.js +56 -56
  203. package/dist/astryx.umd.js.map +4 -4
  204. package/dist/authoring/doc.d.ts.map +1 -1
  205. package/dist/hooks/computeOverflow.d.ts +47 -0
  206. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  207. package/dist/hooks/computeOverflow.js +213 -0
  208. package/dist/hooks/useOverflow.d.ts +19 -0
  209. package/dist/hooks/useOverflow.d.ts.map +1 -1
  210. package/dist/hooks/useOverflow.js +37 -25
  211. package/dist/theme/Theme.js +1 -1
  212. package/dist/theme/defineTheme.d.ts +1 -1
  213. package/dist/theme/defineTheme.js +1 -1
  214. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  215. package/dist/theme/generateThemeRules.js +51 -0
  216. package/dist/theme/hct.d.ts +0 -1
  217. package/dist/theme/hct.d.ts.map +1 -1
  218. package/dist/theme/hct.js +0 -3
  219. package/dist/utils/types.d.ts +13 -0
  220. package/dist/utils/types.d.ts.map +1 -1
  221. package/docs.mjs +32 -461
  222. package/locales/en.json +212 -172
  223. package/locales/fr-FR.json +10 -0
  224. package/locales/pseudo.json +30 -0
  225. package/package.json +3 -1
  226. package/scripts/postinstall.mjs +93 -0
  227. package/src/AppShell/AppShell.tsx +6 -0
  228. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  229. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  230. package/src/Avatar/Avatar.doc.mjs +6 -6
  231. package/src/Avatar/Avatar.tsx +16 -11
  232. package/src/Avatar/AvatarSizeContext.ts +1 -1
  233. package/src/Avatar/AvatarStatusDot.tsx +2 -2
  234. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  235. package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
  236. package/src/AvatarGroup/AvatarGroup.tsx +3 -3
  237. package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
  238. package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
  239. package/src/Banner/Banner.doc.mjs +9 -0
  240. package/src/Banner/Banner.test.tsx +30 -0
  241. package/src/Banner/Banner.tsx +47 -3
  242. package/src/Button/Button.doc.mjs +9 -0
  243. package/src/Button/Button.test.tsx +49 -0
  244. package/src/Button/Button.tsx +25 -1
  245. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  246. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  247. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  248. package/src/Card/Card.doc.mjs +9 -0
  249. package/src/Card/Card.test.tsx +21 -3
  250. package/src/Card/Card.tsx +35 -2
  251. package/src/Carousel/Carousel.doc.mjs +1 -0
  252. package/src/Carousel/Carousel.test.tsx +149 -1
  253. package/src/Carousel/Carousel.tsx +62 -5
  254. package/src/Chat/Chat.doc.mjs +1 -1
  255. package/src/Chat/ChatComposer.doc.mjs +9 -0
  256. package/src/Chat/ChatComposer.test.tsx +35 -0
  257. package/src/Chat/ChatComposer.tsx +58 -9
  258. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  259. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  260. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  261. package/src/Chat/ChatComposerInput.test.tsx +87 -0
  262. package/src/Chat/ChatComposerInput.tsx +35 -3
  263. package/src/Chat/ChatLayout.tsx +3 -1
  264. package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
  265. package/src/Chat/ChatMessage.tsx +4 -2
  266. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  267. package/src/Chat/ChatSendButton.tsx +5 -1
  268. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  269. package/src/Chat/ChatToolCalls.tsx +13 -1
  270. package/src/Chat/useTriggerMenu.tsx +3 -1
  271. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  272. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  273. package/src/CheckboxList/CheckboxList.test.tsx +9 -1
  274. package/src/Citation/Citation.test.tsx +51 -0
  275. package/src/Citation/Citation.tsx +8 -2
  276. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  277. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  278. package/src/ClickableCard/ClickableCard.tsx +11 -1
  279. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  280. package/src/CodeBlock/CodeBlock.tsx +9 -1
  281. package/src/Collapsible/Collapsible.doc.mjs +7 -0
  282. package/src/Collapsible/Collapsible.test.tsx +80 -0
  283. package/src/Collapsible/Collapsible.tsx +58 -5
  284. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  285. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  286. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  287. package/src/ContextMenu/ContextMenu.test.tsx +75 -1
  288. package/src/ContextMenu/ContextMenu.tsx +10 -5
  289. package/src/ContextMenu/index.ts +15 -0
  290. package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
  291. package/src/DateTimeInput/DateTimeInput.tsx +14 -2
  292. package/src/Dialog/Dialog.tsx +12 -2
  293. package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
  294. package/src/DropdownMenu/DropdownMenu.tsx +11 -6
  295. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
  296. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +236 -0
  297. package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
  298. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
  299. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
  300. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +118 -0
  301. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
  302. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
  303. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
  304. package/src/DropdownMenu/index.ts +23 -3
  305. package/src/DropdownMenu/menuItemRoles.ts +22 -0
  306. package/src/Field/Field.doc.mjs +1 -1
  307. package/src/Field/Field.test.tsx +57 -12
  308. package/src/Field/FieldLabel.doc.mjs +1 -1
  309. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  310. package/src/FieldStatus/FieldStatus.test.tsx +78 -40
  311. package/src/FieldStatus/FieldStatus.tsx +22 -4
  312. package/src/FileInput/FileInput.test.tsx +52 -3
  313. package/src/FileInput/FileInput.tsx +13 -2
  314. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  315. package/src/HoverCard/HoverCard.test.tsx +21 -2
  316. package/src/HoverCard/HoverCard.tsx +11 -0
  317. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  318. package/src/HoverCard/useHoverCard.tsx +24 -2
  319. package/src/Icon/Icon.doc.mjs +12 -0
  320. package/src/Icon/Icon.test.tsx +142 -0
  321. package/src/Icon/Icon.tsx +98 -30
  322. package/src/IconButton/IconButton.doc.mjs +8 -0
  323. package/src/IconButton/IconButton.test.tsx +15 -18
  324. package/src/Item/Item.doc.mjs +1 -1
  325. package/src/Layer/useLayer.tsx +8 -0
  326. package/src/MobileNav/MobileNav.tsx +12 -1
  327. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  328. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  329. package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
  330. package/src/MultiSelector/MultiSelector.test.tsx +162 -1
  331. package/src/MultiSelector/MultiSelector.tsx +55 -12
  332. package/src/MultiSelector/hooks.ts +17 -0
  333. package/src/NumberInput/NumberInput.doc.mjs +2 -2
  334. package/src/NumberInput/NumberInput.test.tsx +11 -7
  335. package/src/NumberInput/NumberInput.tsx +1 -2
  336. package/src/Outline/Outline.doc.mjs +123 -3
  337. package/src/Outline/Outline.test.tsx +735 -0
  338. package/src/Outline/Outline.tsx +183 -33
  339. package/src/Outline/useScrollSpy.ts +248 -72
  340. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  341. package/src/OverflowList/OverflowList.test.tsx +111 -0
  342. package/src/OverflowList/OverflowList.tsx +48 -8
  343. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  344. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  345. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  346. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  347. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  348. package/src/SelectableCard/SelectableCard.tsx +27 -14
  349. package/src/Selector/Selector.test.tsx +69 -0
  350. package/src/Selector/Selector.tsx +5 -1
  351. package/src/Selector/SelectorOption.doc.mjs +1 -1
  352. package/src/Selector/hooks.ts +17 -0
  353. package/src/SideNav/SideNav.test.tsx +119 -3
  354. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  355. package/src/SideNav/SideNavHeading.tsx +24 -8
  356. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  357. package/src/SideNav/SideNavItem.tsx +5 -1
  358. package/src/Slider/Slider.test.tsx +62 -2
  359. package/src/Slider/Slider.tsx +15 -1
  360. package/src/Switch/Switch.doc.mjs +1 -1
  361. package/src/Switch/Switch.test.tsx +32 -1
  362. package/src/TabList/Tab.tsx +5 -1
  363. package/src/TabList/TabList.doc.mjs +3 -3
  364. package/src/TabList/TabList.test.tsx +66 -0
  365. package/src/TabList/TabList.tsx +8 -0
  366. package/src/TabList/TabMenu.tsx +4 -1
  367. package/src/Table/Table.doc.mjs +2 -0
  368. package/src/Table/Table.tsx +1 -1
  369. package/src/Table/index.ts +12 -0
  370. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  371. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
  372. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
  373. package/src/Table/plugins/rowStatus/index.ts +7 -0
  374. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  375. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  376. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  377. package/src/Table/plugins/selection/useTableSelection.tsx +18 -1
  378. package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
  379. package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
  380. package/src/Table/plugins/tree/index.ts +12 -0
  381. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
  382. package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
  383. package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
  384. package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
  385. package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
  386. package/src/Table/useBaseTablePlugins.ts +6 -2
  387. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  388. package/src/Table/useTableSelection.doc.mjs +65 -14
  389. package/src/Table/useTableTreeData.doc.mjs +75 -0
  390. package/src/Table/useTableTreeState.doc.mjs +98 -0
  391. package/src/TextArea/TextArea.doc.mjs +1 -1
  392. package/src/TextArea/TextArea.test.tsx +9 -1
  393. package/src/TextInput/TextInput.doc.mjs +1 -1
  394. package/src/Thumbnail/Thumbnail.doc.mjs +9 -0
  395. package/src/Thumbnail/Thumbnail.test.tsx +38 -8
  396. package/src/Thumbnail/Thumbnail.tsx +28 -1
  397. package/src/TimeInput/TimeInput.test.tsx +76 -1
  398. package/src/TimeInput/TimeInput.tsx +19 -6
  399. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  400. package/src/Tokenizer/Tokenizer.test.tsx +137 -2
  401. package/src/Tokenizer/Tokenizer.tsx +16 -4
  402. package/src/Toolbar/Toolbar.tsx +10 -3
  403. package/src/TopNav/TopNav.test.tsx +80 -1
  404. package/src/TopNav/TopNavHeading.tsx +24 -8
  405. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  406. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  407. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  408. package/src/TopNav/TopNavMenu.tsx +83 -5
  409. package/src/TreeList/TreeList.test.tsx +32 -1
  410. package/src/TreeList/TreeListItem.tsx +19 -10
  411. package/src/authoring/doc.ts +1 -3
  412. package/src/docs-script.test.mjs +18 -13
  413. package/src/hooks/computeOverflow.test.ts +422 -0
  414. package/src/hooks/computeOverflow.ts +290 -0
  415. package/src/hooks/useOverflow.test.ts +96 -4
  416. package/src/hooks/useOverflow.ts +77 -33
  417. package/src/postinstall.test.mjs +71 -0
  418. package/src/theme/Theme.doc.mjs +4 -4
  419. package/src/theme/Theme.tsx +1 -1
  420. package/src/theme/defineTheme.ts +1 -1
  421. package/src/theme/generateThemeRules.test.ts +65 -0
  422. package/src/theme/generateThemeRules.ts +56 -0
  423. package/src/theme/hct.ts +0 -4
  424. package/src/utils/types.ts +14 -0
  425. package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
  426. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
  427. package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
  428. package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
@@ -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 {
@@ -34,6 +34,11 @@ export const docs = {
34
34
  description: 'Icon size.',
35
35
  default: "'md'",
36
36
  },
37
+ {
38
+ name: 'label',
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.',
41
+ },
37
42
  ],
38
43
  theming: {
39
44
  targets: [
@@ -45,6 +50,7 @@ export const docs = {
45
50
  bestPractices: [
46
51
  { guidance: true, description: 'Use semantic icon names when available; they adapt to theme changes automatically.' },
47
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.' },
48
54
  { guidance: true, description: 'Use color tokens for icon colors, not hardcoded hex values.' },
49
55
  { guidance: true, description: 'Be mindful of context; decorative icons in compact components can distract rather than help.' },
50
56
  { guidance: false, description: 'Use icons as the sole means of conveying meaning; always provide a text alternative.' },
@@ -79,6 +85,11 @@ export const docsZh = {
79
85
  description: '图标尺寸。',
80
86
  default: "'md'",
81
87
  },
88
+ {
89
+ name: 'label',
90
+ type: 'string',
91
+ description: '有含义的独立图标的可访问名称(无相邻文字的状态图标或纯图标指示器)。设置后会将图标以 role="img" 暴露给辅助技术,并以该文本作为可访问名称(aria-label),同时移除默认的 aria-hidden。省略(默认)用于装饰性图标,图标对辅助技术保持隐藏(aria-hidden="true")。空字符串按装饰性处理。当交互式父元素(Button、IconButton、链接)已命名该控件时请勿设置。',
92
+ },
82
93
  ],
83
94
  theming: {
84
95
  targets: [
@@ -123,5 +134,6 @@ export const docsDense = {
123
134
  icon: 'Semantic icon name or SVG component. Valid names: close, chevronDown, chevronLeft, chevronRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. For others, pass an SVG component.',
124
135
  color: 'Color variant mapped to Astryx icon color tokens.',
125
136
  size: 'Icon size.',
137
+ label: 'Accessible name for a meaningful, standalone icon. Sets role="img" + aria-label and drops the default aria-hidden. Omit (default) for decorative icons (stays aria-hidden). Empty string = decorative. The accessible-name/alt-text prop for icons.',
126
138
  },
127
139
  };
@@ -96,6 +96,52 @@ describe('Icon', () => {
96
96
  expect(screen.getByTestId('icon')).toBeInTheDocument();
97
97
  });
98
98
 
99
+ it('sizes component-mode icons in rem so they scale with root font-size', () => {
100
+ const sizes = {
101
+ xsm: '0.75rem',
102
+ sm: '1rem',
103
+ md: '1.25rem',
104
+ lg: '1.5rem',
105
+ } as const;
106
+ for (const [size, expected] of Object.entries(sizes)) {
107
+ const {unmount} = render(
108
+ <Icon
109
+ icon={TestIcon}
110
+ size={size as keyof typeof sizes}
111
+ data-testid="icon"
112
+ />,
113
+ );
114
+ const style = getComputedStyle(screen.getByTestId('icon'));
115
+ expect(style.width).toBe(expected);
116
+ expect(style.height).toBe(expected);
117
+ unmount();
118
+ }
119
+ });
120
+
121
+ it('sizes registry (string-mode) icons in rem, including fontSize', () => {
122
+ const sizes = {
123
+ xsm: '0.75rem',
124
+ sm: '1rem',
125
+ md: '1.25rem',
126
+ lg: '1.5rem',
127
+ } as const;
128
+ for (const [size, expected] of Object.entries(sizes)) {
129
+ const {unmount} = render(
130
+ <Icon
131
+ icon="check"
132
+ size={size as keyof typeof sizes}
133
+ data-testid="icon"
134
+ />,
135
+ );
136
+ const style = getComputedStyle(screen.getByTestId('icon'));
137
+ expect(style.width).toBe(expected);
138
+ expect(style.height).toBe(expected);
139
+ // fontSize is expressed in rem so 1em-based registry icons scale too.
140
+ expect(style.fontSize).toBe(expected);
141
+ unmount();
142
+ }
143
+ });
144
+
99
145
  it('forwards ref correctly', () => {
100
146
  const ref = vi.fn();
101
147
  render(<Icon icon={TestIcon} ref={ref} />);
@@ -137,4 +183,100 @@ describe('Icon', () => {
137
183
  expect(icon).toHaveAttribute('aria-label', 'Done');
138
184
  expect(icon).toHaveAttribute('aria-hidden', 'false');
139
185
  });
186
+
187
+ describe('label (accessible name)', () => {
188
+ it('makes a component-mode icon meaningful: role="img" + aria-label, no aria-hidden', () => {
189
+ render(<Icon icon={TestIcon} label="Completed" data-testid="icon" />);
190
+ const icon = screen.getByTestId('icon');
191
+ expect(icon).toHaveAttribute('role', 'img');
192
+ expect(icon).toHaveAttribute('aria-label', 'Completed');
193
+ expect(icon).not.toHaveAttribute('aria-hidden');
194
+ });
195
+
196
+ it('makes a string-mode (registry) icon meaningful: role="img" + aria-label, no aria-hidden', () => {
197
+ render(<Icon icon="check" label="Completed" data-testid="icon" />);
198
+ const icon = screen.getByTestId('icon');
199
+ expect(icon).toHaveAttribute('role', 'img');
200
+ expect(icon).toHaveAttribute('aria-label', 'Completed');
201
+ expect(icon).not.toHaveAttribute('aria-hidden');
202
+ });
203
+
204
+ it('keeps the decorative default when label is omitted (component mode)', () => {
205
+ render(<Icon icon={TestIcon} data-testid="icon" />);
206
+ const icon = screen.getByTestId('icon');
207
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
208
+ expect(icon).not.toHaveAttribute('role');
209
+ expect(icon).not.toHaveAttribute('aria-label');
210
+ });
211
+
212
+ it('keeps the decorative default when label is omitted (string mode)', () => {
213
+ render(<Icon icon="check" data-testid="icon" />);
214
+ const icon = screen.getByTestId('icon');
215
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
216
+ expect(icon).not.toHaveAttribute('role');
217
+ expect(icon).not.toHaveAttribute('aria-label');
218
+ });
219
+
220
+ it('treats an empty string label as decorative (component mode)', () => {
221
+ render(<Icon icon={TestIcon} label="" data-testid="icon" />);
222
+ const icon = screen.getByTestId('icon');
223
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
224
+ expect(icon).not.toHaveAttribute('role');
225
+ expect(icon).not.toHaveAttribute('aria-label');
226
+ });
227
+
228
+ it('treats an empty string label as decorative (string mode)', () => {
229
+ render(<Icon icon="check" label="" data-testid="icon" />);
230
+ const icon = screen.getByTestId('icon');
231
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
232
+ expect(icon).not.toHaveAttribute('role');
233
+ expect(icon).not.toHaveAttribute('aria-label');
234
+ });
235
+
236
+ it('lets an explicit aria-hidden win over label (component mode)', () => {
237
+ render(
238
+ <Icon icon={TestIcon} label="Close" aria-hidden data-testid="icon" />,
239
+ );
240
+ const icon = screen.getByTestId('icon');
241
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
242
+ });
243
+
244
+ it('lets an explicit aria-hidden win over label (string mode)', () => {
245
+ render(
246
+ <Icon icon="check" label="Close" aria-hidden data-testid="icon" />,
247
+ );
248
+ const icon = screen.getByTestId('icon');
249
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
250
+ });
251
+
252
+ it('lets an explicit aria-label override the label-derived name (component mode)', () => {
253
+ render(
254
+ <Icon
255
+ icon={TestIcon}
256
+ label="Close"
257
+ aria-label="Dismiss"
258
+ data-testid="icon"
259
+ />,
260
+ );
261
+ expect(screen.getByTestId('icon')).toHaveAttribute(
262
+ 'aria-label',
263
+ 'Dismiss',
264
+ );
265
+ });
266
+
267
+ it('lets an explicit role override the label-derived role (string mode)', () => {
268
+ render(
269
+ <Icon
270
+ icon="check"
271
+ label="Close"
272
+ role="presentation"
273
+ data-testid="icon"
274
+ />,
275
+ );
276
+ expect(screen.getByTestId('icon')).toHaveAttribute(
277
+ 'role',
278
+ 'presentation',
279
+ );
280
+ });
281
+ });
140
282
  });
package/src/Icon/Icon.tsx CHANGED
@@ -111,51 +111,64 @@ const colorStyles = stylex.create({
111
111
  /**
112
112
  * Size styles for direct SVG icon components.
113
113
  * Uses width/height only — SVG components handle their own viewBox scaling.
114
+ *
115
+ * Sizes are expressed in `rem` (relative to the root font-size) so icons scale
116
+ * in step with text when the document font-size changes, matching the rest of
117
+ * the design system's rem-based type scale. Values are the px-equivalents at a
118
+ * 16px root: 12px → 0.75rem, 16px → 1rem, 20px → 1.25rem, 24px → 1.5rem.
114
119
  */
115
120
  const sizeStyles = stylex.create({
116
121
  xsm: {
117
- width: 12,
118
- height: 12,
122
+ width: '0.75rem',
123
+ height: '0.75rem',
119
124
  },
120
125
  sm: {
121
- width: 16,
122
- height: 16,
126
+ width: '1rem',
127
+ height: '1rem',
123
128
  },
124
129
  md: {
125
- width: 20,
126
- height: 20,
130
+ width: '1.25rem',
131
+ height: '1.25rem',
127
132
  },
128
133
  lg: {
129
- width: 24,
130
- height: 24,
134
+ width: '1.5rem',
135
+ height: '1.5rem',
131
136
  },
132
137
  });
133
138
 
134
139
  /**
135
140
  * Size styles for string-based (registry) icons.
136
141
  * Includes fontSize so that 1em-based icons from the registry scale correctly.
142
+ *
143
+ * Expressed in `rem` for the same reason as {@link sizeStyles} — icons track the
144
+ * root font-size instead of being locked to absolute pixels.
137
145
  */
138
146
  const spanSizeStyles = stylex.create({
147
+ /* eslint-disable @astryx/no-hardcoded-styles -- fontSize here sizes 1em-based
148
+ registry SVGs to the icon box; icons use their own 12/16/20/24 scale, not
149
+ the 14px-anchored textSizeVars type scale. Values are rem so icons track
150
+ the root font-size. */
139
151
  xsm: {
140
- width: 12,
141
- height: 12,
142
- fontSize: 12,
152
+ width: '0.75rem',
153
+ height: '0.75rem',
154
+ fontSize: '0.75rem',
143
155
  },
144
156
  sm: {
145
- width: 16,
146
- height: 16,
147
- fontSize: 16,
157
+ width: '1rem',
158
+ height: '1rem',
159
+ fontSize: '1rem',
148
160
  },
149
161
  md: {
150
- width: 20,
151
- height: 20,
152
- fontSize: 20,
162
+ width: '1.25rem',
163
+ height: '1.25rem',
164
+ fontSize: '1.25rem',
153
165
  },
154
166
  lg: {
155
- width: 24,
156
- height: 24,
157
- fontSize: 24,
167
+ width: '1.5rem',
168
+ height: '1.5rem',
169
+ fontSize: '1.5rem',
158
170
  },
171
+ /* eslint-enable @astryx/no-hardcoded-styles */
159
172
  });
160
173
 
161
174
  // =============================================================================
@@ -194,13 +207,57 @@ export interface IconProps extends Omit<
194
207
  color?: IconColor;
195
208
  /**
196
209
  * The size of the icon.
197
- * - 'xsm': 12px
198
- * - 'sm': 16px
199
- * - 'md': 20px
200
- * - 'lg': 24px
210
+ * - 'xsm': 0.75rem (12px at a 16px root)
211
+ * - 'sm': 1rem (16px at a 16px root)
212
+ * - 'md': 1.25rem (20px at a 16px root)
213
+ * - 'lg': 1.5rem (24px at a 16px root)
201
214
  * @default 'md'
202
215
  */
203
216
  size?: IconSize;
217
+ /**
218
+ * Accessible name for the icon. Set this only when the icon is MEANINGFUL on
219
+ * its own — a standalone status glyph or an icon-only indicator with no
220
+ * adjacent text conveying the same information. Providing it exposes the icon
221
+ * to assistive tech as `role="img"` with this string as the accessible name
222
+ * (via `aria-label`) and drops the default `aria-hidden="true"`.
223
+ *
224
+ * Omit it (the default) for decorative icons — the common case, e.g. an icon
225
+ * beside a text label — and the icon stays hidden from assistive tech
226
+ * (`aria-hidden="true"`). An empty string (`''`) is treated the same as
227
+ * omitting it (decorative), since an empty accessible name is meaningless.
228
+ *
229
+ * Don't set `label` when an interactive parent (Button, IconButton, link)
230
+ * already names the control — that produces a duplicate announcement.
231
+ *
232
+ * @example
233
+ * ```
234
+ * // Meaningful, standalone icon
235
+ * <Icon icon="success" label="Completed" />
236
+ *
237
+ * // Decorative icon (default) — omit label
238
+ * <Icon icon="search" />
239
+ * ```
240
+ */
241
+ label?: string;
242
+ }
243
+
244
+ /**
245
+ * Derives the ARIA attributes for an icon from its `label` prop.
246
+ *
247
+ * - Non-empty `label` → meaningful image: `role="img"` + `aria-label`, and no
248
+ * `aria-hidden` (an `aria-hidden` element is removed from the accessibility
249
+ * tree, so its accessible name would be ignored).
250
+ * - Omitted or empty `label` → decorative default: `aria-hidden="true"`.
251
+ *
252
+ * The result is spread BEFORE `{...props}` in both render modes so an explicit
253
+ * `aria-hidden` / `role` / `aria-label` from the consumer always wins.
254
+ */
255
+ function getIconA11yProps(
256
+ label: string | undefined,
257
+ ): {role: 'img'; 'aria-label': string} | {'aria-hidden': 'true'} {
258
+ return label != null && label !== ''
259
+ ? {role: 'img', 'aria-label': label}
260
+ : {'aria-hidden': 'true'};
204
261
  }
205
262
 
206
263
  // =============================================================================
@@ -219,9 +276,14 @@ export function Icon({
219
276
  icon,
220
277
  color = 'inherit',
221
278
  size = 'md',
279
+ label,
222
280
  ref,
223
281
  ...props
224
282
  }: IconProps) {
283
+ // Derive ARIA from `label`: decorative (aria-hidden) by default, or a
284
+ // meaningful image (role="img" + aria-label) when `label` is non-empty.
285
+ const a11yProps = getIconA11yProps(label);
286
+
225
287
  // String mode: resolve from icon registry, wrap in styled span
226
288
  if (typeof icon === 'string') {
227
289
  return (
@@ -229,6 +291,7 @@ export function Icon({
229
291
  name={icon}
230
292
  color={color}
231
293
  size={size}
294
+ a11yProps={a11yProps}
232
295
  spanProps={props}
233
296
  />
234
297
  );
@@ -239,7 +302,10 @@ export function Icon({
239
302
  return (
240
303
  <IconComponent
241
304
  ref={ref}
242
- aria-hidden="true"
305
+ // Derived a11y (decorative default or meaningful `label`) is spread
306
+ // BEFORE {...props} so an explicit aria-hidden/role/aria-label from the
307
+ // consumer still wins as an escape hatch.
308
+ {...a11yProps}
243
309
  {...mergeProps(
244
310
  themeProps('icon', {size, color}),
245
311
  stylex.props(styles.root, colorStyles[color], sizeStyles[size]),
@@ -266,11 +332,13 @@ function IconFromRegistry({
266
332
  name,
267
333
  color,
268
334
  size,
335
+ a11yProps,
269
336
  spanProps,
270
337
  }: {
271
338
  name: IconName;
272
339
  color: IconColor;
273
340
  size: IconSize;
341
+ a11yProps: {role: 'img'; 'aria-label': string} | {'aria-hidden': 'true'};
274
342
  spanProps?: Omit<SVGProps<SVGSVGElement>, 'ref' | 'color'>;
275
343
  }) {
276
344
  const resolvedIcon = getIcon(name);
@@ -281,11 +349,11 @@ function IconFromRegistry({
281
349
 
282
350
  return (
283
351
  <span
284
- // Decorative by default, but placed BEFORE the prop spread so consumers
285
- // can override it (e.g. `aria-hidden={false}` + `role="img"` +
286
- // `aria-label`) to expose a meaningful standalone icon. This matches
287
- // component-mode Icon, which already sets aria-hidden before its spread.
288
- aria-hidden="true"
352
+ // Derived a11y — decorative (aria-hidden) by default, or a meaningful
353
+ // image (role="img" + aria-label) when `label` is set. Placed BEFORE the
354
+ // prop spread so consumers can still override it with explicit
355
+ // aria-hidden/role/aria-label. This mirrors component-mode Icon.
356
+ {...a11yProps}
289
357
  {...(spanProps as React.HTMLAttributes<HTMLSpanElement>)}
290
358
  {...mergeProps(
291
359
  themeProps('icon', {size, color}),
@@ -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
  });
@@ -28,7 +28,7 @@ export const docs = {
28
28
  props: [
29
29
  {name: 'label', type: 'ReactNode', description: 'Primary text identifying this item. Accepts string (auto-truncated) or ReactNode (for rich content).', required: true},
30
30
  {name: 'marker', type: 'ReactNode', description: 'Marker rendered before startContent as a direct flex child. Use for list bullets/counters that need custom baseline alignment.'},
31
- {name: 'startContent', type: 'ReactNode', description: 'Content rendered before the label/description area, such as an icon, avatar, or checkbox.', slotElements: [{__element: 'Avatar', props: {name: 'Ada Lovelace', size: 'xsmall'}}, {__element: 'Icon', props: {icon: 'info', size: 'sm', color: 'secondary'}}]},
31
+ {name: 'startContent', type: 'ReactNode', description: 'Content rendered before the label/description area, such as an icon, avatar, or checkbox.', slotElements: [{__element: 'Avatar', props: {name: 'Ada Lovelace', size: 'sm'}}, {__element: 'Icon', props: {icon: 'info', size: 'sm', color: 'secondary'}}]},
32
32
  {name: 'description', type: 'ReactNode', description: 'Secondary text: subtitle, description, or supporting info.'},
33
33
  {name: 'endContent', type: 'ReactNode', description: 'Content rendered after the label/description area, such as badges, metadata, timestamps, or action buttons.', slotElements: [{__element: 'Badge', props: {label: 3}}, {__element: 'Text', props: {color: 'secondary'}, children: '2h ago'}]},
34
34
  {name: 'as', type: "'div' | 'li' | 'span'", description: 'HTML element to render as the root.', default: "'div'"},
@@ -100,6 +100,12 @@ export interface ContextRenderProps {
100
100
  * non-hashed selector for the layer.
101
101
  */
102
102
  role?: string;
103
+ /**
104
+ * Accessible name applied to the popover container via `aria-label`.
105
+ * Pair with `role` so layers with a named role (e.g. `'dialog'`) expose a
106
+ * proper name to assistive technology.
107
+ */
108
+ 'aria-label'?: string;
103
109
  /**
104
110
  * StyleX styles for the popover container.
105
111
  */
@@ -525,6 +531,7 @@ export function useLayer(
525
531
  alignment = 'center',
526
532
  positioning = 'anchor',
527
533
  role,
534
+ 'aria-label': ariaLabel,
528
535
  xstyle,
529
536
  className: extraClassName,
530
537
  style: extraStyle,
@@ -561,6 +568,7 @@ export function useLayer(
561
568
  ref={popoverRefCallback}
562
569
  id={id}
563
570
  role={role}
571
+ aria-label={ariaLabel}
564
572
  popover={lightDismiss ? 'auto' : 'manual'}
565
573
  className={combinedClassName}
566
574
  style={{...stylexResult.style, ...anchorStyle, ...extraStyle}}
@@ -29,6 +29,7 @@
29
29
  import {
30
30
  useCallback,
31
31
  useEffect,
32
+ useId,
32
33
  useMemo,
33
34
  useRef,
34
35
  useState,
@@ -295,6 +296,10 @@ export function MobileNav({
295
296
  // Read from AppShell context as fallback
296
297
  const appShellMobile = useAppShellMobile();
297
298
  const isOpen = isOpenProp ?? appShellMobile.isMobileNavOpen;
299
+ // Share the id from AppShell context so the toggle's aria-controls resolves to
300
+ // this drawer; fall back to a locally generated id when used standalone.
301
+ const fallbackId = useId();
302
+ const dialogId = appShellMobile.mobileNavId || fallbackId;
298
303
  const onOpenChange = useMemo(
299
304
  () =>
300
305
  onOpenChangeProp ??
@@ -411,6 +416,7 @@ export function MobileNav({
411
416
  return (
412
417
  <dialog
413
418
  ref={mergeRefs(ref, dialogRef)}
419
+ id={dialogId}
414
420
  {...mergeProps(
415
421
  themeProps('mobile-nav', {side: resolvedSide}),
416
422
  stylex.props(
@@ -425,7 +431,12 @@ export function MobileNav({
425
431
  )}
426
432
  {...rest}
427
433
  data-testid={testId}
428
- aria-label={label ?? (typeof header === 'string' ? header : t('@astryx.mobileNav.navigation'))}
434
+ aria-label={
435
+ label ??
436
+ (typeof header === 'string'
437
+ ? header
438
+ : t('@astryx.mobileNav.navigation'))
439
+ }
429
440
  onClick={composeEventHandlers(onClickProp, handleDialogClick)}
430
441
  onCancel={handleCancel}>
431
442
  {/* Drawer panel — tabIndex so showModal() focuses the drawer, not the close button */}