@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
@@ -169,4 +169,101 @@ describe('Citation', () => {
169
169
  expect(el.classList.contains(cls)).toBe(true);
170
170
  }
171
171
  });
172
+
173
+ // --- Source icon: image URL (back-compat) vs ReactNode ---------------------
174
+
175
+ it('renders a legacy string icon as a decorative image (back-compat)', () => {
176
+ // Existing callers pass a favicon URL to `source.icon`. A bare string must
177
+ // still render as <img src>, unchanged from the original behavior.
178
+ const {container} = render(
179
+ <Citation
180
+ source={{
181
+ title: 'GitHub',
182
+ url: 'https://github.com',
183
+ icon: 'https://example.com/favicon.png',
184
+ }}
185
+ number={1}
186
+ data-testid="citation"
187
+ />,
188
+ );
189
+ const img = container.querySelector('img');
190
+ expect(img).not.toBeNull();
191
+ expect(img).toHaveAttribute('src', 'https://example.com/favicon.png');
192
+ // Decorative: empty alt, and the wrapper is aria-hidden.
193
+ expect(img).toHaveAttribute('alt', '');
194
+ expect(container.querySelector('[aria-hidden="true"] img')).toBe(img);
195
+ });
196
+
197
+ it('renders source.src as a decorative image', () => {
198
+ const {container} = render(
199
+ <Citation
200
+ source={{
201
+ title: 'GitHub',
202
+ url: 'https://github.com',
203
+ src: 'https://example.com/logo.png',
204
+ }}
205
+ number={1}
206
+ data-testid="citation"
207
+ />,
208
+ );
209
+ const img = container.querySelector('img');
210
+ expect(img).toHaveAttribute('src', 'https://example.com/logo.png');
211
+ expect(img).toHaveAttribute('alt', '');
212
+ });
213
+
214
+ it('renders a ReactNode icon as-is (not an <img>)', () => {
215
+ const {container} = render(
216
+ <Citation
217
+ source={{
218
+ title: 'GitHub',
219
+ url: 'https://github.com',
220
+ icon: <svg data-testid="custom-icon" />,
221
+ }}
222
+ number={1}
223
+ data-testid="citation"
224
+ />,
225
+ );
226
+ // The node renders directly; no <img> is produced for a node icon.
227
+ expect(container.querySelector('img')).toBeNull();
228
+ expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
229
+ // Still decorative — wrapped in an aria-hidden container.
230
+ expect(
231
+ container.querySelector(
232
+ '[aria-hidden="true"] [data-testid="custom-icon"]',
233
+ ),
234
+ ).not.toBeNull();
235
+ });
236
+
237
+ it('prefers a node icon over src when both are provided', () => {
238
+ const {container} = render(
239
+ <Citation
240
+ source={{
241
+ title: 'GitHub',
242
+ url: 'https://github.com',
243
+ src: 'https://example.com/logo.png',
244
+ icon: <svg data-testid="custom-icon" />,
245
+ }}
246
+ number={1}
247
+ data-testid="citation"
248
+ />,
249
+ );
250
+ expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
251
+ expect(container.querySelector('img')).toBeNull();
252
+ });
253
+
254
+ it('keeps aria-label as the sole accessible name when an icon is present', () => {
255
+ render(
256
+ <Citation
257
+ source={{
258
+ title: 'GitHub',
259
+ url: 'https://github.com',
260
+ icon: 'https://example.com/favicon.png',
261
+ }}
262
+ number={2}
263
+ data-testid="citation"
264
+ />,
265
+ );
266
+ const el = screen.getByTestId('citation');
267
+ expect(el).toHaveAttribute('aria-label', 'Citation 2: GitHub');
268
+ });
172
269
  });
@@ -10,10 +10,14 @@
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
12
12
  * - /packages/core/src/Citation/index.ts (exports if types change)
13
+ * - /packages/core/src/Citation/Citation.doc.mjs (props table, features)
14
+ * - /packages/core/src/Citation/Citation.test.tsx (tests for new/changed behavior)
15
+ * - /apps/storybook/stories/Citation.stories.tsx (storybook stories)
13
16
  * - /packages/cli/templates/blocks/components/Citation/ (showcase blocks)
14
17
  */
15
18
 
16
19
  import type React from 'react';
20
+ import type {ReactNode} from 'react';
17
21
  import * as stylex from '@stylexjs/stylex';
