@astryxdesign/core 0.1.9 → 0.2.0-canary.068bda9

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 (601) 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 +435 -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 +24 -0
  92. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  93. package/dist/DropdownMenu/menuItemHover.js +34 -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 +51 -5
  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/Heading/Heading.d.ts.map +1 -1
  115. package/dist/Heading/Heading.js +3 -2
  116. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  117. package/dist/HoverCard/useHoverCard.js +10 -4
  118. package/dist/Icon/Icon.d.ts +15 -1
  119. package/dist/Icon/Icon.d.ts.map +1 -1
  120. package/dist/Icon/Icon.js +19 -3
  121. package/dist/Item/Item.d.ts +16 -3
  122. package/dist/Item/Item.d.ts.map +1 -1
  123. package/dist/Item/Item.js +31 -6
  124. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  125. package/dist/Layer/layerAnimations.stylex.js +10 -2
  126. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  127. package/dist/Lightbox/Lightbox.js +34 -15
  128. package/dist/List/List.d.ts.map +1 -1
  129. package/dist/List/List.js +6 -3
  130. package/dist/List/ListItem.d.ts +11 -1
  131. package/dist/List/ListItem.d.ts.map +1 -1
  132. package/dist/List/ListItem.js +2 -0
  133. package/dist/Markdown/Markdown.d.ts.map +1 -1
  134. package/dist/Markdown/Markdown.js +7 -0
  135. package/dist/MetadataList/MetadataList.d.ts +1 -1
  136. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  137. package/dist/MetadataList/MetadataList.js +4 -2
  138. package/dist/MultiSelector/MultiSelector.d.ts +9 -1
  139. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  140. package/dist/MultiSelector/MultiSelector.js +81 -43
  141. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  142. package/dist/NumberInput/NumberInput.d.ts +10 -2
  143. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  144. package/dist/NumberInput/NumberInput.js +22 -17
  145. package/dist/Outline/Outline.js +2 -2
  146. package/dist/Pagination/Pagination.d.ts.map +1 -1
  147. package/dist/Pagination/Pagination.js +11 -3
  148. package/dist/PowerSearch/PowerSearch.d.ts +9 -2
  149. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  150. package/dist/PowerSearch/PowerSearch.js +2 -0
  151. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  152. package/dist/ProgressBar/ProgressBar.js +6 -1
  153. package/dist/RadioList/RadioList.d.ts +2 -2
  154. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  155. package/dist/RadioList/RadioListItem.js +5 -5
  156. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  157. package/dist/Resizable/ResizeHandle.js +49 -19
  158. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  159. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  160. package/dist/SegmentedControl/SegmentedControlItem.js +5 -3
  161. package/dist/Selector/Selector.d.ts +8 -0
  162. package/dist/Selector/Selector.d.ts.map +1 -1
  163. package/dist/Selector/Selector.js +66 -17
  164. package/dist/SideNav/SideNav.d.ts.map +1 -1
  165. package/dist/SideNav/SideNav.js +5 -5
  166. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  167. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  168. package/dist/SideNav/SideNavItem.d.ts +1 -1
  169. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  170. package/dist/SideNav/SideNavItem.js +12 -10
  171. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  172. package/dist/Skeleton/Skeleton.js +2 -2
  173. package/dist/Slider/Slider.d.ts.map +1 -1
  174. package/dist/Slider/Slider.js +87 -34
  175. package/dist/Spinner/Spinner.d.ts +3 -2
  176. package/dist/Spinner/Spinner.d.ts.map +1 -1
  177. package/dist/Spinner/Spinner.js +11 -2
  178. package/dist/Switch/Switch.d.ts +8 -1
  179. package/dist/Switch/Switch.d.ts.map +1 -1
  180. package/dist/Switch/Switch.js +171 -91
  181. package/dist/TabList/Tab.js +2 -2
  182. package/dist/TabList/TabList.d.ts +2 -11
  183. package/dist/TabList/TabList.d.ts.map +1 -1
  184. package/dist/TabList/TabList.js +5 -9
  185. package/dist/TabList/TabListContext.d.ts +0 -5
  186. package/dist/TabList/TabListContext.d.ts.map +1 -1
  187. package/dist/TabList/TabListContext.js +0 -5
  188. package/dist/TabList/TabMenu.js +1 -1
  189. package/dist/Table/BaseTable.d.ts.map +1 -1
  190. package/dist/Table/BaseTable.js +30 -4
  191. package/dist/Table/TableCell.js +3 -3
  192. package/dist/Table/TableHeaderCell.js +3 -3
  193. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  194. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  195. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  196. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +37 -27
  197. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  198. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  199. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  200. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +15 -7
  201. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  202. package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
  203. package/dist/Table/types.d.ts +32 -0
  204. package/dist/Table/types.d.ts.map +1 -1
  205. package/dist/Text/Text.d.ts +10 -1
  206. package/dist/Text/Text.d.ts.map +1 -1
  207. package/dist/Text/Text.js +25 -2
  208. package/dist/Text/index.d.ts +2 -2
  209. package/dist/Text/index.d.ts.map +1 -1
  210. package/dist/TextArea/TextArea.d.ts +10 -1
  211. package/dist/TextArea/TextArea.d.ts.map +1 -1
  212. package/dist/TextArea/TextArea.js +119 -52
  213. package/dist/TextInput/TextInput.d.ts +10 -2
  214. package/dist/TextInput/TextInput.d.ts.map +1 -1
  215. package/dist/TextInput/TextInput.js +20 -17
  216. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  217. package/dist/Thumbnail/Thumbnail.js +51 -45
  218. package/dist/TimeInput/TimeInput.d.ts +10 -2
  219. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  220. package/dist/TimeInput/TimeInput.js +17 -17
  221. package/dist/Timestamp/Timestamp.d.ts +31 -3
  222. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  223. package/dist/Timestamp/Timestamp.js +48 -77
  224. package/dist/Timestamp/formatInstant.d.ts +41 -0
  225. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  226. package/dist/Timestamp/formatInstant.js +177 -0
  227. package/dist/Timestamp/index.d.ts +1 -0
  228. package/dist/Timestamp/index.d.ts.map +1 -1
  229. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  230. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  231. package/dist/Timestamp/tooltipEntries.js +163 -0
  232. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  233. package/dist/Toast/ToastViewport.js +61 -9
  234. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  235. package/dist/ToggleButton/ToggleButton.js +3 -1
  236. package/dist/Token/Token.d.ts +34 -4
  237. package/dist/Token/Token.d.ts.map +1 -1
  238. package/dist/Token/Token.js +85 -45
  239. package/dist/Token/TokenLink.d.ts +62 -0
  240. package/dist/Token/TokenLink.d.ts.map +1 -0
  241. package/dist/Token/TokenLink.js +76 -0
  242. package/dist/Token/index.d.ts +1 -1
  243. package/dist/Token/index.d.ts.map +1 -1
  244. package/dist/Tokenizer/Tokenizer.d.ts +9 -2
  245. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  246. package/dist/Tokenizer/Tokenizer.js +4 -2
  247. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  248. package/dist/Tooltip/useTooltip.js +17 -1
  249. package/dist/TopNav/TopNavItem.d.ts +3 -1
  250. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  251. package/dist/TopNav/TopNavItem.js +30 -3
  252. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  253. package/dist/TopNav/TopNavMegaMenu.js +9 -3
  254. package/dist/TreeList/TreeList.d.ts.map +1 -1
  255. package/dist/TreeList/TreeList.js +1 -0
  256. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  257. package/dist/TreeList/TreeListBranches.js +8 -6
  258. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  259. package/dist/TreeList/TreeListItem.js +32 -14
  260. package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
  261. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  262. package/dist/Typeahead/BaseTypeahead.js +22 -11
  263. package/dist/Typeahead/Typeahead.d.ts +9 -2
  264. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  265. package/dist/Typeahead/Typeahead.js +11 -2
  266. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  267. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  268. package/dist/Typeahead/TypeaheadItem.js +20 -10
  269. package/dist/astryx.css +114 -35
  270. package/dist/astryx.umd.js +50 -50
  271. package/dist/astryx.umd.js.map +4 -4
  272. package/dist/hooks/containerReveal.pool.stylex.d.ts +281 -0
  273. package/dist/hooks/containerReveal.pool.stylex.d.ts.map +1 -0
  274. package/dist/hooks/containerReveal.pool.stylex.js +281 -0
  275. package/dist/hooks/index.d.ts +4 -0
  276. package/dist/hooks/index.d.ts.map +1 -1
  277. package/dist/hooks/index.js +3 -1
  278. package/dist/hooks/isRtlElement.d.ts +18 -0
  279. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  280. package/dist/hooks/isRtlElement.js +27 -0
  281. package/dist/hooks/useAnnounce.d.ts +3 -0
  282. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  283. package/dist/hooks/useAnnounce.js +39 -1
  284. package/dist/hooks/useContainerReveal.d.ts +83 -0
  285. package/dist/hooks/useContainerReveal.d.ts.map +1 -0
  286. package/dist/hooks/useContainerReveal.js +123 -0
  287. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  288. package/dist/hooks/useFocusTrap.js +52 -11
  289. package/dist/hooks/useGridFocus.d.ts +6 -1
  290. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  291. package/dist/hooks/useGridFocus.js +12 -9
  292. package/dist/hooks/useHotkeys.d.ts +9 -0
  293. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  294. package/dist/hooks/useHotkeys.js +9 -0
  295. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  296. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  297. package/dist/hooks/useInputStatusIcon.js +169 -0
  298. package/dist/hooks/useKeyboardHint.d.ts +1 -1
  299. package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
  300. package/dist/hooks/useKeyboardHint.js +4 -2
  301. package/dist/hooks/useListFocus.d.ts +39 -1
  302. package/dist/hooks/useListFocus.d.ts.map +1 -1
  303. package/dist/hooks/useListFocus.js +54 -9
  304. package/dist/i18n/InternationalizationContext.d.ts +1 -0
  305. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  306. package/dist/i18n/InternationalizationContext.js +3 -0
  307. package/dist/i18n/InternationalizationProvider.d.ts +8 -1
  308. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  309. package/dist/i18n/InternationalizationProvider.js +5 -1
  310. package/dist/i18n/getLocaleDirection.d.ts +18 -0
  311. package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
  312. package/dist/i18n/getLocaleDirection.js +29 -0
  313. package/dist/i18n/index.d.ts +6 -0
  314. package/dist/i18n/index.d.ts.map +1 -1
  315. package/dist/i18n/index.js +7 -1
  316. package/dist/i18n/useDirection.d.ts +2 -0
  317. package/dist/i18n/useDirection.d.ts.map +1 -0
  318. package/dist/i18n/useDirection.js +27 -0
  319. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  320. package/dist/theme/derivedVarRegistry.js +13 -0
  321. package/dist/theme/index.d.ts +3 -3
  322. package/dist/theme/index.d.ts.map +1 -1
  323. package/dist/theme/types.d.ts +39 -2
  324. package/dist/theme/types.d.ts.map +1 -1
  325. package/dist/utils/index.d.ts +1 -0
  326. package/dist/utils/index.d.ts.map +1 -1
  327. package/dist/utils/index.js +2 -1
  328. package/dist/utils/plainDate.d.ts +17 -0
  329. package/dist/utils/plainDate.d.ts.map +1 -1
  330. package/dist/utils/plainDate.js +23 -4
  331. package/dist/utils/rtlStyles.d.ts +24 -0
  332. package/dist/utils/rtlStyles.d.ts.map +1 -0
  333. package/dist/utils/rtlStyles.js +27 -0
  334. package/locales/en.json +120 -0
  335. package/locales/pseudo.json +90 -0
  336. package/package.json +7 -2
  337. package/src/Avatar/Avatar.doc.mjs +50 -3
  338. package/src/Avatar/Avatar.test.tsx +218 -0
  339. package/src/Avatar/Avatar.tsx +285 -79
  340. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  341. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  342. package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
  343. package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
  344. package/src/AvatarGroup/AvatarGroup.tsx +58 -3
  345. package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
  346. package/src/Banner/Banner.tsx +6 -6
  347. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  348. package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
  349. package/src/Breadcrumbs/index.ts +4 -0
  350. package/src/Button/Button.doc.mjs +0 -4
  351. package/src/Button/Button.test.tsx +16 -0
  352. package/src/Button/Button.tsx +6 -4
  353. package/src/Calendar/Calendar.test.tsx +170 -20
  354. package/src/Calendar/Calendar.tsx +73 -16
  355. package/src/Calendar/dayCellUtils.test.ts +18 -18
  356. package/src/Calendar/dayCellUtils.ts +2 -2
  357. package/src/Calendar/styles.ts +33 -39
  358. package/src/Card/Card.tsx +1 -1
  359. package/src/Carousel/Carousel.tsx +25 -6
  360. package/src/Chat/ChatComposer.doc.mjs +2 -2
  361. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  362. package/src/Chat/ChatComposerInput.tsx +2 -2
  363. package/src/Chat/ChatDictationButton.tsx +7 -1
  364. package/src/Chat/ChatLayout.tsx +4 -4
  365. package/src/Chat/ChatMessageBubble.tsx +15 -10
  366. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  367. package/src/Chat/ChatToolCalls.tsx +9 -2
  368. package/src/Chat/useTriggerMenu.tsx +1 -1
  369. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  370. package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
  371. package/src/CheckboxInput/CheckboxInput.tsx +21 -6
  372. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  373. package/src/CheckboxList/CheckboxList.test.tsx +94 -3
  374. package/src/CheckboxList/CheckboxListItem.tsx +17 -10
  375. package/src/Citation/Citation.doc.mjs +4 -3
  376. package/src/Citation/Citation.test.tsx +97 -0
  377. package/src/Citation/Citation.tsx +43 -11
  378. package/src/CodeBlock/CodeBlock.test.tsx +17 -0
  379. package/src/CodeBlock/CodeBlock.tsx +38 -11
  380. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  381. package/src/Collapsible/Collapsible.test.tsx +7 -0
  382. package/src/Collapsible/Collapsible.tsx +16 -6
  383. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  384. package/src/CommandPalette/CommandPalette.tsx +41 -2
  385. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  386. package/src/ContextMenu/ContextMenu.test.tsx +59 -2
  387. package/src/ContextMenu/ContextMenu.tsx +22 -11
  388. package/src/ContextMenu/index.ts +5 -0
  389. package/src/DateInput/DateInput.doc.mjs +25 -0
  390. package/src/DateInput/DateInput.test.tsx +230 -0
  391. package/src/DateInput/DateInput.tsx +51 -29
  392. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  393. package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
  394. package/src/DateRangeInput/DateRangeInput.tsx +42 -30
  395. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  396. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  397. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  398. package/src/Divider/Divider.test.tsx +32 -8
  399. package/src/Divider/Divider.tsx +14 -1
  400. package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
  401. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  402. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
  403. package/src/DropdownMenu/DropdownMenuItem.tsx +9 -3
  404. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
  405. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
  406. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +9 -3
  407. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +20 -2
  408. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  409. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +663 -0
  410. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +561 -0
  411. package/src/DropdownMenu/index.ts +16 -0
  412. package/src/DropdownMenu/menuItemHover.ts +40 -0
  413. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  414. package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
  415. package/src/Field/Field.doc.mjs +4 -0
  416. package/src/Field/Field.tsx +14 -9
  417. package/src/Field/FieldLabel.test.tsx +25 -0
  418. package/src/Field/FieldLabel.tsx +12 -3
  419. package/src/Field/inputStyles.stylex.ts +2 -0
  420. package/src/FieldStatus/FieldStatus.doc.mjs +1 -0
  421. package/src/FieldStatus/FieldStatus.test.tsx +133 -0
  422. package/src/FieldStatus/FieldStatus.tsx +61 -4
  423. package/src/FileInput/FileInput.doc.mjs +23 -2
  424. package/src/FileInput/FileInput.test.tsx +104 -13
  425. package/src/FileInput/FileInput.tsx +106 -79
  426. package/src/Heading/Heading.tsx +2 -1
  427. package/src/HoverCard/useHoverCard.tsx +15 -3
  428. package/src/Icon/Icon.doc.mjs +13 -0
  429. package/src/Icon/Icon.test.tsx +89 -0
  430. package/src/Icon/Icon.tsx +51 -3
  431. package/src/Item/Item.doc.mjs +5 -0
  432. package/src/Item/Item.test.tsx +88 -0
  433. package/src/Item/Item.tsx +57 -11
  434. package/src/Layer/layerAnimations.stylex.ts +23 -0
  435. package/src/Lightbox/Lightbox.test.tsx +33 -0
  436. package/src/Lightbox/Lightbox.tsx +30 -10
  437. package/src/List/List.test.tsx +17 -29
  438. package/src/List/List.tsx +6 -1
  439. package/src/List/ListItem.doc.mjs +12 -0
  440. package/src/List/ListItem.tsx +13 -0
  441. package/src/Markdown/Markdown.test.tsx +21 -0
  442. package/src/Markdown/Markdown.tsx +4 -0
  443. package/src/MetadataList/MetadataList.test.tsx +24 -0
  444. package/src/MetadataList/MetadataList.tsx +6 -2
  445. package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
  446. package/src/MultiSelector/MultiSelector.test.tsx +402 -0
  447. package/src/MultiSelector/MultiSelector.tsx +126 -64
  448. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  449. package/src/NumberInput/NumberInput.doc.mjs +21 -0
  450. package/src/NumberInput/NumberInput.test.tsx +71 -0
  451. package/src/NumberInput/NumberInput.tsx +35 -26
  452. package/src/Outline/Outline.test.tsx +12 -12
  453. package/src/Outline/Outline.tsx +2 -2
  454. package/src/Pagination/Pagination.tsx +11 -3
  455. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  456. package/src/PowerSearch/PowerSearch.test.tsx +23 -0
  457. package/src/PowerSearch/PowerSearch.tsx +10 -1
  458. package/src/ProgressBar/ProgressBar.test.tsx +34 -0
  459. package/src/ProgressBar/ProgressBar.tsx +16 -1
  460. package/src/RadioList/RadioList.doc.mjs +6 -0
  461. package/src/RadioList/RadioList.test.tsx +26 -4
  462. package/src/RadioList/RadioList.tsx +2 -2
  463. package/src/RadioList/RadioListItem.tsx +12 -5
  464. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  465. package/src/Resizable/ResizeHandle.tsx +70 -20
  466. package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
  467. package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
  468. package/src/Selector/Selector.doc.mjs +15 -0
  469. package/src/Selector/Selector.test.tsx +370 -0
  470. package/src/Selector/Selector.tsx +88 -20
  471. package/src/SideNav/SideNav.tsx +1 -8
  472. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  473. package/src/SideNav/SideNavItem.tsx +5 -3
  474. package/src/Skeleton/Skeleton.test.tsx +16 -0
  475. package/src/Skeleton/Skeleton.tsx +13 -1
  476. package/src/Slider/Slider.doc.mjs +6 -0
  477. package/src/Slider/Slider.test.tsx +240 -12
  478. package/src/Slider/Slider.tsx +96 -23
  479. package/src/Spinner/Spinner.test.tsx +10 -8
  480. package/src/Spinner/Spinner.tsx +15 -5
  481. package/src/Switch/Switch.doc.mjs +142 -42
  482. package/src/Switch/Switch.test.tsx +111 -0
  483. package/src/Switch/Switch.tsx +149 -32
  484. package/src/TabList/Tab.tsx +2 -2
  485. package/src/TabList/TabList.doc.mjs +0 -6
  486. package/src/TabList/TabList.test.tsx +3 -15
  487. package/src/TabList/TabList.tsx +6 -17
  488. package/src/TabList/TabListContext.ts +0 -6
  489. package/src/TabList/TabMenu.tsx +2 -2
  490. package/src/Table/BaseTable.tsx +34 -2
  491. package/src/Table/Table.doc.mjs +11 -0
  492. package/src/Table/Table.test.tsx +85 -0
  493. package/src/Table/TableCell.tsx +3 -3
  494. package/src/Table/TableHeaderCell.tsx +3 -3
  495. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  496. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
  497. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +27 -18
  498. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  499. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  500. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +38 -9
  501. package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
  502. package/src/Table/types.ts +33 -0
  503. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  504. package/src/Text/Heading.doc.mjs +3 -3
  505. package/src/Text/Text.doc.mjs +1 -1
  506. package/src/Text/Text.test.tsx +42 -0
  507. package/src/Text/Text.tsx +25 -1
  508. package/src/Text/index.ts +2 -6
  509. package/src/TextArea/TextArea.doc.mjs +23 -2
  510. package/src/TextArea/TextArea.test.tsx +132 -4
  511. package/src/TextArea/TextArea.tsx +161 -67
  512. package/src/TextInput/TextInput.doc.mjs +21 -0
  513. package/src/TextInput/TextInput.test.tsx +150 -0
  514. package/src/TextInput/TextInput.tsx +28 -25
  515. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  516. package/src/Thumbnail/Thumbnail.test.tsx +24 -1
  517. package/src/Thumbnail/Thumbnail.tsx +28 -35
  518. package/src/TimeInput/TimeInput.doc.mjs +21 -0
  519. package/src/TimeInput/TimeInput.test.tsx +23 -0
  520. package/src/TimeInput/TimeInput.tsx +24 -25
  521. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  522. package/src/Timestamp/Timestamp.test.tsx +371 -0
  523. package/src/Timestamp/Timestamp.tsx +96 -93
  524. package/src/Timestamp/formatInstant.ts +218 -0
  525. package/src/Timestamp/index.ts +4 -0
  526. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  527. package/src/Timestamp/tooltipEntries.ts +210 -0
  528. package/src/Toast/ToastViewport.test.tsx +218 -8
  529. package/src/Toast/ToastViewport.tsx +71 -12
  530. package/src/ToggleButton/ToggleButton.test.tsx +24 -0
  531. package/src/ToggleButton/ToggleButton.tsx +19 -1
  532. package/src/Token/Token.test.tsx +166 -15
  533. package/src/Token/Token.tsx +104 -47
  534. package/src/Token/TokenLink.tsx +107 -0
  535. package/src/Token/index.ts +1 -1
  536. package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
  537. package/src/Tokenizer/Tokenizer.test.tsx +23 -0
  538. package/src/Tokenizer/Tokenizer.tsx +14 -2
  539. package/src/Tooltip/Tooltip.test.tsx +46 -0
  540. package/src/Tooltip/useTooltip.tsx +20 -0
  541. package/src/TopNav/TopNav.test.tsx +93 -2
  542. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  543. package/src/TopNav/TopNavItem.tsx +35 -6
  544. package/src/TopNav/TopNavMegaMenu.tsx +26 -2
  545. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  546. package/src/TreeList/TreeList.doc.mjs +6 -0
  547. package/src/TreeList/TreeList.test.tsx +50 -10
  548. package/src/TreeList/TreeList.tsx +5 -0
  549. package/src/TreeList/TreeListBranches.tsx +8 -6
  550. package/src/TreeList/TreeListItem.tsx +33 -11
  551. package/src/Typeahead/BaseTypeahead.tsx +42 -15
  552. package/src/Typeahead/Typeahead.doc.mjs +13 -0
  553. package/src/Typeahead/Typeahead.test.tsx +112 -0
  554. package/src/Typeahead/Typeahead.tsx +17 -1
  555. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  556. package/src/__tests__/babelPluginAddExtensions.test.ts +190 -0
  557. package/src/__tests__/forcedColors.ts +60 -0
  558. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  559. package/src/doc-mjs.d.ts +19 -0
  560. package/src/hooks/containerReveal.pool.stylex.ts +464 -0
  561. package/src/hooks/index.ts +13 -0
  562. package/src/hooks/isRtlElement.ts +27 -0
  563. package/src/hooks/useAnnounce.test.tsx +75 -2
  564. package/src/hooks/useAnnounce.ts +47 -1
  565. package/src/hooks/useContainerReveal.test.tsx +116 -0
  566. package/src/hooks/useContainerReveal.ts +180 -0
  567. package/src/hooks/useFocusTrap.test.tsx +104 -0
  568. package/src/hooks/useFocusTrap.ts +61 -12
  569. package/src/hooks/useGridFocus.doc.mjs +3 -3
  570. package/src/hooks/useGridFocus.test.tsx +41 -0
  571. package/src/hooks/useGridFocus.ts +21 -10
  572. package/src/hooks/useHotkeys.ts +9 -0
  573. package/src/hooks/useInputStatusIcon.test.tsx +221 -0
  574. package/src/hooks/useInputStatusIcon.tsx +235 -0
  575. package/src/hooks/useKeyboardHint.test.tsx +22 -0
  576. package/src/hooks/useKeyboardHint.tsx +6 -2
  577. package/src/hooks/useListFocus.doc.mjs +25 -3
  578. package/src/hooks/useListFocus.test.tsx +153 -1
  579. package/src/hooks/useListFocus.ts +102 -9
  580. package/src/i18n/InternationalizationContext.ts +4 -0
  581. package/src/i18n/InternationalizationProvider.tsx +12 -2
  582. package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
  583. package/src/i18n/__tests__/useDirection.test.tsx +66 -0
  584. package/src/i18n/getLocaleDirection.ts +29 -0
  585. package/src/i18n/index.ts +6 -0
  586. package/src/i18n/useDirection.ts +29 -0
  587. package/src/reset.css +0 -26
  588. package/src/reset.test.ts +60 -0
  589. package/src/theme/derivedVarRegistry.test.ts +1 -0
  590. package/src/theme/derivedVarRegistry.ts +6 -0
  591. package/src/theme/generateThemeRules.test.ts +42 -0
  592. package/src/theme/index.ts +4 -8
  593. package/src/theme/types.ts +43 -13
  594. package/src/utils/index.ts +2 -0
  595. package/src/utils/plainDate.test.ts +24 -6
  596. package/src/utils/plainDate.ts +18 -4
  597. package/src/utils/rtlStyles.ts +27 -0
  598. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +0 -9
  599. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +0 -1
  600. package/dist/Thumbnail/thumbnail.markers.stylex.js +0 -14
  601. package/src/Thumbnail/thumbnail.markers.stylex.ts +0 -12
