@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
@@ -28,10 +28,12 @@ export {useTableColumnResize} from './plugins/columnResize';
28
28
  export {useTableStickyColumns} from './plugins/stickyColumns';
29
29
  export {useTableGroupedRows} from './plugins/groupedRows';
30
30
  export {useTableRowIndex} from './plugins/rowIndex';
31
+ export {useTableRowStatus} from './plugins/rowStatus';
31
32
  export {
32
33
  useTableRowExpansion,
33
34
  useTableRowExpansionState,
34
35
  } from './plugins/rowExpansion';
36
+ export {useTableTreeData, useTableTreeState} from './plugins/tree';
35
37
  export {resolveContextActions} from './tableContextMenu';
36
38
  export {
37
39
  useTableFiltering,
@@ -109,6 +111,16 @@ export type {
109
111
  UseTableGroupedRowsConfig,
110
112
  UseTableGroupedRowsResult,
111
113
  } from './plugins/groupedRows';
114
+ export type {
115
+ UseTableRowStatusConfig,
116
+ TableRowStatus,
117
+ } from './plugins/rowStatus';
118
+ export type {
119
+ TableTreeRowMeta,
120
+ UseTableTreeDataConfig,
121
+ UseTableTreeStateConfig,
122
+ UseTableTreeStateResult,
123
+ } from './plugins/tree';
112
124
  export type {
113
125
  UseTableFilteringConfig,
114
126
  TableFilterState,
@@ -325,8 +325,12 @@ export function useTableGroupedRows<T extends Record<string, unknown>>(
325
325
  }}
326
326
  aria-label={
327
327
  collapsed
328
- ? t('@astryx.tableGroupedRows.expandGroup', {groupKey: header.groupKey})
329
- : t('@astryx.tableGroupedRows.collapseGroup', {groupKey: header.groupKey})
328
+ ? t('@astryx.tableGroupedRows.expandGroup', {
329
+ groupKey: header.groupKey,
330
+ })
331
+ : t('@astryx.tableGroupedRows.collapseGroup', {
332
+ groupKey: header.groupKey,
333
+ })
330
334
  }
331
335
  aria-expanded={!collapsed}>
332
336
  <span
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  import {describe, it, expect, vi, beforeEach} from 'vitest';
4
- import {render, screen, fireEvent} from '@testing-library/react';
4
+ import {render, renderHook, screen, fireEvent} from '@testing-library/react';
5
5
  import {useState} from 'react';
6
6
  import {Table} from '../../Table';
7
7
  import type {TableColumn} from '../../types';
@@ -153,3 +153,92 @@ describe('useTableRowExpansion', () => {
153
153
  expect(screen.queryByText('File B1')).not.toBeInTheDocument();
154
154
  });
155
155
  });