18
22
  import {
19
23
  colorVars,
@@ -29,11 +33,29 @@ import {mergeProps} from '../utils';
29
33
  import type {BaseProps} from '../BaseProps';
30
34
  import {themeProps} from '../utils/themeProps';
31
35
  import {useTranslator} from '../i18n';
36
+ import {renderIconSlot} from '../Icon';
32
37
 
33
38
  export interface CitationSource {
34
39
  title?: string;
35
40
  url?: string;
36
- icon?: string;
41
+ /**
42
+ * Image URL for a favicon or source logo, rendered as `<img src>` inside the
43
+ * icon circle. This is the idiomatic field for image sources (mirrors
44
+ * `Avatar`/`Thumbnail` `src`). When both `src` and a non-string `icon` are
45
+ * provided, `icon` wins.
46
+ */
47
+ src?: string;
48
+ /**
49
+ * Source icon shown before the label text (label variant only). Accepts:
50
+ * - a React node / Astryx `<Icon>` / SVG element — rendered as-is, or
51
+ * - a string image URL — rendered as `<img src>` for backward compatibility
52
+ * with callers that passed a favicon URL here.
53
+ *
54
+ * Note: unlike icon slots elsewhere in the system, a bare string is treated
55
+ * as an image URL (not a semantic icon name) to preserve the original
56
+ * favicon behavior. Pass an Astryx `<Icon>` (or other node) for a real icon.
57
+ */
58
+ icon?: ReactNode;
37
59
  }
38
60
 
39
61
  export interface CitationProps extends BaseProps<HTMLElement> {
@@ -162,7 +184,18 @@ export function Citation({
162
184
  const t = useTranslator();
163
185
  const title = source.title ?? String(number);
164
186
  const href = source.url;
165
- const icon = source.icon;
187
+
188
+ // Resolve the source icon. A non-string `icon` node renders as-is (an Astryx
189
+ // <Icon>, SVG, avatar, etc.). Otherwise fall back to an image URL: `src`, or
190
+ // a legacy string passed to `icon` (back-compat — favicon URLs still render
191
+ // as <img>). The icon is decorative; the accessible name comes solely from
192
+ // the element's aria-label, so nothing is double-announced.
193
+ const iconNode =
194
+ source.icon != null && typeof source.icon !== 'string' ? source.icon : null;
195
+ const imageSrc =
196
+ source.src ?? (typeof source.icon === 'string' ? source.icon : undefined);
197
+ const hasIcon = iconNode != null || imageSrc != null;
198
+
166
199
  const Tag = href ? 'a' : 'span';
167
200
  // `doc-noteref` is a reference role — only appropriate on the interactive
168
201
  // link form. On a plain (unlinked) span it is not a permitted role
@@ -218,7 +251,7 @@ export function Citation({
218
251
  themeProps('citation', {variant}),
219
252
  stylex.props(
220
253
  styles.label,
221
- icon != null && styles.labelWithIcon,
254
+ hasIcon && styles.labelWithIcon,
222
255
  href != null && styles.labelHover,
223
256
  href != null && styles.labelInteractive,
224
257
  xstyle,
@@ -226,14 +259,13 @@ export function Citation({
226
259
  className,
227
260
  style,
228
261
  )}>
229
- {icon && (
230
- <span {...stylex.props(styles.iconWrap)}>
231
- <img
232
- src={icon}
233
- alt=""
234
- aria-hidden="true"
235
- {...stylex.props(styles.icon)}
236
- />
262
+ {hasIcon && (
263
+ <span aria-hidden="true" {...stylex.props(styles.iconWrap)}>
264
+ {iconNode != null ? (
265
+ renderIconSlot(iconNode, {size: 'sm'})
266
+ ) : (
267
+ <img src={imageSrc} alt="" {...stylex.props(styles.icon)} />
268
+ )}
237
269
  </span>
238
270
  )}
239
271
  <span {...stylex.props(styles.labelText)}>{title}</span>
@@ -14,6 +14,7 @@ import {act, render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import {CodeBlock} from './CodeBlock';
15
15
  import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
16
16
  import {dracula} from '../theme/syntax';
17
+ import {InternationalizationProvider} from '../i18n';
17
18
 
18
19
  function politeRegion(): HTMLElement | null {
19
20
  return document.querySelector('[data-astryx-live-region="polite"]');
@@ -72,6 +73,22 @@ describe('CodeBlock', () => {
72
73
  });
73
74
  });
74
75
 
76
+ it('localizes the copy announcement through the i18n catalog', async () => {
77
+ render(
78
+ <InternationalizationProvider
79
+ locale="fr"
80
+ overrides={{fr: {'@astryx.codeBlock.copied': 'Copié'}}}>
81
+ <CodeBlock code="const x = 1;" language="javascript" />
82
+ </InternationalizationProvider>,
83
+ );
84
+ // The button label and the live-region announcement share the same key.
85
+ fireEvent.click(screen.getByRole('button', {name: 'Copy code'}));
86
+ await waitFor(() => {
87
+ expect(politeRegion()).toHaveTextContent('Copié');
88
+ });
89
+ expect(screen.getByRole('button', {name: 'Copié'})).toBeInTheDocument();
90
+ });
91
+
75
92
  it('keeps the copied indicator a full 2s after a rapid re-copy', async () => {
76
93
  vi.useFakeTimers();
77
94
  try {
@@ -88,6 +88,23 @@ const dynamicStyles = stylex.create({
88
88
  }),
89
89
  });
90
90
 
91
+ // Light reveal so the leading chevron eases into view instead of popping in.
92
+ // Growing the chevron's own footprint (width + inline margin) from zero slides
93
+ // the title into place instead of snapping it over, and clipping keeps the
94
+ // glyph from spilling while it's mid-reveal.
95
+ const chevronReveal = stylex.keyframes({
96
+ from: {
97
+ width: 0,
98
+ marginInlineEnd: 0,
99
+ opacity: 0,
100
+ },
101
+ to: {
102
+ width: '14px',
103
+ marginInlineEnd: spacingVars['--spacing-1'],
104
+ opacity: 1,
105
+ },
106
+ });
107
+
91
108
  const styles = stylex.create({
92
109
  root: {
93
110
  position: 'relative',
@@ -133,7 +150,6 @@ const styles = stylex.create({
133
150
  headerTitle: {
134
151
  display: 'flex',
135
152
  alignItems: 'center',
136
- gap: spacingVars['--spacing-1'],
137
153
  fontSize: typeScaleVars['--text-supporting-size'],
138
154
  fontFamily: typographyVars['--font-family-code'],
139
155
  fontWeight: fontWeightVars['--font-weight-medium'],
@@ -173,13 +189,24 @@ const styles = stylex.create({
173
189
  flexShrink: 0,
174
190
  width: '14px',
175
191
  height: '14px',
192
+ marginInlineEnd: spacingVars['--spacing-1'],
193
+ overflow: 'hidden',
176
194
  color: 'var(--color-syntax-comment)',
195
+ animationName: {
196
+ default: chevronReveal,
197
+ '@media (prefers-reduced-motion: reduce)': 'none',
198
+ },
199
+ animationDuration: durationVars['--duration-medium'],
200
+ animationTimingFunction: easeVars['--ease-standard'],
177
201
  transitionProperty: 'transform',
178
202
  transitionDuration: durationVars['--duration-fast'],
179
203
  transitionTimingFunction: easeVars['--ease-standard'],
180
204
  },
181
- collapseChevronCollapsed: {
182
- transform: 'rotate(180deg)',
205
+ collapseChevronExpanded: {
206
+ // Leading disclosure convention (matches TreeList/Table): the resting
207
+ // chevronRight points right (>) when collapsed; rotate it down (v) when
208
+ // expanded.
209
+ transform: 'rotate(90deg)',
183
210
  },
184
211
  headerCollapsible: {
185
212
  cursor: 'pointer',
@@ -296,7 +323,7 @@ const styles = stylex.create({
296
323
  copyButtonAbsolute: {
297
324
  position: 'absolute',
298
325
  top: spacingVars['--spacing-2'],
299
- right: spacingVars['--spacing-2'],
326
+ insetInlineEnd: spacingVars['--spacing-2'],
300
327
  },
301
328
  });
302
329
 
@@ -759,7 +786,7 @@ export function CodeBlock({
759
786
  setCopied(true);
760
787
  // Swapping the button's aria-label alone isn't reliably announced by
761
788
  // screen readers, so confirm the copy via a polite live region.
762
- announce('Copied');
789
+ announce(t('@astryx.codeBlock.copied'));
763
790
  onCopy?.();
764
791
  // Restart the reset timer on every copy — otherwise a rapid re-copy
765
792
  // is reverted early by the previous click's timer.
@@ -773,7 +800,7 @@ export function CodeBlock({
773
800
  } catch {
774
801
  // Clipboard failures leave the copied state unchanged.
775
802
  }
776
- }, [code, onCopy, announce]);
803
+ }, [code, onCopy, announce, t]);
777
804
 
778
805
  const sizeStyle = size === 'sm' ? styles.sizeSm : styles.sizeMd;
779
806
  // Digits in the largest line number — sizes the gutter column width.
@@ -845,18 +872,18 @@ export function CodeBlock({
845
872
  canCollapse && styles.headerCollapsible,
846
873
  )}>
847
874
  <span {...stylex.props(styles.headerTitle)}>
848
- {title}
849
- {title && languageLabel ? ' — ' : ''}
850
- {languageLabel}
851
875
  {canCollapse && (
852
876
  <span
853
877
  {...stylex.props(
854
878
  styles.collapseChevron,
855
- isCollapsed && styles.collapseChevronCollapsed,
879
+ !isCollapsed && styles.collapseChevronExpanded,
856
880
  )}>
857
- <Icon icon="chevronDown" size="xsm" color="inherit" />
881
+ <Icon icon="chevronRight" size="xsm" color="inherit" />
858
882
  </span>
859
883
  )}
884
+ {title}
885
+ {title && languageLabel ? ' — ' : ''}
886
+ {languageLabel}
860
887
  </span>
861
888
  </div>
862
889
  {copyButtonEl}
@@ -17,6 +17,7 @@ export const docs = {
17
17
  theming: {
18
18
  targets: [
19
19
  {className: 'astryx-collapsible', visualProps: ['density']},
20
+ {className: 'astryx-collapsible-trigger', visualProps: ['density']},
20
21
  {className: 'astryx-collapsible-content', visualProps: ['density']},
21
22
  {className: 'astryx-collapsible-group', visualProps: ['density']},
22
23
  ],
@@ -72,6 +72,13 @@ describe('Collapsible', () => {
72
72
  expect(screen.getByTestId('root')).toHaveClass('astryx-collapsible');
73
73
  });
74
74
 
75
+ it('renders the stable astryx-collapsible-trigger class on the trigger button', () => {
76
+ render(<Collapsible trigger="T">c</Collapsible>);
77
+ expect(screen.getByRole('button')).toHaveClass(
78
+ 'astryx-collapsible-trigger',
79
+ );
80
+ });
81
+
75
82
  it('renders the stable astryx-collapsible-content class on the content area', () => {
76
83
  render(<Collapsible trigger="T">c</Collapsible>);
77
84
  const content = contentFor(screen.getByRole('button'));
@@ -51,7 +51,12 @@ const styles = stylex.create({
51
51
  root: {
52
52
  width: '100%',
53
53
  },
54
- // Trigger button — full width, flex row, no browser button styling
54
+ // Trigger button — full width, flex row, no browser button styling.
55
+ // Anchors heading-adjacent typography (body family, large size, semibold)
56
+ // so the label reads as a section header regardless of where the
57
+ // Collapsible is placed. External themes retarget it independently from the
58
+ // content via the `astryx-collapsible-trigger` target — e.g. a heading font
59
+ // on the trigger while the content stays on the body font.
55
60
  trigger: {
56
61
  all: 'unset',
57
62
  boxSizing: 'border-box',
@@ -113,7 +118,7 @@ const styles = stylex.create({
113
118
  // Anchors body typography so revealed text renders at the system's body
114
119
  // scale (family/size/weight/leading) instead of inheriting from wherever
115
120
  // the Collapsible is placed. External themes override via the
116
- // `astryx-collapsible-content` target.
121
+ // `astryx-collapsible-content` target, independently from the trigger.
117
122
  content: {
118
123
  paddingBlockStart: spacingVars['--spacing-1'],
119
124
  fontFamily: typographyVars['--font-family-body'],
@@ -318,10 +323,15 @@ export function Collapsible({
318
323
  // the system-wide disabled convention (never native `disabled`, which
319
324
  // would swallow events like a wrapping tooltip's hover).
320
325
  tabIndex={isDisabled ? -1 : undefined}
321
- {...stylex.props(
322
- styles.trigger,
323
- density != null && triggerDensity[density],
324
- isDisabled && styles.triggerDisabled,
326
+ {...mergeProps(
327
+ themeProps('collapsible-trigger', {
328
+ density: density ?? undefined,
329
+ }),
330
+ stylex.props(
331
+ styles.trigger,
332
+ density != null && triggerDensity[density],
333
+ isDisabled && styles.triggerDisabled,
334
+ ),
325
335
  )}>
326
336
  <span {...stylex.props(styles.triggerLabel)}>{trigger}</span>
327
337
  <span
@@ -8,10 +8,11 @@
8
8
  */
9
9
 
10
10
  import {describe, it, expect, vi, beforeEach} from 'vitest';
11
- import {render, screen, waitFor, fireEvent} from '@testing-library/react';
11
+ import {render, screen, waitFor, fireEvent, act} from '@testing-library/react';
12
12
  import {CommandPalette} from './CommandPalette';
13
13
  import {createStaticSource} from '@astryxdesign/core/Typeahead';
14
14
  import type {SearchSource, SearchableItem} from '@astryxdesign/core/Typeahead';
15
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
15
16
 
16
17
  const simpleSource = createStaticSource([
17
18
  {id: 'home', label: 'Home'},
@@ -35,6 +36,9 @@ beforeEach(() => {
35
36
  HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
36
37
  this.removeAttribute('open');
37
38
  });
39
+ // The live regions are module-level singletons; reset them so
40
+ // announcements from one test don't leak into the next.
41
+ __resetLiveRegionsForTest();
38
42
  });
39
43
 
40
44
  describe('CommandPalette', () => {
@@ -269,4 +273,94 @@ describe('CommandPalette', () => {
269
273
  expect(screen.getByText('No results')).toBeInTheDocument(),
270
274
  );
271
275
  });
276
+
277
+ describe('screen reader announcements', () => {
278
+ const politeRegion = () =>
279
+ document.querySelector('[data-astryx-live-region="polite"]');
280
+
281
+ it('announces the result count politely after typing a query', async () => {
282
+ render(
283
+ <CommandPalette
284
+ isOpen={true}
285
+ onOpenChange={() => {}}
286
+ searchSource={simpleSource}
287
+ />,
288
+ );
289
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
290
+ // "e" matches both Home and Settings.
291
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'e'}});
292
+ await waitFor(() => {
293
+ expect(politeRegion()).toHaveTextContent('2 results');
294
+ });
295
+ });
296
+
297
+ it('announces the singular form when one item matches', async () => {
298
+ render(
299
+ <CommandPalette
300
+ isOpen={true}
301
+ onOpenChange={() => {}}
302
+ searchSource={simpleSource}
303
+ />,
304
+ );
305
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
306
+ // "set" matches only Settings. Anchored so it cannot pass on "1 results".
307
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'set'}});
308
+ await waitFor(() => {
309
+ expect(politeRegion()).toHaveTextContent(/^1 result$/);
310
+ });
311
+ });
312
+
313
+ it('announces the empty state with the query when nothing matches', async () => {
314
+ render(
315
+ <CommandPalette
316
+ isOpen={true}
317
+ onOpenChange={() => {}}
318
+ searchSource={simpleSource}
319
+ />,
320
+ );
321
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
322
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'zzz'}});
323
+ await waitFor(() => {
324
+ expect(politeRegion()).toHaveTextContent('No results for zzz');
325
+ });
326
+ });
327
+
328
+ it('announces loading politely while a search is in flight', async () => {
329
+ // A source whose searches never resolve, so the palette stays busy and
330
+ // only the loading announcement can reach the live region.
331
+ const source: SearchSource = {
332
+ bootstrap: () => [],
333
+ async search(): Promise<SearchableItem[]> {
334
+ return new Promise<SearchableItem[]>(() => {});
335
+ },
336
+ };
337
+ render(
338
+ <CommandPalette
339
+ isOpen={true}
340
+ onOpenChange={() => {}}
341
+ searchSource={source}
342
+ />,
343
+ );
344
+ fireEvent.change(screen.getByRole('combobox'), {target: {value: 'z'}});
345
+ await waitFor(() => {
346
+ expect(politeRegion()).toHaveTextContent('Loading');
347
+ });
348
+ });
349
+
350
+ it('does not announce on initial open (bootstrap)', async () => {
351
+ render(
352
+ <CommandPalette
353
+ isOpen={true}
354
+ onOpenChange={() => {}}
355
+ searchSource={simpleSource}
356
+ />,
357
+ );
358
+ await waitFor(() => expect(screen.getByText('Home')).toBeInTheDocument());
359
+ // Flush effects and any pending live-region rAF writes.
360
+ await act(async () => {
361
+ await new Promise(resolve => setTimeout(resolve, 50));
362
+ });
363
+ expect(politeRegion()?.textContent ?? '').toBe('');
364
+ });
365
+ });
272
366
  });
@@ -3,7 +3,7 @@
3
3
  'use client';
4
4
  /**
5
5
  * @file CommandPalette.tsx
6
- * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox
6
+ * @input Uses React, Dialog, Layout, CommandPaletteContext, SearchSource, useCombobox, useAnnounce
7
7
  * @output Exports CommandPalette root component and props
8
8
  * @position Core root component; dialog shell with searchSource-driven items
9
9
  *
@@ -36,6 +36,7 @@ import {CommandPaletteInput} from './CommandPaletteInput';
36
36
  import {CommandPaletteFooter} from './CommandPaletteFooter';
37
37
  import {CommandPaletteEmpty} from './CommandPaletteEmpty';
38
38
  import type {BaseProps} from '../BaseProps';
39
+ import {useAnnounce} from '../hooks/useAnnounce';
39
40
  import {useTranslator} from '../i18n';
40
41
 
41
42
  export interface CommandPaletteProps<
@@ -291,6 +292,13 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
291
292
  const isBusy = isPending;
292
293
  const searchVersionRef = useRef(0);
293
294
 
295
+ // Announce search status to screen readers through the shared polite live
296
+ // region (comboboxes-7 announce path, mirroring Selector / BaseTypeahead).
297
+ // The busy spinner and the empty states are otherwise purely visual, so a
298
+ // screen-reader user typing a query would hear nothing when loading starts
299
+ // or results disappear.
300
+ const announce = useAnnounce();
301
+
294
302
  const value = controlledValue ?? internalValue;
295
303
 
296
304
  const setValue = useCallback(
@@ -318,8 +326,12 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
318
326
  setInternalValue('');
319
327
  }
320
328
  searchSource.cancel?.();
329
+ // Clear any lingering result / loading announcement when the palette
330
+ // closes so stale status text does not linger in the a11y tree
331
+ // (matching Selector's onHide).
332
+ announce('');
321
333
  onOpenChange(false);
322
- }, [onOpenChange, searchSource, controlledValue]);
334
+ }, [onOpenChange, searchSource, controlledValue, announce]);
323
335
 
324
336
  const selectItem = useCallback(
325
337
  (itemValue: string) => {
@@ -355,6 +367,14 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
355
367
  startTransition(async () => {
356
368
  const isBootstrap = query === '';
357
369
 
370
+ // Loading started for a user query: tell screen-reader users the
371
+ // spinner appeared. The polite region coalesces rapid updates, so for
372
+ // fast sources the result-count announcement below simply replaces
373
+ // this instead of stacking one "Loading" per keystroke.
374
+ if (!isBootstrap) {
375
+ announce(t('@astryx.commandPalette.loading'));
376
+ }
377
+
358
378
  // Client-filter previous results for instant narrowing while fetch is in flight
359
379
  if (!isBootstrap && searchResults.length > 0) {
360
380
  const lower = query.toLowerCase().trim();
@@ -377,6 +397,23 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
377
397
  setOptimisticResults(items);
378
398
  setSearchResults(items);
379
399
 
400
+ // Announce the outcome from the search commit (not a reactive
401
+ // effect), matching Selector / BaseTypeahead: exactly one
402
+ // announcement per committed query, and the version check above
403
+ // already discards stale keystrokes. Bootstrap stays silent — the
404
+ // same role PowerSearch's mount guard plays — so opening the
405
+ // palette announces nothing; clearing the query only clears any
406
+ // lingering status text.
407
+ if (isBootstrap) {
408
+ announce('');
409
+ } else if (items.length === 0) {
410
+ announce(t('@astryx.commandPalette.noResultsFor', {query}));
411
+ } else {
412
+ announce(
413
+ t('@astryx.commandPalette.resultCount', {count: items.length}),
414
+ );
415
+ }
416
+
380
417
  // When opening with a preselected value, highlight it once
381
418
  // bootstrap results arrive. No value → highlight stays at -1
382
419
  // and ArrowDown naturally moves to the first item.
@@ -396,6 +433,8 @@ export function CommandPalette<T extends SearchableItem = SearchableItem>({
396
433
  value,
397
434
  combobox,
398
435
  setOptimisticResults,
436
+ announce,
437
+ t,
399
438
  ],
400
439
  );
401
440
 
@@ -43,7 +43,7 @@ const styles = stylex.create({
43
43
  backgroundColor: 'transparent',
44
44
  border: 'none',
45
45
  cursor: 'pointer',
46
- textAlign: 'left' as const,
46
+ textAlign: 'start' as const,
47
47
  outline: 'none',
48
48
  userSelect: 'none',
49
49
  },