package/src/Text/Text.tsx CHANGED
@@ -23,6 +23,7 @@ import type {
23
23
  BuiltinTextType,
24
24
  TextSize,
25
25
  TextColor,
26
+ BuiltinTextColor,
26
27
  TextWeight,
27
28
  TextDisplay,
28
29
  TextJustify,
@@ -185,6 +186,24 @@ function resolveStyleType(type: TextType): BuiltinTextType {
185
186
  return 'body';
186
187
  }
187
188
 
189
+ /**
190
+ * Resolve the StyleX baseline color. Built-in colors map to their own color
191
+ * style; custom (theme-defined) colors fall back to the `primary` baseline —
192
+ * their actual color comes from theme CSS (`.astryx-text.<color>` /
193
+ * `.astryx-heading.<color>`), which the rendered `color` class targets.
194
+ *
195
+ * Exported so Heading applies the same custom-color fallback as Text.
196
+ */
197
+ export function resolveStyleColor(color: TextColor): BuiltinTextColor {
198
+ // The `in` guard is a runtime check for consumer-augmented custom colors
199
+ // (which widen `TextColor` beyond the built-ins via TextColorMap); within
200
+ // core the two types coincide, so no cast is needed.
201
+ if (color in colorStyles) {
202
+ return color;
203
+ }
204
+ return 'primary';
205
+ }
206
+
188
207
  /**
189
208
  * Semantic text component. Renders text with type-based styling from the theme.
190
209
  *
@@ -228,6 +247,11 @@ export function Text({
228
247
  // Resolve style type — custom types fall back to 'body' for StyleX baseline
229
248
  const styleType = resolveStyleType(type);
230
249
 
250
+ // Resolve style color — custom colors fall back to 'primary' for StyleX
251
+ // baseline; the real color comes from theme CSS via the rendered `color`
252
+ // class (see themeProps below).
253
+ const styleColor = resolveStyleColor(resolvedColor);
254
+
231
255
  // Resolve wordBreak with smart default
232
256
  const resolvedWordBreak =
233
257
  wordBreak ?? (maxLines === 1 ? 'break-all' : 'break-word');
@@ -257,7 +281,7 @@ export function Text({
257
281
  {...mergeProps(
258
282
  themeProps('text', {type, size, color: resolvedColor}),
259
283
  stylex.props(
260
- colorStyles[resolvedColor],
284
+ colorStyles[styleColor],
261
285
  sizeByTypeStyles[styleType],
262
286
  size && sizeStyles[size],
263
287
  defaultWeightByTypeStyles[styleType],
package/src/Text/index.ts CHANGED
@@ -7,12 +7,7 @@
7
7
  * @position Entry point for Text components
8
8
  */
9
9
 
10
- export {
11
- Text,
12
- type TextProps,
13
- type TextType,
14
- type TextSize,
15
- } from './Text';
10
+ export {Text, type TextProps, type TextType, type TextSize} from './Text';
16
11
  export {
17
12
  Heading,
18
13
  type HeadingProps,
@@ -23,6 +18,7 @@ export {
23
18
  // Re-export shared types from theme for convenience
24
19
  export type {
25
20
  TextColor,
21
+ TextColorMap,
26
22
  TextWeight,
27
23
  TextDisplay,
28
24
  TextJustify,
@@ -98,7 +98,7 @@ export const docs = {
98
98
  name: 'maxLength',
99
99
  type: 'number',
100
100
  description:
101
- 'Maximum number of characters allowed. When set, a character counter (current/max) is displayed below the textarea. Does not enforce the limit natively; the counter shows error styling when exceeded.',
101
+ 'Maximum number of characters allowed. When set, a character counter (current/max) is displayed inside the input container, anchored to the bottom-right beneath the text. Does not enforce the limit natively; when exceeded the counter turns red and shows a warning icon (a non-color cue), and screen-reader users hear the remaining/over-limit count announced.',
102
102
  },
103
103
  {
104
104
  name: 'status',
@@ -106,6 +106,13 @@ export const docs = {
106
106
  description:
107
107
  'Status indicator that applies a colored border and icon. An optional message is displayed in a floating box below the textarea.',
108
108
  },
109
+ {
110
+ name: 'statusVariant',
111
+ type: "'attached' | 'detached' | 'tooltip'",
112
+ description:
113
+ '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.',
114
+ default: "'attached'",
115
+ },
109
116
  {
110
117
  name: 'labelTooltip',
111
118
  type: 'string',
@@ -147,6 +154,12 @@ export const docs = {
147
154
  description:
148
155
  'HTML name attribute for the textarea element, useful for form submissions.',
149
156
  },
157
+ {
158
+ name: 'width',
159
+ type: 'SizeValue',
160
+ description:
161
+ '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.',
162
+ },
150
163
  {
151
164
  name: 'onFocus',
152
165
  type: '(e: FocusEvent<HTMLTextAreaElement>) => void',
@@ -279,6 +292,13 @@ export const docsZh = {
279
292
  type: "{ type: 'warning' | 'error' | 'success'; message?: string }",
280
293
  description: '应用彩色边框和图标的状态指示器。可选消息显示在文本域下方的浮动框中。',
281
294
  },
295
+ {
296
+ name: 'statusVariant',
297
+ type: "'attached' | 'detached' | 'tooltip'",
298
+ description:
299
+ '状态消息相对于输入框的放置方式。attached 直接叠加在输入框下方(带边框处理);detached 作为独立元素浮于下方并留有间距;tooltip 隐藏消息框,并在状态图标上以提示气泡形式显示。',
300
+ default: "'attached'",
301
+ },
282
302
  {
283
303
  name: 'labelTooltip',
284
304
  type: 'string',
@@ -387,8 +407,9 @@ export const docsDense = {
387
407
  isLoading: 'Loading state w/ spinner inside input.',
388
408
  placeholder: 'Placeholder when textarea empty.',
389
409
  rows: 'Visible text rows.',
390
- maxLength: 'Max chars allowed. Shows counter (current/max) below textarea. No native enforcement.',
410
+ maxLength: 'Max chars allowed. Shows counter (current/max) inside the container, bottom-right beneath the text. No native enforcement; over-limit shows red + a warning icon and is announced to screen readers.',
391
411
  status: 'Colored border+icon status. Optional floating message below textarea.',
412
+ 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.',
392
413
  labelTooltip: 'Tooltip in info icon at label end.',
393
414
  startIcon: 'Icon inside leading edge of textarea wrapper.',
394
415
  hasSpellCheck: 'Enables/disables browser spell checking.',
@@ -335,6 +335,23 @@ describe('TextArea', () => {
335
335
  expect(messageElement).toHaveAttribute('id');
336
336
  expect(describedBy).toContain(messageElement.id);
337
337
  });
338
+
339
+ it('shows both the loading spinner and the status icon while busy', () => {
340
+ const {container} = render(
341
+ <TextArea
342
+ label="Description"
343
+ value=""
344
+ onChange={() => {}}
345
+ isLoading
346
+ status={{type: 'error'}}
347
+ />,
348
+ );
349
+ // Matches the other inputs: spinner (role="status") and the status icon
350
+ // render side by side in the end slot, not mutually exclusively.
351
+ expect(screen.getByRole('status')).toBeInTheDocument();
352
+ // Status icon svg is also present alongside the spinner.
353
+ expect(container.querySelectorAll('svg').length).toBeGreaterThan(0);
354
+ });
338
355
  });
339
356
 
340
357
  it('renders tooltip info icon when labelTooltip is provided', () => {
@@ -506,7 +523,70 @@ describe('TextArea', () => {
506
523
  expect(screen.getByText('5/50')).toBeInTheDocument();
507
524
  });
508
525
 
509
- it('counter has aria-live region for screen reader announcements', () => {
526
+ it('announces remaining characters politely as the value nears the limit', async () => {
527
+ const user = userEvent.setup();
528
+ function Wrapper() {
529
+ const [val, setVal] = useState('x'.repeat(44));
530
+ return (
531
+ <TextArea
532
+ label="Description"
533
+ value={val}
534
+ onChange={setVal}
535
+ maxLength={50}
536
+ />
537
+ );
538
+ }
539
+ render(<Wrapper />);
540
+ // Type one more char to cross into the "near the limit" zone (45/50).
541
+ await user.type(screen.getByRole('textbox'), 'x');
542
+ const politeRegion = () =>
543
+ document.querySelector('[data-astryx-live-region="polite"]');
544
+ await waitFor(() => {
545
+ expect(politeRegion()).toHaveTextContent('5 characters remaining');
546
+ });
547
+ });
548
+
549
+ it('announces over-limit assertively once the value exceeds the max', async () => {
550
+ const user = userEvent.setup();
551
+ function Wrapper() {
552
+ const [val, setVal] = useState('x'.repeat(50));
553
+ return (
554
+ <TextArea
555
+ label="Description"
556
+ value={val}
557
+ onChange={setVal}
558
+ maxLength={50}
559
+ />
560
+ );
561
+ }
562
+ render(<Wrapper />);
563
+ await user.type(screen.getByRole('textbox'), 'x');
564
+ const assertiveRegion = () =>
565
+ document.querySelector('[data-astryx-live-region="assertive"]');
566
+ await waitFor(() => {
567
+ expect(assertiveRegion()).toHaveTextContent(
568
+ '1 character over the limit',
569
+ );
570
+ });
571
+ });
572
+
573
+ it('shows a non-color over-limit indicator icon when exceeded', () => {
574
+ const {container} = render(
575
+ <TextArea
576
+ label="Description"
577
+ value={'x'.repeat(55)}
578
+ onChange={() => {}}
579
+ maxLength={50}
580
+ />,
581
+ );
582
+ // The counter renders a warning icon (a shape cue) alongside the red
583
+ // count, so the over-limit state is not conveyed by color alone.
584
+ const counter = screen.getByText('55/50').closest('div');
585
+ expect(counter?.querySelector('svg')).toBeInTheDocument();
586
+ expect(container).toBeInTheDocument();
587
+ });
588
+
589
+ it('does not show the over-limit indicator icon within the limit', () => {
510
590
  render(
511
591
  <TextArea
512
592
  label="Description"
@@ -515,9 +595,8 @@ describe('TextArea', () => {
515
595
  maxLength={50}
516
596
  />,
517
597
  );
518
- const liveRegion = document.querySelector('[aria-live="polite"]');
519
- expect(liveRegion).toBeInTheDocument();
520
- expect(liveRegion).toHaveTextContent('5 characters remaining');
598
+ const counter = screen.getByText('45/50').closest('div');
599
+ expect(counter?.querySelector('svg')).not.toBeInTheDocument();
521
600
  });
522
601
 
523
602
  it('counter is linked to textarea via aria-describedby', () => {
@@ -535,6 +614,22 @@ describe('TextArea', () => {
535
614
  expect(counter).toHaveAttribute('id');
536
615
  expect(describedBy).toContain(counter.id);
537
616
  });
617
+
618
+ it('renders the counter inside the input container (same wrapper as textarea)', () => {
619
+ render(
620
+ <TextArea
621
+ label="Description"
622
+ value="Hello"
623
+ onChange={() => {}}
624
+ maxLength={50}
625
+ />,
626
+ );
627
+ const textarea = screen.getByRole('textbox');
628
+ const counter = screen.getByText('5/50');
629
+ // The counter now lives inside the bordered input container as a sibling
630
+ // overlay of the textarea, not below it as an out-of-container element.
631
+ expect(textarea.parentElement).toBe(counter.parentElement);
632
+ });
538
633
  });
539
634
 
540
635
  describe('hasAutoFocus prop', () => {
@@ -736,3 +831,36 @@ describe('TextArea', () => {
736
831
  });
737
832
  });
738
833
  });
834
+
835
+ describe('TextArea statusVariant forwarding', () => {
836
+ it('defaults to attached (status renders with data-variant="attached")', () => {
837
+ const {container} = render(
838
+ <TextArea
839
+ label="Bio"
840
+ value=""
841
+ onChange={() => {}}
842
+ status={{type: 'error', message: 'Required'}}
843
+ />,
844
+ );
845
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
846
+ 'data-variant',
847
+ 'attached',
848
+ );
849
+ });
850
+
851
+ it('forwards statusVariant="detached" to the underlying Field status', () => {
852
+ const {container} = render(
853
+ <TextArea
854
+ label="Bio"
855
+ value=""
856
+ onChange={() => {}}
857
+ status={{type: 'error', message: 'Required'}}
858
+ statusVariant="detached"
859
+ />,
860
+ );
861
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
862
+ 'data-variant',
863
+ 'detached',
864
+ );
865
+ });
866
+ });
@@ -21,13 +21,13 @@ import {
21
21
  useOptimistic,
22
22
  useTransition,
23
23
  useRef,
24
+ useCallback,
24
25
  type ChangeEvent,
25
26
  type ClipboardEvent,
26
27
  type FocusEvent,
27
28
  type ReactNode,
28
29
  } from 'react';
29
30
  import * as stylex from '@stylexjs/stylex';
30
- import type {IconName} from '../Icon';
31
31
  import {
32
32
  colorVars,
33
33
  spacingVars,
@@ -40,6 +40,7 @@ import {
40
40
  inputStatusBorderStyles,
41
41
  inputStatusHoverShadowStyles,
42
42
  inputStatusFocusWithinStyles,
43
+ type FieldStatusVariant,
43
44
  } from '../Field';
44
45
  import {Icon, renderIconSlot, type IconType} from '../Icon';
45
46
  import {Spinner} from '../Spinner';
@@ -48,25 +49,40 @@ import {mergeProps, mergeRefs} from '../utils';
48
49
  import type {BaseProps} from '../BaseProps';
49
50
  import type {SizeValue} from '../utils/types';
50
51
  import {useInputContainer} from '../hooks/useInputContainer';
52
+ import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
53
+ import {useAnnounce} from '../hooks/useAnnounce';
51
54
  import {useSize} from '../SizeContext/SizeContext';
52
55
  import {themeProps} from '../utils/themeProps';
53
- import {VisuallyHidden} from '../VisuallyHidden';
56
+ import {useTranslator} from '../i18n';
54
57
 
55
58
  const COUNTER_WARNING_THRESHOLD = 0.8;
56
59
 
57
60
  const styles = stylex.create({
61
+ // The wrapper is a bare positioning context. The <textarea> spans the full
62
+ // container and carries its own internal padding; icons, status/spinner, and
63
+ // the character counter are absolutely-positioned overlays anchored to the
64
+ // container corners. This keeps the native resize grip in the true
65
+ // bottom-right corner and lets the scrollbar represent the full input area.
58
66
  wrapper: {
59
67
  zIndex: 1,
60
- alignItems: 'flex-start',
61
- paddingBlock: spacingVars['--spacing-1'],
68
+ display: 'block',
69
+ padding: 0,
70
+ // Internal inline padding for the textarea's text. Defined on the wrapper
71
+ // so the counter (a sibling overlay) inherits the same value and stays
72
+ // aligned with the text edge. Kept as an internal var so it can later be
73
+ // driven by a theme "padding" translation without touching either child.
74
+ '--_textarea-inline-padding': spacingVars['--spacing-2'],
62
75
  },
63
76
  textarea: {
64
77
  display: 'block',
65
- flex: 1,
78
+ width: '100%',
79
+ boxSizing: 'border-box',
66
80
  minWidth: 0,
67
81
  borderWidth: 0,
68
82
  borderStyle: 'none',
69
- padding: 0,
83
+ // Base internal padding; overlays get their own reserved space below.
84
+ paddingBlock: spacingVars['--spacing-1'],
85
+ paddingInline: 'var(--_textarea-inline-padding)',
70
86
  fontFamily: typographyVars['--font-family-body'],
71
87
  fontSize: {
72
88
  default: typeScaleVars['--text-body-size'],
@@ -84,29 +100,59 @@ const styles = stylex.create({
84
100
  textareaDisabled: {
85
101
  cursor: 'not-allowed',
86
102
  },
103
+ // Reserve start padding so text clears the start icon.
104
+ // inline inset + 16px icon (sm) + 8px gap
105
+ textareaWithStartIcon: {
106
+ paddingInlineStart: `calc(var(--_textarea-inline-padding) + ${spacingVars['--spacing-6']})`,
107
+ },
108
+ // Reserve end padding so text clears the status icon / spinner overlay.
109
+ // inline inset + 20px icon (md) + 4px gap
110
+ textareaWithStatus: {
111
+ paddingInlineEnd: `calc(var(--_textarea-inline-padding) + ${spacingVars['--spacing-6']})`,
112
+ },
113
+ // Reserve wider end padding when the spinner AND status icon both show.
114
+ // inline inset + 16px spinner + 8px gap + 20px icon + 4px gap
115
+ textareaWithBusyStatus: {
116
+ paddingInlineEnd: `calc(var(--_textarea-inline-padding) + ${spacingVars['--spacing-12']})`,
117
+ },
118
+ // Reserve bottom padding so text clears the character counter overlay.
119
+ textareaWithCounter: {
120
+ paddingBottom: spacingVars['--spacing-7'],
121
+ },
122
+ startIcon: {
123
+ position: 'absolute',
124
+ top: spacingVars['--spacing-2'],
125
+ insetInlineStart: 'var(--_textarea-inline-padding)',
126
+ pointerEvents: 'none',
127
+ display: 'flex',
128
+ },
129
+ endSlot: {
130
+ position: 'absolute',
131
+ top: spacingVars['--spacing-2'],
132
+ insetInlineEnd: 'var(--_textarea-inline-padding)',
133
+ pointerEvents: 'none',
134
+ display: 'flex',
135
+ alignItems: 'center',
136
+ gap: spacingVars['--spacing-2'],
137
+ },
138
+ // Character counter lives inside the input container, anchored bottom-right
139
+ // underneath the textarea, aligned to the same inline inset as the text.
87
140
  counter: {
141
+ position: 'absolute',
142
+ bottom: spacingVars['--spacing-1'],
143
+ insetInlineEnd: 'var(--_textarea-inline-padding)',
144
+ pointerEvents: 'none',
88
145
  display: 'flex',
89
- justifyContent: 'flex-end',
90
- marginTop: spacingVars['--spacing-1'],
146
+ alignItems: 'center',
147
+ gap: spacingVars['--spacing-1'],
91
148
  fontFamily: typographyVars['--font-family-body'],
92
149
  fontSize: typeScaleVars['--text-supporting-size'],
150
+ lineHeight: typeScaleVars['--text-supporting-leading'],
93
151
  color: colorVars['--color-text-secondary'],
94
152
  },
95
153
  counterError: {
96
154
  color: colorVars['--color-error'],
97
155
  },
98
- statusIcon: {
99
- position: 'absolute',
100
- top: spacingVars['--spacing-2'],
101
- insetInlineEnd: spacingVars['--spacing-2'],
102
- pointerEvents: 'none',
103
- display: 'flex',
104
- },
105
- textareaWithStatus: {
106
- // Reserve space so text doesn't flow under the absolutely-positioned icon.
107
- // 20px (icon md) + 4px gap = 24px (--spacing-6)
108
- paddingInlineEnd: spacingVars['--spacing-6'],
109
- },
110
156
  });
111
157
 
112
158
  const textareaSizeStyles = stylex.create({
@@ -217,6 +263,14 @@ export interface TextAreaProps extends Omit<
217
263
  * If message is provided, displays a floating message box below the textarea.
218
264
  */
219
265
  status?: TextAreaStatus;
266
+ /**
267
+ * How the status message is placed relative to the input.
268
+ * - 'attached': message overlaps directly below the input (bordered treatment)
269
+ * - 'detached': message floats below as a separate element with spacing
270
+ * - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
271
+ * @default 'attached'
272
+ */
273
+ statusVariant?: FieldStatusVariant;
220
274
  /**
221
275
  * Width of the field. Numbers are treated as pixels, strings are used as-is
222
276
  * (e.g. `'100%'`). Sizes the whole field (label, control, and status) so they
@@ -298,6 +352,7 @@ export function TextArea({
298
352
  isDisabled = false,
299
353
  disabledMessage,
300
354
  status,
355
+ statusVariant = 'attached',
301
356
  labelTooltip,
302
357
  startIcon,
303
358
  hasSpellCheck = true,
@@ -316,12 +371,17 @@ export function TextArea({
316
371
  ...rest
317
372
  }: TextAreaProps) {
318
373
  const size = useSize(sizeProp, 'md');
374
+ const t = useTranslator();
375
+ const announce = useAnnounce();
319
376
  const id = useId();
320
377
  const descriptionID = useId();
321
378
  const statusMessageID = useId();
322
379
  const counterID = useId();
323
380
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
324
381
  const containerRef = useRef<HTMLDivElement | null>(null);
382
+ // Tracks the counter "zone" last announced (under / near / over) so we only
383
+ // announce on a zone change, not on every keystroke.
384
+ const counterZoneRef = useRef<'under' | 'near' | 'over' | null>(null);
325
385
 
326
386
  const [, startTransition] = useTransition();
327
387
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
@@ -341,31 +401,62 @@ export function TextArea({
341
401
  isEnabled: showsDisabledMessage,
342
402
  });
343
403
 
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
- };
404
+ const {statusIcon, describedBy: statusTooltipDescribedBy} =
405
+ useInputStatusIcon({
406
+ status,
407
+ statusVariant,
408
+ });
358
409
 
359
410
  const ariaDescribedBy =
360
411
  [
361
412
  description ? descriptionID : null,
362
- status?.message ? statusMessageID : null,
413
+ statusVariant !== 'tooltip' && status?.message ? statusMessageID : null,
414
+ // The tooltip variant renders no message box; describe the input by the
415
+ // tooltip's content instead so the status is still announced.
416
+ statusTooltipDescribedBy,
363
417
  maxLength != null ? counterID : null,
364
418
  showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
365
419
  ]
366
420
  .filter(Boolean)
367
421
  .join(' ') || undefined;
368
422
 
423
+ // Announce the character-count status through the shared live region, but
424
+ // only when the value crosses into a new zone (near the limit / over the
425
+ // limit) so we don't speak on every keystroke. Over-limit is assertive (an
426
+ // error the user should hear immediately); nearing the limit is polite.
427
+ const announceCounter = useCallback(
428
+ (length: number) => {
429
+ if (maxLength == null) {
430
+ return;
431
+ }
432
+ const zone: 'under' | 'near' | 'over' =
433
+ length > maxLength
434
+ ? 'over'
435
+ : length >= maxLength * COUNTER_WARNING_THRESHOLD
436
+ ? 'near'
437
+ : 'under';
438
+ if (zone === counterZoneRef.current) {
439
+ return;
440
+ }
441
+ counterZoneRef.current = zone;
442
+ if (zone === 'over') {
443
+ announce(
444
+ t('@astryx.textArea.charactersOverLimit', {
445
+ count: length - maxLength,
446
+ }),
447
+ 'assertive',
448
+ );
449
+ } else if (zone === 'near') {
450
+ announce(
451
+ t('@astryx.textArea.charactersRemaining', {
452
+ count: maxLength - length,
453
+ }),
454
+ );
455
+ }
456
+ },
457
+ [announce, t, maxLength],
458
+ );
459
+
369
460
  const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
370
461
  // Value can't change while showing a disabled message (the field is
371
462
  // read-only and non-native-disabled), but guard the handler too so the
@@ -374,6 +465,7 @@ export function TextArea({
374
465
  return;
375
466
  }
376
467
  const newValue = e.target.value;
468
+ announceCounter(newValue.length);
377
469
  onChange?.(newValue, e);
378
470
  if (changeAction && !e.defaultPrevented) {
379
471
  startTransition(async () => {
@@ -412,6 +504,7 @@ export function TextArea({
412
504
  }
413
505
  : undefined
414
506
  }
507
+ statusVariant={statusVariant}
415
508
  labelTooltip={labelTooltip}
416
509
  width={width}>
417
510
  <div
@@ -429,18 +522,20 @@ export function TextArea({
429
522
  stylex.props(
430
523
  inputWrapperStyles.base,
431
524
  styles.wrapper,
432
- textareaSizeStyles[size],
433
- effectivelyDisabled && inputWrapperStyles.disabled,
525
+ isDisabled && inputWrapperStyles.disabled,
434
526
  status && inputStatusBorderStyles[status.type],
435
- status && inputStatusHoverShadowStyles[status.type],
527
+ status && !isDisabled && inputStatusHoverShadowStyles[status.type],
436
528
  status && inputStatusFocusWithinStyles[status.type],
437
529
  xstyle,
438
530
  ),
439
531
  className,
440
532
  style,
441
533
  )}>
442
- {startIcon &&
443
- renderIconSlot(startIcon, {size: 'sm', color: 'secondary'})}
534
+ {startIcon && (
535
+ <span {...stylex.props(styles.startIcon)}>
536
+ {renderIconSlot(startIcon, {size: 'sm', color: 'secondary'})}
537
+ </span>
538
+ )}
444
539
  <textarea
445
540
  {...rest}
446
541
  ref={mergeRefs(ref, textareaRef)}
@@ -473,38 +568,37 @@ export function TextArea({
473
568
  aria-busy={isBusy || undefined}
474
569
  {...stylex.props(
475
570
  styles.textarea,
476
- effectivelyDisabled && styles.textareaDisabled,
477
- status && styles.textareaWithStatus,
571
+ textareaSizeStyles[size],
572
+ isDisabled && styles.textareaDisabled,
573
+ Boolean(startIcon) && styles.textareaWithStartIcon,
574
+ (status || isBusy) && styles.textareaWithStatus,
575
+ isBusy && !!statusIcon && styles.textareaWithBusyStatus,
576
+ maxLength != null && styles.textareaWithCounter,
478
577
  )}
479
578
  />
480
- {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
- />
579
+ {(isBusy || statusIcon) && (
580
+ <span {...stylex.props(styles.endSlot)}>
581
+ {isBusy && <Spinner size="sm" />}
582
+ {statusIcon}
488
583
  </span>
489
584
  )}
585
+ {maxLength != null && (
586
+ <div
587
+ id={counterID}
588
+ {...stylex.props(
589
+ styles.counter,
590
+ optimisticValue.length > maxLength && styles.counterError,
591
+ )}>
592
+ {optimisticValue.length > maxLength && (
593
+ // Non-color cue so the over-limit state isn't conveyed by the red
594
+ // color alone (WCAG 1.4.1). Decorative — the count text and the
595
+ // live-region announcement carry the meaning.
596
+ <Icon icon="warning" size="sm" />
597
+ )}
598
+ {optimisticValue.length}/{maxLength}
599
+ </div>
600
+ )}
490
601
  </div>
491
- {maxLength != null && (
492
- <div
493
- id={counterID}
494
- {...stylex.props(
495
- styles.counter,
496
- optimisticValue.length > maxLength && styles.counterError,
497
- )}>
498
- {optimisticValue.length}/{maxLength}
499
- <VisuallyHidden aria-live="polite">
500
- {optimisticValue.length >= maxLength * COUNTER_WARNING_THRESHOLD
501
- ? optimisticValue.length > maxLength
502
- ? `${optimisticValue.length - maxLength} characters over limit`
503
- : `${maxLength - optimisticValue.length} characters remaining`
504
- : ''}
505
- </VisuallyHidden>
506
- </div>
507
- )}
508
602
  {showsDisabledMessage &&
509
603
  disabledMessageTooltip.renderTooltip(disabledMessage)}
510
604
  </Field>