@astryxdesign/core 0.1.2 → 0.1.3

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 (567) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +12 -12
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +11 -5
  5. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +2 -4
  10. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  11. package/dist/ButtonGroup/ButtonGroup.js +0 -1
  12. package/dist/Calendar/Calendar.d.ts +5 -4
  13. package/dist/Calendar/Calendar.d.ts.map +1 -1
  14. package/dist/Calendar/Calendar.js +82 -43
  15. package/dist/Calendar/hooks/index.d.ts +0 -2
  16. package/dist/Calendar/hooks/index.d.ts.map +1 -1
  17. package/dist/Calendar/hooks/index.js +1 -2
  18. package/dist/Calendar/index.d.ts +3 -3
  19. package/dist/Calendar/index.d.ts.map +1 -1
  20. package/dist/Calendar/index.js +1 -1
  21. package/dist/Calendar/styles.d.ts +3 -9
  22. package/dist/Calendar/styles.d.ts.map +1 -1
  23. package/dist/Calendar/styles.js +3 -11
  24. package/dist/Carousel/Carousel.d.ts.map +1 -1
  25. package/dist/Carousel/Carousel.js +17 -3
  26. package/dist/Center/Center.d.ts +11 -1
  27. package/dist/Center/Center.d.ts.map +1 -1
  28. package/dist/Center/Center.js +9 -3
  29. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  30. package/dist/Chat/ChatComposerInput.js +0 -1
  31. package/dist/Chat/ChatMessageList.d.ts +14 -1
  32. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  33. package/dist/Chat/ChatMessageList.js +2 -0
  34. package/dist/Chat/useTriggerMenu.d.ts +8 -1
  35. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  36. package/dist/Chat/useTriggerMenu.js +15 -3
  37. package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +51 -9
  40. package/dist/CheckboxList/CheckboxList.d.ts +13 -1
  41. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  42. package/dist/CheckboxList/CheckboxList.js +39 -9
  43. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  44. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  45. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  46. package/dist/CheckboxList/CheckboxListItem.js +0 -1
  47. package/dist/Citation/Citation.d.ts.map +1 -1
  48. package/dist/Citation/Citation.js +9 -5
  49. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  50. package/dist/CodeBlock/CodeBlock.js +51 -37
  51. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  52. package/dist/CommandPalette/CommandPalette.js +5 -5
  53. package/dist/ContextMenu/ContextMenu.d.ts +4 -5
  54. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  55. package/dist/ContextMenu/ContextMenu.js +93 -11
  56. package/dist/DateInput/DateInput.d.ts +23 -1
  57. package/dist/DateInput/DateInput.d.ts.map +1 -1
  58. package/dist/DateInput/DateInput.js +167 -99
  59. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  60. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  61. package/dist/DateRangeInput/DateRangeInput.js +42 -9
  62. package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
  63. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  64. package/dist/DateTimeInput/DateTimeInput.js +78 -15
  65. package/dist/Dialog/Dialog.d.ts.map +1 -1
  66. package/dist/Dialog/Dialog.js +12 -0
  67. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
  68. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  69. package/dist/DropdownMenu/DropdownMenu.js +42 -14
  70. package/dist/Field/Field.d.ts +19 -2
  71. package/dist/Field/Field.d.ts.map +1 -1
  72. package/dist/Field/Field.js +4 -0
  73. package/dist/Field/FieldLabel.d.ts +21 -2
  74. package/dist/Field/FieldLabel.d.ts.map +1 -1
  75. package/dist/Field/FieldLabel.js +41 -26
  76. package/dist/FileInput/FileInput.d.ts +22 -1
  77. package/dist/FileInput/FileInput.d.ts.map +1 -1
  78. package/dist/FileInput/FileInput.js +56 -10
  79. package/dist/Grid/Grid.d.ts +11 -1
  80. package/dist/Grid/Grid.d.ts.map +1 -1
  81. package/dist/Grid/Grid.js +32 -6
  82. package/dist/InputGroup/InputGroup.d.ts +4 -3
  83. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  84. package/dist/InputGroup/InputGroup.js +16 -6
  85. package/dist/InputGroup/InputGroupContext.d.ts +4 -0
  86. package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
  87. package/dist/InputGroup/InputGroupContext.js +2 -2
  88. package/dist/InputGroup/groupStyles.d.ts +1 -1
  89. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  90. package/dist/InputGroup/groupStyles.js +3 -3
  91. package/dist/InputGroup/index.d.ts +1 -0
  92. package/dist/InputGroup/index.d.ts.map +1 -1
  93. package/dist/Kbd/Kbd.d.ts.map +1 -1
  94. package/dist/Kbd/Kbd.js +33 -1
  95. package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
  96. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  97. package/dist/Layer/layerAnimations.stylex.js +8 -4
  98. package/dist/Layer/useLayer.d.ts +10 -0
  99. package/dist/Layer/useLayer.d.ts.map +1 -1
  100. package/dist/Layer/useLayer.js +64 -9
  101. package/dist/Layout/Layout.d.ts +4 -11
  102. package/dist/Layout/Layout.d.ts.map +1 -1
  103. package/dist/Layout/Layout.js +10 -11
  104. package/dist/Layout/LayoutFooter.d.ts +2 -2
  105. package/dist/Layout/LayoutFooter.d.ts.map +1 -1
  106. package/dist/Layout/LayoutHeader.d.ts +2 -2
  107. package/dist/Layout/LayoutHeader.d.ts.map +1 -1
  108. package/dist/Layout/LayoutPanel.d.ts +2 -2
  109. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  110. package/dist/Layout/padding.stylex.d.ts +51 -0
  111. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  112. package/dist/Layout/padding.stylex.js +63 -0
  113. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  114. package/dist/Lightbox/Lightbox.js +4 -2
  115. package/dist/Link/Link.d.ts +16 -1
  116. package/dist/Link/Link.d.ts.map +1 -1
  117. package/dist/Link/Link.js +18 -7
  118. package/dist/Markdown/Markdown.d.ts.map +1 -1
  119. package/dist/Markdown/Markdown.js +7 -0
  120. package/dist/MoreMenu/MoreMenu.d.ts +1 -7
  121. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  122. package/dist/MoreMenu/MoreMenu.js +0 -2
  123. package/dist/MultiSelector/MultiSelector.d.ts +27 -2
  124. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  125. package/dist/MultiSelector/MultiSelector.js +141 -42
  126. package/dist/MultiSelector/hooks.d.ts +12 -1
  127. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  128. package/dist/MultiSelector/hooks.js +14 -1
  129. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  130. package/dist/NavMenu/NavHeadingMenu.js +34 -3
  131. package/dist/NumberInput/NumberInput.d.ts +23 -2
  132. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  133. package/dist/NumberInput/NumberInput.js +62 -12
  134. package/dist/Pagination/Pagination.d.ts +1 -1
  135. package/dist/Pagination/Pagination.d.ts.map +1 -1
  136. package/dist/Pagination/Pagination.js +15 -1
  137. package/dist/Popover/Popover.d.ts +18 -2
  138. package/dist/Popover/Popover.d.ts.map +1 -1
  139. package/dist/Popover/Popover.js +5 -2
  140. package/dist/Popover/usePopover.d.ts +38 -3
  141. package/dist/Popover/usePopover.d.ts.map +1 -1
  142. package/dist/Popover/usePopover.js +24 -9
  143. package/dist/PowerSearch/PowerSearch.d.ts +11 -1
  144. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  145. package/dist/PowerSearch/PowerSearch.js +6 -1
  146. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  147. package/dist/ProgressBar/ProgressBar.js +3 -5
  148. package/dist/RadioList/RadioList.d.ts +20 -2
  149. package/dist/RadioList/RadioList.d.ts.map +1 -1
  150. package/dist/RadioList/RadioList.js +105 -9
  151. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  152. package/dist/RadioList/RadioListItem.js +13 -2
  153. package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
  154. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  155. package/dist/SegmentedControl/SegmentedControl.js +78 -44
  156. package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
  157. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  158. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  159. package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
  160. package/dist/Selector/Selector.d.ts +24 -1
  161. package/dist/Selector/Selector.d.ts.map +1 -1
  162. package/dist/Selector/Selector.js +122 -43
  163. package/dist/Selector/hooks.d.ts +8 -1
  164. package/dist/Selector/hooks.d.ts.map +1 -1
  165. package/dist/Selector/hooks.js +14 -1
  166. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  167. package/dist/SideNav/SideNavHeading.js +2 -0
  168. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  169. package/dist/SideNav/SideNavItem.js +1 -1
  170. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  171. package/dist/Skeleton/Skeleton.js +8 -2
  172. package/dist/Slider/Slider.d.ts +21 -0
  173. package/dist/Slider/Slider.d.ts.map +1 -1
  174. package/dist/Slider/Slider.js +36 -9
  175. package/dist/Spinner/Spinner.d.ts.map +1 -1
  176. package/dist/Spinner/Spinner.js +1 -1
  177. package/dist/Stack/Stack.d.ts +38 -23
  178. package/dist/Stack/Stack.d.ts.map +1 -1
  179. package/dist/Stack/Stack.js +25 -1
  180. package/dist/Stack/StackItem.d.ts +12 -23
  181. package/dist/Stack/StackItem.d.ts.map +1 -1
  182. package/dist/Stack/StackItem.js +8 -1
  183. package/dist/Switch/Switch.d.ts +23 -2
  184. package/dist/Switch/Switch.d.ts.map +1 -1
  185. package/dist/Switch/Switch.js +41 -11
  186. package/dist/TabList/Tab.d.ts.map +1 -1
  187. package/dist/TabList/Tab.js +6 -0
  188. package/dist/TabList/TabList.d.ts +16 -4
  189. package/dist/TabList/TabList.d.ts.map +1 -1
  190. package/dist/TabList/TabList.js +87 -10
  191. package/dist/TabList/TabListContext.d.ts +5 -0
  192. package/dist/TabList/TabListContext.d.ts.map +1 -1
  193. package/dist/TabList/TabListContext.js +5 -0
  194. package/dist/TabList/TabMenu.d.ts.map +1 -1
  195. package/dist/TabList/TabMenu.js +6 -1
  196. package/dist/Table/BaseTable.d.ts.map +1 -1
  197. package/dist/Table/BaseTable.js +11 -8
  198. package/dist/Table/Table.d.ts.map +1 -1
  199. package/dist/Table/Table.js +10 -1
  200. package/dist/Table/TableCell.d.ts +8 -1
  201. package/dist/Table/TableCell.d.ts.map +1 -1
  202. package/dist/Table/TableCell.js +10 -10
  203. package/dist/Table/TableHeaderCell.d.ts +8 -1
  204. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  205. package/dist/Table/TableHeaderCell.js +16 -16
  206. package/dist/Table/index.d.ts +2 -1
  207. package/dist/Table/index.d.ts.map +1 -1
  208. package/dist/Table/index.js +2 -2
  209. package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
  210. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  211. package/dist/Table/plugins/pagination/paginateData.js +8 -3
  212. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  213. package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
  214. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  215. package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
  216. package/dist/Table/tableContextMenu.d.ts +33 -0
  217. package/dist/Table/tableContextMenu.d.ts.map +1 -0
  218. package/dist/Table/tableContextMenu.js +133 -0
  219. package/dist/Table/types.d.ts +66 -0
  220. package/dist/Table/types.d.ts.map +1 -1
  221. package/dist/TextArea/TextArea.d.ts +22 -1
  222. package/dist/TextArea/TextArea.d.ts.map +1 -1
  223. package/dist/TextArea/TextArea.js +41 -9
  224. package/dist/TextInput/TextInput.d.ts +23 -2
  225. package/dist/TextInput/TextInput.d.ts.map +1 -1
  226. package/dist/TextInput/TextInput.js +55 -10
  227. package/dist/TimeInput/TimeInput.d.ts +23 -1
  228. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  229. package/dist/TimeInput/TimeInput.js +147 -76
  230. package/dist/Toast/Toast.d.ts.map +1 -1
  231. package/dist/Toast/Toast.js +14 -0
  232. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  233. package/dist/Toast/ToastViewport.js +131 -3
  234. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  235. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  236. package/dist/Tokenizer/Tokenizer.js +26 -4
  237. package/dist/Toolbar/Toolbar.d.ts +3 -3
  238. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  239. package/dist/Toolbar/Toolbar.js +51 -7
  240. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  241. package/dist/Tooltip/useTooltip.js +56 -9
  242. package/dist/TopNav/TopNavHeading.d.ts +7 -1
  243. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  244. package/dist/TopNav/TopNavHeading.js +8 -0
  245. package/dist/TreeList/TreeList.d.ts +1 -1
  246. package/dist/TreeList/TreeList.d.ts.map +1 -1
  247. package/dist/TreeList/TreeList.js +72 -2
  248. package/dist/TreeList/TreeListItem.d.ts +11 -1
  249. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  250. package/dist/TreeList/TreeListItem.js +51 -12
  251. package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
  252. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
  253. package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
  254. package/dist/Typeahead/BaseTypeahead.d.ts +13 -0
  255. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  256. package/dist/Typeahead/BaseTypeahead.js +69 -6
  257. package/dist/Typeahead/Typeahead.d.ts +26 -3
  258. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  259. package/dist/Typeahead/Typeahead.js +65 -29
  260. package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
  261. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  262. package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
  263. package/dist/VisuallyHidden/index.d.ts +11 -0
  264. package/dist/VisuallyHidden/index.d.ts.map +1 -0
  265. package/dist/VisuallyHidden/index.js +13 -0
  266. package/dist/astryx.css +18 -0
  267. package/dist/astryx.umd.js +55 -55
  268. package/dist/astryx.umd.js.map +4 -4
  269. package/dist/hooks/focusableSelector.d.ts +22 -0
  270. package/dist/hooks/focusableSelector.d.ts.map +1 -0
  271. package/dist/hooks/focusableSelector.js +24 -0
  272. package/dist/hooks/index.d.ts +12 -2
  273. package/dist/hooks/index.d.ts.map +1 -1
  274. package/dist/hooks/index.js +7 -2
  275. package/dist/hooks/useAnnounce.d.ts +40 -0
  276. package/dist/hooks/useAnnounce.d.ts.map +1 -0
  277. package/dist/hooks/useAnnounce.js +142 -0
  278. package/dist/hooks/useClickableContainer.d.ts +16 -1
  279. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  280. package/dist/hooks/useClickableContainer.js +16 -4
  281. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  282. package/dist/hooks/useEntryAnimation.js +4 -4
  283. package/dist/hooks/useFocusTrap.d.ts +18 -0
  284. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  285. package/dist/hooks/useFocusTrap.js +87 -3
  286. package/dist/hooks/useGridFocus.d.ts +88 -1
  287. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  288. package/dist/hooks/useGridFocus.js +261 -62
  289. package/dist/hooks/useKeyboardHint.d.ts +82 -0
  290. package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
  291. package/dist/hooks/useKeyboardHint.js +220 -0
  292. package/dist/hooks/useListFocus.d.ts +78 -7
  293. package/dist/hooks/useListFocus.d.ts.map +1 -1
  294. package/dist/hooks/useListFocus.js +305 -52
  295. package/dist/hooks/useLongPress.d.ts +21 -0
  296. package/dist/hooks/useLongPress.d.ts.map +1 -0
  297. package/dist/hooks/useLongPress.js +89 -0
  298. package/dist/hooks/useTreeFocus.d.ts +157 -0
  299. package/dist/hooks/useTreeFocus.d.ts.map +1 -0
  300. package/dist/hooks/useTreeFocus.js +337 -0
  301. package/dist/hooks/useTypeahead.d.ts +63 -0
  302. package/dist/hooks/useTypeahead.d.ts.map +1 -0
  303. package/dist/hooks/useTypeahead.js +117 -0
  304. package/dist/index.d.ts +1 -0
  305. package/dist/index.d.ts.map +1 -1
  306. package/dist/index.js +1 -0
  307. package/dist/utils/dateTypes.d.ts +12 -0
  308. package/dist/utils/dateTypes.d.ts.map +1 -1
  309. package/dist/utils/dateTypes.js +41 -1
  310. package/dist/utils/index.d.ts +4 -0
  311. package/dist/utils/index.d.ts.map +1 -1
  312. package/dist/utils/index.js +3 -1
  313. package/dist/utils/inputAria.d.ts +29 -0
  314. package/dist/utils/inputAria.d.ts.map +1 -0
  315. package/dist/utils/inputAria.js +30 -0
  316. package/dist/utils/timeParser.d.ts.map +1 -1
  317. package/dist/utils/timeParser.js +5 -2
  318. package/docs.mjs +2 -2
  319. package/package.json +12 -8
  320. package/src/Avatar/Avatar.test.tsx +37 -0
  321. package/src/Avatar/Avatar.tsx +10 -5
  322. package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
  323. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
  324. package/src/Button/Button.tsx +3 -16
  325. package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
  326. package/src/ButtonGroup/ButtonGroup.tsx +0 -1
  327. package/src/Calendar/Calendar.doc.mjs +4 -4
  328. package/src/Calendar/Calendar.test.tsx +168 -30
  329. package/src/Calendar/Calendar.tsx +121 -66
  330. package/src/Calendar/hooks/index.ts +0 -6
  331. package/src/Calendar/index.ts +1 -3
  332. package/src/Calendar/styles.ts +4 -9
  333. package/src/Carousel/Carousel.doc.mjs +7 -1
  334. package/src/Carousel/Carousel.test.tsx +20 -0
  335. package/src/Carousel/Carousel.tsx +16 -1
  336. package/src/Center/Center.doc.mjs +12 -2
  337. package/src/Center/Center.tsx +28 -3
  338. package/src/Chat/ChatComposerInput.test.tsx +26 -7
  339. package/src/Chat/ChatComposerInput.tsx +0 -1
  340. package/src/Chat/ChatDictationButton.doc.mjs +1 -1
  341. package/src/Chat/ChatMessageList.doc.mjs +8 -0
  342. package/src/Chat/ChatMessageList.test.tsx +18 -0
  343. package/src/Chat/ChatMessageList.tsx +16 -0
  344. package/src/Chat/useTriggerMenu.tsx +22 -4
  345. package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
  346. package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
  347. package/src/CheckboxInput/CheckboxInput.tsx +78 -6
  348. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  349. package/src/CheckboxList/CheckboxList.test.tsx +192 -26
  350. package/src/CheckboxList/CheckboxList.tsx +56 -3
  351. package/src/CheckboxList/CheckboxListContext.tsx +7 -0
  352. package/src/CheckboxList/CheckboxListItem.tsx +0 -3
  353. package/src/Citation/Citation.doc.mjs +10 -0
  354. package/src/Citation/Citation.test.tsx +121 -0
  355. package/src/Citation/Citation.tsx +15 -3
  356. package/src/CodeBlock/CodeBlock.test.tsx +115 -0
  357. package/src/CodeBlock/CodeBlock.tsx +63 -33
  358. package/src/CommandPalette/CommandPalette.test.tsx +47 -1
  359. package/src/CommandPalette/CommandPalette.tsx +9 -21
  360. package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
  361. package/src/ContextMenu/ContextMenu.test.tsx +155 -1
  362. package/src/ContextMenu/ContextMenu.tsx +113 -16
  363. package/src/DateInput/DateInput.doc.mjs +286 -56
  364. package/src/DateInput/DateInput.test.tsx +248 -2
  365. package/src/DateInput/DateInput.tsx +196 -100
  366. package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
  367. package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
  368. package/src/DateRangeInput/DateRangeInput.tsx +63 -5
  369. package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
  370. package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
  371. package/src/DateTimeInput/DateTimeInput.tsx +125 -17
  372. package/src/Dialog/Dialog.doc.mjs +2 -2
  373. package/src/Dialog/Dialog.tsx +12 -0
  374. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
  375. package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
  376. package/src/DropdownMenu/DropdownMenu.tsx +52 -18
  377. package/src/Field/Field.doc.mjs +59 -18
  378. package/src/Field/Field.test.tsx +32 -0
  379. package/src/Field/Field.tsx +23 -5
  380. package/src/Field/FieldLabel.tsx +55 -17
  381. package/src/FileInput/FileInput.doc.mjs +17 -0
  382. package/src/FileInput/FileInput.test.tsx +283 -49
  383. package/src/FileInput/FileInput.tsx +76 -4
  384. package/src/Grid/Grid.doc.mjs +17 -4
  385. package/src/Grid/Grid.test.tsx +43 -30
  386. package/src/Grid/Grid.tsx +38 -3
  387. package/src/Icon/Icon.doc.mjs +3 -3
  388. package/src/InputGroup/InputGroup.doc.mjs +111 -23
  389. package/src/InputGroup/InputGroup.test.tsx +257 -60
  390. package/src/InputGroup/InputGroup.tsx +23 -5
  391. package/src/InputGroup/InputGroupContext.ts +9 -4
  392. package/src/InputGroup/groupStyles.ts +7 -1
  393. package/src/InputGroup/index.ts +1 -0
  394. package/src/Kbd/Kbd.test.tsx +22 -4
  395. package/src/Kbd/Kbd.tsx +35 -3
  396. package/src/Layer/layerAnimations.stylex.ts +20 -4
  397. package/src/Layer/useLayer.doc.mjs +5 -0
  398. package/src/Layer/useLayer.test.tsx +111 -0
  399. package/src/Layer/useLayer.tsx +90 -9
  400. package/src/Layout/Layout.tsx +8 -16
  401. package/src/Layout/LayoutFooter.doc.mjs +4 -4
  402. package/src/Layout/LayoutFooter.tsx +3 -3
  403. package/src/Layout/LayoutHeader.doc.mjs +4 -4
  404. package/src/Layout/LayoutHeader.tsx +3 -3
  405. package/src/Layout/LayoutPanel.tsx +3 -3
  406. package/src/Layout/padding.stylex.ts +52 -0
  407. package/src/Lightbox/Lightbox.doc.mjs +9 -0
  408. package/src/Lightbox/Lightbox.test.tsx +53 -5
  409. package/src/Lightbox/Lightbox.tsx +9 -4
  410. package/src/Link/Link.doc.mjs +14 -0
  411. package/src/Link/Link.test.tsx +50 -0
  412. package/src/Link/Link.tsx +39 -4
  413. package/src/Markdown/Markdown.test.tsx +11 -0
  414. package/src/Markdown/Markdown.tsx +7 -0
  415. package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
  416. package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
  417. package/src/MoreMenu/MoreMenu.tsx +0 -9
  418. package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
  419. package/src/MultiSelector/MultiSelector.test.tsx +375 -8
  420. package/src/MultiSelector/MultiSelector.tsx +194 -48
  421. package/src/MultiSelector/hooks.ts +27 -0
  422. package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
  423. package/src/NavMenu/NavHeadingMenu.tsx +50 -3
  424. package/src/NumberInput/NumberInput.doc.mjs +17 -0
  425. package/src/NumberInput/NumberInput.test.tsx +242 -40
  426. package/src/NumberInput/NumberInput.tsx +89 -11
  427. package/src/Pagination/Pagination.doc.mjs +3 -3
  428. package/src/Pagination/Pagination.test.tsx +117 -5
  429. package/src/Pagination/Pagination.tsx +21 -1
  430. package/src/Popover/Popover.doc.mjs +26 -0
  431. package/src/Popover/Popover.test.tsx +121 -0
  432. package/src/Popover/Popover.tsx +23 -2
  433. package/src/Popover/usePopover.doc.mjs +9 -3
  434. package/src/Popover/usePopover.tsx +70 -12
  435. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  436. package/src/PowerSearch/PowerSearch.test.tsx +97 -1
  437. package/src/PowerSearch/PowerSearch.tsx +16 -3
  438. package/src/ProgressBar/ProgressBar.test.tsx +34 -26
  439. package/src/ProgressBar/ProgressBar.tsx +3 -4
  440. package/src/RadioList/RadioList.doc.mjs +9 -0
  441. package/src/RadioList/RadioList.test.tsx +252 -16
  442. package/src/RadioList/RadioList.tsx +166 -10
  443. package/src/RadioList/RadioListItem.tsx +14 -2
  444. package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
  445. package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
  446. package/src/SegmentedControl/SegmentedControl.tsx +102 -54
  447. package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
  448. package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
  449. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  450. package/src/Selector/Selector.doc.mjs +26 -0
  451. package/src/Selector/Selector.test.tsx +387 -15
  452. package/src/Selector/Selector.tsx +175 -67
  453. package/src/Selector/hooks.ts +23 -0
  454. package/src/SideNav/SideNav.test.tsx +48 -0
  455. package/src/SideNav/SideNavHeading.tsx +8 -2
  456. package/src/SideNav/SideNavItem.tsx +2 -4
  457. package/src/Skeleton/Skeleton.test.tsx +25 -0
  458. package/src/Skeleton/Skeleton.tsx +11 -1
  459. package/src/Slider/Slider.doc.mjs +15 -0
  460. package/src/Slider/Slider.test.tsx +155 -2
  461. package/src/Slider/Slider.tsx +53 -5
  462. package/src/Spinner/Spinner.tsx +7 -1
  463. package/src/Stack/Stack.doc.mjs +77 -0
  464. package/src/Stack/Stack.test.tsx +55 -5
  465. package/src/Stack/Stack.tsx +73 -24
  466. package/src/Stack/StackItem.test.tsx +23 -0
  467. package/src/Stack/StackItem.tsx +20 -23
  468. package/src/Switch/Switch.doc.mjs +15 -0
  469. package/src/Switch/Switch.test.tsx +162 -27
  470. package/src/Switch/Switch.tsx +61 -21
  471. package/src/TabList/Tab.tsx +6 -0
  472. package/src/TabList/TabList.doc.mjs +6 -0
  473. package/src/TabList/TabList.test.tsx +251 -0
  474. package/src/TabList/TabList.tsx +103 -12
  475. package/src/TabList/TabListContext.ts +7 -3
  476. package/src/TabList/TabMenu.tsx +6 -3
  477. package/src/Table/BaseTable.tsx +21 -17
  478. package/src/Table/Table.doc.mjs +2 -0
  479. package/src/Table/Table.test.tsx +10 -0
  480. package/src/Table/Table.tsx +8 -0
  481. package/src/Table/TableCell.tsx +25 -23
  482. package/src/Table/TableHeaderCell.tsx +28 -29
  483. package/src/Table/index.ts +3 -2
  484. package/src/Table/plugins/pagination/paginateData.ts +10 -3
  485. package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
  486. package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
  487. package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
  488. package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
  489. package/src/Table/tableContextMenu.test.tsx +157 -0
  490. package/src/Table/tableContextMenu.tsx +142 -0
  491. package/src/Table/types.ts +74 -0
  492. package/src/Table/useTableColumnResize.doc.mjs +3 -3
  493. package/src/Table/useTableStickyColumns.doc.mjs +2 -2
  494. package/src/Text/Text.doc.mjs +18 -18
  495. package/src/TextArea/TextArea.doc.mjs +17 -0
  496. package/src/TextArea/TextArea.test.tsx +167 -35
  497. package/src/TextArea/TextArea.tsx +62 -15
  498. package/src/TextInput/TextInput.doc.mjs +17 -0
  499. package/src/TextInput/TextInput.test.tsx +168 -34
  500. package/src/TextInput/TextInput.tsx +77 -9
  501. package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
  502. package/src/TimeInput/TimeInput.doc.mjs +340 -60
  503. package/src/TimeInput/TimeInput.test.tsx +255 -7
  504. package/src/TimeInput/TimeInput.tsx +176 -70
  505. package/src/Timestamp/Timestamp.doc.mjs +19 -19
  506. package/src/Toast/Toast.tsx +14 -0
  507. package/src/Toast/ToastViewport.test.tsx +214 -0
  508. package/src/Toast/ToastViewport.tsx +167 -21
  509. package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
  510. package/src/Tokenizer/Tokenizer.test.tsx +105 -1
  511. package/src/Tokenizer/Tokenizer.tsx +37 -1
  512. package/src/Toolbar/Toolbar.test.tsx +109 -6
  513. package/src/Toolbar/Toolbar.tsx +59 -5
  514. package/src/Tooltip/Tooltip.test.tsx +68 -0
  515. package/src/Tooltip/useTooltip.tsx +55 -8
  516. package/src/TopNav/TopNav.test.tsx +50 -0
  517. package/src/TopNav/TopNavHeading.tsx +17 -1
  518. package/src/TreeList/TreeList.test.tsx +295 -6
  519. package/src/TreeList/TreeList.tsx +73 -1
  520. package/src/TreeList/TreeListItem.tsx +51 -5
  521. package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
  522. package/src/Typeahead/BaseTypeahead.tsx +93 -9
  523. package/src/Typeahead/Typeahead.doc.mjs +141 -30
  524. package/src/Typeahead/Typeahead.test.tsx +331 -1
  525. package/src/Typeahead/Typeahead.tsx +105 -33
  526. package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
  527. package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
  528. package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
  529. package/src/VisuallyHidden/index.ts +15 -0
  530. package/src/docs-script.test.mjs +50 -0
  531. package/src/hooks/focusableSelector.ts +25 -0
  532. package/src/hooks/index.ts +28 -2
  533. package/src/hooks/useAnnounce.test.tsx +101 -0
  534. package/src/hooks/useAnnounce.ts +168 -0
  535. package/src/hooks/useClickableContainer.ts +16 -4
  536. package/src/hooks/useEntryAnimation.ts +16 -4
  537. package/src/hooks/useFocusTrap.test.tsx +132 -0
  538. package/src/hooks/useFocusTrap.ts +98 -4
  539. package/src/hooks/useGridFocus.doc.mjs +40 -2
  540. package/src/hooks/useGridFocus.test.tsx +132 -0
  541. package/src/hooks/useGridFocus.ts +343 -67
  542. package/src/hooks/useKeyboardHint.doc.mjs +101 -0
  543. package/src/hooks/useKeyboardHint.test.tsx +70 -0
  544. package/src/hooks/useKeyboardHint.tsx +332 -0
  545. package/src/hooks/useListFocus.doc.mjs +50 -10
  546. package/src/hooks/useListFocus.test.tsx +375 -0
  547. package/src/hooks/useListFocus.ts +395 -57
  548. package/src/hooks/useLongPress.test.tsx +134 -0
  549. package/src/hooks/useLongPress.ts +119 -0
  550. package/src/hooks/useTreeFocus.doc.mjs +140 -0
  551. package/src/hooks/useTreeFocus.test.tsx +214 -0
  552. package/src/hooks/useTreeFocus.ts +559 -0
  553. package/src/hooks/useTypeahead.test.tsx +109 -0
  554. package/src/hooks/useTypeahead.ts +183 -0
  555. package/src/index.ts +1 -0
  556. package/src/theme/derivedVarRegistry.test.ts +10 -5
  557. package/src/utils/dateTypes.test.ts +40 -0
  558. package/src/utils/dateTypes.ts +40 -0
  559. package/src/utils/index.ts +9 -0
  560. package/src/utils/inputAria.test.ts +39 -0
  561. package/src/utils/inputAria.ts +58 -0
  562. package/src/utils/timeParser.test.ts +10 -0
  563. package/src/utils/timeParser.ts +5 -2
  564. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
  565. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
  566. package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
  567. package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
