@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
@@ -337,7 +337,9 @@ describe('SideNavHeading', () => {
337
337
  it('whole heading is popover trigger when menu provided without hrefs', () => {
338
338
  render(<SideNavHeading heading="My App" menu={<div>Menu</div>} />);
339
339
  const button = screen.getByRole('button');
340
- expect(button).toHaveAttribute('aria-haspopup', 'dialog');
340
+ // The popup is no longer a dialog (role: 'none' on usePopover), so the
341
+ // trigger advertises a generic popup rather than aria-haspopup="dialog".
342
+ expect(button).toHaveAttribute('aria-haspopup', 'true');
341
343
  expect(button).toHaveAttribute('aria-expanded', 'false');
342
344
  });
343
345
 
@@ -351,7 +353,7 @@ describe('SideNavHeading', () => {
351
353
  const button = screen.getByRole('button');
352
354
  // The trigger button uses aria attributes from usePopover and
353
355
  // an onClick handler from useMenuHover for click-to-lock toggle.
354
- expect(button).toHaveAttribute('aria-haspopup', 'dialog');
356
+ expect(button).toHaveAttribute('aria-haspopup', 'true');
355
357
  expect(button).toHaveAttribute('aria-expanded');
356
358
  });
357
359
 
@@ -364,13 +366,92 @@ describe('SideNavHeading', () => {
364
366
  />,
365
367
  );
366
368
  const button = screen.getByRole('button', {name: 'Open menu'});
367
- expect(button).toHaveAttribute('aria-haspopup', 'dialog');
369
+ expect(button).toHaveAttribute('aria-haspopup', 'true');
368
370
  });
369
371
 
370
372
  it('passes data-testid', () => {
371
373
  render(<SideNavHeading heading="My App" data-testid="nav-header" />);
372
374
  expect(screen.getByTestId('nav-header')).toBeInTheDocument();
373
375
  });
376
+
377
+ // ===========================================================================
378
+ // Menu popover semantics — the popup must not be announced as a modal
379
+ // dialog, and role="menu" must be scoped to the actual menu items so the
380
+ // heading button is not an invalid child of the menu.
381
+ // ===========================================================================
382
+
383
+ // The popover layer keeps `popover` content display:none in jsdom even
384
+ // when open, hiding it from role queries — so these tests assert the
385
+ // popup's ARIA semantics at the DOM level instead.
386
+ describe('menu popover semantics', () => {
387
+ const menuItems = (
388
+ <>
389
+ <div role="menuitem">Alpha</div>
390
+ <div role="menuitem">Beta</div>
391
+ </>
392
+ );
393
+
394
+ it('does not wrap the heading menu popup in a modal dialog', async () => {
395
+ const user = userEvent.setup();
396
+ render(<SideNavHeading heading="My App" menu={menuItems} />);
397
+ await user.click(screen.getByRole('button', {name: 'Open menu'}));
398
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
399
+ expect(document.querySelector('[aria-modal="true"]')).toBeNull();
400
+ });
401
+
402
+ it('scopes role="menu" to only menuitem children with an accessible name', async () => {
403
+ const user = userEvent.setup();
404
+ render(<SideNavHeading heading="My App" menu={menuItems} />);
405
+ await user.click(screen.getByRole('button', {name: 'Open menu'}));
406
+ const menu = document.querySelector('[role="menu"]');
407
+ expect(menu).not.toBeNull();
408
+ expect(menu).toHaveAttribute('aria-label', 'My App');
409
+ const children = Array.from(menu!.children);
410
+ expect(children.length).toBeGreaterThan(0);
411
+ for (const child of children) {
412
+ expect(child).toHaveAttribute('role', 'menuitem');
413
+ }
414
+ });
415
+
416
+ it('keeps the popup heading button outside the menu and closes on click', async () => {
417
+ const user = userEvent.setup();
418
+ render(<SideNavHeading heading="My App" menu={menuItems} />);
419
+ const trigger = screen.getByRole('button', {name: 'Open menu'});
420
+ await user.click(trigger);
421
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
422
+ const menu = document.querySelector('[role="menu"]');
423
+ expect(menu).not.toBeNull();
424
+ // The heading replica button in the popup is a sibling of the menu,
425
+ // not an invalid menu child.
426
+ const headingButton = Array.from(
427
+ document.querySelectorAll('button'),
428
+ ).find(b => b !== trigger && b.textContent?.includes('My App'));
429
+ expect(headingButton).toBeDefined();
430
+ expect(menu!.contains(headingButton!)).toBe(false);
431
+ // Clicking it still closes the popup.
432
+ fireEvent.click(headingButton!);
433
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
434
+ });
435
+
436
+ it('applies the same semantics in mixed mode (menu + hrefs)', async () => {
437
+ const user = userEvent.setup();
438
+ render(
439
+ <SideNavHeading
440
+ heading="Product"
441
+ headingHref="/product"
442
+ menu={menuItems}
443
+ />,
444
+ );
445
+ await user.click(screen.getByRole('button', {name: 'Open menu'}));
446
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
447
+ const menu = document.querySelector('[role="menu"]');
448
+ expect(menu).not.toBeNull();
449
+ expect(menu).toHaveAttribute('aria-label', 'Product');
450
+ for (const child of Array.from(menu!.children)) {
451
+ expect(child).toHaveAttribute('role', 'menuitem');
452
+ }
453
+ });
454
+ });
374
455
  });
