@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,10 +9,11 @@
9
9
  * SYNC: When TimeInput.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen, fireEvent} 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 {TimeInput} from './TimeInput';
16
+ import {InputGroup, InputGroupText} from '../InputGroup';
16
17
  import type {ISOTimeString} from '../utils';
17
18
 
18
19
  describe('TimeInput', () => {
@@ -23,11 +24,7 @@ describe('TimeInput', () => {
23
24
 
24
25
  it('renders with placeholder', () => {
25
26
  render(
26
- <TimeInput
27
- label="Time"
28
- onChange={() => {}}
29
- placeholder="Pick a time"
30
- />,
27
+ <TimeInput label="Time" onChange={() => {}} placeholder="Pick a time" />,
31
28
  );
32
29
  expect(screen.getByPlaceholderText('Pick a time')).toBeInTheDocument();
33
30
  });
@@ -210,4 +207,255 @@ describe('TimeInput', () => {
210
207
  fireEvent.click(wrapper);
211
208
  expect(input).toHaveFocus();
212
209
  });
210
+ describe('InputGroup integration', () => {
211
+ it('labels grouped TimeInput from the group and inner input labels', () => {
212
+ render(
213
+ <InputGroup label="Schedule" description="Use local time">
214
+ <InputGroupText>Starts</InputGroupText>
215
+ <TimeInput
216
+ label="Start time"
217
+ isLabelHidden
218
+ value={'09:00' as ISOTimeString}
219
+ onChange={() => {}}
220
+ />
221
+ </InputGroup>,
222
+ );
223
+
224
+ const group = screen.getByRole('group', {name: 'Schedule'});
225
+ const groupLabelID = group.getAttribute('aria-labelledby');
226
+ const input = screen.getByRole('textbox', {
227
+ name: 'Schedule Start time',
228
+ });
229
+ const labelledByIDs =
230
+ input.getAttribute('aria-labelledby')?.split(' ') ?? [];
231
+
232
+ expect(labelledByIDs).toHaveLength(2);
233
+ expect(labelledByIDs[0]).toBe(groupLabelID);
234
+ expect(document.getElementById(labelledByIDs[1])).toHaveTextContent(
235
+ 'Start time',
236
+ );
237
+ expect(input).not.toHaveAttribute('aria-label');
238
+ expect(input).toHaveAttribute(
239
+ 'aria-describedby',
240
+ group.getAttribute('aria-describedby'),
241
+ );
242
+ });
243
+
244
+ it('includes group and local described-by content when grouped', () => {
245
+ render(
246
+ <InputGroup
247
+ label="Schedule"
248
+ description="Use local time"
249
+ status={{type: 'warning', message: 'Schedule is unusual'}}>
250
+ <InputGroupText>Starts</InputGroupText>
251
+ <TimeInput
252
+ label="Start time"
253
+ isLabelHidden
254
+ value={'09:00' as ISOTimeString}
255
+ onChange={() => {}}
256
+ description="Business hours only"
257
+ status={{type: 'error', message: 'Start time is required'}}
258
+ isDisabled
259
+ disabledMessage="Time edits are locked"
260
+ />
261
+ </InputGroup>,
262
+ );
263
+
264
+ const input = screen.getByRole('textbox', {
265
+ name: 'Schedule Start time',
266
+ });
267
+ const describedByIDs =
268
+ input.getAttribute('aria-describedby')?.split(' ') ?? [];
269
+ const describedText = describedByIDs
270
+ .map(id => document.getElementById(id)?.textContent)
271
+ .join(' ');
272
+
273
+ expect(describedText).toContain('Use local time');
274
+ expect(describedText).toContain('Schedule is unusual');
275
+ expect(describedText).toContain('Business hours only');
276
+ expect(describedText).toContain('Start time is required');
277
+ expect(describedText).toContain('Time edits are locked');
278
+ });
279
+
280
+ it('does not render duplicate Field label chrome when grouped', () => {
281
+ render(
282
+ <InputGroup label="Schedule">
283
+ <InputGroupText>Starts</InputGroupText>
284
+ <TimeInput
285
+ label="Start time"
286
+ isLabelHidden
287
+ value={'09:00' as ISOTimeString}
288
+ onChange={() => {}}
289
+ />
290
+ </InputGroup>,
291
+ );
292
+
293
+ expect(screen.getByText('Schedule')).toBeInTheDocument();
294
+ expect(screen.getByText('Start time')).toBeInTheDocument();
295
+ expect(screen.getByText('Start time').tagName).toBe('SPAN');
296
+ expect(document.querySelector('label')).toBeNull();
297
+ });
298
+
299
+ it('suppresses the local status icon when grouped', () => {
300
+ const {container} = render(
301
+ <InputGroup label="Schedule">
302
+ <InputGroupText>Starts</InputGroupText>
303
+ <TimeInput
304
+ label="Start time"
305
+ isLabelHidden
306
+ value={'09:00' as ISOTimeString}
307
+ onChange={() => {}}
308
+ status={{type: 'error'}}
309
+ />
310
+ </InputGroup>,
311
+ );
312
+
313
+ // The clock icon remains, but the trailing status icon is suppressed in
314
+ // grouped mode so the shared InputGroup border/status treatment is not
315
+ // duplicated.
316
+ expect(container.querySelectorAll('svg')).toHaveLength(1);
317
+ });
318
+ });
319
+
320
+ describe('disabledMessage', () => {
321
+ // jsdom does not implement the Popover API used by the tooltip, so mock
322
+ // showPopover/hidePopover to toggle a `popover-open` attribute the tests
323
+ // can assert on.
324
+ beforeEach(() => {
325
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
326
+ this.setAttribute('popover-open', '');
327
+ });
328
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
329
+ this.removeAttribute('popover-open');
330
+ });
331
+ });
332
+
333
+ // jsdom popover content is in the DOM but not "visible" in the
334
+ // accessibility tree; use hidden: true to find it.
335
+ const h = {hidden: true} as const;
336
+
337
+ it('shows the reason tooltip on hover when disabled with a reason', async () => {
338
+ render(
339
+ <TimeInput
340
+ label="Time"
341
+ isDisabled
342
+ disabledMessage="You need the Editor role"
343
+ />,
344
+ );
345
+
346
+ const container = screen.getByLabelText('Time')
347
+ .parentElement as HTMLElement;
348
+ const tooltip = screen.getByRole('tooltip', h);
349
+ expect(tooltip).toHaveTextContent('You need the Editor role');
350
+
351
+ fireEvent.mouseEnter(container);
352
+ await waitFor(() => {
353
+ expect(tooltip).toHaveAttribute('popover-open');
354
+ });
355
+
356
+ fireEvent.mouseLeave(container);
357
+ await waitFor(() => {
358
+ expect(tooltip).not.toHaveAttribute('popover-open');
359
+ });
360
+ });
361
+
362
+ it('shows the reason tooltip on keyboard focus', async () => {
363
+ const user = userEvent.setup();
364
+ render(
365
+ <TimeInput
366
+ label="Time"
367
+ isDisabled
368
+ disabledMessage="You need the Editor role"
369
+ />,
370
+ );
371
+
372
+ const tooltip = screen.getByRole('tooltip', h);
373
+ await user.tab();
374
+ expect(screen.getByLabelText('Time')).toHaveFocus();
375
+ await waitFor(() => {
376
+ expect(tooltip).toHaveAttribute('popover-open');
377
+ });
378
+ });
379
+
380
+ it('does not render a tooltip when not disabled', () => {
381
+ render(
382
+ <TimeInput label="Time" disabledMessage="You need the Editor role" />,
383
+ );
384
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
385
+ });
386
+
387
+ it('does not render a tooltip when disabled without a reason', () => {
388
+ render(<TimeInput label="Time" isDisabled />);
389
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
390
+ });
391
+
392
+ it('keeps the input focusable via aria-disabled when a reason is provided', () => {
393
+ render(
394
+ <TimeInput
395
+ label="Time"
396
+ isDisabled
397
+ disabledMessage="You need the Editor role"
398
+ />,
399
+ );
400
+ const input = screen.getByLabelText('Time');
401
+ expect(input).not.toBeDisabled();
402
+ expect(input).toHaveAttribute('aria-disabled', 'true');
403
+ expect(input).toHaveAttribute('readonly');
404
+ });
405
+
406
+ it('links the reason tooltip from the input via aria-describedby', () => {
407
+ render(
408
+ <TimeInput
409
+ label="Time"
410
+ isDisabled
411
+ disabledMessage="You need the Editor role"
412
+ />,
413
+ );
414
+ const input = screen.getByLabelText('Time');
415
+ const tooltip = screen.getByRole('tooltip', h);
416
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
417
+ });
418
+
419
+ it('blocks typing and arrow-key changes while focusable-disabled', async () => {
420
+ const user = userEvent.setup();
421
+ const onChange = vi.fn();
422
+ render(
423
+ <TimeInput
424
+ label="Time"
425
+ onChange={onChange}
426
+ isDisabled
427
+ disabledMessage="You need the Editor role"
428
+ />,
429
+ );
430
+
431
+ const input = screen.getByLabelText('Time');
432
+ await user.type(input, '2:30 PM');
433
+ expect(input).toHaveValue('');
434
+ input.focus();
435
+ fireEvent.keyDown(input, {key: 'ArrowUp'});
436
+ expect(onChange).not.toHaveBeenCalled();
437
+ });
438
+
439
+ it('remains natively disabled when disabled without a reason', () => {
440
+ render(<TimeInput label="Time" isDisabled />);
441
+ const input = screen.getByLabelText('Time');
442
+ expect(input).toBeDisabled();
443
+ expect(input).not.toHaveAttribute('aria-disabled');
444
+ });
445
+
446
+ it('does not swap in the format-hint placeholder on focus while disabled', () => {
447
+ render(
448
+ <TimeInput
449
+ label="Time"
450
+ isDisabled
451
+ disabledMessage="You need the Editor role"
452
+ placeholder="Select a time"
453
+ />,
454
+ );
455
+ const input = screen.getByLabelText('Time');
456
+ input.focus();
457
+ fireEvent.focus(input);
458
+ expect(input).toHaveAttribute('placeholder', 'Select a time');
459
+ });
460
+ });
213
461
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file TimeInput.tsx
7
- * @input Uses React, useId, useState, useEffect, useCallback, useRef, Field, Icon
7
+ * @input Uses React, useId, useState, useCallback, useRef, Field, Icon, InputGroupContext
8
8
  * @output Exports TimeInput component, TimeInputProps