156
+
157
+ describe('useTableRowExpansionState cycle guard', () => {
158
+ /** A row whose children array contains the row itself (plus a real child). */
159
+ function makeSelfReferential(): TreeItem[] {
160
+ const x: TreeItem = {id: 'x', name: 'Self', children: []};
161
+ const y: TreeItem = {id: 'y', name: 'Leaf Y', children: []};
162
+ x.children.push(x, y);
163
+ return [x];
164
+ }
165
+
166
+ /** root -> child -> grand, where grand's children point back at root. */
167
+ function makeDeepCycle(): TreeItem[] {
168
+ const root: TreeItem = {id: 'root', name: 'Root', children: []};
169
+ const child: TreeItem = {id: 'child', name: 'Child', children: []};
170
+ const grand: TreeItem = {id: 'grand', name: 'Grand', children: []};
171
+ root.children.push(child);
172
+ child.children.push(grand);
173
+ grand.children.push(root);
174
+ return [root];
175
+ }
176
+
177
+ function renderState(baseData: TreeItem[], expandedKeys: Set<string>) {
178
+ const setExpandedKeys = vi.fn();
179
+ const hook = renderHook(() =>
180
+ useTableRowExpansionState<TreeItem>({
181
+ baseData,
182
+ getChildren: item => item.children,
183
+ getRowKey: item => item.id,
184
+ expandedKeys,
185
+ setExpandedKeys,
186
+ }),
187
+ );
188
+ return {hook, setExpandedKeys};
189
+ }
190
+
191
+ it('terminates on a self-referential expanded row and flattens each key once', () => {
192
+ const {hook} = renderState(makeSelfReferential(), new Set(['x']));
193
+ const {data, expansionConfig} = hook.result.current;
194
+ expect(data.map(item => item.id)).toEqual(['x', 'y']);
195
+ expect(expansionConfig.getDepth?.(data[0])).toBe(0);
196
+ expect(expansionConfig.getDepth?.(data[1])).toBe(1);
197
+ });
198
+
199
+ it('terminates on a deeper cycle back to the root and flattens each key once', () => {
200
+ const {hook} = renderState(
201
+ makeDeepCycle(),
202
+ new Set(['root', 'child', 'grand']),
203
+ );
204
+ const {data, expansionConfig} = hook.result.current;
205
+ expect(data.map(item => item.id)).toEqual(['root', 'child', 'grand']);
206
+ expect(expansionConfig.getDepth?.(data[0])).toBe(0);
207
+ expect(expansionConfig.getDepth?.(data[1])).toBe(1);
208
+ expect(expansionConfig.getDepth?.(data[2])).toBe(2);
209
+ // The cycle guard keeps isAllExpanded computable — true, not a crash.
210
+ expect(expansionConfig.isAllExpanded).toBe(true);
211
+ });
212
+
213
+ it('terminates when collecting allExpandableKeys on cyclic data with nothing expanded', () => {
214
+ const {hook, setExpandedKeys} = renderState(
215
+ makeDeepCycle(),
216
+ new Set<string>(),
217
+ );
218
+ const {data, expansionConfig} = hook.result.current;
219
+ expect(data.map(item => item.id)).toEqual(['root']);
220
+ expansionConfig.onToggleExpandAll?.(true);
221
+ const nextKeys = setExpandedKeys.mock.calls[0][0] as Set<string>;
222
+ expect(Array.from(nextKeys)).toEqual(['root', 'child', 'grand']);
223
+ });
224
+
225
+ it('re-walks a shared child under each expanded parent (ancestor-path, not visited-set, semantics)', () => {
226
+ const leaf: TreeItem = {id: 'leaf', name: 'Leaf', children: []};
227
+ const shared: TreeItem = {id: 's', name: 'Shared', children: [leaf]};
228
+ const p1: TreeItem = {id: 'p1', name: 'Parent 1', children: [shared]};
229
+ const p2: TreeItem = {id: 'p2', name: 'Parent 2', children: [shared]};
230
+ const {hook} = renderState([p1, p2], new Set(['p1', 'p2', 's']));
231
+ // 's' is on neither parent's ancestor path, so it must flatten under
232
+ // both — the guard only skips true cycles, matching pre-guard behavior
233
+ // for acyclic (DAG-shaped) data.
234
+ const {data} = hook.result.current;
235
+ expect(data.map(item => item.id)).toEqual([
236
+ 'p1',
237
+ 's',
238
+ 'leaf',
239
+ 'p2',
240
+ 's',
241
+ 'leaf',
242
+ ]);
243
+ });
244
+ });
@@ -141,12 +141,19 @@ export function useTableRowExpansionState<T extends Record<string, unknown>>({
141
141
 
142
142
  const depthMap = useMemo(() => {
143
143
  const map = new Map<string, number>();
144
+ // Ancestor keys on the current walk path — guards against cyclic data.
145
+ const path = new Set<string>();
144
146
  function walk(items: T[], depth: number) {
145
147
  for (const item of items) {
146
148
  const key = getRowKey(item);
149
+ if (path.has(key)) {
150
+ continue; // cyclic edge — skip
151
+ }
147
152
  map.set(key, depth);
148
153
  if (expandedKeys.has(key)) {
154
+ path.add(key);
149
155
  walk(getChildren(item), depth + 1);
156
+ path.delete(key);
150
157
  }
151
158
  }
152
159
  }
@@ -156,12 +163,19 @@ export function useTableRowExpansionState<T extends Record<string, unknown>>({
156
163
 
157
164
  const data = useMemo(() => {
158
165
  const result: T[] = [];
166
+ // Ancestor keys on the current walk path — guards against cyclic data.
167
+ const path = new Set<string>();
159
168
  function walk(items: T[]) {
160
169
  for (const item of items) {
161
- result.push(item);
162
170
  const key = getRowKey(item);
171
+ if (path.has(key)) {
172
+ continue; // cyclic edge — skip
173
+ }
174
+ result.push(item);
163
175
  if (expandedKeys.has(key)) {
176
+ path.add(key);
164
177
  walk(getChildren(item));
178
+ path.delete(key);
165
179
  }
166
180
  }
167
181
  }
@@ -172,11 +186,19 @@ export function useTableRowExpansionState<T extends Record<string, unknown>>({
172
186
  // Every expandable key across the whole tree (drives expand-all).
173
187
  const allExpandableKeys = useMemo(() => {
174
188
  const keys: string[] = [];
189
+ // Ancestor keys on the current walk path — guards against cyclic data.
190
+ const path = new Set<string>();
175
191
  function walk(items: T[]) {
176
192
  for (const item of items) {
193
+ const key = getRowKey(item);
194
+ if (path.has(key)) {
195
+ continue; // cyclic edge — skip
196
+ }
177
197
  if (isExpandable(item)) {
178
- keys.push(getRowKey(item));
198
+ keys.push(key);
199
+ path.add(key);
179
200
  walk(getChildren(item));
201
+ path.delete(key);
180
202
  }
181
203
  }
182
204
  }
@@ -399,7 +421,11 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
399
421
  <ExpansionChevron
400
422
  isExpanded={isExpanded}
401
423
  onToggle={() => onToggle(key)}
402
- ariaLabel={isExpanded ? t('@astryx.tableRowExpansion.collapseRow') : t('@astryx.tableRowExpansion.expandRow')}
424
+ ariaLabel={
425
+ isExpanded
426
+ ? t('@astryx.tableRowExpansion.collapseRow')
427
+ : t('@astryx.tableRowExpansion.expandRow')
428
+ }
403
429
  />
404
430
  );
405
431
  },
@@ -457,7 +483,11 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
457
483
  <ExpansionChevron
458
484
  isExpanded={isExpanded}
459
485
  onToggle={() => onToggle(key)}
460
- ariaLabel={isExpanded ? t('@astryx.tableRowExpansion.collapseRow') : t('@astryx.tableRowExpansion.expandRow')}
486
+ ariaLabel={
487
+ isExpanded
488
+ ? t('@astryx.tableRowExpansion.collapseRow')
489
+ : t('@astryx.tableRowExpansion.expandRow')
490
+ }
461
491
  />
462
492
  ) : (
463
493
  <span {...stylex.props(expansionStyles.placeholder)} />
@@ -499,7 +529,9 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
499
529
  {...stylex.props(expansionStyles.chevronButton)}
500
530
  onClick={() => onToggleExpandAll(!allExpanded)}
501
531
  aria-label={
502
- allExpanded ? t('@astryx.tableRowExpansion.collapseAllRows') : t('@astryx.tableRowExpansion.expandAllRows')
532
+ allExpanded
533
+ ? t('@astryx.tableRowExpansion.collapseAllRows')
534
+ : t('@astryx.tableRowExpansion.expandAllRows')
503
535
  }>
504
536
  <span
505
537
  {...stylex.props(
@@ -0,0 +1,7 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ export {useTableRowStatus} from './useTableRowStatus';
4
+ export type {
5
+ UseTableRowStatusConfig,
6
+ TableRowStatus,
7
+ } from './useTableRowStatus';
@@ -0,0 +1,138 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+ import {render, screen, within} from '@testing-library/react';
5
+ import {Table} from '../../Table';
6
+ import type {TableColumn} from '../../types';
7
+ import {useTableRowStatus, type TableRowStatus} from './useTableRowStatus';
8
+
9
+ interface Row extends Record<string, unknown> {
10
+ id: string;
11
+ name: string;
12
+ state: 'error' | 'warning' | 'ok' | 'done';
13
+ }
14
+
15
+ const data: Row[] = [
16
+ {id: 'a', name: 'Alice', state: 'error'},
17
+ {id: 'b', name: 'Bob', state: 'ok'},
18
+ {id: 'c', name: 'Carol', state: 'warning'},
19
+ ];
20
+
21
+ const columns: TableColumn<Row>[] = [{key: 'name', header: 'Name'}];
22
+
23
+ function getStatus(item: Row): TableRowStatus | null {
24
+ if (item.state === 'error') {
25
+ return {color: 'red', label: 'Error'};
26
+ }
27
+ if (item.state === 'warning') {
28
+ return {color: 'orange', label: 'Warning'};
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function Harness({
34
+ rows = data,
35
+ statusFn = getStatus,
36
+ }: {
37
+ rows?: Row[];
38
+ statusFn?: (item: Row) => TableRowStatus | null;
39
+ }) {
40
+ const rowStatus = useTableRowStatus<Row>({getStatus: statusFn});
41
+ return (
42
+ <Table data={rows} columns={columns} idKey="id" plugins={{rowStatus}} />
43
+ );
44
+ }
45
+
46
+ describe('useTableRowStatus', () => {
47
+ it('prepends a narrow status column with an empty header', () => {
48
+ render(<Harness />);
49
+ const headers = screen.getAllByRole('columnheader');
50
+ // Status column is first; its header is empty.
51
+ expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
52
+ expect(headers[0].textContent).toBe('');
53
+ });
54
+
55
+ it('renders a labeled dot for rows with a status', () => {
56
+ render(<Harness />);
57
+ expect(screen.getByRole('img', {name: 'Error'})).toBeInTheDocument();
58
+ expect(screen.getByRole('img', {name: 'Warning'})).toBeInTheDocument();
59
+ });
60
+
61
+ it('renders a dot (not an icon) by default', () => {
62
+ render(<Harness />);
63
+ // Default (no icon) renders a plain colored dot: no svg in the indicator.
64
+ const dot = screen.getByRole('img', {name: 'Error'});
65
+ expect(dot.querySelector('svg')).toBeNull();
66
+ });
67
+
68
+ it('renders no indicator for rows returning null', () => {
69
+ render(<Harness />);
70
+ const rows = screen.getAllByRole('row');
71
+ // rows[2] is Bob (state ok): no status indicator in his status cell.
72
+ const bob = rows[2];
73
+ expect(within(bob).getByText('Bob')).toBeInTheDocument();
74
+ expect(within(bob).queryByRole('img')).not.toBeInTheDocument();
75
+ });
76
+
77
+ it('maps a semantic color name to its icon color token', () => {
78
+ render(<Harness />);
79
+ const errorDot = screen.getByRole('img', {name: 'Error'});
80
+ // 'red' resolves to var(--color-icon-red); StyleX emits it on the inline
81
+ // style of the inner dot element.
82
+ const dot = errorDot.querySelector('span');
83
+ expect(dot?.getAttribute('style')).toContain('--color-icon-red');
84
+ });
85
+
86
+ it('passes through a raw CSS color as an escape hatch', () => {
87
+ render(
88
+ <Harness
89
+ statusFn={item =>
90
+ item.state === 'error' ? {color: 'rgb(1, 2, 3)', label: 'Raw'} : null
91
+ }
92
+ />,
93
+ );
94
+ const indicator = screen.getByRole('img', {name: 'Raw'});
95
+ const dot = indicator.querySelector('span');
96
+ expect(dot?.getAttribute('style')).toContain('rgb(1, 2, 3)');
97
+ });
98
+
99
+ it('renders an icon as the status signifier when icon is provided', () => {
100
+ render(
101
+ <Harness
102
+ statusFn={item =>
103
+ item.state === 'error'
104
+ ? {color: 'red', icon: 'error', label: 'Error'}
105
+ : null
106
+ }
107
+ />,
108
+ );
109
+ // Icon-mode still exposes the accessible label via role=img.
110
+ const indicator = screen.getByRole('img', {name: 'Error'});
111
+ expect(indicator).toBeInTheDocument();
112
+ // An SVG icon is rendered inside the indicator (dot mode has no svg).
113
+ expect(indicator.querySelector('svg')).not.toBeNull();
114
+ });
115
+
116
+ it('exposes the required label as the accessible name in dot mode', () => {
117
+ render(
118
+ <Harness
119
+ statusFn={item =>
120
+ item.state === 'error' ? {color: 'red', label: 'Error'} : null
121
+ }
122
+ />,
123
+ );
124
+ // Label is required, so every dot is announced via role=img with its name.
125
+ const indicator = screen.getByRole('img', {name: 'Error'});
126
+ expect(indicator).toBeInTheDocument();
127
+ // Dot mode renders no svg (that is icon mode).
128
+ expect(indicator.querySelector('svg')).toBeNull();
129
+ expect(screen.getByText('Alice')).toBeInTheDocument();
130
+ });
131
+
132
+ it('renders the status header with empty data and no indicators', () => {
133
+ render(<Harness rows={[]} />);
134
+ const headers = screen.getAllByRole('columnheader');
135
+ expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
136
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
137
+ });
138
+ });
@@ -0,0 +1,189 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useTableRowStatus.tsx
7
+ * @input React, StyleX, Icon, Table types
8
+ * @output Exports useTableRowStatus hook + config type
9
+ * @position Row-status plugin; consumed by Table via plugins prop
10
+ *
11
+ * SYNC: When modified, update these files to stay in sync:
12
+ * - /packages/core/src/Table/index.ts (exports)
13
+ */
14
+
15
+ import {useMemo} from 'react';
16
+ import * as stylex from '@stylexjs/stylex';
17
+ import {Icon, type IconColor, type IconName} from '../../../Icon';
18
+ import {Tooltip} from '../../../Tooltip';
19
+ import type {TableColumn, TablePlugin} from '../../types';
20
+
21
+ /**
22
+ * Semantic status colors, resolved to the design system's icon color tokens.
23
+ * Prefer these over raw CSS so status colors stay consistent with the theme.
24
+ */
25
+ export type TableRowStatusColor =
26
+ | 'accent'
27
+ | 'success'
28
+ | 'error'
29
+ | 'warning'
30
+ | 'red'
31
+ | 'orange'
32
+ | 'green'
33
+ | 'yellow'
34
+ | 'blue'
35
+ | 'gray';
36
+
37
+ const SEMANTIC_COLORS: Record<TableRowStatusColor, string> = {
38
+ accent: 'var(--color-icon-accent)',
39
+ success: 'var(--color-icon-green)',
40
+ error: 'var(--color-icon-red)',
41
+ warning: 'var(--color-icon-orange)',
42
+ red: 'var(--color-icon-red)',
43
+ orange: 'var(--color-icon-orange)',
44
+ green: 'var(--color-icon-green)',
45
+ yellow: 'var(--color-icon-yellow)',
46
+ blue: 'var(--color-icon-blue)',
47
+ gray: 'var(--color-icon-gray)',
48
+ };
49
+
50
+ /** Icon colors that map cleanly from a semantic status color. */
51
+ const ICON_COLOR_BY_STATUS: Record<TableRowStatusColor, IconColor> = {
52
+ accent: 'accent',
53
+ success: 'success',
54
+ error: 'error',
55
+ warning: 'warning',
56
+ red: 'red',
57
+ orange: 'warning',
58
+ green: 'green',
59
+ yellow: 'warning',
60
+ blue: 'blue',
61
+ gray: 'gray',
62
+ };
63
+
64
+ /**
65
+ * A row's status indicator. `color` accepts a semantic status color
66
+ * (mapped to a theme token) or any raw CSS color string as an escape hatch.
67
+ * By default the plugin renders a colored status dot. Provide `icon` to signal
68
+ * status by shape as well as color, which is more accessible when several
69
+ * statuses coexist. `label` is required so the status is never conveyed by
70
+ * color alone — it names the indicator for assistive technology and shows on
71
+ * hover. Return `null` for rows with no status.
72
+ */
73
+ export interface TableRowStatus {
74
+ /** Semantic status color (preferred) or a raw CSS color string. */
75
+ color: TableRowStatusColor | (string & {});
76
+ /** Optional icon rendered as the signifier instead of the dot (shape as an a11y differentiator). */
77
+ icon?: IconName;
78
+ /**
79
+ * Accessible name for the status, announced to assistive technology and
80
+ * shown in a tooltip on hover. Required: a status must never be conveyed by
81
+ * color alone.
82
+ */
83
+ label: string;
84
+ }
85
+
86
+ /** Configuration for {@link useTableRowStatus}. */
87
+ export interface UseTableRowStatusConfig<T extends Record<string, unknown>> {
88
+ /**
89
+ * Derive the status indicator for a row. Return `null` for no indicator.
90
+ * Memoize with `useCallback` for a stable plugin identity across renders.
91
+ *
92
+ * @example
93
+ * ```
94
+ * getStatus: row =>
95
+ * row.hasError ? {color: 'error', icon: 'error', label: 'Error'} : null
96
+ * ```
97
+ */
98
+ getStatus: (item: T) => TableRowStatus | null;
99
+ }
100
+
101
+ // The status column holds a small centered dot (or an icon when provided).
102
+ // A fixed narrow width keeps every row's indicator aligned in one gutter.
103
+ const STATUS_COLUMN_WIDTH = {type: 'pixel' as const, value: 28};
104
+
105
+ /** Resolve a semantic color name to a token, or pass a raw CSS color through. */
106
+ function resolveColor(color: string): string {
107
+ return (SEMANTIC_COLORS as Record<string, string>)[color] ?? color;
108
+ }
109
+
110
+ const styles = stylex.create({
111
+ // Centers the dot or icon within the narrow status column.
112
+ wrap: {
113
+ display: 'flex',
114
+ alignItems: 'center',
115
+ justifyContent: 'center',
116
+ },
117
+ dot: (color: string) => ({
118
+ width: '8px',
119
+ height: '8px',
120
+ borderRadius: '50%',
121
+ backgroundColor: color,
122
+ flexShrink: 0,
123
+ }),
124
+ });
125
+
126
+ /**
127
+ * Returns a {@link TablePlugin} that prepends a narrow column signaling per-row
128
+ * status: a colored status dot by default, or an icon when `icon` is provided
129
+ * (shape + color is more accessible than color alone).
130
+ *
131
+ * @example
132
+ * ```
133
+ * const rowStatus = useTableRowStatus<Row>({
134
+ * getStatus: row =>
135
+ * row.state === 'error'
136
+ * ? {color: 'error', icon: 'error', label: 'Error'}
137
+ * : null,
138
+ * });
139
+ * <Table data={data} columns={columns} idKey="id" plugins={{rowStatus}} />;
140
+ * ```
141
+ */
142
+ export function useTableRowStatus<T extends Record<string, unknown>>(
143
+ config: UseTableRowStatusConfig<T>,
144
+ ): TablePlugin<T> {
145
+ const {getStatus} = config;
146
+
147
+ return useMemo(
148
+ (): TablePlugin<T> => ({
149
+ transformColumns(columns) {
150
+ const statusColumn: TableColumn<T> = {
151
+ key: '__rowStatus',
152
+ header: '',
153
+ width: STATUS_COLUMN_WIDTH,
154
+ resizable: false,
155
+ renderCell: (item: T) => {
156
+ const status = getStatus(item);
157
+ if (!status) {
158
+ return null;
159
+ }
160
+ const signifier = status.icon ? (
161
+ <Icon
162
+ icon={status.icon}
163
+ size="xsm"
164
+ color={
165
+ ICON_COLOR_BY_STATUS[status.color as TableRowStatusColor] ??
166
+ 'primary'
167
+ }
168
+ />
169
+ ) : (
170
+ <span {...stylex.props(styles.dot(resolveColor(status.color)))} />
171
+ );
172
+ return (
173
+ <Tooltip content={status.label}>
174
+ <span
175
+ {...stylex.props(styles.wrap)}
176
+ role="img"
177
+ aria-label={status.label}>
178
+ {signifier}
179
+ </span>
180
+ </Tooltip>
181
+ );
182
+ },
183
+ };
184
+ return [statusColumn, ...columns];
185
+ },
186
+ }),
187
+ [getStatus],
188
+ );
189
+ }
@@ -40,9 +40,11 @@ const selectableColumns: TableColumn<SelectableUser>[] = [
40
40
  function SelectionTable({
41
41
  getIsItemSelectable,
42
42
  getIsItemEnabled,
43
+ getRowLabel,
43
44
  }: {
44
45
  getIsItemSelectable?: (item: SelectableUser) => boolean;
45
46
  getIsItemEnabled?: (item: SelectableUser) => boolean;
47
+ getRowLabel?: (item: SelectableUser) => string;
46
48
  }) {
47
49
  const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
48
50
 
@@ -75,6 +77,7 @@ function SelectionTable({
75
77
  },
76
78
  getIsItemSelectable,
77
79
  getIsItemEnabled,
80
+ getRowLabel,
78
81
  });
79
82
 
80
83
  return (
@@ -109,6 +112,19 @@ describe('useTableSelection', () => {
109
112
  expect(rowCheckboxes).toHaveLength(3);
110
113
  });
111
114
 
115
+ it('derives per-row accessible names from getRowLabel', () => {
116
+ render(<SelectionTable getRowLabel={item => item.name} />);
117
+ expect(screen.getByLabelText('Select Alice')).toBeInTheDocument();
118
+ expect(screen.getByLabelText('Select Bob')).toBeInTheDocument();
119
+ expect(screen.getByLabelText('Select Charlie')).toBeInTheDocument();
120
+ expect(screen.queryByLabelText('Select row')).not.toBeInTheDocument();
121
+ });
122
+
123
+ it('keeps the "Select all rows" header label when getRowLabel is provided', () => {
124
+ render(<SelectionTable getRowLabel={item => item.name} />);
125
+ expect(screen.getByLabelText('Select all rows')).toBeInTheDocument();
126
+ });
127
+
112
128
  it('toggles individual row selection on click', async () => {
113
129
  const user = userEvent.setup();
114
130
  render(<SelectionTable />);
@@ -68,6 +68,18 @@ export interface UseTableSelectionConfig<T extends Record<string, unknown>> {
68
68
  getIsItemSelectable?: (item: T) => boolean;
69
69
  /** Is this row's checkbox interactive? Disabled rows show disabled checkbox. @default () => true */
70
70
  getIsItemEnabled?: (item: T) => boolean;
71
+ /**
72
+ * Derive a human-readable identity for a row, used in the row checkbox's
73
+ * hidden label as `Select ${getRowLabel(item)}`. Without it, every row
74
+ * checkbox announces an undifferentiated "Select row" to screen readers.
75
+ *
76
+ * @example
77
+ * ```
78
+ * getRowLabel: item => item.name
79
+ * // Checkbox accessible names: "Select Alice", "Select Bob", ...
80
+ * ```
81
+ */
82
+ getRowLabel?: (item: T) => string;
71
83
  }
72
84
 
73
85
  // =============================================================================
@@ -235,6 +247,7 @@ function SelectionCellContentInner<T extends Record<string, unknown>>({
235
247
  const isSelected = useIsItemSelected(store, item);
236
248
  const selectable = config.getIsItemSelectable?.(item) ?? true;
237
249
  const enabled = config.getIsItemEnabled?.(item) ?? true;
250
+ const rowLabel = config.getRowLabel?.(item);
238
251
 
239
252
  if (!selectable) {
240
253
  return null;
@@ -242,7 +255,11 @@ function SelectionCellContentInner<T extends Record<string, unknown>>({
242
255
 
243
256
  return (
244
257
  <CheckboxInput
245
- label={t('@astryx.table.selection.selectRow')}
258
+ label={
259
+ rowLabel != null
260
+ ? t('@astryx.table.selection.selectRowNamed', {label: rowLabel})
261
+ : t('@astryx.table.selection.selectRow')
262
+ }
246
263
  isLabelHidden
247
264
  value={isSelected}
248
265
  onChange={() =>