375
456
 
376
457
  // =============================================================================
@@ -476,6 +557,41 @@ describe('SideNavHeading collapsed', () => {
476
557
  );
477
558
  expect(screen.getByTestId('nav-header')).toBeInTheDocument();
478
559
  });
560
+
561
+ it('collapsed menu popup is not a dialog and scopes role="menu" to menu items', async () => {
562
+ const user = userEvent.setup();
563
+ render(
564
+ <CollapsedWrapper>
565
+ <SideNavHeading
566
+ heading="My App"
567
+ icon={<span data-testid="app-icon">🏠</span>}
568
+ menu={
569
+ <>
570
+ <div role="menuitem">Alpha</div>
571
+ <div role="menuitem">Beta</div>
572
+ </>
573
+ }
574
+ />
575
+ </CollapsedWrapper>,
576
+ );
577
+ await user.click(screen.getByRole('button', {name: 'My App'}));
578
+ // No modal dialog wrapper around the menu popup.
579
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
580
+ expect(document.querySelector('[aria-modal="true"]')).toBeNull();
581
+ // role="menu" is scoped to the menu items and has a direct name.
582
+ const menu = document.querySelector('[role="menu"]');
583
+ expect(menu).not.toBeNull();
584
+ expect(menu).toHaveAttribute('aria-label', 'My App');
585
+ const children = Array.from(menu!.children);
586
+ expect(children.length).toBeGreaterThan(0);
587
+ for (const child of children) {
588
+ expect(child).toHaveAttribute('role', 'menuitem');
589
+ }
590
+ // No button (trigger or heading replica) lives inside the menu.
591
+ for (const button of Array.from(document.querySelectorAll('button'))) {
592
+ expect(menu!.contains(button)).toBe(false);
593
+ }
594
+ });
479
595
  });
480
596
 
481
597
  // =============================================================================