@@ -9,12 +9,39 @@
9
9
  * SYNC: When SegmentedControl components change, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen} from '@testing-library/react';
12
+ import {describe, it, expect, vi, beforeEach} from 'vitest';
13
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {SegmentedControl} from './SegmentedControl';
16
16
  import {SegmentedControlItem} from './SegmentedControlItem';
17
17
 
18
+ // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
19
+ // reflects its open state via a `popover-open` attribute the tests can assert.
20
+ beforeEach(() => {
21
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
22
+ this.setAttribute('popover-open', '');
23
+ const event = new Event('toggle', {bubbles: false});
24
+ Object.defineProperty(event, 'newState', {value: 'open'});
25
+ this.dispatchEvent(event);
26
+ });
27
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
28
+ this.removeAttribute('popover-open');
29
+ const event = new Event('toggle', {bubbles: false});
30
+ Object.defineProperty(event, 'newState', {value: 'closed'});
31
+ this.dispatchEvent(event);
32
+ });
33
+ const originalMatches = HTMLElement.prototype.matches;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ (HTMLElement.prototype as any).matches = function (
36
+ selector: string,
37
+ ): boolean {
38
+ if (selector === ':popover-open') {
39
+ return this.hasAttribute('popover-open');
40
+ }
41
+ return originalMatches.call(this, selector);
42
+ };
43
+ });
44
+
18
45
  describe('SegmentedControl', () => {
19
46
  it('renders a radiogroup with radio buttons', () => {
20
47
  render(
@@ -56,10 +83,7 @@ describe('SegmentedControl', () => {
56
83
  const handleChange = vi.fn();
57
84
 
58
85
  render(
59
- <SegmentedControl
60
- value="grid"
61
- onChange={handleChange}
62
- label="View mode">
86
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
63
87
  <SegmentedControlItem value="grid" label="Grid" />
64
88
  <SegmentedControlItem value="list" label="List" />
65
89
  </SegmentedControl>,
@@ -74,10 +98,7 @@ describe('SegmentedControl', () => {
74
98
  const handleChange = vi.fn();
75
99
 
76
100
  render(
77
- <SegmentedControl
78
- value="grid"
79
- onChange={handleChange}
80
- label="View mode">
101
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
81
102
  <SegmentedControlItem value="grid" label="Grid" />
82
103
  <SegmentedControlItem value="list" label="List" />
83
104
  </SegmentedControl>,
@@ -196,6 +217,45 @@ describe('SegmentedControl', () => {
196
217
  '-1',
197
218
  );
198
219
  });
220
+
221
+ it('keeps a tab stop when the value matches no item (tab-stop repair)', () => {
222
+ render(
223
+ <SegmentedControl
224
+ value="nonexistent"
225
+ onChange={() => {}}
226
+ label="View mode">
227
+ <SegmentedControlItem value="grid" label="Grid" />
228
+ <SegmentedControlItem value="list" label="List" />
229
+ <SegmentedControlItem value="table" label="Table" />
230
+ </SegmentedControl>,
231
+ );
232
+
233
+ // No item is selected, but the group must remain Tab-reachable: the first
234
+ // enabled radio is promoted to tabIndex=0.
235
+ expect(screen.getByRole('radio', {name: 'Grid'})).toHaveAttribute(
236
+ 'tabIndex',
237
+ '0',
238
+ );
239
+ });
240
+
241
+ it('promotes the first enabled item when the value matches no item and the first is disabled', () => {
242
+ render(
243
+ <SegmentedControl
244
+ value="nonexistent"
245
+ onChange={() => {}}
246
+ label="View mode">
247
+ <SegmentedControlItem value="grid" label="Grid" isDisabled />
248
+ <SegmentedControlItem value="list" label="List" />
249
+ <SegmentedControlItem value="table" label="Table" />
250
+ </SegmentedControl>,
251
+ );
252
+
253
+ // The disabled first item is skipped; the first ENABLED radio is tabbable.
254
+ expect(screen.getByRole('radio', {name: 'List'})).toHaveAttribute(
255
+ 'tabIndex',
256
+ '0',
257
+ );
258
+ });
199
259
  });
200
260
 
201
261
  describe('SegmentedControl keyboard navigation', () => {
@@ -204,10 +264,7 @@ describe('SegmentedControl keyboard navigation', () => {
204
264
  const handleChange = vi.fn();
205
265
 
206
266
  render(
207
- <SegmentedControl
208
- value="grid"
209
- onChange={handleChange}
210
- label="View mode">
267
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
211
268
  <SegmentedControlItem value="grid" label="Grid" />
212
269
  <SegmentedControlItem value="list" label="List" />
213
270
  <SegmentedControlItem value="table" label="Table" />
@@ -226,10 +283,7 @@ describe('SegmentedControl keyboard navigation', () => {
226
283
  const handleChange = vi.fn();
227
284
 
228
285
  render(
229
- <SegmentedControl
230
- value="list"
231
- onChange={handleChange}
232
- label="View mode">
286
+ <SegmentedControl value="list" onChange={handleChange} label="View mode">
233
287
  <SegmentedControlItem value="grid" label="Grid" />
234
288
  <SegmentedControlItem value="list" label="List" />
235
289
  <SegmentedControlItem value="table" label="Table" />
@@ -248,10 +302,7 @@ describe('SegmentedControl keyboard navigation', () => {
248
302
  const handleChange = vi.fn();
249
303
 
250
304
  render(
251
- <SegmentedControl
252
- value="table"
253
- onChange={handleChange}
254
- label="View mode">
305
+ <SegmentedControl value="table" onChange={handleChange} label="View mode">
255
306
  <SegmentedControlItem value="grid" label="Grid" />
256
307
  <SegmentedControlItem value="list" label="List" />
257
308
  <SegmentedControlItem value="table" label="Table" />
@@ -270,10 +321,7 @@ describe('SegmentedControl keyboard navigation', () => {
270
321
  const handleChange = vi.fn();
271
322
 
272
323
  render(
273
- <SegmentedControl
274
- value="grid"
275
- onChange={handleChange}
276
- label="View mode">
324
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
277
325
  <SegmentedControlItem value="grid" label="Grid" />
278
326
  <SegmentedControlItem value="list" label="List" />
279
327
  <SegmentedControlItem value="table" label="Table" />
@@ -292,10 +340,7 @@ describe('SegmentedControl keyboard navigation', () => {
292
340
  const handleChange = vi.fn();
293
341
 
294
342
  render(
295
- <SegmentedControl
296
- value="table"
297
- onChange={handleChange}
298
- label="View mode">
343
+ <SegmentedControl value="table" onChange={handleChange} label="View mode">
299
344
  <SegmentedControlItem value="grid" label="Grid" />
300
345
  <SegmentedControlItem value="list" label="List" />
301
346
  <SegmentedControlItem value="table" label="Table" />
@@ -314,10 +359,7 @@ describe('SegmentedControl keyboard navigation', () => {
314
359
  const handleChange = vi.fn();
315
360
 
316
361
  render(
317
- <SegmentedControl
318
- value="grid"
319
- onChange={handleChange}
320
- label="View mode">
362
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
321
363
  <SegmentedControlItem value="grid" label="Grid" />
322
364
  <SegmentedControlItem value="list" label="List" />
323
365
  <SegmentedControlItem value="table" label="Table" />
@@ -351,6 +393,41 @@ describe('SegmentedControl disabled state', () => {
351
393
  );
352
394
  });
353
395
 
396
+ it('removes the tab stop from the selected item when the group is disabled (navigation-13)', () => {
397
+ render(
398
+ <SegmentedControl
399
+ value="grid"
400
+ onChange={() => {}}
401
+ label="View mode"
402
+ isDisabled>
403
+ <SegmentedControlItem value="grid" label="Grid" />
404
+ <SegmentedControlItem value="list" label="List" />
405
+ </SegmentedControl>,
406
+ );
407
+ // Selected segment must not be a focusable-but-dead tab stop when disabled.
408
+ const selected = screen.getByRole('radio', {name: 'Grid'});
409
+ expect(selected).toHaveAttribute('tabIndex', '-1');
410
+ expect(selected).toHaveAttribute('aria-disabled', 'true');
411
+ // No enabled segment is tabbable either.
412
+ expect(screen.getByRole('radio', {name: 'List'})).toHaveAttribute(
413
+ 'tabIndex',
414
+ '-1',
415
+ );
416
+ });
417
+
418
+ it('removes the tab stop from an individually disabled selected item', () => {
419
+ render(
420
+ <SegmentedControl value="grid" onChange={() => {}} label="View mode">
421
+ <SegmentedControlItem value="grid" label="Grid" isDisabled />
422
+ <SegmentedControlItem value="list" label="List" />
423
+ </SegmentedControl>,
424
+ );
425
+ expect(screen.getByRole('radio', {name: 'Grid'})).toHaveAttribute(
426
+ 'tabIndex',
427
+ '-1',
428
+ );
429
+ });
430
+
354
431
  it('does not call onChange when group is disabled', async () => {
355
432
  const user = userEvent.setup({pointerEventsCheck: 0});
356
433
  const handleChange = vi.fn();
@@ -375,10 +452,7 @@ describe('SegmentedControl disabled state', () => {
375
452
  const handleChange = vi.fn();
376
453
 
377
454
  render(
378
- <SegmentedControl
379
- value="grid"
380
- onChange={handleChange}
381
- label="View mode">
455
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
382
456
  <SegmentedControlItem value="grid" label="Grid" />
383
457
  <SegmentedControlItem value="list" label="List" isDisabled />
384
458
  </SegmentedControl>,
@@ -398,10 +472,7 @@ describe('SegmentedControl disabled state', () => {
398
472
  const handleChange = vi.fn();
399
473
 
400
474
  render(
401
- <SegmentedControl
402
- value="grid"
403
- onChange={handleChange}
404
- label="View mode">
475
+ <SegmentedControl value="grid" onChange={handleChange} label="View mode">
405
476
  <SegmentedControlItem value="grid" label="Grid" />
406
477
  <SegmentedControlItem value="list" label="List" isDisabled />
407
478
  <SegmentedControlItem value="table" label="Table" />
@@ -415,4 +486,105 @@ describe('SegmentedControl disabled state', () => {
415
486
  expect(handleChange).toHaveBeenCalledWith('table');
416
487
  expect(screen.getByRole('radio', {name: 'Table'})).toHaveFocus();
417
488
  });
489
+
490
+ describe('disabledMessage', () => {
491
+ const h = {hidden: true} as const;
492
+
493
+ function renderControl(props?: {onChange?: (v: string) => void}) {
494
+ return render(
495
+ <SegmentedControl
496
+ value="grid"
497
+ onChange={props?.onChange ?? (() => {})}
498
+ label="View mode"
499
+ isDisabled
500
+ disabledMessage="Choose a project to switch views">
501
+ <SegmentedControlItem value="grid" label="Grid" />
502
+ <SegmentedControlItem value="list" label="List" />
503
+ </SegmentedControl>,
504
+ );
505
+ }
506
+
507
+ it('shows the reason tooltip on hover when the control is disabled with a reason', async () => {
508
+ renderControl();
509
+ const tooltip = screen.getByRole('tooltip', h);
510
+ expect(tooltip).toHaveTextContent('Choose a project to switch views');
511
+ const group = screen.getByRole('radiogroup');
512
+ fireEvent.mouseEnter(group);
513
+ await waitFor(() => expect(tooltip).toHaveAttribute('popover-open'));
514
+ fireEvent.mouseLeave(group);
515
+ await waitFor(() => expect(tooltip).not.toHaveAttribute('popover-open'));
516
+ });
517
+
518
+ it('shows the reason tooltip on keyboard focus', async () => {
519
+ const user = userEvent.setup();
520
+ renderControl();
521
+ const tooltip = screen.getByRole('tooltip', h);
522
+ await user.tab();
523
+ await waitFor(() => expect(tooltip).toHaveAttribute('popover-open'));
524
+ });
525
+
526
+ it('does not render a tooltip when not disabled', () => {
527
+ render(
528
+ <SegmentedControl
529
+ value="grid"
530
+ onChange={() => {}}
531
+ label="View mode"
532
+ disabledMessage="Choose a project to switch views">
533
+ <SegmentedControlItem value="grid" label="Grid" />
534
+ </SegmentedControl>,
535
+ );
536
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
537
+ });
538
+
539
+ it('does not render a tooltip when disabled without a reason', () => {
540
+ render(
541
+ <SegmentedControl
542
+ value="grid"
543
+ onChange={() => {}}
544
+ label="View mode"
545
+ isDisabled>
546
+ <SegmentedControlItem value="grid" label="Grid" />
547
+ </SegmentedControl>,
548
+ );
549
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
550
+ });
551
+
552
+ it('keeps the selected segment focusable when a reason is provided', () => {
553
+ renderControl();
554
+ const selected = screen.getByRole('radio', {name: 'Grid', hidden: true});
555
+ expect(selected).toHaveAttribute('aria-disabled', 'true');
556
+ expect(selected).toHaveAttribute('tabindex', '0');
557
+ });
558
+
559
+ it('links the reason tooltip from the group via aria-describedby', () => {
560
+ renderControl();
561
+ const group = screen.getByRole('radiogroup');
562
+ const tooltip = screen.getByRole('tooltip', h);
563
+ expect(group.getAttribute('aria-describedby')).toContain(tooltip.id);
564
+ });
565
+
566
+ it('blocks selection while focusable-disabled', () => {
567
+ const onChange = vi.fn();
568
+ renderControl({onChange});
569
+ const list = screen.getByRole('radio', {name: 'List', hidden: true});
570
+ fireEvent.click(list);
571
+ expect(onChange).not.toHaveBeenCalled();
572
+ });
573
+
574
+ it('drops all segments from the tab order when disabled without a reason', () => {
575
+ render(
576
+ <SegmentedControl
577
+ value="grid"
578
+ onChange={() => {}}
579
+ label="View mode"
580
+ isDisabled>
581
+ <SegmentedControlItem value="grid" label="Grid" />
582
+ <SegmentedControlItem value="list" label="List" />
583
+ </SegmentedControl>,
584
+ );
585
+ for (const radio of screen.getAllByRole('radio', h)) {
586
+ expect(radio).toHaveAttribute('tabindex', '-1');
587
+ }
588
+ });
589
+ });
418
590
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file SegmentedControl.tsx
7
- * @input Uses React, StyleX, SegmentedControlContext
7
+ * @input Uses React, StyleX, SegmentedControlContext, useListFocus, useKeyboardHint
8
8
  * @output Exports SegmentedControl component and SegmentedControlProps type
9
9
  * @position Container wrapper; provides context to SegmentedControlItem children
10
10
  *
@@ -15,10 +15,13 @@
15
15
  * - /packages/cli/templates/blocks/components/SegmentedControl/ (showcase blocks)
16
16
  */
17
17
 
18
- import React, {useMemo, useRef, useCallback, type ReactNode} from 'react';
18
+ import React, {useMemo, useCallback, type ReactNode} from 'react';
19
19
  import * as stylex from '@stylexjs/stylex';
20
20
  import {colorVars, spacingVars, radiusVars} from '../theme/tokens.stylex';
21
21
  import {SegmentedControlContext} from './SegmentedControlContext';
22
+ import {useListFocus} from '../hooks/useListFocus';
23
+ import {useKeyboardHint} from '../hooks/useKeyboardHint';
24
+ import {useTooltip} from '../Tooltip';
22
25
  import type {
23
26
  SegmentedControlSize,
24
27
  SegmentedControlLayout,
@@ -62,6 +65,17 @@ export interface SegmentedControlProps extends Omit<
62
65
  * @default false
63
66
  */
64
67
  isDisabled?: boolean;
68
+ /**
69
+ * Explains why the control is disabled. Applies to the whole-group disabled
70
+ * state (`isDisabled`), not individual segments. When set together with
71
+ * `isDisabled`, the control shows a tooltip with this text on hover and
72
+ * keyboard focus, and stays focusable (via `aria-disabled`) so the reason is
73
+ * discoverable by keyboard and assistive technology. Selection stays blocked.
74
+ *
75
+ * Use this instead of wrapping a disabled control in `Tooltip` — disabled
76
+ * controls don't emit the pointer events an external tooltip needs.
77
+ */
78
+ disabledMessage?: string;
65
79
  /**
66
80
  * SegmentedControlItem children.
67
81
  */
@@ -89,6 +103,12 @@ const styles = stylex.create({
89
103
  opacity: 0.5,
90
104
  pointerEvents: 'none',
91
105
  },
106
+ // Disabled *with* a disabledMessage: keep the dimmed look but leave pointer
107
+ // events on so the group can receive hover and surface the reason tooltip.
108
+ // Selection is still blocked by the isDisabled guards.
109
+ disabledWithMessage: {
110
+ opacity: 0.5,
111
+ },
92
112
  });
93
113
 
94
114
  const sizeStyles = stylex.create({
@@ -127,98 +147,126 @@ export function SegmentedControl({
127
147
  size: sizeProp,
128
148
  layout = 'hug',
129
149
  isDisabled = false,
150
+ disabledMessage,
130
151
  children,
131
152
  xstyle,
132
153
  className,
133
154
  style,
134
155
  }: SegmentedControlProps) {
135
156
  const size = useSize(sizeProp, 'md');
136
- const containerRef = useRef<HTMLDivElement>(null);
137
157
 
138
- const handleKeyDown = useCallback(
139
- (e: React.KeyboardEvent) => {
140
- if (isDisabled) {
141
- return;
142
- }
158
+ // Disabled-reason tooltip. Applies to the whole-group disabled state. Disabled
159
+ // controls swallow pointer events, so the tooltip listeners attach to the
160
+ // radiogroup container (and the container keeps pointer events on in this
161
+ // mode) while the radios stay perceivable via aria-disabled. Selection is
162
+ // blocked by the isDisabled guards in the click/focus handlers.
163
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
164
+ const disabledMessageTooltip = useTooltip({
165
+ placement: 'above',
166
+ // The radiogroup container is not naturally focusable; focusin bubbles up
167
+ // from the radios, so always attach focus listeners.
168
+ focusTrigger: 'always',
169
+ isEnabled: showsDisabledMessage,
170
+ });
143
171
 
144
- const container = containerRef.current;
145
- if (!container) {
146
- return;
147
- }
172
+ // Roving tabindex + arrow/Home/End navigation is owned by the shared
173
+ // useListFocus primitive: it stamps a single tab stop (tabIndex 0/-1) across
174
+ // the radios, skips disabled ones, wraps at the ends, handles Home/End, and
175
+ // repairs the tab stop on mount and whenever items mount/disable — replacing
176
+ // the component's former inline keyboard handler and tab-stop repair effect.
177
+ const {listRef, handleKeyDown, handleFocus} = useListFocus<HTMLDivElement>({
178
+ itemSelector: '[role="radio"]:not([aria-disabled="true"])',
179
+ hasRovingTabIndex: true,
180
+ wrap: true,
181
+ orientation: 'horizontal',
182
+ });
148
183
 
149
- const items = Array.from(
150
- container.querySelectorAll<HTMLButtonElement>(
151
- '[role="radio"]:not([aria-disabled="true"])',
152
- ),
153
- );
154
- if (items.length === 0) {
184
+ const hint = useKeyboardHint({
185
+ orientation: 'horizontal',
186
+ isEnabled: !isDisabled,
187
+ });
188
+
189
+ const handleContainerKeyDown = useCallback(
190
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
191
+ hint.onKeyDown(e);
192
+ handleKeyDown(e);
193
+ },
194
+ [hint, handleKeyDown],
195
+ );
196
+
197
+ // Selection-follows-focus (APG radiogroup): useListFocus only *moves* focus,
198
+ // so whenever it lands focus on a new radio (arrow/Home/End, or a click) we
199
+ // select that radio's value. Reading the focused element's data-value here
200
+ // keeps selection in lockstep with focus without duplicating the navigation
201
+ // logic. Disabled radios are ignored (they should never become the value),
202
+ // and the already-selected value is skipped so an initial Tab-in (or a click
203
+ // on the current segment) is a no-op, matching click behavior.
204
+ const handleContainerFocus = useCallback(
205
+ (e: React.FocusEvent) => {
206
+ hint.onFocus(e);
207
+ handleFocus(e);
208
+ if (isDisabled) {
155
209
  return;
156
210
  }
157
-
158
- const currentIndex = items.findIndex(
159
- item => item === document.activeElement,
211
+ const focused = (e.target as HTMLElement | null)?.closest<HTMLElement>(
212
+ '[role="radio"][data-value]',
160
213
  );
161
- let nextIndex: number;
162
-
163
- switch (e.key) {
164
- case 'ArrowRight':
165
- nextIndex =
166
- currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;
167
- break;
168
- case 'ArrowLeft':
169
- nextIndex =
170
- currentIndex === -1
171
- ? items.length - 1
172
- : (currentIndex - 1 + items.length) % items.length;
173
- break;
174
- case 'Home':
175
- nextIndex = 0;
176
- break;
177
- case 'End':
178
- nextIndex = items.length - 1;
179
- break;
180
- default:
181
- return;
214
+ if (!focused || focused.getAttribute('aria-disabled') === 'true') {
215
+ return;
182
216
  }
183
-
184
- e.preventDefault();
185
- const nextItem = items[nextIndex];
186
- nextItem.focus();
187
- const nextValue = nextItem.dataset.value;
188
- if (nextValue != null) {
217
+ const nextValue = focused.dataset.value;
218
+ if (nextValue != null && nextValue !== value) {
189
219
  onChange(nextValue);
190
220
  }
191
221
  },
192
- [isDisabled, onChange],
222
+ [hint, handleFocus, isDisabled, onChange, value],
193
223
  );
194
224
 
195
225
  const contextValue = useMemo(
196
- () => ({value, onChange, size, layout, isDisabled}),
197
- [value, onChange, size, layout, isDisabled],
226
+ () => ({
227
+ value,
228
+ onChange,
229
+ size,
230
+ layout,
231
+ isDisabled,
232
+ hasDisabledMessage: showsDisabledMessage,
233
+ }),
234
+ [value, onChange, size, layout, isDisabled, showsDisabledMessage],
198
235
  );
199
236
 
200
237
  return (
201
238
  <SegmentedControlContext value={contextValue}>
202
239
  <div
203
- ref={mergeRefs(ref, containerRef)}
240
+ ref={mergeRefs(ref, listRef, disabledMessageTooltip.ref)}
204
241
  role="radiogroup"
205
242
  aria-label={label}
206
243
  aria-disabled={isDisabled || undefined}
207
- onKeyDown={handleKeyDown}
244
+ aria-describedby={
245
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : undefined
246
+ }
247
+ onKeyDown={handleContainerKeyDown}
248
+ onFocus={handleContainerFocus}
249
+ onBlur={hint.onBlur}
208
250
  {...mergeProps(
209
251
  themeProps('segmented-control', {size}),
210
252
  stylex.props(
211
253
  styles.container,
212
254
  sizeStyles[size],
213
255
  layout === 'fill' && styles.fill,
214
- isDisabled && styles.disabled,
256
+ isDisabled &&
257
+ (showsDisabledMessage
258
+ ? styles.disabledWithMessage
259
+ : styles.disabled),
215
260
  xstyle,
216
261
  ),
217
262
  className,
218
263
  style,
219
264
  )}>
220
265
  {children}
266
+ {hint.hintElement}
221
267
  </div>
268
+ {showsDisabledMessage &&
269
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
222
270
  </SegmentedControlContext>
223
271
  );
224
272
  }
@@ -22,6 +22,13 @@ export interface SegmentedControlContextValue {
22
22
  size: SegmentedControlSize;
23
23
  layout: SegmentedControlLayout;
24
24
  isDisabled: boolean;
25
+ /**
26
+ * True when the whole control is disabled *and* a `disabledMessage` is set.
27
+ * In that mode the selected segment stays focusable (via `aria-disabled`
28
+ * rather than being dropped from the tab order) so the group's disabled-reason
29
+ * tooltip is keyboard-discoverable; selection is still blocked.
30
+ */
31
+ hasDisabledMessage?: boolean;
25
32
  }
26
33
 
27
34
  export const SegmentedControlContext =
@@ -173,6 +173,12 @@ export function SegmentedControlItem({
173
173
 
174
174
  const isSelected = ctx.value === value;
175
175
  const isItemDisabled = isDisabled || ctx.isDisabled;
176
+ // When the whole group is disabled with a disabledMessage, keep the selected
177
+ // segment focusable so the group's reason tooltip is keyboard-discoverable.
178
+ // Per-item disabling (`isDisabled` on the item) always drops out of the tab
179
+ // order. Activation stays blocked by the isItemDisabled guard in handleClick.
180
+ const keepsSelectedFocusable =
181
+ isSelected && (ctx.hasDisabledMessage ?? false) && !isDisabled;
176
182
  const size: SegmentedControlSize = ctx.size;
177
183
  const isFill = ctx.layout === 'fill';
178
184
 
@@ -195,7 +201,14 @@ export function SegmentedControlItem({
195
201
  aria-disabled={isItemDisabled || undefined}
196
202
  aria-label={isLabelHidden ? label : undefined}
197
203
  data-value={value}
198
- tabIndex={isSelected ? 0 : -1}
204
+ // Disabled items (including when the whole group is disabled) are not tab
205
+ // stops — otherwise the selected segment stays keyboard-focusable but is
206
+ // silently dead (arrows and activation are no-ops) (navigation-13). The
207
+ // exception is a whole-group disabledMessage, where the selected segment
208
+ // stays focusable so the reason tooltip is keyboard-discoverable.
209
+ tabIndex={
210
+ (isSelected && !isItemDisabled) || keepsSelectedFocusable ? 0 : -1
211
+ }
199
212
  onClick={handleClick}
200
213
  {...mergeProps(
201
214
  themeProps('segmented-control-item', {
@@ -46,7 +46,7 @@ export const docs = {
46
46
  props: {gap: 1},
47
47
  children: [
48
48
  {__element: 'XDSHeading', props: {level: 3}, children: 'Pro plan'},
49
- {__element: 'XDSText', props: {type: 'body'}, children: '$29/month unlimited projects and priority support.'},
49
+ {__element: 'XDSText', props: {type: 'body'}, children: '$29/month, unlimited projects and priority support.'},
50
50
  ],
51
51
  },
52
52
  },