@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241

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 (414) hide show
  1. package/dist/Avatar/Avatar.d.ts +6 -1
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +46 -12
  4. package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
  5. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
  8. package/dist/Breadcrumbs/index.d.ts +1 -0
  9. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  10. package/dist/Breadcrumbs/index.js +2 -1
  11. package/dist/Calendar/Calendar.d.ts.map +1 -1
  12. package/dist/Calendar/Calendar.js +50 -9
  13. package/dist/Calendar/dayCellUtils.d.ts +4 -4
  14. package/dist/Calendar/dayCellUtils.js +2 -2
  15. package/dist/Calendar/styles.d.ts +30 -31
  16. package/dist/Calendar/styles.d.ts.map +1 -1
  17. package/dist/Calendar/styles.js +30 -31
  18. package/dist/Card/Card.js +1 -1
  19. package/dist/Carousel/Carousel.d.ts.map +1 -1
  20. package/dist/Carousel/Carousel.js +22 -12
  21. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  22. package/dist/Chat/ChatDictationButton.js +3 -1
  23. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  24. package/dist/Chat/ChatMessageBubble.js +8 -8
  25. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  26. package/dist/Chat/ChatToolCalls.js +10 -3
  27. package/dist/CheckboxInput/CheckboxInput.js +4 -4
  28. package/dist/Citation/Citation.d.ts +22 -1
  29. package/dist/Citation/Citation.d.ts.map +1 -1
  30. package/dist/Citation/Citation.js +20 -6
  31. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  32. package/dist/CodeBlock/CodeBlock.js +13 -7
  33. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  34. package/dist/Collapsible/Collapsible.js +3 -1
  35. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  36. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  37. package/dist/CommandPalette/CommandPalette.js +42 -3
  38. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  39. package/dist/ContextMenu/ContextMenu.js +14 -5
  40. package/dist/ContextMenu/index.d.ts +1 -0
  41. package/dist/ContextMenu/index.d.ts.map +1 -1
  42. package/dist/ContextMenu/index.js +3 -1
  43. package/dist/DateInput/DateInput.d.ts +1 -0
  44. package/dist/DateInput/DateInput.d.ts.map +1 -1
  45. package/dist/DateInput/DateInput.js +31 -19
  46. package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
  47. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  48. package/dist/DateRangeInput/DateRangeInput.js +20 -19
  49. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  50. package/dist/DateTimeInput/DateTimeInput.js +23 -18
  51. package/dist/Divider/Divider.d.ts +2 -2
  52. package/dist/Divider/Divider.d.ts.map +1 -1
  53. package/dist/Divider/Divider.js +13 -0
  54. package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
  55. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  56. package/dist/DropdownMenu/DropdownMenu.js +22 -5
  57. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
  59. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  60. package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
  61. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  62. package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
  63. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
  64. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
  65. package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
  66. package/dist/DropdownMenu/index.d.ts +10 -0
  67. package/dist/DropdownMenu/index.d.ts.map +1 -1
  68. package/dist/DropdownMenu/index.js +13 -0
  69. package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
  70. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  71. package/dist/DropdownMenu/menuItemHover.js +33 -0
  72. package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
  73. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
  74. package/dist/DropdownMenu/menuItemRoles.js +10 -1
  75. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  76. package/dist/DropdownMenu/renderDropdownItems.js +21 -6
  77. package/dist/Field/Field.d.ts +3 -1
  78. package/dist/Field/Field.d.ts.map +1 -1
  79. package/dist/Field/Field.js +4 -1
  80. package/dist/Field/inputStyles.stylex.d.ts +1 -0
  81. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  82. package/dist/Field/inputStyles.stylex.js +1 -0
  83. package/dist/FieldStatus/FieldStatus.d.ts +4 -1
  84. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  85. package/dist/FieldStatus/FieldStatus.js +4 -2
  86. package/dist/FileInput/FileInput.d.ts +1 -0
  87. package/dist/FileInput/FileInput.d.ts.map +1 -1
  88. package/dist/FileInput/FileInput.js +91 -65
  89. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  90. package/dist/HoverCard/useHoverCard.js +10 -4
  91. package/dist/Icon/Icon.d.ts +15 -1
  92. package/dist/Icon/Icon.d.ts.map +1 -1
  93. package/dist/Icon/Icon.js +19 -3
  94. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  95. package/dist/Lightbox/Lightbox.js +15 -9
  96. package/dist/List/List.d.ts.map +1 -1
  97. package/dist/List/List.js +6 -3
  98. package/dist/Markdown/Markdown.d.ts.map +1 -1
  99. package/dist/Markdown/Markdown.js +7 -0
  100. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  101. package/dist/MultiSelector/MultiSelector.js +65 -40
  102. package/dist/NumberInput/NumberInput.d.ts +1 -0
  103. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  104. package/dist/NumberInput/NumberInput.js +20 -17
  105. package/dist/Outline/Outline.js +2 -2
  106. package/dist/RadioList/RadioList.d.ts +2 -2
  107. package/dist/RadioList/RadioListItem.js +4 -4
  108. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  109. package/dist/Resizable/ResizeHandle.js +49 -19
  110. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  111. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  112. package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
  113. package/dist/Selector/Selector.d.ts.map +1 -1
  114. package/dist/Selector/Selector.js +58 -16
  115. package/dist/SideNav/SideNav.d.ts.map +1 -1
  116. package/dist/SideNav/SideNav.js +5 -5
  117. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  118. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  119. package/dist/SideNav/SideNavItem.d.ts +1 -1
  120. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  121. package/dist/SideNav/SideNavItem.js +12 -10
  122. package/dist/Slider/Slider.d.ts.map +1 -1
  123. package/dist/Slider/Slider.js +87 -34
  124. package/dist/Spinner/Spinner.d.ts +3 -2
  125. package/dist/Spinner/Spinner.d.ts.map +1 -1
  126. package/dist/Spinner/Spinner.js +11 -2
  127. package/dist/Switch/Switch.d.ts +8 -1
  128. package/dist/Switch/Switch.d.ts.map +1 -1
  129. package/dist/Switch/Switch.js +160 -91
  130. package/dist/TabList/TabList.d.ts +2 -11
  131. package/dist/TabList/TabList.d.ts.map +1 -1
  132. package/dist/TabList/TabList.js +5 -9
  133. package/dist/TabList/TabListContext.d.ts +0 -5
  134. package/dist/TabList/TabListContext.d.ts.map +1 -1
  135. package/dist/TabList/TabListContext.js +0 -5
  136. package/dist/Table/BaseTable.d.ts.map +1 -1
  137. package/dist/Table/BaseTable.js +30 -4
  138. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  139. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  140. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  141. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
  142. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  143. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  144. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  145. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +14 -6
  146. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  147. package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
  148. package/dist/Table/types.d.ts +32 -0
  149. package/dist/Table/types.d.ts.map +1 -1
  150. package/dist/TextArea/TextArea.d.ts +1 -0
  151. package/dist/TextArea/TextArea.d.ts.map +1 -1
  152. package/dist/TextArea/TextArea.js +16 -19
  153. package/dist/TextInput/TextInput.d.ts +1 -0
  154. package/dist/TextInput/TextInput.d.ts.map +1 -1
  155. package/dist/TextInput/TextInput.js +18 -17
  156. package/dist/TimeInput/TimeInput.d.ts +1 -0
  157. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  158. package/dist/TimeInput/TimeInput.js +15 -17
  159. package/dist/Timestamp/Timestamp.d.ts +31 -3
  160. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  161. package/dist/Timestamp/Timestamp.js +47 -76
  162. package/dist/Timestamp/formatInstant.d.ts +41 -0
  163. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  164. package/dist/Timestamp/formatInstant.js +177 -0
  165. package/dist/Timestamp/index.d.ts +1 -0
  166. package/dist/Timestamp/index.d.ts.map +1 -1
  167. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  168. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  169. package/dist/Timestamp/tooltipEntries.js +163 -0
  170. package/dist/Token/Token.d.ts +30 -3
  171. package/dist/Token/Token.d.ts.map +1 -1
  172. package/dist/Token/Token.js +17 -2
  173. package/dist/Token/index.d.ts +1 -1
  174. package/dist/Token/index.d.ts.map +1 -1
  175. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  176. package/dist/Tokenizer/Tokenizer.js +2 -2
  177. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  178. package/dist/Tooltip/useTooltip.js +17 -1
  179. package/dist/TopNav/TopNavItem.d.ts +3 -1
  180. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  181. package/dist/TopNav/TopNavItem.js +30 -3
  182. package/dist/TreeList/TreeList.d.ts.map +1 -1
  183. package/dist/TreeList/TreeList.js +1 -0
  184. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  185. package/dist/TreeList/TreeListBranches.js +8 -6
  186. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  187. package/dist/TreeList/TreeListItem.js +32 -14
  188. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  189. package/dist/Typeahead/BaseTypeahead.js +19 -10
  190. package/dist/Typeahead/Typeahead.js +1 -1
  191. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  192. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  193. package/dist/Typeahead/TypeaheadItem.js +20 -10
  194. package/dist/astryx.css +38 -14
  195. package/dist/astryx.umd.js +52 -52
  196. package/dist/astryx.umd.js.map +4 -4
  197. package/dist/hooks/index.d.ts +2 -0
  198. package/dist/hooks/index.d.ts.map +1 -1
  199. package/dist/hooks/index.js +1 -0
  200. package/dist/hooks/isRtlElement.d.ts +18 -0
  201. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  202. package/dist/hooks/isRtlElement.js +27 -0
  203. package/dist/hooks/useAnnounce.d.ts +3 -0
  204. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  205. package/dist/hooks/useAnnounce.js +39 -1
  206. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  207. package/dist/hooks/useFocusTrap.js +52 -11
  208. package/dist/hooks/useGridFocus.d.ts +6 -1
  209. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  210. package/dist/hooks/useGridFocus.js +12 -9
  211. package/dist/hooks/useHotkeys.d.ts +9 -0
  212. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  213. package/dist/hooks/useHotkeys.js +9 -0
  214. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  215. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  216. package/dist/hooks/useInputStatusIcon.js +158 -0
  217. package/dist/hooks/useListFocus.d.ts +39 -1
  218. package/dist/hooks/useListFocus.d.ts.map +1 -1
  219. package/dist/hooks/useListFocus.js +54 -9
  220. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  221. package/dist/theme/derivedVarRegistry.js +13 -0
  222. package/dist/utils/index.d.ts +1 -0
  223. package/dist/utils/index.d.ts.map +1 -1
  224. package/dist/utils/index.js +2 -1
  225. package/dist/utils/plainDate.d.ts +17 -0
  226. package/dist/utils/plainDate.d.ts.map +1 -1
  227. package/dist/utils/plainDate.js +23 -4
  228. package/dist/utils/rtlStyles.d.ts +24 -0
  229. package/dist/utils/rtlStyles.d.ts.map +1 -0
  230. package/dist/utils/rtlStyles.js +27 -0
  231. package/locales/en.json +76 -0
  232. package/locales/pseudo.json +57 -0
  233. package/package.json +6 -1
  234. package/src/Avatar/Avatar.doc.mjs +18 -4
  235. package/src/Avatar/Avatar.test.tsx +106 -0
  236. package/src/Avatar/Avatar.tsx +62 -10
  237. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  238. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  239. package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
  240. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  241. package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
  242. package/src/Breadcrumbs/index.ts +4 -0
  243. package/src/Button/Button.doc.mjs +0 -4
  244. package/src/Calendar/Calendar.test.tsx +170 -20
  245. package/src/Calendar/Calendar.tsx +73 -16
  246. package/src/Calendar/dayCellUtils.test.ts +18 -18
  247. package/src/Calendar/dayCellUtils.ts +2 -2
  248. package/src/Calendar/styles.ts +33 -39
  249. package/src/Card/Card.tsx +1 -1
  250. package/src/Carousel/Carousel.tsx +25 -6
  251. package/src/Chat/ChatComposer.doc.mjs +2 -2
  252. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  253. package/src/Chat/ChatDictationButton.tsx +7 -1
  254. package/src/Chat/ChatMessageBubble.tsx +15 -10
  255. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  256. package/src/Chat/ChatToolCalls.tsx +9 -2
  257. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  258. package/src/CheckboxInput/CheckboxInput.tsx +4 -4
  259. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  260. package/src/Citation/Citation.doc.mjs +4 -3
  261. package/src/Citation/Citation.test.tsx +97 -0
  262. package/src/Citation/Citation.tsx +43 -11
  263. package/src/CodeBlock/CodeBlock.tsx +35 -8
  264. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  265. package/src/Collapsible/Collapsible.test.tsx +7 -0
  266. package/src/Collapsible/Collapsible.tsx +16 -6
  267. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  268. package/src/CommandPalette/CommandPalette.tsx +41 -2
  269. package/src/ContextMenu/ContextMenu.test.tsx +42 -0
  270. package/src/ContextMenu/ContextMenu.tsx +13 -11
  271. package/src/ContextMenu/index.ts +5 -0
  272. package/src/DateInput/DateInput.doc.mjs +15 -5
  273. package/src/DateInput/DateInput.test.tsx +210 -3
  274. package/src/DateInput/DateInput.tsx +41 -29
  275. package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
  276. package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
  277. package/src/DateRangeInput/DateRangeInput.tsx +29 -27
  278. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  279. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  280. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  281. package/src/Divider/Divider.test.tsx +32 -8
  282. package/src/Divider/Divider.tsx +14 -1
  283. package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
  284. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  285. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
  286. package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
  287. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
  288. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  289. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
  290. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
  291. package/src/DropdownMenu/index.ts +16 -0
  292. package/src/DropdownMenu/menuItemHover.ts +39 -0
  293. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  294. package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
  295. package/src/Field/Field.tsx +14 -9
  296. package/src/Field/inputStyles.stylex.ts +2 -0
  297. package/src/FieldStatus/FieldStatus.tsx +10 -4
  298. package/src/FileInput/FileInput.doc.mjs +13 -7
  299. package/src/FileInput/FileInput.test.tsx +84 -16
  300. package/src/FileInput/FileInput.tsx +96 -79
  301. package/src/HoverCard/useHoverCard.tsx +15 -3
  302. package/src/Icon/Icon.doc.mjs +13 -0
  303. package/src/Icon/Icon.test.tsx +89 -0
  304. package/src/Icon/Icon.tsx +51 -3
  305. package/src/Lightbox/Lightbox.tsx +11 -3
  306. package/src/List/List.test.tsx +17 -29
  307. package/src/List/List.tsx +6 -1
  308. package/src/Markdown/Markdown.test.tsx +21 -0
  309. package/src/Markdown/Markdown.tsx +4 -0
  310. package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
  311. package/src/MultiSelector/MultiSelector.test.tsx +306 -3
  312. package/src/MultiSelector/MultiSelector.tsx +88 -57
  313. package/src/NumberInput/NumberInput.doc.mjs +11 -5
  314. package/src/NumberInput/NumberInput.test.tsx +48 -0
  315. package/src/NumberInput/NumberInput.tsx +25 -26
  316. package/src/Outline/Outline.test.tsx +12 -12
  317. package/src/Outline/Outline.tsx +2 -2
  318. package/src/RadioList/RadioList.doc.mjs +6 -0
  319. package/src/RadioList/RadioList.tsx +2 -2
  320. package/src/RadioList/RadioListItem.tsx +4 -4
  321. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  322. package/src/Resizable/ResizeHandle.tsx +70 -20
  323. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
  324. package/src/Selector/Selector.doc.mjs +8 -0
  325. package/src/Selector/Selector.test.tsx +328 -3
  326. package/src/Selector/Selector.tsx +70 -19
  327. package/src/SideNav/SideNav.tsx +1 -8
  328. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  329. package/src/SideNav/SideNavItem.tsx +5 -3
  330. package/src/Slider/Slider.doc.mjs +6 -0
  331. package/src/Slider/Slider.test.tsx +240 -12
  332. package/src/Slider/Slider.tsx +96 -23
  333. package/src/Spinner/Spinner.test.tsx +10 -8
  334. package/src/Spinner/Spinner.tsx +15 -5
  335. package/src/Switch/Switch.doc.mjs +142 -42
  336. package/src/Switch/Switch.test.tsx +22 -0
  337. package/src/Switch/Switch.tsx +90 -33
  338. package/src/TabList/TabList.doc.mjs +0 -6
  339. package/src/TabList/TabList.test.tsx +3 -15
  340. package/src/TabList/TabList.tsx +6 -17
  341. package/src/TabList/TabListContext.ts +0 -6
  342. package/src/Table/BaseTable.tsx +34 -2
  343. package/src/Table/Table.doc.mjs +11 -0
  344. package/src/Table/Table.test.tsx +85 -0
  345. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  346. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
  347. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  348. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  349. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +37 -8
  350. package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
  351. package/src/Table/types.ts +33 -0
  352. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  353. package/src/TextArea/TextArea.doc.mjs +11 -5
  354. package/src/TextArea/TextArea.tsx +17 -26
  355. package/src/TextInput/TextInput.doc.mjs +11 -5
  356. package/src/TextInput/TextInput.test.tsx +130 -3
  357. package/src/TextInput/TextInput.tsx +18 -25
  358. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  359. package/src/TimeInput/TimeInput.doc.mjs +11 -5
  360. package/src/TimeInput/TimeInput.tsx +14 -25
  361. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  362. package/src/Timestamp/Timestamp.test.tsx +371 -0
  363. package/src/Timestamp/Timestamp.tsx +96 -93
  364. package/src/Timestamp/formatInstant.ts +218 -0
  365. package/src/Timestamp/index.ts +4 -0
  366. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  367. package/src/Timestamp/tooltipEntries.ts +210 -0
  368. package/src/Token/Token.test.tsx +21 -15
  369. package/src/Token/Token.tsx +31 -14
  370. package/src/Token/index.ts +1 -1
  371. package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
  372. package/src/Tokenizer/Tokenizer.tsx +4 -2
  373. package/src/Tooltip/Tooltip.test.tsx +46 -0
  374. package/src/Tooltip/useTooltip.tsx +20 -0
  375. package/src/TopNav/TopNav.test.tsx +93 -2
  376. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  377. package/src/TopNav/TopNavItem.tsx +35 -6
  378. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  379. package/src/TreeList/TreeList.doc.mjs +6 -0
  380. package/src/TreeList/TreeList.test.tsx +50 -10
  381. package/src/TreeList/TreeList.tsx +5 -0
  382. package/src/TreeList/TreeListBranches.tsx +8 -6
  383. package/src/TreeList/TreeListItem.tsx +33 -11
  384. package/src/Typeahead/BaseTypeahead.tsx +29 -14
  385. package/src/Typeahead/Typeahead.doc.mjs +6 -0
  386. package/src/Typeahead/Typeahead.test.tsx +27 -0
  387. package/src/Typeahead/Typeahead.tsx +1 -1
  388. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  389. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  390. package/src/doc-mjs.d.ts +19 -0
  391. package/src/hooks/index.ts +6 -0
  392. package/src/hooks/isRtlElement.ts +27 -0
  393. package/src/hooks/useAnnounce.test.tsx +75 -2
  394. package/src/hooks/useAnnounce.ts +47 -1
  395. package/src/hooks/useFocusTrap.test.tsx +104 -0
  396. package/src/hooks/useFocusTrap.ts +61 -12
  397. package/src/hooks/useGridFocus.doc.mjs +3 -3
  398. package/src/hooks/useGridFocus.test.tsx +41 -0
  399. package/src/hooks/useGridFocus.ts +21 -10
  400. package/src/hooks/useHotkeys.ts +9 -0
  401. package/src/hooks/useInputStatusIcon.test.tsx +162 -0
  402. package/src/hooks/useInputStatusIcon.tsx +224 -0
  403. package/src/hooks/useListFocus.doc.mjs +25 -3
  404. package/src/hooks/useListFocus.test.tsx +153 -1
  405. package/src/hooks/useListFocus.ts +102 -9
  406. package/src/reset.css +0 -26
  407. package/src/reset.test.ts +60 -0
  408. package/src/theme/derivedVarRegistry.test.ts +1 -0
  409. package/src/theme/derivedVarRegistry.ts +6 -0
  410. package/src/theme/generateThemeRules.test.ts +42 -0
  411. package/src/utils/index.ts +2 -0
  412. package/src/utils/plainDate.test.ts +24 -6
  413. package/src/utils/plainDate.ts +18 -4
  414. package/src/utils/rtlStyles.ts +27 -0