@@ -120,7 +120,12 @@ export function SideNavCollapseButton({
120
120
  return (
121
121
  <Button
122
122
  ref={ref}
123
- label={label ?? (isCollapsed ? t('@astryx.sideNavCollapseButton.expandSidebar') : t('@astryx.sideNavCollapseButton.collapseSidebar'))}
123
+ label={
124
+ label ??
125
+ (isCollapsed
126
+ ? t('@astryx.sideNavCollapseButton.expandSidebar')
127
+ : t('@astryx.sideNavCollapseButton.collapseSidebar'))
128
+ }
124
129
  variant="ghost"
125
130
  {...props}
126
131
  onClick={composeEventHandlers(onClickProp, toggle)}
@@ -351,6 +351,10 @@ export function SideNavHeading({
351
351
 
352
352
  const popover = usePopover({
353
353
  dialogLabel: t('@astryx.sideNav.heading.dialogLabel'),
354
+ // The popup exposes its own role="menu" semantics; a role="dialog"
355
+ // aria-modal wrapper would announce "dialog, Navigation menu" around a
356
+ // menu (the anti-pattern removed in a478a3dcf).
357
+ role: 'none',
354
358
  hasCloseButton: false,
355
359
  });
356
360
 
@@ -428,7 +432,6 @@ export function SideNavHeading({
428
432
  {popover.render(
429
433
  <div
430
434
  ref={menuRef}
431
- role="menu"
432
435
  {...stylex.props(styles.popoverContent)}
433
436
  {...contentProps}>
434
437
  <button
@@ -461,9 +464,14 @@ export function SideNavHeading({
461
464
  )}
462
465
  </span>
463
466
  </button>
464
- <NavHeadingCloseContext value={closeMenuCtx}>
465
- {menu}
466
- </NavHeadingCloseContext>
467
+ {/* The menu role is scoped to the actual menu items so the
468
+ heading button above stays a valid sibling, not an invalid
469
+ child of a role="menu" element. */}
470
+ <div role="menu" aria-label={heading}>
471
+ <NavHeadingCloseContext value={closeMenuCtx}>
472
+ {menu}
473
+ </NavHeadingCloseContext>
474
+ </div>
467
475
  </div>,
468
476
  {placement: 'below', alignment: 'start', xstyle: styles.popover},
469
477
  )}
@@ -620,11 +628,15 @@ export function SideNavHeading({
620
628
  {popover.render(
621
629
  <div
622
630
  ref={menuRef}
623
- role="menu"
624
631
  {...stylex.props(styles.popoverContent)}
625
632
  {...contentProps}>
626
633
  {popoverHeadingContent}
627
- {menu}
634
+ {/* The menu role is scoped to the actual menu items so the
635
+ heading button above stays a valid sibling, not an invalid
636
+ child of a role="menu" element. */}
637
+ <div role="menu" aria-label={heading}>
638
+ {menu}
639
+ </div>
628
640
  </div>,
629
641
  {
630
642
  placement: 'below',
@@ -683,11 +695,15 @@ export function SideNavHeading({
683
695
  {popover.render(
684
696
  <div
685
697
  ref={menuRef}
686
- role="menu"
687
698
  {...stylex.props(styles.popoverContent)}
688
699
  {...contentProps}>
689
700
  {popoverHeadingContent}
690
- {menu}
701
+ {/* The menu role is scoped to the actual menu items so the
702
+ heading button above stays a valid sibling, not an invalid
703
+ child of a role="menu" element. */}
704
+ <div role="menu" aria-label={heading}>
705
+ {menu}
706
+ </div>
691
707
  </div>,
692
708
  {
693
709
  placement: 'below',
@@ -23,12 +23,12 @@ export const docs = {
23
23
  {
24
24
  name: 'icon',
25
25
  type: 'IconType',
26
- description: 'Icon displayed in the outline (unselected) variant. See `npx astryx docs icons` for valid semantic names.',
26
+ description: 'Icon displayed in the outline (unselected) variant. See `astryx docs icons` for valid semantic names.',
27
27
  },
28
28
  {
29
29
  name: 'selectedIcon',
30
30
  type: 'IconType',
31
- description: 'Icon displayed when the item is selected (filled variant). See `npx astryx docs icons` for valid semantic names.',
31
+ description: 'Icon displayed when the item is selected (filled variant). See `astryx docs icons` for valid semantic names.',
32
32
  },
33
33
  {
34
34
  name: 'isSelected',
@@ -628,7 +628,11 @@ export function SideNavItem({
628
628
  <button
629
629
  type="button"
630
630
  onClick={handleToggleClick}
631
- aria-label={isItemCollapsed ? t('@astryx.sideNavItem.expand', {label}) : t('@astryx.sideNavItem.collapse', {label})}
631
+ aria-label={
632
+ isItemCollapsed
633
+ ? t('@astryx.sideNavItem.expand', {label})
634
+ : t('@astryx.sideNavItem.collapse', {label})
635
+ }
632
636
  aria-expanded={!isItemCollapsed}
633
637
  aria-controls={`${id}-children`}
634
638
  {...stylex.props(styles.expandToggle)}>
@@ -155,6 +155,60 @@ describe('Slider', () => {
155
155
  expect(ariaHidden.length).toBeGreaterThanOrEqual(2);
156
156
  });
157
157
 
158
+ // --- Required state ---
159
+
160
+ it('conveys required state through the accessible description', () => {
161
+ render(<Slider label="Volume" value={50} isRequired />);
162
+ const slider = screen.getByRole('slider');
163
+ expect(slider).toHaveAccessibleDescription(/Required/);
164
+ // aria-required is not a supported property of role="slider" in
165
+ // WAI-ARIA 1.2, so it must never appear on the thumb.
166
+ expect(slider).not.toHaveAttribute('aria-required');
167
+ });
168
+
169
+ it('conveys required state on both thumbs of a range slider', () => {
170
+ render(
171
+ <Slider
172
+ label="Price range"
173
+ value={[20, 80] as [number, number]}
174
+ isRequired
175
+ />,
176
+ );
177
+ const sliders = screen.getAllByRole('slider');
178
+ expect(sliders).toHaveLength(2);
179
+ for (const thumb of sliders) {
180
+ expect(thumb).toHaveAccessibleDescription(/Required/);
181
+ expect(thumb).not.toHaveAttribute('aria-required');
182
+ }
183
+ });
184
+
185
+ it('combines required with other describedby parts in the description', () => {
186
+ render(
187
+ <Slider
188
+ label="Volume"
189
+ value={50}
190
+ description="Adjust the volume level"
191
+ isRequired
192
+ />,
193
+ );
194
+ const slider = screen.getByRole('slider');
195
+ expect(slider).toHaveAccessibleDescription(/Adjust the volume level/);
196
+ expect(slider).toHaveAccessibleDescription(/Required/);
197
+ });
198
+
199
+ it('does not mention required without isRequired', () => {
200
+ render(
201
+ <Slider
202
+ label="Volume"
203
+ value={50}
204
+ description="Adjust the volume level"
205
+ />,
206
+ );
207
+ const slider = screen.getByRole('slider');
208
+ expect(slider).not.toHaveAccessibleDescription(/Required/);
209
+ expect(slider).not.toHaveAttribute('aria-required');
210
+ });
211
+
158
212
  // --- Disabled guards ---
159
213
 
160
214
  it('disables thumbs when isDisabled is true', () => {
@@ -549,7 +603,11 @@ describe('Slider', () => {
549
603
  it('submits both range values under the same name', () => {
550
604
  const {container} = render(
551
605
  <form>
552
- <Slider label="Price" htmlName="price" value={[20, 80] as [number, number]} />
606
+ <Slider
607
+ label="Price"
608
+ htmlName="price"
609
+ value={[20, 80] as [number, number]}
610
+ />
553
611
  </form>,
554
612
  );
555
613
  const data = new FormData(container.querySelector('form')!);
@@ -562,7 +620,9 @@ describe('Slider', () => {
562
620
  <Slider label="Volume" htmlName="volume" value={50} isDisabled />
563
621
  </form>,
564
622
  );
565
- expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
623
+ expect([
624
+ ...new FormData(container.querySelector('form')!).keys(),
625
+ ]).toEqual([]);
566
626
  });
567
627
  });
568
628
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file Slider.tsx
7
- * @input Uses React, useId, useRef, useCallback, Field, Tooltip, useTooltip
7
+ * @input Uses React, useId, useRef, useCallback, Field, Tooltip, useTooltip, VisuallyHidden
8
8
  * @output Exports Slider component, SliderProps, SliderSingleProps, SliderRangeProps, SliderBaseProps
9
9
  * @position Core implementation; consumed by index.ts, tested by Slider.test.tsx
10
10
  *
@@ -38,6 +38,7 @@ import {
38
38
  import {Field} from '../Field/Field';
39
39
  import {Tooltip} from '../Tooltip/Tooltip';
40
40
  import {useTooltip} from '../Tooltip';
41
+ import {VisuallyHidden} from '../VisuallyHidden';
41
42
  import type {InputStatus} from '../Field/types';
42
43
  import {mergeProps, mergeRefs} from '../utils';
43
44
  import type {BaseProps} from '../BaseProps';
@@ -393,6 +394,7 @@ export function Slider({ref, ...props}: SliderProps) {
393
394
  const id = useId();
394
395
  const descriptionID = useId();
395
396
  const statusMessageID = useId();
397
+ const requiredID = useId();
396
398
 
397
399
  const trackRef = useRef<HTMLDivElement>(null);
398
400
  const draggingThumbRef = useRef<number | null>(null);
@@ -412,6 +414,12 @@ export function Slider({ref, ...props}: SliderProps) {
412
414
  isEnabled: showsDisabledMessage,
413
415
  });
414
416
 
417
+ // Required state. `aria-required` is not a supported property of
418
+ // role="slider" in WAI-ARIA 1.2, so the thumb instead points its
419
+ // aria-describedby at a visually hidden "Required" span — mirroring the
420
+ // Field label's visible indicator (where isOptional takes precedence).
421
+ const conveysRequired = isRequired && !isOptional;
422
+
415
423
  // Build aria-describedby
416
424
  const describedByParts: string[] = [];
417
425
  if (description) {
@@ -420,6 +428,9 @@ export function Slider({ref, ...props}: SliderProps) {
420
428
  if (status?.message) {
421
429
  describedByParts.push(statusMessageID);
422
430
  }
431
+ if (conveysRequired) {
432
+ describedByParts.push(requiredID);
433
+ }
423
434
  if (showsDisabledMessage) {
424
435
  describedByParts.push(disabledMessageTooltip.describedBy);
425
436
  }
@@ -921,6 +932,9 @@ export function Slider({ref, ...props}: SliderProps) {
921
932
 
922
933
  {textDisplay}
923
934
  </div>
935
+ {conveysRequired && (
936
+ <VisuallyHidden id={requiredID}>Required</VisuallyHidden>
937
+ )}
924
938
  {showsDisabledMessage &&
925
939
  disabledMessageTooltip.renderTooltip(disabledMessage)}
926
940
  </Field>
@@ -107,7 +107,7 @@ export const docs = {
107
107
  {
108
108
  name: 'labelIcon',
109
109
  type: 'IconType',
110
- description: 'Icon displayed before the label text. See `npx astryx docs icons` for valid semantic names.',
110
+ description: 'Icon displayed before the label text. See `astryx docs icons` for valid semantic names.',
111
111
  },
112
112
  {
113
113
  name: 'labelTooltip',
@@ -9,10 +9,15 @@
9
9
  * SYNC: When Switch.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeEach} from 'vitest';
12
+ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
13
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {Switch} from './Switch';
16
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
17
+
18
+ afterEach(() => {
19
+ __resetLiveRegionsForTest();
20
+ });
16
21
 
17
22
  // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
18
23
  // reflects its open state via a `popover-open` attribute the tests can assert.
@@ -324,6 +329,32 @@ describe('Switch', () => {
324
329
  expect(describedBy).toBeTruthy();
325
330
  });
326
331
 
332
+ // Regression: the status is conditionally mounted, so it must be announced
333
+ // through the persistent useAnnounce live region — a live region born
334
+ // together with its content is not reliably announced.
335
+ it('announces a status message that appears after mount', async () => {
336
+ const {rerender} = render(
337
+ <Switch label="Enable notifications" value={false} onChange={() => {}} />,
338
+ );
339
+ expect(
340
+ document.querySelector('[data-astryx-live-region="assertive"]'),
341
+ ).toBeNull();
342
+
343
+ rerender(
344
+ <Switch
345
+ label="Enable notifications"
346
+ value={false}
347
+ onChange={() => {}}
348
+ status={{type: 'error', message: 'Failed to save setting'}}
349
+ />,
350
+ );
351
+ await waitFor(() => {
352
+ expect(
353
+ document.querySelector('[data-astryx-live-region="assertive"]'),
354
+ ).toHaveTextContent('Failed to save setting');
355
+ });
356
+ });
357
+
327
358
  it('calls onFocus and onBlur callbacks', async () => {
328
359
  const user = userEvent.setup();
329
360
  const handleFocus = vi.fn();
@@ -138,7 +138,11 @@ const styles = stylex.create({
138
138
  },
139
139
  indicator: {
140
140
  position: 'absolute',
141
- bottom: '-1px',
141
+ // Sits on the tab's bottom edge by default (-1px). When the tab strip
142
+ // reserves space for a divider rail — TabList `hasDivider` or a Toolbar
143
+ // with a bottom divider — that ancestor sets `--_tab-indicator-bottom`
144
+ // to drop the indicator onto the rail beneath the reserved gap.
145
+ bottom: 'var(--_tab-indicator-bottom, -1px)',
142
146
  left: spacingVars['--spacing-3'],
143
147
  right: spacingVars['--spacing-3'],
144
148
  height: '2px',
@@ -92,7 +92,7 @@ export const docs = {
92
92
  bestPractices: [
93
93
  { guidance: true, description: 'Keep tab labels short and descriptive so users can quickly scan available sections.' },
94
94
  { guidance: true, description: 'Use TabMenu to group overflow items when horizontal space is limited rather than scrolling tabs off-screen.' },
95
- { guidance: true, description: 'When using hasDivider with action buttons alongside tabs, use a smaller button size (sm) so the actions don\'t overpower the tab row.' },
95
+ { guidance: true, description: 'When using hasDivider with action buttons alongside tabs, match the Button size to the TabList size (both md, both sm); the divided tab strip reserves space so tabs and same-size buttons align to a shared baseline above the rail.' },
96
96
  { guidance: false, description: 'Use tabs for sequential steps or workflows; use a stepper or wizard pattern instead.' },
97
97
  { guidance: false, description: 'Place more than 6–8 visible tabs before the overflow menu; prioritize the most important categories.' },
98
98
  { guidance: false, description: 'Confuse TabList with SegmentedControl or ToggleButton. TabList is for navigation between views. SegmentedControl and ToggleButton are input controls: SegmentedControl always has exactly one selected option, while ToggleButton can be toggled on or off.' },
@@ -113,7 +113,7 @@ export const docsZh = {
113
113
  bestPractices: [
114
114
  { guidance: true, description: 'Keep tab labels short and descriptive so users can quickly scan available sections.' },
115
115
  { guidance: true, description: 'Use TabMenu to group overflow items when horizontal space is limited rather than scrolling tabs off-screen.' },
116
- { guidance: true, description: 'When using hasDivider with action buttons alongside tabs, use a smaller button size (sm) so the actions don\'t overpower the tab row.' },
116
+ { guidance: true, description: 'When using hasDivider with action buttons alongside tabs, match the Button size to the TabList size (both md, both sm); the divided tab strip reserves space so tabs and same-size buttons align to a shared baseline above the rail.' },
117
117
  { guidance: false, description: 'Use tabs for sequential steps or workflows; use a stepper or wizard pattern instead.' },
118
118
  { guidance: false, description: 'Place more than 6–8 visible tabs before the overflow menu; prioritize the most important categories.' },
119
119
  { guidance: false, description: 'Confuse TabList with SegmentedControl or ToggleButton. TabList is for navigation between views. SegmentedControl and ToggleButton are input controls: SegmentedControl always has exactly one selected option, while ToggleButton can be toggled on or off.' },
@@ -135,7 +135,7 @@ export const docsDense = {
135
135
  bestPractices: [
136
136
  { guidance: true, description: 'Keep tab labels short and descriptive so users can quickly scan available sections.' },
137
137
  { guidance: true, description: 'Use TabMenu to group overflow items when horizontal space is limited rather than scrolling tabs off-screen.' },
138
- { guidance: true, description: 'When using hasDivider with action buttons alongside tabs, use a smaller button size (sm) so the actions don\'t overpower the tab row.' },
138
+ { guidance: true, description: 'When using hasDivider with action buttons alongside tabs, match the Button size to the TabList size (both md, both sm); the divided tab strip reserves space so tabs and same-size buttons align to a shared baseline above the rail.' },
139
139
  { guidance: false, description: 'Use tabs for sequential steps or workflows; use a stepper or wizard pattern instead.' },
140
140
  { guidance: false, description: 'Place more than 6–8 visible tabs before the overflow menu; prioritize the most important categories.' },
141
141
  { guidance: false, description: 'Confuse TabList with SegmentedControl or ToggleButton. TabList is for navigation between views. SegmentedControl and ToggleButton are input controls: SegmentedControl always has exactly one selected option, while ToggleButton can be toggled on or off.' },
@@ -323,6 +323,72 @@ describe('TabList', () => {
323
323
  });
324
324
  });
325
325
 
326
+ describe('TabList divider gap', () => {
327
+ // The divider adds the reserved gap + indicator offset via a single class
328
+ // (StyleX applies deterministic classes in the test env). Capture that
329
+ // class set once so the assertions describe intent, not opaque hashes.
330
+ function navClasses(hasDivider: boolean): Set<string> {
331
+ const {unmount} = render(
332
+ <TabList value="home" onChange={() => {}} hasDivider={hasDivider}>
333
+ <Tab value="home" label="Home" />
334
+ </TabList>,
335
+ );
336
+ const nav = screen.getByRole('navigation');
337
+ const classes = new Set(nav.className.split(/\s+/).filter(Boolean));
338
+ unmount();
339
+ return classes;
340
+ }
341
+
342
+ it('adds divider-only styling classes when hasDivider is set', () => {
343
+ const withDivider = navClasses(true);
344
+ const withoutDivider = navClasses(false);
345
+
346
+ // Every class the plain nav has must still be present when divided: the
347
+ // divider only adds styling (border + reserved gap + indicator offset),
348
+ // it never removes the base nav styles.
349
+ for (const cls of withoutDivider) {
350
+ expect(withDivider.has(cls)).toBe(true);
351
+ }
352
+
353
+ // And it must add at least one class the undivided nav does not have.
354
+ const added = [...withDivider].filter(c => !withoutDivider.has(c));
355
+ expect(added.length).toBeGreaterThan(0);
356
+ });
357
+
358
+ it('does not add divider styling to an undivided tab list (default)', () => {
359
+ // Default (no hasDivider) and explicit hasDivider={false} are identical:
360
+ // the non-divided path is untouched by the divider gap change.
361
+ expect(navClasses(false)).toEqual(
362
+ (() => {
363
+ const {unmount} = render(
364
+ <TabList value="home" onChange={() => {}}>
365
+ <Tab value="home" label="Home" />
366
+ </TabList>,
367
+ );
368
+ const nav = screen.getByRole('navigation');
369
+ const classes = new Set(nav.className.split(/\s+/).filter(Boolean));
370
+ unmount();
371
+ return classes;
372
+ })(),
373
+ );
374
+ });
375
+
376
+ it('keeps the selected indicator rendered under a divider', () => {
377
+ render(
378
+ <TabList value="home" onChange={() => {}} hasDivider>
379
+ <Tab value="home" label="Home" />
380
+ <Tab value="away" label="Away" />
381
+ </TabList>,
382
+ );
383
+ const selected = screen.getByRole('button', {name: 'Home'});
384
+ // The indicator span carries the selected marker; the divider must not
385
+ // drop it (it is repositioned onto the rail, not removed).
386
+ expect(
387
+ selected.querySelector('[data-selected="selected"]'),
388
+ ).toBeInTheDocument();
389
+ });
390
+ });
391
+
326
392
  describe('TabList keyboard navigation (roving tabindex)', () => {
327
393
  it('exposes the tab strip as a single Tab stop (only selected tab is tabbable)', () => {
328
394
  render(
@@ -96,6 +96,14 @@ const styles = stylex.create({
96
96
  borderBottomWidth: borderVars['--border-width'],
97
97
  borderBottomStyle: 'solid',
98
98
  borderBottomColor: colorVars['--color-border'],
99
+ // Reserve a gap between the tabs and the divider rail so the hover pill
100
+ // (which fills the tab height) no longer touches the underline, and an
101
+ // adjacent same-size Button aligns to the tabs rather than butting the
102
+ // rail. The tabs keep their element-size height; this padding grows the
103
+ // strip. `--_tab-indicator-bottom` drops the selected indicator through
104
+ // the reserved gap (+ the 1px border) so it still sits on the rail.
105
+ paddingBlockEnd: spacingVars['--spacing-1'],
106
+ '--_tab-indicator-bottom': `calc(-1 * (${spacingVars['--spacing-1']} + ${borderVars['--border-width']}))`,
99
107
  },
100
108
  });
101
109
 
@@ -122,7 +122,10 @@ const styles = stylex.create({
122
122
  },
123
123
  indicator: {
124
124
  position: 'absolute',
125
- bottom: '-1px',
125
+ // Mirrors Tab's indicator: sits on the bottom edge by default (-1px), or
126
+ // drops onto the divider rail when an ancestor (TabList `hasDivider` or a
127
+ // Toolbar with a bottom divider) sets `--_tab-indicator-bottom`.
128
+ bottom: 'var(--_tab-indicator-bottom, -1px)',
126
129
  left: spacingVars['--spacing-3'],
127
130
  right: spacingVars['--spacing-3'],
128
131
  height: '2px',
@@ -111,6 +111,8 @@ export const docs = {
111
111
  {name: 'useTableSelection'},
112
112
  {name: 'useTableSelectionState'},
113
113
  {name: 'useTableSortable'},
114
+ {name: 'useTableTreeData'},
115
+ {name: 'useTableTreeState'},
114
116
  {name: 'useTablePagination'},
115
117
  {name: 'useTableColumnSettings'},
116
118
  {name: 'useTableFiltering'},
@@ -275,7 +275,7 @@ function TableInner<T extends Record<string, unknown>>({
275
275
  * columns={[
276
276
  * { key: 'name', header: 'Name', width: proportional(1), renderCell: (u) => (
277
277
  * <HStack gap={2} align="center">
278
- * <Avatar name={u.name} size="small" />
278
+ * <Avatar name={u.name} size="md" />
279
279
  * <Text weight="semibold">{u.name}</Text>
280
280
  * </HStack>
281
281
  * )},