@astryxdesign/core 0.1.9 → 0.2.0-canary.136a00f

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 (560) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/Avatar/Avatar.d.ts +42 -3
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +190 -59
  5. package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
  6. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  7. package/dist/AvatarGroup/AvatarGroup.d.ts +7 -1
  8. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  9. package/dist/AvatarGroup/AvatarGroup.js +58 -6
  10. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  11. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  12. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
  13. package/dist/Banner/Banner.js +2 -2
  14. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  15. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
  16. package/dist/Breadcrumbs/index.d.ts +1 -0
  17. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  18. package/dist/Breadcrumbs/index.js +2 -1
  19. package/dist/Button/Button.d.ts +1 -1
  20. package/dist/Button/Button.d.ts.map +1 -1
  21. package/dist/Button/Button.js +6 -4
  22. package/dist/Calendar/Calendar.d.ts.map +1 -1
  23. package/dist/Calendar/Calendar.js +50 -9
  24. package/dist/Calendar/dayCellUtils.d.ts +4 -4
  25. package/dist/Calendar/dayCellUtils.js +2 -2
  26. package/dist/Calendar/styles.d.ts +30 -31
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +30 -31
  29. package/dist/Card/Card.js +1 -1
  30. package/dist/Carousel/Carousel.d.ts.map +1 -1
  31. package/dist/Carousel/Carousel.js +22 -12
  32. package/dist/Chat/ChatComposerInput.js +1 -1
  33. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  34. package/dist/Chat/ChatDictationButton.js +3 -1
  35. package/dist/Chat/ChatLayout.js +4 -4
  36. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  37. package/dist/Chat/ChatMessageBubble.js +8 -8
  38. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  39. package/dist/Chat/ChatToolCalls.js +10 -3
  40. package/dist/Chat/useTriggerMenu.js +2 -2
  41. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  42. package/dist/CheckboxInput/CheckboxInput.js +6 -6
  43. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  44. package/dist/CheckboxList/CheckboxListItem.js +19 -4
  45. package/dist/Citation/Citation.d.ts +22 -1
  46. package/dist/Citation/Citation.d.ts.map +1 -1
  47. package/dist/Citation/Citation.js +20 -6
  48. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  49. package/dist/CodeBlock/CodeBlock.js +16 -10
  50. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  51. package/dist/Collapsible/Collapsible.js +3 -1
  52. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  53. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  54. package/dist/CommandPalette/CommandPalette.js +42 -3
  55. package/dist/CommandPalette/CommandPaletteItem.js +1 -1
  56. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  57. package/dist/ContextMenu/ContextMenu.js +23 -5
  58. package/dist/ContextMenu/index.d.ts +1 -0
  59. package/dist/ContextMenu/index.d.ts.map +1 -1
  60. package/dist/ContextMenu/index.js +3 -1
  61. package/dist/DateInput/DateInput.d.ts +10 -2
  62. package/dist/DateInput/DateInput.d.ts.map +1 -1
  63. package/dist/DateInput/DateInput.js +33 -19
  64. package/dist/DateRangeInput/DateRangeInput.d.ts +10 -2
  65. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  66. package/dist/DateRangeInput/DateRangeInput.js +23 -20
  67. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  68. package/dist/DateTimeInput/DateTimeInput.js +23 -18
  69. package/dist/Divider/Divider.d.ts +2 -2
  70. package/dist/Divider/Divider.d.ts.map +1 -1
  71. package/dist/Divider/Divider.js +13 -0
  72. package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
  73. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  74. package/dist/DropdownMenu/DropdownMenu.js +22 -5
  75. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  76. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
  77. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenuItem.js +5 -2
  79. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +8 -11
  80. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  81. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +3 -1
  82. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  83. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuRadioItem.js +5 -2
  85. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
  86. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
  87. package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
  88. package/dist/DropdownMenu/index.d.ts +10 -0
  89. package/dist/DropdownMenu/index.d.ts.map +1 -1
  90. package/dist/DropdownMenu/index.js +13 -0
  91. package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
  92. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  93. package/dist/DropdownMenu/menuItemHover.js +33 -0
  94. package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
  95. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
  96. package/dist/DropdownMenu/menuItemRoles.js +10 -1
  97. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  98. package/dist/DropdownMenu/renderDropdownItems.js +21 -6
  99. package/dist/Field/Field.d.ts +3 -1
  100. package/dist/Field/Field.d.ts.map +1 -1
  101. package/dist/Field/Field.js +4 -1
  102. package/dist/Field/FieldLabel.d.ts +2 -1
  103. package/dist/Field/FieldLabel.d.ts.map +1 -1
  104. package/dist/Field/FieldLabel.js +9 -5
  105. package/dist/Field/inputStyles.stylex.d.ts +1 -0
  106. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  107. package/dist/Field/inputStyles.stylex.js +1 -0
  108. package/dist/FieldStatus/FieldStatus.d.ts +10 -0
  109. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  110. package/dist/FieldStatus/FieldStatus.js +39 -3
  111. package/dist/FileInput/FileInput.d.ts +10 -2
  112. package/dist/FileInput/FileInput.d.ts.map +1 -1
  113. package/dist/FileInput/FileInput.js +93 -65
  114. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  115. package/dist/HoverCard/useHoverCard.js +10 -4
  116. package/dist/Icon/Icon.d.ts +15 -1
  117. package/dist/Icon/Icon.d.ts.map +1 -1
  118. package/dist/Icon/Icon.js +19 -3
  119. package/dist/Item/Item.d.ts +16 -3
  120. package/dist/Item/Item.d.ts.map +1 -1
  121. package/dist/Item/Item.js +31 -6
  122. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  123. package/dist/Layer/layerAnimations.stylex.js +10 -2
  124. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  125. package/dist/Lightbox/Lightbox.js +19 -13
  126. package/dist/List/List.d.ts.map +1 -1
  127. package/dist/List/List.js +6 -3
  128. package/dist/List/ListItem.d.ts +11 -1
  129. package/dist/List/ListItem.d.ts.map +1 -1
  130. package/dist/List/ListItem.js +2 -0
  131. package/dist/Markdown/Markdown.d.ts.map +1 -1
  132. package/dist/Markdown/Markdown.js +7 -0
  133. package/dist/MetadataList/MetadataList.d.ts +1 -1
  134. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  135. package/dist/MetadataList/MetadataList.js +4 -2
  136. package/dist/MultiSelector/MultiSelector.d.ts +9 -1
  137. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  138. package/dist/MultiSelector/MultiSelector.js +80 -42
  139. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  140. package/dist/NumberInput/NumberInput.d.ts +10 -2
  141. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  142. package/dist/NumberInput/NumberInput.js +22 -17
  143. package/dist/Outline/Outline.js +2 -2
  144. package/dist/Pagination/Pagination.d.ts.map +1 -1
  145. package/dist/Pagination/Pagination.js +11 -3
  146. package/dist/PowerSearch/PowerSearch.d.ts +9 -2
  147. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  148. package/dist/PowerSearch/PowerSearch.js +2 -0
  149. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  150. package/dist/ProgressBar/ProgressBar.js +6 -1
  151. package/dist/RadioList/RadioList.d.ts +2 -2
  152. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  153. package/dist/RadioList/RadioListItem.js +5 -5
  154. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  155. package/dist/Resizable/ResizeHandle.js +49 -19
  156. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  157. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  158. package/dist/SegmentedControl/SegmentedControlItem.js +5 -3
  159. package/dist/Selector/Selector.d.ts +8 -0
  160. package/dist/Selector/Selector.d.ts.map +1 -1
  161. package/dist/Selector/Selector.js +66 -17
  162. package/dist/SideNav/SideNav.d.ts.map +1 -1
  163. package/dist/SideNav/SideNav.js +5 -5
  164. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  165. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  166. package/dist/SideNav/SideNavItem.d.ts +1 -1
  167. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  168. package/dist/SideNav/SideNavItem.js +12 -10
  169. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  170. package/dist/Skeleton/Skeleton.js +2 -2
  171. package/dist/Slider/Slider.d.ts.map +1 -1
  172. package/dist/Slider/Slider.js +87 -34
  173. package/dist/Spinner/Spinner.d.ts +3 -2
  174. package/dist/Spinner/Spinner.d.ts.map +1 -1
  175. package/dist/Spinner/Spinner.js +11 -2
  176. package/dist/Switch/Switch.d.ts +8 -1
  177. package/dist/Switch/Switch.d.ts.map +1 -1
  178. package/dist/Switch/Switch.js +171 -91
  179. package/dist/TabList/Tab.js +2 -2
  180. package/dist/TabList/TabList.d.ts +2 -11
  181. package/dist/TabList/TabList.d.ts.map +1 -1
  182. package/dist/TabList/TabList.js +5 -9
  183. package/dist/TabList/TabListContext.d.ts +0 -5
  184. package/dist/TabList/TabListContext.d.ts.map +1 -1
  185. package/dist/TabList/TabListContext.js +0 -5
  186. package/dist/TabList/TabMenu.js +1 -1
  187. package/dist/Table/BaseTable.d.ts.map +1 -1
  188. package/dist/Table/BaseTable.js +30 -4
  189. package/dist/Table/TableCell.js +3 -3
  190. package/dist/Table/TableHeaderCell.js +3 -3
  191. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  192. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  193. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  194. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +37 -27
  195. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  196. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  197. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  198. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +15 -7
  199. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  200. package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
  201. package/dist/Table/types.d.ts +32 -0
  202. package/dist/Table/types.d.ts.map +1 -1
  203. package/dist/TextArea/TextArea.d.ts +10 -1
  204. package/dist/TextArea/TextArea.d.ts.map +1 -1
  205. package/dist/TextArea/TextArea.js +18 -19
  206. package/dist/TextInput/TextInput.d.ts +10 -2
  207. package/dist/TextInput/TextInput.d.ts.map +1 -1
  208. package/dist/TextInput/TextInput.js +20 -17
  209. package/dist/Thumbnail/Thumbnail.js +2 -2
  210. package/dist/TimeInput/TimeInput.d.ts +10 -2
  211. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  212. package/dist/TimeInput/TimeInput.js +17 -17
  213. package/dist/Timestamp/Timestamp.d.ts +31 -3
  214. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  215. package/dist/Timestamp/Timestamp.js +47 -76
  216. package/dist/Timestamp/formatInstant.d.ts +41 -0
  217. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  218. package/dist/Timestamp/formatInstant.js +177 -0
  219. package/dist/Timestamp/index.d.ts +1 -0
  220. package/dist/Timestamp/index.d.ts.map +1 -1
  221. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  222. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  223. package/dist/Timestamp/tooltipEntries.js +163 -0
  224. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  225. package/dist/Toast/ToastViewport.js +61 -9
  226. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  227. package/dist/ToggleButton/ToggleButton.js +3 -1
  228. package/dist/Token/Token.d.ts +34 -4
  229. package/dist/Token/Token.d.ts.map +1 -1
  230. package/dist/Token/Token.js +85 -45
  231. package/dist/Token/TokenLink.d.ts +62 -0
  232. package/dist/Token/TokenLink.d.ts.map +1 -0
  233. package/dist/Token/TokenLink.js +76 -0
  234. package/dist/Token/index.d.ts +1 -1
  235. package/dist/Token/index.d.ts.map +1 -1
  236. package/dist/Tokenizer/Tokenizer.d.ts +9 -2
  237. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  238. package/dist/Tokenizer/Tokenizer.js +4 -2
  239. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  240. package/dist/Tooltip/useTooltip.js +17 -1
  241. package/dist/TopNav/TopNavItem.d.ts +3 -1
  242. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  243. package/dist/TopNav/TopNavItem.js +30 -3
  244. package/dist/TreeList/TreeList.d.ts.map +1 -1
  245. package/dist/TreeList/TreeList.js +1 -0
  246. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  247. package/dist/TreeList/TreeListBranches.js +8 -6
  248. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  249. package/dist/TreeList/TreeListItem.js +32 -14
  250. package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
  251. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  252. package/dist/Typeahead/BaseTypeahead.js +22 -11
  253. package/dist/Typeahead/Typeahead.d.ts +9 -2
  254. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  255. package/dist/Typeahead/Typeahead.js +11 -2
  256. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  257. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  258. package/dist/Typeahead/TypeaheadItem.js +20 -10
  259. package/dist/astryx.css +70 -30
  260. package/dist/astryx.umd.js +51 -51
  261. package/dist/astryx.umd.js.map +4 -4
  262. package/dist/hooks/index.d.ts +2 -0
  263. package/dist/hooks/index.d.ts.map +1 -1
  264. package/dist/hooks/index.js +1 -0
  265. package/dist/hooks/isRtlElement.d.ts +18 -0
  266. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  267. package/dist/hooks/isRtlElement.js +27 -0
  268. package/dist/hooks/useAnnounce.d.ts +3 -0
  269. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  270. package/dist/hooks/useAnnounce.js +39 -1
  271. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  272. package/dist/hooks/useFocusTrap.js +52 -11
  273. package/dist/hooks/useGridFocus.d.ts +6 -1
  274. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  275. package/dist/hooks/useGridFocus.js +12 -9
  276. package/dist/hooks/useHotkeys.d.ts +9 -0
  277. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  278. package/dist/hooks/useHotkeys.js +9 -0
  279. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  280. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  281. package/dist/hooks/useInputStatusIcon.js +158 -0
  282. package/dist/hooks/useKeyboardHint.d.ts +1 -1
  283. package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
  284. package/dist/hooks/useKeyboardHint.js +4 -2
  285. package/dist/hooks/useListFocus.d.ts +39 -1
  286. package/dist/hooks/useListFocus.d.ts.map +1 -1
  287. package/dist/hooks/useListFocus.js +54 -9
  288. package/dist/i18n/InternationalizationContext.d.ts +1 -0
  289. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  290. package/dist/i18n/InternationalizationContext.js +3 -0
  291. package/dist/i18n/InternationalizationProvider.d.ts +8 -1
  292. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  293. package/dist/i18n/InternationalizationProvider.js +5 -1
  294. package/dist/i18n/getLocaleDirection.d.ts +18 -0
  295. package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
  296. package/dist/i18n/getLocaleDirection.js +29 -0
  297. package/dist/i18n/index.d.ts +6 -0
  298. package/dist/i18n/index.d.ts.map +1 -1
  299. package/dist/i18n/index.js +7 -1
  300. package/dist/i18n/useDirection.d.ts +2 -0
  301. package/dist/i18n/useDirection.d.ts.map +1 -0
  302. package/dist/i18n/useDirection.js +27 -0
  303. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  304. package/dist/theme/derivedVarRegistry.js +13 -0
  305. package/dist/utils/index.d.ts +1 -0
  306. package/dist/utils/index.d.ts.map +1 -1
  307. package/dist/utils/index.js +2 -1
  308. package/dist/utils/plainDate.d.ts +17 -0
  309. package/dist/utils/plainDate.d.ts.map +1 -1
  310. package/dist/utils/plainDate.js +23 -4
  311. package/dist/utils/rtlStyles.d.ts +24 -0
  312. package/dist/utils/rtlStyles.d.ts.map +1 -0
  313. package/dist/utils/rtlStyles.js +27 -0
  314. package/locales/en.json +112 -0
  315. package/locales/pseudo.json +84 -0
  316. package/package.json +6 -1
  317. package/src/Avatar/Avatar.doc.mjs +50 -3
  318. package/src/Avatar/Avatar.test.tsx +218 -0
  319. package/src/Avatar/Avatar.tsx +285 -79
  320. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  321. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  322. package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
  323. package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
  324. package/src/AvatarGroup/AvatarGroup.tsx +58 -3
  325. package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
  326. package/src/Banner/Banner.tsx +6 -6
  327. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  328. package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
  329. package/src/Breadcrumbs/index.ts +4 -0
  330. package/src/Button/Button.doc.mjs +0 -4
  331. package/src/Button/Button.test.tsx +16 -0
  332. package/src/Button/Button.tsx +6 -4
  333. package/src/Calendar/Calendar.test.tsx +170 -20
  334. package/src/Calendar/Calendar.tsx +73 -16
  335. package/src/Calendar/dayCellUtils.test.ts +18 -18
  336. package/src/Calendar/dayCellUtils.ts +2 -2
  337. package/src/Calendar/styles.ts +33 -39
  338. package/src/Card/Card.tsx +1 -1
  339. package/src/Carousel/Carousel.tsx +25 -6
  340. package/src/Chat/ChatComposer.doc.mjs +2 -2
  341. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  342. package/src/Chat/ChatComposerInput.tsx +2 -2
  343. package/src/Chat/ChatDictationButton.tsx +7 -1
  344. package/src/Chat/ChatLayout.tsx +4 -4
  345. package/src/Chat/ChatMessageBubble.tsx +15 -10
  346. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  347. package/src/Chat/ChatToolCalls.tsx +9 -2
  348. package/src/Chat/useTriggerMenu.tsx +1 -1
  349. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  350. package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
  351. package/src/CheckboxInput/CheckboxInput.tsx +21 -6
  352. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  353. package/src/CheckboxList/CheckboxList.test.tsx +94 -3
  354. package/src/CheckboxList/CheckboxListItem.tsx +17 -10
  355. package/src/Citation/Citation.doc.mjs +4 -3
  356. package/src/Citation/Citation.test.tsx +97 -0
  357. package/src/Citation/Citation.tsx +43 -11
  358. package/src/CodeBlock/CodeBlock.test.tsx +17 -0
  359. package/src/CodeBlock/CodeBlock.tsx +38 -11
  360. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  361. package/src/Collapsible/Collapsible.test.tsx +7 -0
  362. package/src/Collapsible/Collapsible.tsx +16 -6
  363. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  364. package/src/CommandPalette/CommandPalette.tsx +41 -2
  365. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  366. package/src/ContextMenu/ContextMenu.test.tsx +59 -2
  367. package/src/ContextMenu/ContextMenu.tsx +22 -11
  368. package/src/ContextMenu/index.ts +5 -0
  369. package/src/DateInput/DateInput.doc.mjs +25 -0
  370. package/src/DateInput/DateInput.test.tsx +230 -0
  371. package/src/DateInput/DateInput.tsx +51 -29
  372. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  373. package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
  374. package/src/DateRangeInput/DateRangeInput.tsx +42 -30
  375. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  376. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  377. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  378. package/src/Divider/Divider.test.tsx +32 -8
  379. package/src/Divider/Divider.tsx +14 -1
  380. package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
  381. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  382. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
  383. package/src/DropdownMenu/DropdownMenuItem.tsx +9 -3
  384. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
  385. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
  386. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +9 -3
  387. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +20 -2
  388. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  389. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
  390. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
  391. package/src/DropdownMenu/index.ts +16 -0
  392. package/src/DropdownMenu/menuItemHover.ts +39 -0
  393. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  394. package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
  395. package/src/Field/Field.tsx +14 -9
  396. package/src/Field/FieldLabel.test.tsx +25 -0
  397. package/src/Field/FieldLabel.tsx +12 -3
  398. package/src/Field/inputStyles.stylex.ts +2 -0
  399. package/src/FieldStatus/FieldStatus.test.tsx +72 -0
  400. package/src/FieldStatus/FieldStatus.tsx +48 -2
  401. package/src/FileInput/FileInput.doc.mjs +23 -2
  402. package/src/FileInput/FileInput.test.tsx +104 -13
  403. package/src/FileInput/FileInput.tsx +106 -79
  404. package/src/HoverCard/useHoverCard.tsx +15 -3
  405. package/src/Icon/Icon.doc.mjs +13 -0
  406. package/src/Icon/Icon.test.tsx +89 -0
  407. package/src/Icon/Icon.tsx +51 -3
  408. package/src/Item/Item.doc.mjs +5 -0
  409. package/src/Item/Item.test.tsx +88 -0
  410. package/src/Item/Item.tsx +57 -11
  411. package/src/Layer/layerAnimations.stylex.ts +23 -0
  412. package/src/Lightbox/Lightbox.tsx +15 -7
  413. package/src/List/List.test.tsx +17 -29
  414. package/src/List/List.tsx +6 -1
  415. package/src/List/ListItem.doc.mjs +12 -0
  416. package/src/List/ListItem.tsx +13 -0
  417. package/src/Markdown/Markdown.test.tsx +21 -0
  418. package/src/Markdown/Markdown.tsx +4 -0
  419. package/src/MetadataList/MetadataList.test.tsx +24 -0
  420. package/src/MetadataList/MetadataList.tsx +6 -2
  421. package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
  422. package/src/MultiSelector/MultiSelector.test.tsx +402 -0
  423. package/src/MultiSelector/MultiSelector.tsx +120 -59
  424. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  425. package/src/NumberInput/NumberInput.doc.mjs +21 -0
  426. package/src/NumberInput/NumberInput.test.tsx +71 -0
  427. package/src/NumberInput/NumberInput.tsx +35 -26
  428. package/src/Outline/Outline.test.tsx +12 -12
  429. package/src/Outline/Outline.tsx +2 -2
  430. package/src/Pagination/Pagination.tsx +11 -3
  431. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  432. package/src/PowerSearch/PowerSearch.test.tsx +23 -0
  433. package/src/PowerSearch/PowerSearch.tsx +10 -1
  434. package/src/ProgressBar/ProgressBar.test.tsx +34 -0
  435. package/src/ProgressBar/ProgressBar.tsx +16 -1
  436. package/src/RadioList/RadioList.doc.mjs +6 -0
  437. package/src/RadioList/RadioList.test.tsx +26 -4
  438. package/src/RadioList/RadioList.tsx +2 -2
  439. package/src/RadioList/RadioListItem.tsx +12 -5
  440. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  441. package/src/Resizable/ResizeHandle.tsx +70 -20
  442. package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
  443. package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
  444. package/src/Selector/Selector.doc.mjs +15 -0
  445. package/src/Selector/Selector.test.tsx +370 -0
  446. package/src/Selector/Selector.tsx +88 -20
  447. package/src/SideNav/SideNav.tsx +1 -8
  448. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  449. package/src/SideNav/SideNavItem.tsx +5 -3
  450. package/src/Skeleton/Skeleton.test.tsx +16 -0
  451. package/src/Skeleton/Skeleton.tsx +13 -1
  452. package/src/Slider/Slider.doc.mjs +6 -0
  453. package/src/Slider/Slider.test.tsx +240 -12
  454. package/src/Slider/Slider.tsx +96 -23
  455. package/src/Spinner/Spinner.test.tsx +10 -8
  456. package/src/Spinner/Spinner.tsx +15 -5
  457. package/src/Switch/Switch.doc.mjs +142 -42
  458. package/src/Switch/Switch.test.tsx +111 -0
  459. package/src/Switch/Switch.tsx +149 -32
  460. package/src/TabList/Tab.tsx +2 -2
  461. package/src/TabList/TabList.doc.mjs +0 -6
  462. package/src/TabList/TabList.test.tsx +3 -15
  463. package/src/TabList/TabList.tsx +6 -17
  464. package/src/TabList/TabListContext.ts +0 -6
  465. package/src/TabList/TabMenu.tsx +2 -2
  466. package/src/Table/BaseTable.tsx +34 -2
  467. package/src/Table/Table.doc.mjs +11 -0
  468. package/src/Table/Table.test.tsx +85 -0
  469. package/src/Table/TableCell.tsx +3 -3
  470. package/src/Table/TableHeaderCell.tsx +3 -3
  471. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  472. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
  473. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +27 -18
  474. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  475. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  476. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +38 -9
  477. package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
  478. package/src/Table/types.ts +33 -0
  479. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  480. package/src/TextArea/TextArea.doc.mjs +21 -0
  481. package/src/TextArea/TextArea.test.tsx +23 -0
  482. package/src/TextArea/TextArea.tsx +27 -26
  483. package/src/TextInput/TextInput.doc.mjs +21 -0
  484. package/src/TextInput/TextInput.test.tsx +150 -0
  485. package/src/TextInput/TextInput.tsx +28 -25
  486. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  487. package/src/Thumbnail/Thumbnail.tsx +1 -1
  488. package/src/TimeInput/TimeInput.doc.mjs +21 -0
  489. package/src/TimeInput/TimeInput.test.tsx +23 -0
  490. package/src/TimeInput/TimeInput.tsx +24 -25
  491. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  492. package/src/Timestamp/Timestamp.test.tsx +371 -0
  493. package/src/Timestamp/Timestamp.tsx +96 -93
  494. package/src/Timestamp/formatInstant.ts +218 -0
  495. package/src/Timestamp/index.ts +4 -0
  496. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  497. package/src/Timestamp/tooltipEntries.ts +210 -0
  498. package/src/Toast/ToastViewport.test.tsx +218 -8
  499. package/src/Toast/ToastViewport.tsx +71 -12
  500. package/src/ToggleButton/ToggleButton.test.tsx +24 -0
  501. package/src/ToggleButton/ToggleButton.tsx +19 -1
  502. package/src/Token/Token.test.tsx +166 -15
  503. package/src/Token/Token.tsx +104 -47
  504. package/src/Token/TokenLink.tsx +107 -0
  505. package/src/Token/index.ts +1 -1
  506. package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
  507. package/src/Tokenizer/Tokenizer.test.tsx +23 -0
  508. package/src/Tokenizer/Tokenizer.tsx +14 -2
  509. package/src/Tooltip/Tooltip.test.tsx +46 -0
  510. package/src/Tooltip/useTooltip.tsx +20 -0
  511. package/src/TopNav/TopNav.test.tsx +93 -2
  512. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  513. package/src/TopNav/TopNavItem.tsx +35 -6
  514. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  515. package/src/TreeList/TreeList.doc.mjs +6 -0
  516. package/src/TreeList/TreeList.test.tsx +50 -10
  517. package/src/TreeList/TreeList.tsx +5 -0
  518. package/src/TreeList/TreeListBranches.tsx +8 -6
  519. package/src/TreeList/TreeListItem.tsx +33 -11
  520. package/src/Typeahead/BaseTypeahead.tsx +42 -15
  521. package/src/Typeahead/Typeahead.doc.mjs +13 -0
  522. package/src/Typeahead/Typeahead.test.tsx +112 -0
  523. package/src/Typeahead/Typeahead.tsx +17 -1
  524. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  525. package/src/__tests__/forcedColors.ts +60 -0
  526. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  527. package/src/doc-mjs.d.ts +19 -0
  528. package/src/hooks/index.ts +6 -0
  529. package/src/hooks/isRtlElement.ts +27 -0
  530. package/src/hooks/useAnnounce.test.tsx +75 -2
  531. package/src/hooks/useAnnounce.ts +47 -1
  532. package/src/hooks/useFocusTrap.test.tsx +104 -0
  533. package/src/hooks/useFocusTrap.ts +61 -12
  534. package/src/hooks/useGridFocus.doc.mjs +3 -3
  535. package/src/hooks/useGridFocus.test.tsx +41 -0
  536. package/src/hooks/useGridFocus.ts +21 -10
  537. package/src/hooks/useHotkeys.ts +9 -0
  538. package/src/hooks/useInputStatusIcon.test.tsx +162 -0
  539. package/src/hooks/useInputStatusIcon.tsx +224 -0
  540. package/src/hooks/useKeyboardHint.test.tsx +22 -0
  541. package/src/hooks/useKeyboardHint.tsx +6 -2
  542. package/src/hooks/useListFocus.doc.mjs +25 -3
  543. package/src/hooks/useListFocus.test.tsx +153 -1
  544. package/src/hooks/useListFocus.ts +102 -9
  545. package/src/i18n/InternationalizationContext.ts +4 -0
  546. package/src/i18n/InternationalizationProvider.tsx +12 -2
  547. package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
  548. package/src/i18n/__tests__/useDirection.test.tsx +66 -0
  549. package/src/i18n/getLocaleDirection.ts +29 -0
  550. package/src/i18n/index.ts +6 -0
  551. package/src/i18n/useDirection.ts +29 -0
  552. package/src/reset.css +0 -26
  553. package/src/reset.test.ts +60 -0
  554. package/src/theme/derivedVarRegistry.test.ts +1 -0
  555. package/src/theme/derivedVarRegistry.ts +6 -0
  556. package/src/theme/generateThemeRules.test.ts +42 -0
  557. package/src/utils/index.ts +2 -0
  558. package/src/utils/plainDate.test.ts +24 -6
  559. package/src/utils/plainDate.ts +18 -4
  560. package/src/utils/rtlStyles.ts +27 -0