@@ -41,8 +41,9 @@ import {Button} from '../Button';
41
41
  import {Icon} from '../Icon';
42
42
  import {useLayer} from '../Layer';
43
43
  import {useScrollOverflow} from '../hooks/useScrollOverflow';
44
+ import {isRtlElement} from '../hooks/isRtlElement';
44
45
  import type {BaseProps} from '../BaseProps';
45
- import {mergeProps, mergeRefs} from '../utils';
46
+ import {mergeProps, mergeRefs, rtlStyles} from '../utils';
46
47
  import type {SpacingStep} from '../utils/types';
47
48
  import {themeProps} from '../utils/themeProps';
48
49
  import {useTranslator} from '../i18n';
@@ -166,10 +167,16 @@ const styles = stylex.create({
166
167
  transitionTimingFunction: easeVars['--ease-standard'],
167
168
  },
168
169
  buttonPillStart: {
169
- transform: 'translateX(-50%)',
170
+ transform: {
171
+ default: 'translateX(-50%)',
172
+ ':is([dir="rtl"] *)': 'translateX(50%)',
173
+ },
170
174
  },
171
175
  buttonPillEnd: {
172
- transform: 'translateX(50%)',
176
+ transform: {
177
+ default: 'translateX(50%)',
178
+ ':is([dir="rtl"] *)': 'translateX(-50%)',
179
+ },
173
180
  },
174
181
  buttonHidden: {
175
182
  opacity: 0,
@@ -347,7 +354,11 @@ export function Carousel({
347
354
  typeof window.matchMedia === 'function' &&
348
355
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;
349
356
  el.scrollBy({
350
- left: direction * Math.max(amount, itemWidth),
357
+ // `direction` is the logical intent (-1 = toward content start, +1 =
358
+ // toward content end). Under RTL the scroll axis is inverted (start is
359
+ // scrollLeft 0, the end is negative), so flip the physical delta sign.
360
+ left:
361
+ (isRtlElement(el) ? -1 : 1) * direction * Math.max(amount, itemWidth),
351
362
  behavior: prefersReducedMotion ? 'auto' : 'smooth',
352
363
  });
353
364
  }, []);
@@ -425,7 +436,11 @@ export function Carousel({
425
436
  !overflowStart && styles.buttonHidden,
426
437
  )}>
427
438
  <Button
428
- icon={<Icon icon="chevronLeft" size="xsm" />}
439
+ icon={
440
+ <span {...stylex.props(rtlStyles.mirror)}>
441
+ <Icon icon="chevronLeft" size="xsm" />
442
+ </span>
443
+ }
429
444
  label={t('@astryx.carousel.scrollLeft')}
430
445
  variant="ghost"
431
446
  size="sm"
@@ -446,7 +461,11 @@ export function Carousel({
446
461
  !overflowEnd && styles.buttonHidden,
447
462
  )}>
448
463
  <Button
449
- icon={<Icon icon="chevronRight" size="xsm" />}
464
+ icon={
465
+ <span {...stylex.props(rtlStyles.mirror)}>
466
+ <Icon icon="chevronRight" size="xsm" />
467
+ </span>
468
+ }
450
469
  label={t('@astryx.carousel.scrollRight')}
451
470
  variant="ghost"
452
471
  size="sm"
@@ -60,7 +60,7 @@ export const docs = {
60
60
  name: 'elevation',
61
61
  type: "'none' | 'low'",
62
62
  description:
63
- "Resting elevation of the composer body. `low` (the default) keeps today's raised look — low at rest, bumping to med on hover / focus. `none` flattens it and draws a border with the same rest / hover / focus treatment as a text input (emphasized border → accent on focus, matching inset rings).",
63
+ "Resting elevation of the composer body. `low` (the default) keeps today's raised look: low at rest, bumping to med on hover / focus. `none` flattens it and draws a border with the same rest / hover / focus treatment as a text input (emphasized border → accent on focus, matching inset rings).",
64
64
  default: "'low'",
65
65
  },
66
66
  {
@@ -109,7 +109,7 @@ export const docs = {
109
109
  {
110
110
  name: 'input',
111
111
  type: 'ReactNode',
112
- description: 'Slot: custom input element. Replaces the default input. Use ChatComposerInput for trigger menus/tokens, or wire any input (plain textarea, rich editor) to the composition contract via useChatComposerContext() — read value/onChange/onSubmit/canSend and register a focus control on inputControlRef so body-click-to-focus works.',
112
+ description: 'Slot: custom input element. Replaces the default input. Use ChatComposerInput for trigger menus/tokens, or wire any input (plain textarea, rich editor) to the composition contract via useChatComposerContext(): read value/onChange/onSubmit/canSend and register a focus control on inputControlRef so body-click-to-focus works.',
113
113
  slotElements: [
114
114
  {
115
115
  __element: 'TextInput',
@@ -83,7 +83,7 @@ export const docs = {
83
83
  {
84
84
  name: 'onKeyDown',
85
85
  type: '(event) => void',
86
- description: 'Key-down handler invoked before the built-in Enter/history behavior (after any open trigger menu). The seam for platform-specific keys: call event.preventDefault() to suppress the default submit (e.g. newline on a touch keyboard), or act on the event yourself to add behavior (e.g. submit on Cmd/Ctrl+Enter). IME composition is always respected — Enter never submits mid-composition.',
86
+ description: 'Key-down handler invoked before the built-in Enter/history behavior (after any open trigger menu). The seam for platform-specific keys: call event.preventDefault() to suppress the default submit (e.g. newline on a touch keyboard), or act on the event yourself to add behavior (e.g. submit on Cmd/Ctrl+Enter). IME composition is always respected; Enter never submits mid-composition.',
87
87
  },
88
88
  ],
89
89
  };
@@ -26,6 +26,7 @@ import {Button} from '../Button';
26
26
  import {Icon} from '../Icon';
27
27
  import {mergeProps} from '../utils';
28
28
  import type {BaseProps} from '../BaseProps';
29
+ import {useTranslator} from '../i18n';
29
30
 
30
31
  // =============================================================================
31
32
  // Types
@@ -110,13 +111,18 @@ export function ChatDictationButton({
110
111
  style,
111
112
  ...rest
112
113
  }: ChatDictationButtonProps) {
114
+ const t = useTranslator();
115
+
113
116
  if (isHiddenWhenUnsupported && !dictation.isSupported) {
114
117
  return null;
115
118
  }
116
119
 
117
120
  const {isListening, bands, volume: rawVolume} = dictation;
118
121
  const accessibleLabel =
119
- label ?? (isListening ? 'Stop dictation' : 'Start dictation');
122
+ label ??
123
+ (isListening
124
+ ? t('@astryx.chatDictationButton.stopDictation')
125
+ : t('@astryx.chatDictationButton.startDictation'));
120
126
 
121
127
  // Boost each band for visibility — quiet speech (0-10%) maps to full visual range
122
128
  const boostedBands = bands.map(b => Math.min(Math.pow(b / 0.2, 0.5), 1));
@@ -160,27 +160,32 @@ const styles = stylex.create({
160
160
  backgroundColor: 'transparent',
161
161
  color: colorVars['--color-text-primary'],
162
162
  },
163
- // Grouped bubble corners — assistant (left side tight)
163
+ // Grouped bubble corners — assistant (inline-start side tight).
164
+ // Logical radii so the tail follows reading direction: inline-start is the
165
+ // left edge under LTR and the right edge under RTL (assistant tucks toward
166
+ // the start of the line in both directions).
164
167
  groupFirstAssistant: {
165
- borderBottomLeftRadius: radiusVars['--radius-inner'],
168
+ borderEndStartRadius: radiusVars['--radius-inner'],
166
169
  },
167
170
  groupMiddleAssistant: {
168
- borderTopLeftRadius: radiusVars['--radius-inner'],
169
- borderBottomLeftRadius: radiusVars['--radius-inner'],
171
+ borderStartStartRadius: radiusVars['--radius-inner'],
172
+ borderEndStartRadius: radiusVars['--radius-inner'],
170
173
  },
171
174
  groupLastAssistant: {
172
- borderTopLeftRadius: radiusVars['--radius-inner'],
175
+ borderStartStartRadius: radiusVars['--radius-inner'],
173
176
  },
174
- // Grouped bubble corners — user (right side tight)
177
+ // Grouped bubble corners — user (inline-end side tight).
178
+ // Logical radii: inline-end is the right edge under LTR and the left edge
179
+ // under RTL (user tucks toward the end of the line in both directions).
175
180
  groupFirstUser: {
176
- borderBottomRightRadius: radiusVars['--radius-inner'],
181
+ borderEndEndRadius: radiusVars['--radius-inner'],
177
182
  },
178
183
  groupMiddleUser: {
179
- borderTopRightRadius: radiusVars['--radius-inner'],
180
- borderBottomRightRadius: radiusVars['--radius-inner'],
184
+ borderStartEndRadius: radiusVars['--radius-inner'],
185
+ borderEndEndRadius: radiusVars['--radius-inner'],
181
186
  },
182
187
  groupLastUser: {
183
- borderTopRightRadius: radiusVars['--radius-inner'],
188
+ borderStartEndRadius: radiusVars['--radius-inner'],
184
189
  },
185
190
  });
186
191
 
@@ -28,6 +28,11 @@ describe('ChatSystemMessage', () => {
28
28
  expect(screen.getByText('Today')).toBeTruthy();
29
29
  });
30
30
 
31
+ it('exposes the divider variant label as the separator accessible name', () => {
32
+ render(<ChatSystemMessage variant="divider">Today</ChatSystemMessage>);
33
+ expect(screen.getByRole('separator')).toHaveAccessibleName('Today');
34
+ });
35
+
31
36
  it('renders icon', () => {
32
37
  render(
33
38
  <ChatSystemMessage icon={<span data-testid="icon">*</span>}>
@@ -37,6 +37,7 @@ import {Icon, type IconName} from '../Icon';
37
37
  import {Spinner} from '../Spinner';
38
38
  import {VisuallyHidden} from '../VisuallyHidden';
39
39
  import {themeProps} from '../utils/themeProps';
40
+ import {useTranslator} from '../i18n';
40
41
 
41
42
  // =============================================================================
42
43
  // Types
@@ -363,6 +364,7 @@ function getToolCallKey(call: ChatToolCallItem): string {
363
364
  // =============================================================================
364
365
 
365
366
  function CallRow({call}: {call: ChatToolCallItem}) {
367
+ const t = useTranslator();
366
368
  const status = call.status ?? 'complete';
367
369
  const hasDetail = call.resultDetail != null;
368
370
  const [isDetailOpen, setIsDetailOpen] = useState(false);
@@ -414,7 +416,11 @@ function CallRow({call}: {call: ChatToolCallItem}) {
414
416
  // as real text so it reaches screen readers, keyboard, and touch
415
417
  // users. Rendering it inside the row also folds it into the
416
418
  // accessible name of expandable (role="button") rows.
417
- <VisuallyHidden>{`Error: ${call.errorMessage}`}</VisuallyHidden>
419
+ <VisuallyHidden>
420
+ {t('@astryx.chatToolCalls.error', {
421
+ message: call.errorMessage ?? '',
422
+ })}
423
+ </VisuallyHidden>
418
424
  )}
419
425
  </span>
420
426
  <span {...stylex.props(styles.callName)}>{call.name}</span>
@@ -498,6 +504,7 @@ function CallRow({call}: {call: ChatToolCallItem}) {
498
504
  * ```
499
505
  */
500
506
  export function ChatToolCalls(props: ChatToolCallsProps) {
507
+ const t = useTranslator();
501
508
  const {
502
509
  calls,
503
510
  label: _customLabel,
@@ -580,7 +587,7 @@ export function ChatToolCalls(props: ChatToolCallsProps) {
580
587
  <Icon icon="wrench" size="sm" color="inherit" />
581
588
  </span>
582
589
  <span {...stylex.props(styles.groupLabel)}>
583
- {calls.length} tool calls
590
+ {t('@astryx.chatToolCalls.groupLabel', {count: calls.length})}
584
591
  </span>
585
592
  </>
586
593
  ) : (
@@ -121,6 +121,12 @@ export const docs = {
121
121
  description:
122
122
  'Status indicator. Displays a colored message box below the checkbox and sets aria-invalid for errors.',
123
123
  },
124
+ {
125
+ name: 'width',
126
+ type: 'SizeValue',
127
+ description:
128
+ '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.',
129
+ },
124
130
  ],
125
131
  theming: {
126
132
  targets: [
@@ -198,12 +198,12 @@ const wrapperSizeStyles = stylex.create({
198
198
 
199
199
  const checkboxSizeStyles = stylex.create({
200
200
  sm: {
201
- width: 18,
202
- height: 18,
201
+ width: 20,
202
+ height: 20,
203
203
  },
204
204
  md: {
205
- width: 22,
206
- height: 22,
205
+ width: 24,
206
+ height: 24,
207
207
  },
208
208
  });
209
209
 
@@ -88,6 +88,12 @@ export const docs = {
88
88
  type: "{type: 'warning' | 'error' | 'success', message?: string}",
89
89
  description: 'Status indicator ({ type, message }).',
90
90
  },
91
+ {
92
+ name: 'width',
93
+ type: 'SizeValue',
94
+ description:
95
+ '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.',
96
+ },
91
97
  {
92
98
  name: 'xstyle',
93
99
  type: 'StyleXStyles',
@@ -39,7 +39,7 @@ export const docs = {
39
39
  name: 'Icon',
40
40
  required: false,
41
41
  description:
42
- 'An optional favicon or source icon displayed before the label text. Only available in the label variant.',
42
+ 'An optional source icon shown before the label text. Accepts a favicon/logo image URL (via source.src) or a React node such as an Astryx <Icon>. Only available in the label variant.',
43
43
  },
44
44
  {
45
45
  name: 'Label text',
@@ -71,7 +71,7 @@ export const docs = {
71
71
  name: 'source',
72
72
  type: 'CitationSource',
73
73
  description:
74
- 'The citation source object containing title, url, and optional icon.',
74
+ 'The citation source object containing title, url, an optional image src, and an optional icon node.',
75
75
  required: true,
76
76
  },
77
77
  {
@@ -121,7 +121,8 @@ export const docsDense = {
121
121
  ],
122
122
  },
123
123
  propDescriptions: {
124
- source: 'citation source object with title, url, optional icon.',
124
+ source:
125
+ 'citation source object with title, url, optional image src, and optional icon node.',
125
126
  number: 'display index for this citation.',
126
127
  variant: 'display style: label chip with source title or compact numbered badge.',
127
128
  },
@@ -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>
@@ -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',
@@ -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