@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
@@ -10,6 +10,7 @@ import type {ReactNode} from 'react';
10
10
  import * as stylex from '@stylexjs/stylex';
11
11
  import {Divider} from '../Divider';
12
12
  import {DropdownMenuItem} from './DropdownMenuItem';
13
+ import {DropdownMenuSubMenu} from './DropdownMenuSubMenu';
13
14
  import {
14
15
  spacingVars,
15
16
  typographyVars,
@@ -49,18 +50,14 @@ function getSectionKey(section: DropdownMenuSection, index: number): string {
49
50
  * Converts data-driven items into DropdownMenuItem components,
50
51
  * so both modes share the same rendering and keyboard navigation path.
51
52
  */
52
- export function renderDropdownItems(
53
- items: DropdownMenuOption[],
54
- ): ReactNode {
53
+ export function renderDropdownItems(items: DropdownMenuOption[]): ReactNode {
55
54
  const elements: ReactNode[] = [];
56
55
 
57
56
  for (let i = 0; i < items.length; i++) {
58
57
  const option = items[i];
59
58
 
60
59
  if ('type' in option && option.type === 'divider') {
61
- elements.push(
62
- <Divider key={`divider-${i}`} xstyle={styles.divider} />,
63
- );
60
+ elements.push(<Divider key={`divider-${i}`} xstyle={styles.divider} />);
64
61
  } else if ('type' in option && option.type === 'section') {
65
62
  elements.push(
66
63
  <div
@@ -84,15 +81,32 @@ export function renderDropdownItems(
84
81
  </div>,
85
82
  );
86
83
  } else if (!('type' in option)) {
87
- elements.push(
88
- <DropdownMenuItem
89
- key={getItemKey(option)}
90
- icon={option.icon}
91
- label={option.label}
92
- onClick={option.onClick}
93
- isDisabled={option.isDisabled}
94
- />,
95
- );
84
+ // A plain item that declares nested `items` becomes a submenu (data-mode
85
+ // parity with the compound DropdownMenuSubMenu API); otherwise it's a
86
+ // leaf. renderDropdownItems owns the recursion and hands the rendered
87
+ // children to DropdownMenuSubMenu — so DropdownMenuSubMenu never imports
88
+ // this module back (no import cycle).
89
+ if (option.items && option.items.length > 0) {
90
+ elements.push(
91
+ <DropdownMenuSubMenu
92
+ key={getItemKey(option)}
93
+ icon={option.icon}
94
+ label={option.label}
95
+ isDisabled={option.isDisabled}>
96
+ {renderDropdownItems(option.items)}
97
+ </DropdownMenuSubMenu>,
98
+ );
99
+ } else {
100
+ elements.push(
101
+ <DropdownMenuItem
102
+ key={getItemKey(option)}
103
+ icon={option.icon}
104
+ label={option.label}
105
+ onClick={option.onClick}
106
+ isDisabled={option.isDisabled}
107
+ />,
108
+ );
109
+ }
96
110
  }
97
111
  }
98
112
 
@@ -22,6 +22,7 @@ import type {BaseProps} from '../BaseProps';
22
22
  import type {SizeValue} from '../utils/types';
23
23
  import {FieldLabel} from './FieldLabel';
24
24
  import {FieldStatus} from '../FieldStatus/FieldStatus';
25
+ import type {FieldStatusVariant} from '../FieldStatus/FieldStatus';
25
26
  import {spacingVars, borderVars} from '../theme/tokens.stylex';
26
27
  import type {IconType} from '../Icon';
27
28
  import {mergeProps} from '../utils';
@@ -156,9 +157,10 @@ export interface FieldProps extends Omit<
156
157
  * How the status message is rendered relative to the input.
157
158
  * - 'attached': Status sits directly below the input (default, for bordered inputs)
158
159
  * - 'detached': Status is a separate element below the field (for checkboxes, switches, sliders)
160
+ * - 'tooltip': No message box; the input surfaces status through a tooltip on its on-field icon
159
161
  * @default 'attached'
160
162
  */
161
- statusVariant?: 'attached' | 'detached';
163
+ statusVariant?: FieldStatusVariant;
162
164
  /**
163
165
  * Width of the field. Numbers are treated as pixels, strings are used as-is
164
166
  * (e.g. `'100%'`). Sizes the whole field — label, control, and status — so
@@ -239,14 +241,17 @@ export function Field({
239
241
  />
240
242
  );
241
243
 
242
- const statusNode = status?.message ? (
243
- <FieldStatus
244
- type={status.type}
245
- message={status.message}
246
- id={resolvedMessageID}
247
- variant={statusVariant}
248
- />
249
- ) : null;
244
+ // The 'tooltip' variant surfaces status through the input's on-field icon
245
+ // tooltip, so Field renders no message box for it.
246
+ const statusNode =
247
+ status?.message && statusVariant !== 'tooltip' ? (
248
+ <FieldStatus
249
+ type={status.type}
250
+ message={status.message}
251
+ id={resolvedMessageID}
252
+ variant={statusVariant}
253
+ />
254
+ ) : null;
250
255
 
251
256
  // ─── Horizontal-labels mode ───────────────────────────────────────────
252
257
  // Use display:contents so the parent grid's `auto 1fr` columns place
@@ -12,6 +12,7 @@
12
12
  import {describe, it, expect, vi} from 'vitest';
13
13
  import {render, screen} from '@testing-library/react';
14
14
  import {TestIcon} from '../__tests__/TestIcon';
15
+ import {InternationalizationProvider} from '../i18n';
15
16
  import {FieldLabel} from './FieldLabel';
16
17
 
17
18
  describe('FieldLabel', () => {
@@ -36,6 +37,30 @@ describe('FieldLabel', () => {
36
37
  expect(screen.getByText(/Required/)).toBeInTheDocument();
37
38
  });
38
39
 
40
+ it('localizes the required indicator via the i18n provider', () => {
41
+ render(
42
+ <InternationalizationProvider
43
+ locale="fr"
44
+ overrides={{fr: {'@astryx.field.required': 'Obligatoire'}}}>
45
+ <FieldLabel label="Nom" inputID="name-input" isRequired />
46
+ </InternationalizationProvider>,
47
+ );
48
+ expect(screen.getByText(/Obligatoire/)).toBeInTheDocument();
49
+ expect(screen.queryByText(/Required/)).not.toBeInTheDocument();
50
+ });
51
+
52
+ it('localizes the optional indicator via the i18n provider', () => {
53
+ render(
54
+ <InternationalizationProvider
55
+ locale="fr"
56
+ overrides={{fr: {'@astryx.field.optional': 'Facultatif'}}}>
57
+ <FieldLabel label="Nom" inputID="name-input" isOptional />
58
+ </InternationalizationProvider>,
59
+ );
60
+ expect(screen.getByText(/Facultatif/)).toBeInTheDocument();
61
+ expect(screen.queryByText(/Optional/)).not.toBeInTheDocument();
62
+ });
63
+
39
64
  it('shows Optional when both isOptional and isRequired are true', () => {
40
65
  render(
41
66
  <FieldLabel label="Name" inputID="name-input" isOptional isRequired />,
@@ -1,8 +1,10 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ 'use client';
4
+
3
5
  /**
4
6
  * @file FieldLabel.tsx
5
- * @input Uses React, Icon, IconType
7
+ * @input Uses React, Icon, IconType, useTranslator
6
8
  * @output Exports FieldLabel component, FieldLabelProps
7
9
  * @position Core label implementation; used by Field, CheckboxInput, Switch
8
10
  *
@@ -10,6 +12,7 @@
10
12
  * - /packages/core/src/Field/Field.doc.mjs (props table, features, implementation notes)
11
13
  * - /packages/core/src/Field/index.ts (exports if types change)
12
14
  * - /packages/cli/templates/blocks/components/Field/ (showcase blocks)
15
+ * - /packages/core/locales/en.json (@astryx.field.required / @astryx.field.optional)
13
16
  */
14
17
 
15
18
  import type {ReactNode} from 'react';
@@ -26,6 +29,7 @@ import {
26
29
  } from '../theme/tokens.stylex';
27
30
  import {Icon, renderIconSlot, type IconType} from '../Icon';
28
31
  import {Tooltip} from '../Tooltip';
32
+ import {useTranslator} from '../i18n';
29
33
  import {themeProps} from '../utils/themeProps';
30
34
 
31
35
  const styles = stylex.create({
@@ -48,7 +52,7 @@ const styles = stylex.create({
48
52
  borderStyle: 'none',
49
53
  clip: 'rect(0, 0, 0, 0)',
50
54
  height: 1,
51
- left: 0,
55
+ insetInlineStart: 0,
52
56
  margin: -1,
53
57
  overflow: 'hidden',
54
58
  padding: 0,
@@ -177,7 +181,12 @@ export function FieldLabel({
177
181
  ref,
178
182
  ...rest
179
183
  }: FieldLabelProps) {
180
- const statusText = isOptional ? 'Optional' : isRequired ? 'Required' : null;
184
+ const t = useTranslator();
185
+ const statusText = isOptional
186
+ ? t('@astryx.field.optional')
187
+ : isRequired
188
+ ? t('@astryx.field.required')
189
+ : null;
181
190
 
182
191
  // A group label (e.g. for a radiogroup) must not be a literal `<label>`
183
192
  // element: a `<label>` semantically names a single form control and can't be
@@ -67,6 +67,8 @@ export const inputWrapperStyles = stylex.create({
67
67
  cursor: 'not-allowed',
68
68
  opacity: 0.5,
69
69
  borderColor: colorVars['--color-border-emphasized'],
70
+ // Suppress the base hover ring — a disabled control must not react to hover.
71
+ boxShadow: 'none',
70
72
  },
71
73
  });
72
74
 
@@ -300,6 +300,78 @@ describe('FieldStatus', () => {
300
300
  });
301
301
  });
302
302
 
303
+ // The detached message must convey status by more than color/position:
304
+ // a leading status glyph precedes the message text (WCAG 1.4.1). The glyph
305
+ // is decorative for AT (aria-hidden) because the message text already names
306
+ // the status in words and it is announced via the live region.
307
+ describe('detached leading status icon (use-of-color a11y)', () => {
308
+ it('renders a leading status icon before the message for the detached variant', () => {
309
+ render(
310
+ <FieldStatus
311
+ type="error"
312
+ message="Something went wrong"
313
+ variant="detached"
314
+ data-testid="fs"
315
+ />,
316
+ );
317
+ const el = screen.getByTestId('fs');
318
+ const icon = el.querySelector('[aria-hidden="true"]');
319
+ const text = screen.getByText('Something went wrong');
320
+ expect(icon).toBeInTheDocument();
321
+ // Icon comes before the message text in document order.
322
+ expect(
323
+ icon!.compareDocumentPosition(text) & Node.DOCUMENT_POSITION_FOLLOWING,
324
+ ).toBeTruthy();
325
+ });
326
+
327
+ it('marks the status icon aria-hidden (visual redundancy, not a second announcement)', () => {
328
+ render(
329
+ <FieldStatus
330
+ type="warning"
331
+ message="Heads up"
332
+ variant="detached"
333
+ data-testid="fs"
334
+ />,
335
+ );
336
+ const icon = screen
337
+ .getByTestId('fs')
338
+ .querySelector('[aria-hidden="true"]');
339
+ expect(icon).toBeInTheDocument();
340
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
341
+ });
342
+
343
+ it('renders a status icon for each status type in the detached variant', () => {
344
+ for (const type of ['error', 'warning', 'success'] as const) {
345
+ const {unmount} = render(
346
+ <FieldStatus
347
+ type={type}
348
+ message="msg"
349
+ variant="detached"
350
+ data-testid="fs"
351
+ />,
352
+ );
353
+ expect(
354
+ screen.getByTestId('fs').querySelector('[aria-hidden="true"]'),
355
+ ).toBeInTheDocument();
356
+ unmount();
357
+ }
358
+ });
359
+
360
+ it('does not render a leading status icon for the attached variant', () => {
361
+ render(
362
+ <FieldStatus
363
+ type="error"
364
+ message="msg"
365
+ variant="attached"
366
+ data-testid="fs"
367
+ />,
368
+ );
369
+ expect(
370
+ screen.getByTestId('fs').querySelector('[aria-hidden="true"]'),
371
+ ).toBeNull();
372
+ });
373
+ });
374
+
303
375
  describe('edge cases', () => {
304
376
  it('renders an empty message without crashing', () => {
305
377
  render(<FieldStatus type="error" message="" data-testid="fs" />);
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * @file FieldStatus.tsx
5
- * @input Uses React, stylex, theme tokens, useAnnounce
5
+ * @input Uses React, stylex, theme tokens, useAnnounce, Icon
6
6
  * @output Exports FieldStatus component, FieldStatusProps
7
7
  * @position Core implementation; consumed by Field, Switch, CheckboxInput, and the FieldStatus entrypoint
8
8
  *
@@ -31,6 +31,19 @@ import {
31
31
  import type {InputStatusType} from '../Field/types';
32
32
  import {useEntryAnimation} from '../hooks/useEntryAnimation';
33
33
  import {themeProps} from '../utils/themeProps';
34
+ import {Icon} from '../Icon';
35
+ import type {IconName} from '../Icon';
36
+
37
+ /**
38
+ * Maps each status type to its status glyph. Mirrors the mapping the input
39
+ * controls already use for the on-field status affordance, so the detached
40
+ * message shows the same icon a consumer sees elsewhere for that status.
41
+ */
42
+ const statusIconMap: Record<InputStatusType, IconName> = {
43
+ warning: 'warning',
44
+ error: 'error',
45
+ success: 'success',
46
+ };
34
47
 
35
48
  const styles = stylex.create({
36
49
  base: {
@@ -52,6 +65,20 @@ const styles = stylex.create({
52
65
  paddingInline: spacingVars['--spacing-2'],
53
66
  borderRadius: radiusVars['--radius-element'],
54
67
  },
68
+ detachedContent: {
69
+ display: 'flex',
70
+ alignItems: 'flex-start',
71
+ gap: spacingVars['--spacing-1'],
72
+ },
73
+ detachedIcon: {
74
+ // Center the glyph within the first text-line box so it aligns to the
75
+ // first line of the message (rather than the top of the flex row) when the
76
+ // message wraps to multiple lines.
77
+ display: 'inline-flex',
78
+ alignItems: 'center',
79
+ height: `calc(${typeScaleVars['--text-supporting-size']} * ${typeScaleVars['--text-supporting-leading']})`,
80
+ flexShrink: 0,
81
+ },
55
82
  });
56
83
 
57
84
  const colorStyles = stylex.create({
@@ -85,6 +112,7 @@ const colorStyles = stylex.create({
85
112
  export interface FieldStatusVariantMap {
86
113
  attached: true;
87
114
  detached: true;
115
+ tooltip: true;
88
116
  }
89
117
 
90
118
  /**
@@ -114,6 +142,15 @@ export interface FieldStatusProps extends BaseProps<HTMLDivElement> {
114
142
  /**
115
143
  * A status message component for form fields.
116
144
  *
145
+ * The `detached` variant renders a leading status icon before the message so
146
+ * status is not conveyed by color or position alone (WCAG 1.4.1). The icon is
147
+ * decorative for assistive tech (`aria-hidden`): the message text already names
148
+ * the status in words and is announced through the live region. The `attached`
149
+ * variant keeps its status affordance on the bordered input, so it renders no
150
+ * icon here to avoid a duplicate. The `tooltip` variant renders no message box
151
+ * at all — the input surfaces the status through a tooltip on its on-field
152
+ * icon — so callers skip rendering FieldStatus for it.
153
+ *
117
154
  * Screen-reader announcements go through the persistent `useAnnounce` live
118
155
  * regions (assertive for errors, polite otherwise) rather than `role`/
119
156
  * `aria-live` on the rendered element. Live regions that mount together with
@@ -176,7 +213,16 @@ export function FieldStatus({
176
213
  className,
177
214
  style,
178
215
  )}>
179
- {message}
216
+ {variant === 'detached' ? (
217
+ <span {...stylex.props(styles.detachedContent)}>
218
+ <span {...stylex.props(styles.detachedIcon)}>
219
+ <Icon icon={statusIconMap[type]} size="sm" color="inherit" />
220
+ </span>
221
+ <span>{message}</span>
222
+ </span>
223
+ ) : (
224
+ message
225
+ )}
180
226
  </div>
181
227
  );
182
228
  }
@@ -78,7 +78,7 @@ export const docs = {
78
78
  name: 'isRequired',
79
79
  type: 'boolean',
80
80
  description:
81
- 'Displays a "Required" indicator next to the label and sets aria-required. Mutually exclusive with isOptional.',
81
+ 'Displays a "Required" indicator next to the label and adds a screen-reader-only "Required" description to the trigger (assistive tech does not reliably announce aria-required on the trigger). Mutually exclusive with isOptional.',
82
82
  default: 'false',
83
83
  },
84
84
  {
@@ -119,12 +119,25 @@ export const docs = {
119
119
  description:
120
120
  'Validation status: applies a colored border. If message is provided, displays a floating message below the input. Error type also sets aria-invalid.',
121
121
  },
122
+ {
123
+ name: 'statusVariant',
124
+ type: "'attached' | 'detached' | 'tooltip'",
125
+ description:
126
+ '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.',
127
+ default: "'attached'",
128
+ },
122
129
  {
123
130
  name: 'labelTooltip',
124
131
  type: 'string',
125
132
  description:
126
133
  'Tooltip text displayed in an info icon at the end of the label.',
127
134
  },
135
+ {
136
+ name: 'width',
137
+ type: 'SizeValue',
138
+ description:
139
+ '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.',
140
+ },
128
141
  ],
129
142
  theming: {
130
143
  targets: [
@@ -246,6 +259,13 @@ export const docsZh = {
246
259
  type: "{type: 'error' | 'warning' | 'success', message?: string}",
247
260
  description: '验证状态。',
248
261
  },
262
+ {
263
+ name: 'statusVariant',
264
+ type: "'attached' | 'detached' | 'tooltip'",
265
+ description:
266
+ '状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
267
+ default: "'attached'",
268
+ },
249
269
  ],
250
270
  theming: {
251
271
  targets: [
@@ -308,7 +328,7 @@ export const docsDense = {
308
328
  isLabelHidden: 'Visually hides label; keeps screen reader access.',
309
329
  description: 'Description text between label+input.',
310
330
  isOptional: 'Shows "Optional" indicator.',
311
- isRequired: 'Shows "Required" indicator+sets aria-required.',
331
+ isRequired: 'Shows "Required" indicator+SR-only trigger description.',
312
332
  isDisabled: 'Disables input, prevents interaction.',
313
333
  disabledMessage:
314
334
  'Explains why input is disabled. With isDisabled, shows tooltip on hover/focus + keeps trigger focusable via aria-disabled (opening picker stays blocked). Use instead of wrapping a disabled FileInput in Tooltip.',
@@ -316,6 +336,7 @@ export const docsDense = {
316
336
  placeholder: 'Placeholder when no files selected.',
317
337
  mode: "Visual mode: 'input' (compact) or 'dropzone' (drag-and-drop).",
318
338
  status: 'Validation status; colored border. Message floats below.',
339
+ 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.',
319
340
  labelTooltip: 'Tooltip in info icon at label end.',
320
341
  },
321
342
  };
@@ -147,16 +147,33 @@ describe('FileInput', () => {
147
147
  expect(screen.getByText('Upload')).toBeInTheDocument();
148
148
  });
149
149
 
150
- it('sets aria-required when isRequired is true', () => {
150
+ it('conveys required state through the accessible description', () => {
151
151
  render(
152
152
  <FileInput label="Resume" isRequired value={null} onChange={() => {}} />,
153
153
  );
154
- // aria-required lives on the focusable role="button" wrapper, not the
154
+ const trigger = screen.getByRole('button', {name: 'Resume'});
155
+ // Required is conveyed via a visually hidden "Required" node referenced
156
+ // from aria-describedby on the focusable role="button" trigger — not the
155
157
  // hidden file input (forms-6).
156
- expect(screen.getByRole('button', {name: 'Resume'})).toHaveAttribute(
157
- 'aria-required',
158
- 'true',
158
+ expect(trigger).toHaveAccessibleDescription(/Required/);
159
+ // aria-required is not a supported property of role="button" in
160
+ // WAI-ARIA 1.2 (AT does not announce it), so it must never appear.
161
+ expect(trigger).not.toHaveAttribute('aria-required');
162
+ });
163
+
164
+ it('combines required with the description in the accessible description', () => {
165
+ render(
166
+ <FileInput
167
+ label="Resume"
168
+ description="PDF only"
169
+ isRequired
170
+ value={null}
171
+ onChange={() => {}}
172
+ />,
159
173
  );
174
+ const trigger = screen.getByRole('button', {name: 'Resume'});
175
+ expect(trigger).toHaveAccessibleDescription(/PDF only/);
176
+ expect(trigger).toHaveAccessibleDescription(/Required/);
160
177
  });
161
178
 
162
179
  it('places aria-describedby on the focusable button, not the hidden input (forms-6)', () => {
@@ -176,11 +193,11 @@ describe('FileInput', () => {
176
193
  expect(input).toHaveAttribute('aria-hidden', 'true');
177
194
  });
178
195
 
179
- it('does not set aria-required by default', () => {
196
+ it('does not mention required without isRequired', () => {
180
197
  render(<FileInput label="Resume" value={null} onChange={() => {}} />);
181
- expect(screen.getByRole('button', {name: 'Resume'})).not.toHaveAttribute(
182
- 'aria-required',
183
- );
198
+ const trigger = screen.getByRole('button', {name: 'Resume'});
199
+ expect(trigger).not.toHaveAccessibleDescription(/Required/);
200
+ expect(trigger).not.toHaveAttribute('aria-required');
184
201
  });
185
202
 
186
203
  it('sets disabled attribute when isDisabled is true', () => {
@@ -353,6 +370,40 @@ describe('FileInput', () => {
353
370
  // FieldStatus, but the polite channel must stay empty.)
354
371
  expect(politeRegion()?.textContent ?? '').toBe('');
355
372
  });
373
+
374
+ it('announces a validation error exactly once across live regions', async () => {
375
+ render(
376
+ <FileInput
377
+ label="Upload"
378
+ value={null}
379
+ onChange={() => {}}
380
+ accept=".pdf"
381
+ />,
382
+ );
383
+ fireEvent.change(fileInputEl(), {
384
+ target: {files: [createFile('note.txt', 100)]},
385
+ });
386
+ const errorText = '"note.txt" is not an accepted file type';
387
+ // The error lands in the assertive announce region (via the FieldStatus
388
+ // the derived error status mounts)…
389
+ await waitFor(() => {
390
+ expect(
391
+ document.querySelector('[data-astryx-live-region="assertive"]'),
392
+ ).toHaveTextContent(errorText);
393
+ });
394
+ // …and in exactly one live region overall. FileInput's own role="status"
395
+ // region used to duplicate the FieldStatus announcement, so the same
396
+ // error was read twice (assertively, then politely).
397
+ const liveRegions = Array.from(
398
+ document.querySelectorAll(
399
+ '[role="status"], [role="alert"], [aria-live]',
400
+ ),
401
+ );
402
+ const regionsWithError = liveRegions.filter(el =>
403
+ (el.textContent ?? '').includes(errorText),
404
+ );
405
+ expect(regionsWithError).toHaveLength(1);
406
+ });
356
407
  });
357
408
 
358
409
  describe('validation', () => {
@@ -719,16 +770,20 @@ describe('FileInput', () => {
719
770
  />,
720
771
  );
721
772
 
722
- const trigger = screen.getByRole('button');
723
773
  const tooltip = screen.getByRole('tooltip', h);
724
774
  expect(tooltip).toHaveTextContent('You need the Editor role');
725
775
 
726
- fireEvent.mouseEnter(trigger);
776
+ // The trigger button is visually hidden, so the disabled-reason tooltip
777
+ // anchors its hover listeners to the visible container surface.
778
+ const surface = document.querySelector(
779
+ '.astryx-file-input',
780
+ ) as HTMLElement;
781
+ fireEvent.mouseEnter(surface);
727
782
  await waitFor(() => {
728
783
  expect(tooltip).toHaveAttribute('popover-open');
729
784
  });
730
785
 
731
- fireEvent.mouseLeave(trigger);
786
+ fireEvent.mouseLeave(surface);
732
787
  await waitFor(() => {
733
788
  expect(tooltip).not.toHaveAttribute('popover-open');
734
789
  });
@@ -809,7 +864,10 @@ describe('FileInput', () => {
809
864
  });
810
865
 
811
866
  it('blocks opening the file picker while focusable-disabled', async () => {
812
- const user = userEvent.setup();
867
+ // The trigger is a visually-hidden button (pointer-events: none) — real
868
+ // pointer clicks land on the container surface, so bypass the pointer
869
+ // check to exercise the disabled guard directly.
870
+ const user = userEvent.setup({pointerEventsCheck: 0});
813
871
  render(
814
872
  <FileInput
815
873
  label="Resume"
@@ -849,3 +907,36 @@ describe('FileInput', () => {
849
907
  });
850
908
  });
851
909
  });
910
+
911
+ describe('FileInput statusVariant forwarding', () => {
912
+ it('defaults to attached (status renders with data-variant="attached")', () => {
913
+ const {container} = render(
914
+ <FileInput
915
+ label="Upload"
916
+ value={null}
917
+ onChange={() => {}}
918
+ status={{type: 'error', message: 'Something went wrong'}}
919
+ />,
920
+ );
921
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
922
+ 'data-variant',
923
+ 'attached',
924
+ );
925
+ });
926
+
927
+ it('forwards statusVariant="detached" to the underlying Field status', () => {
928
+ const {container} = render(
929
+ <FileInput
930
+ label="Upload"
931
+ value={null}
932
+ onChange={() => {}}
933
+ status={{type: 'error', message: 'Something went wrong'}}
934
+ statusVariant="detached"
935
+ />,
936
+ );
937
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
938
+ 'data-variant',
939
+ 'detached',
940
+ );
941
+ });
942
+ });