@@ -27,7 +27,6 @@ import {
27
27
  type ReactNode,
28
28
  } from 'react';
29
29
  import * as stylex from '@stylexjs/stylex';
30
- import type {IconName} from '../Icon';
31
30
  import {
32
31
  colorVars,
33
32
  spacingVars,
@@ -40,14 +39,16 @@ import {
40
39
  inputStatusBorderStyles,
41
40
  inputStatusHoverShadowStyles,
42
41
  inputStatusFocusWithinStyles,
42
+ type FieldStatusVariant,
43
43
  } from '../Field';
44
- import {Icon, renderIconSlot, type IconType} from '../Icon';
44
+ import {renderIconSlot, type IconType} from '../Icon';
45
45
  import {Spinner} from '../Spinner';
46
46
  import {useTooltip} from '../Tooltip';
47
47
  import {mergeProps, mergeRefs} from '../utils';
48
48
  import type {BaseProps} from '../BaseProps';
49
49
  import type {SizeValue} from '../utils/types';
50
50
  import {useInputContainer} from '../hooks/useInputContainer';
51
+ import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
51
52
  import {useSize} from '../SizeContext/SizeContext';
52
53
  import {themeProps} from '../utils/themeProps';
53
54
  import {VisuallyHidden} from '../VisuallyHidden';
@@ -217,6 +218,14 @@ export interface TextAreaProps extends Omit<
217
218
  * If message is provided, displays a floating message box below the textarea.
218
219
  */
219
220
  status?: TextAreaStatus;
221
+ /**
222
+ * How the status message is placed relative to the input.
223
+ * - 'attached': message overlaps directly below the input (bordered treatment)
224
+ * - 'detached': message floats below as a separate element with spacing
225
+ * - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
226
+ * @default 'attached'
227
+ */
228
+ statusVariant?: FieldStatusVariant;
220
229
  /**
221
230
  * Width of the field. Numbers are treated as pixels, strings are used as-is
222
231
  * (e.g. `'100%'`). Sizes the whole field (label, control, and status) so they
@@ -298,6 +307,7 @@ export function TextArea({
298
307
  isDisabled = false,
299
308
  disabledMessage,
300
309
  status,
310
+ statusVariant = 'attached',
301
311
  labelTooltip,
302
312
  startIcon,
303
313
  hasSpellCheck = true,
@@ -341,25 +351,19 @@ export function TextArea({
341
351
  isEnabled: showsDisabledMessage,
342
352
  });
343
353
 
344
- const statusIconMap: Record<TextAreaStatusType, IconName> = {
345
- warning: 'warning',
346
- error: 'error',
347
- success: 'success',
348
- };
349
-
350
- const statusIconColorMap: Record<
351
- TextAreaStatusType,
352
- 'warning' | 'error' | 'success'
353
- > = {
354
- warning: 'warning',
355
- error: 'error',
356
- success: 'success',
357
- };
354
+ const {statusIcon, describedBy: statusTooltipDescribedBy} =
355
+ useInputStatusIcon({
356
+ status,
357
+ statusVariant,
358
+ });
358
359
 
359
360
  const ariaDescribedBy =
360
361
  [
361
362
  description ? descriptionID : null,
362
- status?.message ? statusMessageID : null,
363
+ statusVariant !== 'tooltip' && status?.message ? statusMessageID : null,
364
+ // The tooltip variant renders no message box; describe the input by the
365
+ // tooltip's content instead so the status is still announced.
366
+ statusTooltipDescribedBy,
363
367
  maxLength != null ? counterID : null,
364
368
  showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
365
369
  ]
@@ -412,6 +416,7 @@ export function TextArea({
412
416
  }
413
417
  : undefined
414
418
  }
419
+ statusVariant={statusVariant}
415
420
  labelTooltip={labelTooltip}
416
421
  width={width}>
417
422
  <div
@@ -432,7 +437,9 @@ export function TextArea({
432
437
  textareaSizeStyles[size],
433
438
  effectivelyDisabled && inputWrapperStyles.disabled,
434
439
  status && inputStatusBorderStyles[status.type],
435
- status && inputStatusHoverShadowStyles[status.type],
440
+ status &&
441
+ !effectivelyDisabled &&
442
+ inputStatusHoverShadowStyles[status.type],
436
443
  status && inputStatusFocusWithinStyles[status.type],
437
444
  xstyle,
438
445
  ),
@@ -478,14 +485,8 @@ export function TextArea({
478
485
  )}
479
486
  />
480
487
  {isBusy && <Spinner size="sm" />}
481
- {status && (
482
- <span {...stylex.props(styles.statusIcon)}>
483
- <Icon
484
- icon={statusIconMap[status.type]}
485
- size="md"
486
- color={statusIconColorMap[status.type]}
487
- />
488
- </span>
488
+ {statusIcon && (
489
+ <span {...stylex.props(styles.statusIcon)}>{statusIcon}</span>
489
490
  )}
490
491
  </div>
491
492
  {maxLength != null && (
@@ -114,6 +114,13 @@ export const docs = {
114
114
  description:
115
115
  'Validation status: applies a colored border and status icon. If message is provided, displays a floating message below the input. Error type also sets aria-invalid.',
116
116
  },
117
+ {
118
+ name: 'statusVariant',
119
+ type: "'attached' | 'detached' | 'tooltip'",
120
+ description:
121
+ 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing; tooltip hides the message box and surfaces it in a tooltip on the status icon.',
122
+ default: "'attached'",
123
+ },
117
124
  {
118
125
  name: 'hasClear',
119
126
  type: 'boolean',
@@ -133,6 +140,12 @@ export const docs = {
133
140
  description:
134
141
  'The HTML name attribute for the input, useful for form submissions.',
135
142
  },
143
+ {
144
+ name: 'width',
145
+ type: 'SizeValue',
146
+ description:
147
+ 'Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned.',
148
+ },
136
149
  ],
137
150
  theming: {
138
151
  targets: [
@@ -274,6 +287,13 @@ export const docsZh = {
274
287
  description:
275
288
  '验证状态:应用彩色边框和状态图标。如果提供了 message,在输入框下方显示浮动消息。错误类型还会设置 aria-invalid。',
276
289
  },
290
+ {
291
+ name: 'statusVariant',
292
+ type: "'attached' | 'detached' | 'tooltip'",
293
+ description:
294
+ '状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
295
+ default: "'attached'",
296
+ },
277
297
  {
278
298
  name: 'hasClear',
279
299
  type: 'boolean',
@@ -368,6 +388,7 @@ export const docsDense = {
368
388
  labelTooltip: 'Tooltip in info icon at label end.',
369
389
  startIcon: 'SVG icon at input start (e.g. heroicons or lucide).',
370
390
  status: 'Validation status; colored border+icon. Message floats below. Error sets aria-invalid.',
391
+ statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
371
392
  hasClear: 'Shows clear button when input has value. Clears value on click.',
372
393
  hasAutoFocus: 'Auto-focus input on mount.',
373
394
  htmlName: 'HTML name attr for form submissions.',
@@ -13,6 +13,7 @@ import {describe, it, expect, vi, beforeEach} from 'vitest';
13
13
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {TestIcon} from '../__tests__/TestIcon';
16
+ import {InputGroup} from '../InputGroup';
16
17
  import {TextInput} from './TextInput';
17
18
 
18
19
  // Mock showPopover/hidePopover since jsdom does not implement them. Used by the
@@ -229,6 +230,26 @@ describe('TextInput', () => {
229
230
  expect(screen.getByText('Invalid email address')).toBeInTheDocument();
230
231
  });
231
232
 
233
+ it('has no dangling aria-describedby ids inside InputGroup (WCAG 1.3.1)', () => {
234
+ // Inside an InputGroup no Field renders, so the status message element
235
+ // does not exist; aria-describedby must not reference its id.
236
+ render(
237
+ <InputGroup label="Contact">
238
+ <TextInput
239
+ label="Email"
240
+ value=""
241
+ onChange={() => {}}
242
+ status={{type: 'error', message: 'Invalid email address'}}
243
+ />
244
+ </InputGroup>,
245
+ );
246
+ const input = screen.getByRole('textbox');
247
+ const describedBy = input.getAttribute('aria-describedby') ?? '';
248
+ for (const idToken of describedBy.split(/\s+/).filter(Boolean)) {
249
+ expect(document.getElementById(idToken)).not.toBeNull();
250
+ }
251
+ });
252
+
232
253
  it('does not render status message when not provided', () => {
233
254
  render(
234
255
  <TextInput
@@ -659,3 +680,132 @@ describe('TextInput', () => {
659
680
  });
660
681
  });
661
682
  });
683
+
684
+ describe('TextInput statusVariant forwarding', () => {
685
+ it('defaults to attached (status renders with data-variant="attached")', () => {
686
+ const {container} = render(
687
+ <TextInput
688
+ label="Email"
689
+ value=""
690
+ onChange={() => {}}
691
+ status={{type: 'error', message: 'Invalid email'}}
692
+ />,
693
+ );
694
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
695
+ 'data-variant',
696
+ 'attached',
697
+ );
698
+ });
699
+
700
+ it('forwards statusVariant="detached" to the underlying Field status', () => {
701
+ const {container} = render(
702
+ <TextInput
703
+ label="Email"
704
+ value=""
705
+ onChange={() => {}}
706
+ status={{type: 'error', message: 'Invalid email'}}
707
+ statusVariant="detached"
708
+ />,
709
+ );
710
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
711
+ 'data-variant',
712
+ 'detached',
713
+ );
714
+ });
715
+
716
+ it('renders no message box for statusVariant="tooltip"', () => {
717
+ const {container} = render(
718
+ <TextInput
719
+ label="Email"
720
+ value=""
721
+ onChange={() => {}}
722
+ status={{type: 'error', message: 'Invalid email'}}
723
+ statusVariant="tooltip"
724
+ />,
725
+ );
726
+ expect(
727
+ container.querySelector('.astryx-field-status'),
728
+ ).not.toBeInTheDocument();
729
+ });
730
+
731
+ it('surfaces the status message in a tooltip for statusVariant="tooltip"', () => {
732
+ render(
733
+ <TextInput
734
+ label="Email"
735
+ value=""
736
+ onChange={() => {}}
737
+ status={{type: 'error', message: 'Invalid email'}}
738
+ statusVariant="tooltip"
739
+ />,
740
+ );
741
+ const tooltip = screen.getByRole('tooltip', h);
742
+ expect(tooltip).toHaveTextContent('Invalid email');
743
+ });
744
+
745
+ it('describes the input by the status tooltip for statusVariant="tooltip"', () => {
746
+ render(
747
+ <TextInput
748
+ label="Email"
749
+ value=""
750
+ onChange={() => {}}
751
+ status={{type: 'error', message: 'Invalid email'}}
752
+ statusVariant="tooltip"
753
+ />,
754
+ );
755
+ const input = screen.getByRole('textbox');
756
+ const tooltip = screen.getByRole('tooltip', h);
757
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
758
+ });
759
+
760
+ it('renders the tooltip status affordance as a focusable button (WCAG 2.1.1)', () => {
761
+ render(
762
+ <TextInput
763
+ label="Email"
764
+ value=""
765
+ onChange={() => {}}
766
+ status={{type: 'error', message: 'Invalid email'}}
767
+ statusVariant="tooltip"
768
+ />,
769
+ );
770
+ // The status affordance is a real button with an accessible name naming
771
+ // the status type (WCAG 4.1.2), so keyboard-only users (no AT) can reach it.
772
+ const statusButton = screen.getByRole('button', {name: /error details/i});
773
+ expect(statusButton).toBeInTheDocument();
774
+ expect(statusButton).toHaveAttribute('type', 'button');
775
+ });
776
+
777
+ it('opens the status tooltip on keyboard focus for statusVariant="tooltip"', async () => {
778
+ const user = userEvent.setup();
779
+ render(
780
+ <TextInput
781
+ label="Email"
782
+ value=""
783
+ onChange={() => {}}
784
+ status={{type: 'error', message: 'Invalid email'}}
785
+ statusVariant="tooltip"
786
+ />,
787
+ );
788
+ const tooltip = screen.getByRole('tooltip', h);
789
+ // Tab from the input to the status button; keyboard focus reveals the tip.
790
+ await user.tab();
791
+ await user.tab();
792
+ await waitFor(() => {
793
+ expect(tooltip).toHaveAttribute('popover-open');
794
+ });
795
+ });
796
+
797
+ it('describes the status button by the tooltip content for statusVariant="tooltip"', () => {
798
+ render(
799
+ <TextInput
800
+ label="Email"
801
+ value=""
802
+ onChange={() => {}}
803
+ status={{type: 'error', message: 'Invalid email'}}
804
+ statusVariant="tooltip"
805
+ />,
806
+ );
807
+ const statusButton = screen.getByRole('button', {name: /error details/i});
808
+ const tooltip = screen.getByRole('tooltip', h);
809
+ expect(statusButton.getAttribute('aria-describedby')).toContain(tooltip.id);
810
+ });
811
+ });
@@ -27,7 +27,6 @@ import {
27
27
  type ReactNode,
28
28
  } from 'react';
29
29
  import * as stylex from '@stylexjs/stylex';
30
- import type {IconName} from '../Icon';
31
30
  import {
32
31
  colorVars,
33
32
  sizeVars,
@@ -39,11 +38,11 @@ import {
39
38
  import {
40
39
  Field,
41
40
  type InputStatus,
42
- type InputStatusType,
43
41
  inputWrapperStyles,
44
42
  inputStatusBorderStyles,
45
43
  inputStatusHoverShadowStyles,
46
44
  inputStatusFocusWithinStyles,
45
+ type FieldStatusVariant,
47
46
  } from '../Field';
48
47
  import {Icon, renderIconSlot, type IconType} from '../Icon';
49
48
  import {Spinner} from '../Spinner';
@@ -119,6 +118,7 @@ export type {
119
118
  import {mergeProps, mergeRefs} from '../utils';
120
119
  import {useSize} from '../SizeContext/SizeContext';
121
120
  import {useInputContainer} from '../hooks/useInputContainer';
121
+ import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
122
122
  import {useInputGroup} from '../InputGroup/InputGroupContext';
123
123
  import type {BaseProps} from '../BaseProps';
124
124
  import type {SizeValue} from '../utils/types';
@@ -198,6 +198,14 @@ export interface TextInputProps extends Omit<
198
198
  * If message is provided, displays a floating message box below the input.
199
199
  */
200
200
  status?: InputStatus;
201
+ /**
202
+ * How the status message is placed relative to the input.
203
+ * - 'attached': message overlaps directly below the input (bordered treatment)
204
+ * - 'detached': message floats below as a separate element with spacing
205
+ * - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
206
+ * @default 'attached'
207
+ */
208
+ statusVariant?: FieldStatusVariant;
201
209
  /**
202
210
  * The size of the input.
203
211
  * - 'sm': Compact size (18px height)
@@ -280,6 +288,7 @@ export function TextInput({
280
288
  disabledMessage,
281
289
  startIcon,
282
290
  status,
291
+ statusVariant = 'attached',
283
292
  size: sizeProp,
284
293
  onChange,
285
294
  changeAction,
@@ -328,26 +337,25 @@ export function TextInput({
328
337
  isEnabled: showsDisabledMessage,
329
338
  });
330
339
 
331
- const statusIconMap: Record<InputStatusType, IconName> = {
332
- warning: 'warning',
333
- error: 'error',
334
- success: 'success',
335
- };
336
-
337
- const statusIconColorMap: Record<
338
- InputStatusType,
339
- 'warning' | 'error' | 'success'
340
- > = {
341
- warning: 'warning',
342
- error: 'error',
343
- success: 'success',
344
- };
340
+ const {statusIcon, describedBy: statusTooltipDescribedBy} =
341
+ useInputStatusIcon({
342
+ status,
343
+ statusVariant,
344
+ isInGroup: !!inputGroup,
345
+ });
345
346
 
346
347
  const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
347
348
  inputLabelID,
348
349
  [
349
350
  description ? descriptionID : null,
350
- status?.message ? statusMessageID : null,
351
+ // The status message element is rendered by Field, which is skipped
352
+ // inside an InputGroup — only reference it when it actually exists.
353
+ !inputGroup && statusVariant !== 'tooltip' && status?.message
354
+ ? statusMessageID
355
+ : null,
356
+ // The tooltip variant renders no message box; describe the input by the
357
+ // tooltip's content instead so the status is still announced.
358
+ statusTooltipDescribedBy,
351
359
  showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
352
360
  ],
353
361
  inputGroup,
@@ -402,7 +410,7 @@ export function TextInput({
402
410
  sizeStyles[size],
403
411
  isDisabled && inputWrapperStyles.disabled,
404
412
  status && inputStatusBorderStyles[status.type],
405
- status && inputStatusHoverShadowStyles[status.type],
413
+ status && !isDisabled && inputStatusHoverShadowStyles[status.type],
406
414
  status && inputStatusFocusWithinStyles[status.type],
407
415
  inputGroup && groupStyles.inGroup,
408
416
  xstyle,
@@ -456,13 +464,7 @@ export function TextInput({
456
464
  </button>
457
465
  )}
458
466
  {isBusy && <Spinner size="sm" />}
459
- {status && !inputGroup && (
460
- <Icon
461
- icon={statusIconMap[status.type]}
462
- size="md"
463
- color={statusIconColorMap[status.type]}
464
- />
465
- )}
467
+ {statusIcon}
466
468
  </div>
467
469
  );
468
470
 
@@ -495,6 +497,7 @@ export function TextInput({
495
497
  }
496
498
  : undefined
497
499
  }
500
+ statusVariant={statusVariant}
498
501
  labelTooltip={labelTooltip}
499
502
  width={width}>
500
503
  {inputWrapper}
@@ -149,7 +149,7 @@ export const docsDense = {
149
149
  onClick: '(e) => void. Adds button semantics + hover overlay.',
150
150
  isLoading: 'Skeleton (no src) or upload overlay (with src). Default: false.',
151
151
  isDisabled: 'Disabled state. Default: false.',
152
- showRemoveOn: "When remove button shows: hover (default — reveal on hover/focus, stays visible on touch) | always. Only when onRemove set.",
152
+ showRemoveOn: "When remove button shows: hover (default: reveal on hover/focus, stays visible on touch) | always. Only when onRemove set.",
153
153
  xstyle: 'stylex.create() for layout.',
154
154
  className: 'CSS class. Prefer xstyle.',
155
155
  style: 'Inline styles. Prefer xstyle.',
@@ -207,7 +207,7 @@ const styles = stylex.create({
207
207
  removeSlot: {
208
208
  position: 'absolute',
209
209
  top: spacingVars['--spacing-1'],
210
- right: spacingVars['--spacing-1'],
210
+ insetInlineEnd: spacingVars['--spacing-1'],
211
211
  zIndex: 1,
212
212
  lineHeight: 0,
213
213
  },
@@ -229,12 +229,25 @@ export const docs = {
229
229
  description:
230
230
  'Status indicator that colors the border and displays an icon. When a message is provided it is rendered below the input.',
231
231
  },
232
+ {
233
+ name: 'statusVariant',
234
+ type: "'attached' | 'detached' | 'tooltip'",
235
+ description:
236
+ 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing; tooltip hides the message box and surfaces it in a tooltip on the status icon.',
237
+ default: "'attached'",
238
+ },
232
239
  {
233
240
  name: 'labelTooltip',
234
241
  type: 'string',
235
242
  description:
236
243
  'Tooltip text rendered as an info icon at the end of the label row.',
237
244
  },
245
+ {
246
+ name: 'width',
247
+ type: 'SizeValue',
248
+ description:
249
+ 'Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned.',
250
+ },
238
251
  {
239
252
  name: 'xstyle',
240
253
  type: 'StyleXStyles',
@@ -372,6 +385,13 @@ export const docsZh = {
372
385
  description:
373
386
  '为边框着色并显示图标的状态指示器。当提供消息时,消息渲染在输入框下方。',
374
387
  },
388
+ {
389
+ name: 'statusVariant',
390
+ type: "'attached' | 'detached' | 'tooltip'",
391
+ description:
392
+ '状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
393
+ default: "'attached'",
394
+ },
375
395
  {
376
396
  name: 'labelTooltip',
377
397
  type: 'string',
@@ -545,6 +565,7 @@ export const docsDense = {
545
565
  placeholder: 'Placeholder when empty. Focused+empty shows format hint.',
546
566
  size: 'Input element height.',
547
567
  status: 'Colored border+icon. Message rendered below input.',
568
+ statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
548
569
  labelTooltip: 'Tooltip as info icon at label row end.',
549
570
  xstyle:
550
571
  'StyleX styles for layout customization. Must be stylex.create() value, not inline style.',
@@ -571,3 +571,26 @@ describe('TimeInput', () => {
571
571
  });
572
572
  });
573
573
  });
574
+
575
+
576
+ describe('TimeInput statusVariant forwarding', () => {
577
+ it('defaults to attached (status renders with data-variant="attached")', () => {
578
+ const {container} = render(
579
+ <TimeInput label="Start" value={undefined} onChange={() => {}} status={{type: 'error', message: 'Required'}} />,
580
+ );
581
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
582
+ 'data-variant',
583
+ 'attached',
584
+ );
585
+ });
586
+
587
+ it('forwards statusVariant="detached" to the underlying Field status', () => {
588
+ const {container} = render(
589
+ <TimeInput label="Start" value={undefined} onChange={() => {}} status={{type: 'error', message: 'Required'}} statusVariant="detached" />,
590
+ );
591
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
592
+ 'data-variant',
593
+ 'detached',
594
+ );
595
+ });
596
+ });
@@ -29,7 +29,6 @@ import {
29
29
  type FocusEvent,
30
30
  } from 'react';
31
31
  import * as stylex from '@stylexjs/stylex';
32
- import type {IconName} from '../Icon';
33
32
  import {
34
33
  colorVars,
35
34
  sizeVars,
@@ -41,11 +40,11 @@ import {
41
40
  import {
42
41
  Field,
43
42
  type InputStatus,
44
- type InputStatusType,
45
43
  inputWrapperStyles,
46
44
  inputStatusBorderStyles,
47
45
  inputStatusHoverShadowStyles,
48
46
  inputStatusFocusWithinStyles,
47
+ type FieldStatusVariant,
49
48
  } from '../Field';
50
49
  import {Icon} from '../Icon';
51
50
  import {Spinner} from '../Spinner';
@@ -67,6 +66,7 @@ import type {SizeValue} from '../utils/types';
67
66
  import {useSize} from '../SizeContext/SizeContext';
68
67
  import {useAnnounce} from '../hooks/useAnnounce';
69
68
  import {useInputContainer} from '../hooks/useInputContainer';
69
+ import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
70
70
  import {useInputGroup} from '../InputGroup/InputGroupContext';
71
71
  import {groupStyles} from '../InputGroup/groupStyles';
72
72
  import {useTooltip} from '../Tooltip';
@@ -298,6 +298,14 @@ export interface TimeInputProps extends Omit<
298
298
  * If message is provided, displays below the input.
299
299
  */
300
300
  status?: InputStatus;
301
+ /**
302
+ * How the status message is placed relative to the input.
303
+ * - 'attached': message overlaps directly below the input (bordered treatment)
304
+ * - 'detached': message floats below as a separate element with spacing
305
+ * - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
306
+ * @default 'attached'
307
+ */
308
+ statusVariant?: FieldStatusVariant;
301
309
 
302
310
  /**
303
311
  * Width of the field. Numbers are treated as pixels, strings are used as-is
@@ -347,6 +355,7 @@ export function TimeInput({
347
355
  placeholder: placeholderFromProps,
348
356
  size: sizeProp,
349
357
  status,
358
+ statusVariant = 'attached',
350
359
  labelTooltip,
351
360
  width,
352
361
  xstyle,
@@ -400,26 +409,21 @@ export function TimeInput({
400
409
  });
401
410
 
402
411
  // Status icon mapping
403
- const statusIconMap: Record<InputStatusType, IconName> = {
404
- warning: 'warning',
405
- error: 'error',
406
- success: 'success',
407
- };
408
-
409
- const statusIconColorMap: Record<
410
- InputStatusType,
411
- 'warning' | 'error' | 'success'
412
- > = {
413
- warning: 'warning',
414
- error: 'error',
415
- success: 'success',
416
- };
412
+ const {statusIcon, describedBy: statusTooltipDescribedBy} =
413
+ useInputStatusIcon({
414
+ status,
415
+ statusVariant,
416
+ isInGroup: !!inputGroup,
417
+ });
417
418
 
418
419
  const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
419
420
  inputLabelID,
420
421
  [
421
422
  description ? descriptionID : null,
422
- status?.message ? statusMessageID : null,
423
+ statusVariant !== 'tooltip' && status?.message ? statusMessageID : null,
424
+ // The tooltip variant renders no message box; describe the input by the
425
+ // tooltip's content instead so the status is still announced.
426
+ statusTooltipDescribedBy,
423
427
  showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
424
428
  ],
425
429
  inputGroup,
@@ -610,7 +614,7 @@ export function TimeInput({
610
614
  sizeStyles[size],
611
615
  isDisabled && inputWrapperStyles.disabled,
612
616
  status && inputStatusBorderStyles[status.type],
613
- status && inputStatusHoverShadowStyles[status.type],
617
+ status && !isDisabled && inputStatusHoverShadowStyles[status.type],
614
618
  status && inputStatusFocusWithinStyles[status.type],
615
619
  inputGroup && groupStyles.inGroup,
616
620
  xstyle,
@@ -681,13 +685,7 @@ export function TimeInput({
681
685
  <Icon icon="close" size="sm" color="secondary" />
682
686
  </button>
683
687
  )}
684
- {status && !inputGroup && (
685
- <Icon
686
- icon={statusIconMap[status.type]}
687
- size="md"
688
- color={statusIconColorMap[status.type]}
689
- />
690
- )}
688
+ {statusIcon}
691
689
  </div>
692
690
  );
693
691
 
@@ -720,6 +718,7 @@ export function TimeInput({
720
718
  }
721
719
  : undefined
722
720
  }
721
+ statusVariant={statusVariant}
723
722
  labelTooltip={labelTooltip}
724
723
  width={width}>
725
724
  {inputWrapper}