9
9
  * @position Core implementation; consumed by index.ts, tested by TimeInput.test.tsx
10
10
  *
@@ -48,6 +48,7 @@ import {
48
48
  } from '../Field';
49
49
  import {Icon} from '../Icon';
50
50
  import {Spinner} from '../Spinner';
51
+ import {VisuallyHidden} from '../VisuallyHidden';
51
52
  import {
52
53
  type ISOTimeString,
53
54
  parseTimeInput,
@@ -58,11 +59,15 @@ import {
58
59
  isTimeInRange,
59
60
  mergeProps,
60
61
  mergeRefs,
62
+ getInputARIA,
61
63
  } from '../utils';
62
64
  import type {BaseProps} from '../BaseProps';
63
65
  import type {SizeValue} from '../utils/types';
64
66
  import {useSize} from '../SizeContext/SizeContext';
65
67
  import {useInputContainer} from '../hooks/useInputContainer';
68
+ import {useInputGroup} from '../InputGroup/InputGroupContext';
69
+ import {groupStyles} from '../InputGroup/groupStyles';
70
+ import {useTooltip} from '../Tooltip';
66
71
  import {themeProps} from '../utils/themeProps';
67
72
 
68
73
  const styles = stylex.create({
@@ -183,6 +188,29 @@ export interface TimeInputProps extends Omit<
183
188
  */
184
189
  isDisabled?: boolean;
185
190
 
191
+ /**
192
+ * Explains why the input is disabled. When set together with
193
+ * `isDisabled`, the input shows a tooltip with this text on hover and
194
+ * keyboard focus, and the field stays focusable (via `aria-disabled`)
195
+ * so the reason is discoverable by keyboard and assistive technology.
196
+ * Typing and arrow-key adjustment stay blocked.
197
+ *
198
+ * Use this instead of wrapping a disabled input in `Tooltip` — disabled
199
+ * controls don't emit the pointer events an external tooltip needs.
200
+ *
201
+ * @example
202
+ * ```
203
+ * <TimeInput
204
+ * label="Start time"
205
+ * value={time}
206
+ * onChange={setTime}
207
+ * isDisabled
208
+ * disabledMessage="You need the Editor role to change this"
209
+ * />
210
+ * ```
211
+ */
212
+ disabledMessage?: string;
213
+
186
214
  /**
187
215
  * The selected time in ISO format (HH:MM or HH:MM:SS).
188
216
  */
@@ -301,6 +329,7 @@ export function TimeInput({
301
329
  isOptional = false,
302
330
  isRequired = false,
303
331
  isDisabled = false,
332
+ disabledMessage,
304
333
  value,
305
334
  onChange,
306
335
  changeAction,
@@ -325,15 +354,30 @@ export function TimeInput({
325
354
  const size = useSize(sizeProp, 'md');
326
355
 
327
356
  const id = useId();
357
+ const inputLabelID = useId();
328
358
  const descriptionID = useId();
329
359
  const statusMessageID = useId();
330
360
  const inputRef = useRef<HTMLInputElement | null>(null);
331
361
  const containerRef = useRef<HTMLDivElement | null>(null);
362
+ const inputGroup = useInputGroup();
332
363
 
333
364
  const [, startTransition] = useTransition();
334
365
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
335
366
  const isBusy = isLoading || optimisticValue !== value;
336
367
 
368
+ // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
369
+ // tooltip listeners attach to the input container (which already exists) and
370
+ // the input stays perceivable via aria-disabled instead of the disabled
371
+ // attribute. Typing is blocked with readOnly and value mutation guards.
372
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
373
+ const disabledMessageTooltip = useTooltip({
374
+ placement: 'above',
375
+ // The container div is not naturally focusable; focusin bubbles up from
376
+ // the input, so always attach focus listeners.
377
+ focusTrigger: 'always',
378
+ isEnabled: showsDisabledMessage,
379
+ });
380
+
337
381
  // Status icon mapping
338
382
  const statusIconMap: Record<InputStatusType, IconName> = {
339
383
  warning: 'warning',
@@ -350,13 +394,15 @@ export function TimeInput({
350
394
  success: 'success',
351
395
  };
352
396
 
353
- const ariaDescribedBy =
397
+ const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
398
+ inputLabelID,
354
399
  [
355
400
  description ? descriptionID : null,
356
401
  status?.message ? statusMessageID : null,
357
- ]
358
- .filter(Boolean)
359
- .join(' ') || undefined;
402
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
403
+ ],
404
+ inputGroup,
405
+ );
360
406
 
361
407
  // Pending input while user is typing (null = show formatted value)
362
408
  const [pendingInput, setPendingInput] = useState<string | null>(null);
@@ -415,6 +461,11 @@ export function TimeInput({
415
461
  // Handle input text change - update immediately if valid
416
462
  const handleInputChange = useCallback(
417
463
  (e: React.ChangeEvent<HTMLInputElement>) => {
464
+ // With a disabledMessage the input drops `disabled` for focusability, so
465
+ // guard value mutation explicitly (readOnly also blocks typing).
466
+ if (isDisabled) {
467
+ return;
468
+ }
418
469
  const newValue = e.target.value;
419
470
  setPendingInput(newValue);
420
471
 
@@ -424,13 +475,19 @@ export function TimeInput({
424
475
  fireChange(parsed);
425
476
  }
426
477
  },
427
- [hasSeconds, min, max, value, fireChange],
478
+ [hasSeconds, min, max, value, fireChange, isDisabled],
428
479
  );
429
480
 
430
481
  // Handle focus
431
482
  const handleFocus = useCallback(() => {
483
+ // A disabled input stays focusable (via aria-disabled) so its reason is
484
+ // discoverable, but it must not present editing affordances — keep the
485
+ // static placeholder rather than swapping in the format hint.
486
+ if (isDisabled) {
487
+ return;
488
+ }
432
489
  setIsFocused(true);
433
- }, []);
490
+ }, [isDisabled]);
434
491
 
435
492
  // Handle blur - validate and clear pending input
436
493
  const handleBlur = useCallback(
@@ -466,6 +523,11 @@ export function TimeInput({
466
523
  // Handle keyboard navigation on input
467
524
  const handleInputKeyDown = useCallback(
468
525
  (e: KeyboardEvent<HTMLInputElement>) => {
526
+ // Arrow-key adjustment mutates the value; block it while showing a
527
+ // disabled reason (the input keeps focusability via aria-disabled).
528
+ if (isDisabled) {
529
+ return;
530
+ }
469
531
  if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
470
532
  e.preventDefault();
471
533
 
@@ -492,7 +554,7 @@ export function TimeInput({
492
554
  }
493
555
  }
494
556
  },
495
- [value, hasSeconds, increment, min, max, fireChange],
557
+ [value, hasSeconds, increment, min, max, fireChange, isDisabled],
496
558
  );
497
559
 
498
560
  // Handle clear button click
@@ -509,6 +571,109 @@ export function TimeInput({
509
571
  disabled: isDisabled,
510
572
  });
511
573
 
574
+ const inputWrapper = (
575
+ <div
576
+ ref={el => {
577
+ containerRef.current = el;
578
+ // Anchor + hover/focus listeners for the disabled-message tooltip.
579
+ // Handlers are gated internally by isEnabled, so attaching
580
+ // unconditionally is safe.
581
+ disabledMessageTooltip.ref(el);
582
+ }}
583
+ onClick={handleWrapperClick}
584
+ onMouseUp={handleWrapperMouseUp}
585
+ {...mergeProps(
586
+ themeProps('time-input', {size, status: status?.type ?? null}),
587
+ stylex.props(
588
+ inputWrapperStyles.base,
589
+ sizeStyles[size],
590
+ isDisabled && inputWrapperStyles.disabled,
591
+ status && inputStatusBorderStyles[status.type],
592
+ status && inputStatusHoverShadowStyles[status.type],
593
+ status && inputStatusFocusWithinStyles[status.type],
594
+ inputGroup && groupStyles.inGroup,
595
+ xstyle,
596
+ ),
597
+ className,
598
+ style,
599
+ )}>
600
+ <div {...stylex.props(styles.icon)}>
601
+ <Icon icon="clock" size="sm" color="secondary" />
602
+ </div>
603
+ {inputGroup && <VisuallyHidden id={inputLabelID}>{label}</VisuallyHidden>}
604
+ {inputGroup && description && (
605
+ <VisuallyHidden as="div" id={descriptionID}>
606
+ {description}
607
+ </VisuallyHidden>
608
+ )}
609
+ {inputGroup && status?.message && (
610
+ <VisuallyHidden
611
+ as="div"
612
+ id={statusMessageID}
613
+ role={status.type === 'error' ? 'alert' : 'status'}
614
+ aria-live={status.type === 'error' ? 'assertive' : 'polite'}>
615
+ {status.message}
616
+ </VisuallyHidden>
617
+ )}
618
+ <input
619
+ ref={mergeRefs(ref, inputRef)}
620
+ id={id}
621
+ type="text"
622
+ value={displayValue}
623
+ onChange={handleInputChange}
624
+ onFocus={handleFocus}
625
+ onBlur={handleBlur}
626
+ onKeyDown={handleInputKeyDown}
627
+ placeholder={displayPlaceholder}
628
+ // With a disabledMessage the input keeps focusability via
629
+ // aria-disabled so the reason is focus-discoverable; typing and
630
+ // arrow-key adjustment are blocked with readOnly and the guards.
631
+ disabled={isDisabled && !showsDisabledMessage}
632
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
633
+ readOnly={showsDisabledMessage || undefined}
634
+ autoFocus={hasAutoFocus}
635
+ data-autofocus={hasAutoFocus || undefined}
636
+ aria-describedby={ariaDescribedBy}
637
+ aria-required={isRequired === true ? 'true' : undefined}
638
+ aria-invalid={status?.type === 'error' ? 'true' : undefined}
639
+ aria-busy={isBusy || undefined}
640
+ aria-labelledby={ariaLabelledBy}
641
+ {...stylex.props(
642
+ styles.input,
643
+ isDisabled && styles.inputDisabled,
644
+ !isInputValid && styles.inputInvalid,
645
+ )}
646
+ />
647
+ {isBusy && <Spinner size="sm" />}
648
+ {hasClear && value && !isDisabled && (
649
+ <button
650
+ type="button"
651
+ onClick={handleClear}
652
+ aria-label={`Clear ${label}`}
653
+ {...stylex.props(styles.clearButton)}>
654
+ <Icon icon="close" size="sm" color="secondary" />
655
+ </button>
656
+ )}
657
+ {status && !inputGroup && (
658
+ <Icon
659
+ icon={statusIconMap[status.type]}
660
+ size="md"
661
+ color={statusIconColorMap[status.type]}
662
+ />
663
+ )}
664
+ </div>
665
+ );
666
+
667
+ if (inputGroup) {
668
+ return (
669
+ <>
670
+ {inputWrapper}
671
+ {showsDisabledMessage &&
672
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
673
+ </>
674
+ );
675
+ }
676
+
512
677
  return (
513
678
  <Field
514
679
  label={label}
@@ -530,68 +695,9 @@ export function TimeInput({
530
695
  }
531
696
  labelTooltip={labelTooltip}
532
697
  width={width}>
533
- <div
534
- ref={containerRef}
535
- onClick={handleWrapperClick}
536
- onMouseUp={handleWrapperMouseUp}
537
- {...mergeProps(
538
- themeProps('time-input', {size, status: status?.type ?? null}),
539
- stylex.props(
540
- inputWrapperStyles.base,
541
- sizeStyles[size],
542
- isDisabled && inputWrapperStyles.disabled,
543
- status && inputStatusBorderStyles[status.type],
544
- status && inputStatusHoverShadowStyles[status.type],
545
- status && inputStatusFocusWithinStyles[status.type],
546
- xstyle,
547
- ),
548
- className,
549
- style,
550
- )}>
551
- <div {...stylex.props(styles.icon)}>
552
- <Icon icon="clock" size="sm" color="secondary" />
553
- </div>
554
- <input
555
- ref={mergeRefs(ref, inputRef)}
556
- id={id}
557
- type="text"
558
- value={displayValue}
559
- onChange={handleInputChange}
560
- onFocus={handleFocus}
561
- onBlur={handleBlur}
562
- onKeyDown={handleInputKeyDown}
563
- placeholder={displayPlaceholder}
564
- disabled={isDisabled}
565
- autoFocus={hasAutoFocus}
566
- data-autofocus={hasAutoFocus || undefined}
567
- aria-describedby={ariaDescribedBy}
568
- aria-required={isRequired === true ? 'true' : undefined}
569
- aria-invalid={status?.type === 'error' ? 'true' : undefined}
570
- aria-busy={isBusy || undefined}
571
- {...stylex.props(
572
- styles.input,
573
- isDisabled && styles.inputDisabled,
574
- !isInputValid && styles.inputInvalid,
575
- )}
576
- />
577
- {isBusy && <Spinner size="sm" />}
578
- {hasClear && value && !isDisabled && (
579
- <button
580
- type="button"
581
- onClick={handleClear}
582
- aria-label={`Clear ${label}`}
583
- {...stylex.props(styles.clearButton)}>
584
- <Icon icon="close" size="sm" color="secondary" />
585
- </button>
586
- )}
587
- {status && (
588
- <Icon
589
- icon={statusIconMap[status.type]}
590
- size="md"
591
- color={statusIconColorMap[status.type]}
592
- />
593
- )}
594
- </div>
698
+ {inputWrapper}
699
+ {showsDisabledMessage &&
700
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
595
701
  </Field>
596
702
  );
597
703
  }