@astryxdesign/core 0.1.6 → 0.1.7-canary.0a6de96

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 (521) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +2 -0
  3. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  4. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  5. package/dist/AlertDialog/AlertDialog.js +4 -1
  6. package/dist/AppShell/AppShell.d.ts.map +1 -1
  7. package/dist/AppShell/AppShell.js +3 -1
  8. package/dist/AvatarGroup/AvatarGroup.d.ts +1 -1
  9. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  10. package/dist/AvatarGroup/AvatarGroup.js +4 -1
  11. package/dist/Banner/Banner.d.ts.map +1 -1
  12. package/dist/Banner/Banner.js +8 -6
  13. package/dist/Breadcrumbs/Breadcrumbs.d.ts +1 -1
  14. package/dist/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
  15. package/dist/Breadcrumbs/Breadcrumbs.js +4 -1
  16. package/dist/Button/Button.d.ts +10 -2
  17. package/dist/Button/Button.d.ts.map +1 -1
  18. package/dist/Button/Button.js +49 -6
  19. package/dist/ButtonGroup/ButtonGroup.d.ts +20 -5
  20. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  21. package/dist/ButtonGroup/ButtonGroup.js +27 -11
  22. package/dist/Calendar/Calendar.d.ts.map +1 -1
  23. package/dist/Calendar/Calendar.js +29 -7
  24. package/dist/Carousel/Carousel.d.ts +1 -1
  25. package/dist/Carousel/Carousel.d.ts.map +1 -1
  26. package/dist/Carousel/Carousel.js +8 -5
  27. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  28. package/dist/Chat/ChatComposer.js +4 -1
  29. package/dist/Chat/ChatComposerDrawer.d.ts +1 -1
  30. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  31. package/dist/Chat/ChatComposerDrawer.js +9 -2
  32. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  33. package/dist/Chat/ChatComposerInput.js +6 -2
  34. package/dist/Chat/ChatLayout.d.ts +1 -11
  35. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  36. package/dist/Chat/ChatLayout.js +6 -4
  37. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  38. package/dist/Chat/ChatLayoutScrollButton.js +4 -2
  39. package/dist/Chat/ChatMessage.d.ts +1 -1
  40. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  41. package/dist/Chat/ChatMessage.js +8 -2
  42. package/dist/Chat/ChatMessageBubble.d.ts +1 -1
  43. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  44. package/dist/Chat/ChatMessageBubble.js +3 -1
  45. package/dist/Chat/ChatMessageList.d.ts +1 -1
  46. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  47. package/dist/Chat/ChatMessageList.js +3 -1
  48. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  49. package/dist/Chat/ChatMessageMetadata.js +13 -8
  50. package/dist/Chat/ChatPastedTextToken.d.ts.map +1 -1
  51. package/dist/Chat/ChatPastedTextToken.js +3 -1
  52. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  53. package/dist/Chat/ChatSendButton.js +3 -1
  54. package/dist/Chat/index.d.ts +1 -1
  55. package/dist/Chat/index.d.ts.map +1 -1
  56. package/dist/Chat/useChatStreamScroll.d.ts +12 -1
  57. package/dist/Chat/useChatStreamScroll.d.ts.map +1 -1
  58. package/dist/Chat/useChatStreamScroll.js +43 -6
  59. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  60. package/dist/Chat/useSpeechRecognition.js +4 -2
  61. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  62. package/dist/Chat/useTriggerMenu.js +4 -2
  63. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  64. package/dist/CheckboxInput/CheckboxInput.js +4 -2
  65. package/dist/CheckboxList/CheckboxList.d.ts +1 -1
  66. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  67. package/dist/CheckboxList/CheckboxList.js +3 -1
  68. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  69. package/dist/CheckboxList/CheckboxListItem.js +3 -1
  70. package/dist/Citation/Citation.d.ts.map +1 -1
  71. package/dist/Citation/Citation.js +10 -2
  72. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  73. package/dist/CodeBlock/CodeBlock.js +71 -53
  74. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  75. package/dist/Collapsible/Collapsible.js +8 -1
  76. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  77. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  78. package/dist/CommandPalette/CommandPalette.js +8 -3
  79. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  80. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  81. package/dist/CommandPalette/CommandPaletteInput.js +4 -1
  82. package/dist/CommandPalette/CommandPaletteList.d.ts +1 -1
  83. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  84. package/dist/CommandPalette/CommandPaletteList.js +4 -1
  85. package/dist/ContextMenu/ContextMenu.d.ts +1 -1
  86. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  87. package/dist/ContextMenu/ContextMenu.js +8 -4
  88. package/dist/ContextMenu/index.d.ts +3 -0
  89. package/dist/ContextMenu/index.d.ts.map +1 -1
  90. package/dist/ContextMenu/index.js +7 -1
  91. package/dist/DateInput/DateInput.d.ts +1 -1
  92. package/dist/DateInput/DateInput.d.ts.map +1 -1
  93. package/dist/DateInput/DateInput.js +10 -5
  94. package/dist/DateRangeInput/DateRangeInput.d.ts +1 -1
  95. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  96. package/dist/DateRangeInput/DateRangeInput.js +11 -6
  97. package/dist/DateTimeInput/DateTimeInput.d.ts +1 -1
  98. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  99. package/dist/DateTimeInput/DateTimeInput.js +15 -7
  100. package/dist/Dialog/Dialog.d.ts.map +1 -1
  101. package/dist/Dialog/Dialog.js +15 -12
  102. package/dist/Dialog/DialogHeader.d.ts +1 -1
  103. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  104. package/dist/Dialog/DialogHeader.js +7 -3
  105. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  106. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  107. package/dist/DropdownMenu/DropdownMenu.js +19 -9
  108. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +89 -0
  109. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
  110. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +170 -0
  111. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
  112. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  113. package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
  114. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +69 -0
  115. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
  116. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +70 -0
  117. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
  118. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
  119. package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
  120. package/dist/DropdownMenu/index.d.ts +5 -1
  121. package/dist/DropdownMenu/index.d.ts.map +1 -1
  122. package/dist/DropdownMenu/index.js +10 -1
  123. package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
  124. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
  125. package/dist/DropdownMenu/menuItemRoles.js +15 -0
  126. package/dist/EmptyState/EmptyState.d.ts +4 -1
  127. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  128. package/dist/FileInput/FileInput.d.ts.map +1 -1
  129. package/dist/FileInput/FileInput.js +11 -1
  130. package/dist/Icon/Icon.d.ts +17 -12
  131. package/dist/Icon/Icon.d.ts.map +1 -1
  132. package/dist/Icon/Icon.js +28 -20
  133. package/dist/Kbd/Kbd.js +1 -1
  134. package/dist/Layer/useLayer.d.ts +10 -0
  135. package/dist/Layer/useLayer.d.ts.map +1 -1
  136. package/dist/Layer/useLayer.js +23 -1
  137. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  138. package/dist/Lightbox/Lightbox.js +6 -4
  139. package/dist/Link/Link.d.ts +5 -1
  140. package/dist/Link/Link.d.ts.map +1 -1
  141. package/dist/Link/Link.js +32 -1
  142. package/dist/Markdown/Markdown.d.ts.map +1 -1
  143. package/dist/Markdown/Markdown.js +10 -8
  144. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  145. package/dist/MobileNav/MobileNav.js +4 -2
  146. package/dist/MobileNav/MobileNavToggle.d.ts +1 -1
  147. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  148. package/dist/MobileNav/MobileNavToggle.js +4 -1
  149. package/dist/MoreMenu/MoreMenu.d.ts +1 -1
  150. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  151. package/dist/MoreMenu/MoreMenu.js +4 -1
  152. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  153. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  154. package/dist/MultiSelector/MultiSelector.js +13 -6
  155. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  156. package/dist/NumberInput/NumberInput.js +5 -1
  157. package/dist/Outline/Outline.d.ts +1 -1
  158. package/dist/Outline/Outline.d.ts.map +1 -1
  159. package/dist/Outline/Outline.js +4 -1
  160. package/dist/Pagination/Pagination.d.ts +1 -1
  161. package/dist/Pagination/Pagination.d.ts.map +1 -1
  162. package/dist/Pagination/Pagination.js +38 -11
  163. package/dist/Popover/usePopover.d.ts.map +1 -1
  164. package/dist/Popover/usePopover.js +4 -1
  165. package/dist/PowerSearch/PowerSearch.d.ts +1 -1
  166. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  167. package/dist/PowerSearch/PowerSearch.js +22 -12
  168. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +1 -1
  169. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearchEditPopover.js +26 -20
  171. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  172. package/dist/PowerSearch/PowerSearchToken.js +7 -3
  173. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  174. package/dist/PowerSearch/PowerSearchValueEditor.js +34 -21
  175. package/dist/PowerSearch/formatFilterValue.d.ts +2 -1
  176. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  177. package/dist/PowerSearch/formatFilterValue.js +19 -9
  178. package/dist/PowerSearch/index.d.ts +2 -1
  179. package/dist/PowerSearch/index.d.ts.map +1 -1
  180. package/dist/PowerSearch/index.js +1 -0
  181. package/dist/PowerSearch/resolveOperatorLabel.d.ts +23 -0
  182. package/dist/PowerSearch/resolveOperatorLabel.d.ts.map +1 -0
  183. package/dist/PowerSearch/resolveOperatorLabel.js +28 -0
  184. package/dist/PowerSearch/types.d.ts +28 -2
  185. package/dist/PowerSearch/types.d.ts.map +1 -1
  186. package/dist/PowerSearch/usePowerSearchConfig.d.ts.map +1 -1
  187. package/dist/PowerSearch/usePowerSearchConfig.js +27 -27
  188. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  189. package/dist/PowerSearch/usePowerSearchSource.js +15 -8
  190. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  191. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  192. package/dist/Resizable/ResizeHandle.js +4 -1
  193. package/dist/Selector/Selector.d.ts.map +1 -1
  194. package/dist/Selector/Selector.js +52 -14
  195. package/dist/SideNav/SideNav.d.ts.map +1 -1
  196. package/dist/SideNav/SideNav.js +4 -2
  197. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  198. package/dist/SideNav/SideNavCollapseButton.js +3 -1
  199. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  200. package/dist/SideNav/SideNavHeading.js +5 -3
  201. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  202. package/dist/SideNav/SideNavItem.js +7 -1
  203. package/dist/Switch/Switch.d.ts.map +1 -1
  204. package/dist/Switch/Switch.js +4 -2
  205. package/dist/TabList/TabList.d.ts +1 -1
  206. package/dist/TabList/TabList.d.ts.map +1 -1
  207. package/dist/TabList/TabList.js +4 -1
  208. package/dist/Table/BaseTable.d.ts.map +1 -1
  209. package/dist/Table/BaseTable.js +29 -16
  210. package/dist/Table/Table.d.ts.map +1 -1
  211. package/dist/Table/Table.js +5 -3
  212. package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
  213. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  214. package/dist/Table/plugins/filtering/useTableFiltering.js +56 -19
  215. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +1 -1
  216. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  217. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +12 -5
  218. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  219. package/dist/Table/plugins/pagination/useTablePagination.js +6 -3
  220. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  221. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +8 -6
  222. package/dist/Table/plugins/rowIndex/useTableRowIndex.d.ts +1 -1
  223. package/dist/Table/plugins/rowIndex/useTableRowIndex.js +1 -1
  224. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  225. package/dist/Table/plugins/selection/useTableSelection.js +5 -2
  226. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  227. package/dist/Table/plugins/sortable/useTableSortable.js +6 -4
  228. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +2 -2
  229. package/dist/Table/types.d.ts +17 -10
  230. package/dist/Table/types.d.ts.map +1 -1
  231. package/dist/TextInput/TextInput.d.ts.map +1 -1
  232. package/dist/TextInput/TextInput.js +5 -1
  233. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  234. package/dist/Thumbnail/Thumbnail.js +8 -2
  235. package/dist/TimeInput/TimeInput.d.ts +1 -1
  236. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  237. package/dist/TimeInput/TimeInput.js +7 -2
  238. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  239. package/dist/Timestamp/Timestamp.js +18 -3
  240. package/dist/Toast/Toast.d.ts.map +1 -1
  241. package/dist/Toast/Toast.js +3 -1
  242. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  243. package/dist/Toast/ToastViewport.js +3 -1
  244. package/dist/Token/Token.d.ts.map +1 -1
  245. package/dist/Token/Token.js +8 -2
  246. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  247. package/dist/Tokenizer/Tokenizer.js +3 -1
  248. package/dist/TopNav/TopNav.d.ts +2 -1
  249. package/dist/TopNav/TopNav.d.ts.map +1 -1
  250. package/dist/TopNav/TopNav.js +4 -1
  251. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  252. package/dist/TopNav/TopNavHeading.js +5 -3
  253. package/dist/TreeList/TreeList.d.ts.map +1 -1
  254. package/dist/TreeList/TreeList.js +5 -1
  255. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  256. package/dist/TreeList/TreeListItem.js +7 -1
  257. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  258. package/dist/Typeahead/BaseTypeahead.js +8 -4
  259. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  260. package/dist/Typeahead/Typeahead.js +3 -1
  261. package/dist/VisuallyHidden/VisuallyHidden.d.ts +1 -0
  262. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
  263. package/dist/VisuallyHidden/VisuallyHidden.js +1 -0
  264. package/dist/astryx.css +35 -3
  265. package/dist/astryx.umd.js +62 -60
  266. package/dist/astryx.umd.js.map +4 -4
  267. package/dist/authoring/doc.d.ts +145 -0
  268. package/dist/authoring/doc.d.ts.map +1 -0
  269. package/dist/authoring/doc.js +100 -0
  270. package/dist/authoring/index.d.ts +29 -0
  271. package/dist/authoring/index.d.ts.map +1 -0
  272. package/dist/authoring/index.js +28 -0
  273. package/dist/authoring/integration.d.ts +35 -0
  274. package/dist/authoring/integration.d.ts.map +1 -0
  275. package/dist/authoring/integration.js +30 -0
  276. package/dist/authoring/template.d.ts +66 -0
  277. package/dist/authoring/template.d.ts.map +1 -0
  278. package/dist/authoring/template.js +55 -0
  279. package/dist/config.d.ts +80 -0
  280. package/dist/config.d.ts.map +1 -0
  281. package/dist/config.js +40 -0
  282. package/dist/docs-types.d.ts +13 -4
  283. package/dist/docs-types.d.ts.map +1 -1
  284. package/dist/i18n/InternationalizationContext.d.ts +12 -0
  285. package/dist/i18n/InternationalizationContext.d.ts.map +1 -0
  286. package/dist/i18n/InternationalizationContext.js +29 -0
  287. package/dist/i18n/InternationalizationProvider.d.ts +61 -0
  288. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -0
  289. package/dist/i18n/InternationalizationProvider.js +44 -0
  290. package/dist/i18n/index.d.ts +24 -0
  291. package/dist/i18n/index.d.ts.map +1 -0
  292. package/dist/i18n/index.js +21 -0
  293. package/dist/i18n/resolve.d.ts +22 -0
  294. package/dist/i18n/resolve.d.ts.map +1 -0
  295. package/dist/i18n/resolve.js +141 -0
  296. package/dist/i18n/translator.d.ts +25 -0
  297. package/dist/i18n/translator.d.ts.map +1 -0
  298. package/dist/i18n/translator.js +1 -0
  299. package/dist/i18n/types.d.ts +47 -0
  300. package/dist/i18n/types.d.ts.map +1 -0
  301. package/dist/i18n/types.js +1 -0
  302. package/dist/i18n/useTranslator.d.ts +15 -0
  303. package/dist/i18n/useTranslator.d.ts.map +1 -0
  304. package/dist/i18n/useTranslator.js +39 -0
  305. package/dist/index.d.ts +1 -0
  306. package/dist/index.d.ts.map +1 -1
  307. package/dist/index.js +3 -0
  308. package/dist/theme/hct.d.ts +0 -1
  309. package/dist/theme/hct.d.ts.map +1 -1
  310. package/dist/theme/hct.js +0 -3
  311. package/dist/utils/color.d.ts +13 -1
  312. package/dist/utils/color.d.ts.map +1 -1
  313. package/dist/utils/color.js +23 -1
  314. package/dist/utils/index.d.ts +1 -1
  315. package/dist/utils/index.d.ts.map +1 -1
  316. package/dist/utils/index.js +1 -1
  317. package/locales/en.json +746 -0
  318. package/locales/pseudo.json +560 -0
  319. package/package.json +23 -2
  320. package/src/AlertDialog/AlertDialog.doc.mjs +5 -0
  321. package/src/AlertDialog/AlertDialog.tsx +4 -1
  322. package/src/AppShell/AppShell.doc.mjs +8 -2
  323. package/src/AppShell/AppShell.tsx +3 -1
  324. package/src/AvatarGroup/AvatarGroup.tsx +4 -1
  325. package/src/Banner/Banner.tsx +10 -5
  326. package/src/Breadcrumbs/Breadcrumbs.tsx +4 -1
  327. package/src/Button/Button.doc.mjs +8 -0
  328. package/src/Button/Button.test.tsx +27 -0
  329. package/src/Button/Button.tsx +51 -5
  330. package/src/ButtonGroup/ButtonGroup.test.tsx +386 -2
  331. package/src/ButtonGroup/ButtonGroup.tsx +27 -11
  332. package/src/Calendar/Calendar.test.tsx +103 -2
  333. package/src/Calendar/Calendar.tsx +29 -6
  334. package/src/Card/Card.doc.mjs +3 -3
  335. package/src/Carousel/Carousel.tsx +10 -7
  336. package/src/Chat/ChatComposer.tsx +5 -1
  337. package/src/Chat/ChatComposerDrawer.tsx +9 -2
  338. package/src/Chat/ChatComposerInput.tsx +10 -4
  339. package/src/Chat/ChatLayout.test.tsx +127 -2
  340. package/src/Chat/ChatLayout.tsx +5 -12
  341. package/src/Chat/ChatLayoutScrollButton.tsx +4 -2
  342. package/src/Chat/ChatMessage.tsx +5 -1
  343. package/src/Chat/ChatMessageBubble.tsx +2 -0
  344. package/src/Chat/ChatMessageList.doc.mjs +1 -1
  345. package/src/Chat/ChatMessageList.tsx +2 -0
  346. package/src/Chat/ChatMessageMetadata.tsx +15 -9
  347. package/src/Chat/ChatPastedTextToken.tsx +3 -1
  348. package/src/Chat/ChatSendButton.tsx +3 -1
  349. package/src/Chat/ChatToolCalls.doc.mjs +1 -1
  350. package/src/Chat/index.ts +1 -0
  351. package/src/Chat/useChatStreamScroll.test.tsx +168 -0
  352. package/src/Chat/useChatStreamScroll.ts +67 -11
  353. package/src/Chat/useSpeechRecognition.ts +11 -2
  354. package/src/Chat/useTriggerMenu.tsx +4 -2
  355. package/src/CheckboxInput/CheckboxInput.test.tsx +16 -0
  356. package/src/CheckboxInput/CheckboxInput.tsx +4 -2
  357. package/src/CheckboxList/CheckboxList.doc.mjs +6 -1
  358. package/src/CheckboxList/CheckboxList.tsx +2 -0
  359. package/src/CheckboxList/CheckboxListItem.tsx +7 -1
  360. package/src/Citation/Citation.tsx +4 -2
  361. package/src/ClickableCard/ClickableCard.doc.mjs +1 -1
  362. package/src/CodeBlock/CodeBlock.tsx +91 -40
  363. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  364. package/src/Collapsible/Collapsible.test.tsx +6 -0
  365. package/src/Collapsible/Collapsible.tsx +19 -4
  366. package/src/CommandPalette/CommandPalette.tsx +10 -3
  367. package/src/CommandPalette/CommandPaletteInput.test.tsx +1 -1
  368. package/src/CommandPalette/CommandPaletteInput.tsx +5 -1
  369. package/src/CommandPalette/CommandPaletteList.tsx +4 -1
  370. package/src/ContextMenu/ContextMenu.test.tsx +75 -1
  371. package/src/ContextMenu/ContextMenu.tsx +14 -6
  372. package/src/ContextMenu/index.ts +15 -0
  373. package/src/DateInput/DateInput.doc.mjs +2 -2
  374. package/src/DateInput/DateInput.test.tsx +17 -5
  375. package/src/DateInput/DateInput.tsx +13 -5
  376. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  377. package/src/DateRangeInput/DateRangeInput.tsx +14 -6
  378. package/src/DateTimeInput/DateTimeInput.doc.mjs +2 -2
  379. package/src/DateTimeInput/DateTimeInput.tsx +17 -7
  380. package/src/Dialog/Dialog.tsx +11 -15
  381. package/src/Dialog/DialogHeader.tsx +7 -3
  382. package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
  383. package/src/DropdownMenu/DropdownMenu.tsx +23 -14
  384. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
  385. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +237 -0
  386. package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
  387. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +6 -0
  388. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
  389. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +119 -0
  390. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
  391. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
  392. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
  393. package/src/DropdownMenu/index.ts +23 -3
  394. package/src/DropdownMenu/menuItemRoles.ts +22 -0
  395. package/src/EmptyState/EmptyState.doc.mjs +3 -3
  396. package/src/EmptyState/EmptyState.tsx +4 -1
  397. package/src/Field/Field.doc.mjs +2 -2
  398. package/src/FileInput/FileInput.test.tsx +40 -1
  399. package/src/FileInput/FileInput.tsx +12 -1
  400. package/src/Grid/Grid.doc.mjs +5 -5
  401. package/src/Icon/Icon.test.tsx +46 -0
  402. package/src/Icon/Icon.tsx +37 -24
  403. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  404. package/src/Kbd/Kbd.test.tsx +26 -0
  405. package/src/Kbd/Kbd.tsx +1 -1
  406. package/src/Layer/useLayer.doc.mjs +8 -5
  407. package/src/Layer/useLayer.test.tsx +136 -1
  408. package/src/Layer/useLayer.tsx +34 -2
  409. package/src/Lightbox/Lightbox.doc.mjs +7 -2
  410. package/src/Lightbox/Lightbox.tsx +6 -4
  411. package/src/Link/Link.test.tsx +74 -2
  412. package/src/Link/Link.tsx +48 -1
  413. package/src/Markdown/Markdown.tsx +12 -4
  414. package/src/MobileNav/MobileNav.tsx +4 -2
  415. package/src/MobileNav/MobileNavToggle.tsx +5 -3
  416. package/src/MoreMenu/MoreMenu.tsx +4 -1
  417. package/src/MultiSelector/MultiSelector.tsx +14 -5
  418. package/src/NumberInput/NumberInput.tsx +3 -1
  419. package/src/Outline/Outline.tsx +4 -1
  420. package/src/OverflowList/OverflowList.doc.mjs +2 -2
  421. package/src/Pagination/Pagination.tsx +35 -12
  422. package/src/Popover/usePopover.tsx +6 -1
  423. package/src/PowerSearch/PowerSearch.doc.mjs +9 -4
  424. package/src/PowerSearch/PowerSearch.tsx +22 -10
  425. package/src/PowerSearch/PowerSearchEditPopover.tsx +27 -20
  426. package/src/PowerSearch/PowerSearchToken.tsx +17 -6
  427. package/src/PowerSearch/PowerSearchValueEditor.tsx +34 -21
  428. package/src/PowerSearch/formatFilterValue.test.ts +282 -0
  429. package/src/PowerSearch/formatFilterValue.ts +21 -8
  430. package/src/PowerSearch/index.ts +5 -0
  431. package/src/PowerSearch/resolveOperatorLabel.ts +34 -0
  432. package/src/PowerSearch/types.ts +33 -9
  433. package/src/PowerSearch/usePowerSearchConfig.ts +59 -27
  434. package/src/PowerSearch/usePowerSearchSource.ts +17 -8
  435. package/src/RadioList/RadioList.doc.mjs +1 -1
  436. package/src/Resizable/ResizeHandle.tsx +4 -1
  437. package/src/Section/Section.doc.mjs +4 -4
  438. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  439. package/src/Selector/Selector.doc.mjs +6 -3
  440. package/src/Selector/Selector.test.tsx +114 -6
  441. package/src/Selector/Selector.tsx +68 -17
  442. package/src/SideNav/SideNav.tsx +4 -2
  443. package/src/SideNav/SideNavCollapseButton.tsx +3 -1
  444. package/src/SideNav/SideNavHeading.tsx +5 -3
  445. package/src/SideNav/SideNavItem.tsx +3 -1
  446. package/src/Slider/Slider.doc.mjs +2 -2
  447. package/src/Stack/Stack.doc.mjs +10 -10
  448. package/src/Switch/Switch.doc.mjs +2 -2
  449. package/src/Switch/Switch.test.tsx +16 -0
  450. package/src/Switch/Switch.tsx +4 -2
  451. package/src/TabList/TabList.tsx +4 -1
  452. package/src/Table/BaseTable.tsx +36 -19
  453. package/src/Table/Table.test.tsx +132 -1
  454. package/src/Table/Table.tsx +6 -4
  455. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +1 -1
  456. package/src/Table/plugins/filtering/useTableFiltering.tsx +31 -30
  457. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +7 -5
  458. package/src/Table/plugins/pagination/useTablePagination.tsx +4 -1
  459. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -8
  460. package/src/Table/plugins/rowIndex/useTableRowIndex.tsx +1 -1
  461. package/src/Table/plugins/selection/useTableSelection.tsx +6 -7
  462. package/src/Table/plugins/sortable/useTableSortable.tsx +8 -5
  463. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +4 -4
  464. package/src/Table/types.ts +18 -12
  465. package/src/Table/useTableFiltering.doc.mjs +2 -2
  466. package/src/Table/useTableGroupedRows.doc.mjs +3 -3
  467. package/src/Table/useTableSortable.doc.mjs +11 -11
  468. package/src/Text/Text.doc.mjs +2 -2
  469. package/src/TextInput/TextInput.tsx +3 -1
  470. package/src/Thumbnail/Thumbnail.tsx +4 -2
  471. package/src/TimeInput/TimeInput.doc.mjs +2 -2
  472. package/src/TimeInput/TimeInput.tsx +6 -2
  473. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  474. package/src/Timestamp/Timestamp.test.tsx +120 -7
  475. package/src/Timestamp/Timestamp.tsx +21 -1
  476. package/src/Toast/Toast.tsx +3 -1
  477. package/src/Toast/ToastViewport.tsx +3 -1
  478. package/src/Token/Token.tsx +4 -2
  479. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  480. package/src/Tokenizer/Tokenizer.tsx +4 -4
  481. package/src/Toolbar/Toolbar.doc.mjs +1 -1
  482. package/src/TopNav/TopNav.doc.mjs +1 -0
  483. package/src/TopNav/TopNav.test.tsx +30 -0
  484. package/src/TopNav/TopNav.tsx +6 -4
  485. package/src/TopNav/TopNavHeading.tsx +5 -3
  486. package/src/TreeList/TreeList.tsx +5 -1
  487. package/src/TreeList/TreeListItem.tsx +6 -1
  488. package/src/Typeahead/BaseTypeahead.tsx +11 -4
  489. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  490. package/src/Typeahead/Typeahead.tsx +3 -1
  491. package/src/VisuallyHidden/VisuallyHidden.tsx +1 -0
  492. package/src/authoring/doc.ts +166 -0
  493. package/src/authoring/index.test.ts +52 -0
  494. package/src/authoring/index.ts +50 -0
  495. package/src/authoring/integration.ts +42 -0
  496. package/src/authoring/template.ts +74 -0
  497. package/src/config.test.ts +16 -0
  498. package/src/config.ts +92 -0
  499. package/src/docPropLiterals.test.ts +938 -0
  500. package/src/docs-types.ts +15 -10
  501. package/src/i18n/InternationalizationContext.ts +39 -0
  502. package/src/i18n/InternationalizationProvider.doc.mjs +72 -0
  503. package/src/i18n/InternationalizationProvider.tsx +82 -0
  504. package/src/i18n/__tests__/e2e-pagination.test.tsx +172 -0
  505. package/src/i18n/__tests__/e2e-powersearch.test.tsx +130 -0
  506. package/src/i18n/__tests__/resolve.test.ts +284 -0
  507. package/src/i18n/index.ts +32 -0
  508. package/src/i18n/resolve.ts +165 -0
  509. package/src/i18n/translator.ts +31 -0
  510. package/src/i18n/types.ts +53 -0
  511. package/src/i18n/useTranslator.doc.mjs +46 -0
  512. package/src/i18n/useTranslator.ts +50 -0
  513. package/src/index.ts +3 -0
  514. package/src/theme/hct.ts +0 -4
  515. package/src/utils/color.test.ts +101 -1
  516. package/src/utils/color.ts +23 -1
  517. package/src/utils/index.ts +8 -1
  518. package/dist/NavItem/index.d.ts +0 -9
  519. package/dist/NavItem/index.d.ts.map +0 -1
  520. package/dist/NavItem/index.js +0 -10
  521. package/src/NavItem/index.ts +0 -11
package/src/Icon/Icon.tsx CHANGED
@@ -111,51 +111,64 @@ const colorStyles = stylex.create({
111
111
  /**
112
112
  * Size styles for direct SVG icon components.
113
113
  * Uses width/height only — SVG components handle their own viewBox scaling.
114
+ *
115
+ * Sizes are expressed in `rem` (relative to the root font-size) so icons scale
116
+ * in step with text when the document font-size changes, matching the rest of
117
+ * the design system's rem-based type scale. Values are the px-equivalents at a
118
+ * 16px root: 12px → 0.75rem, 16px → 1rem, 20px → 1.25rem, 24px → 1.5rem.
114
119
  */
115
120
  const sizeStyles = stylex.create({
116
121
  xsm: {
117
- width: 12,
118
- height: 12,
122
+ width: '0.75rem',
123
+ height: '0.75rem',
119
124
  },
120
125
  sm: {
121
- width: 16,
122
- height: 16,
126
+ width: '1rem',
127
+ height: '1rem',
123
128
  },
124
129
  md: {
125
- width: 20,
126
- height: 20,
130
+ width: '1.25rem',
131
+ height: '1.25rem',
127
132
  },
128
133
  lg: {
129
- width: 24,
130
- height: 24,
134
+ width: '1.5rem',
135
+ height: '1.5rem',
131
136
  },
132
137
  });
133
138
 
134
139
  /**
135
140
  * Size styles for string-based (registry) icons.
136
141
  * Includes fontSize so that 1em-based icons from the registry scale correctly.
142
+ *
143
+ * Expressed in `rem` for the same reason as {@link sizeStyles} — icons track the
144
+ * root font-size instead of being locked to absolute pixels.
137
145
  */
138
146
  const spanSizeStyles = stylex.create({
147
+ /* eslint-disable @astryx/no-hardcoded-styles -- fontSize here sizes 1em-based
148
+ registry SVGs to the icon box; icons use their own 12/16/20/24 scale, not
149
+ the 14px-anchored textSizeVars type scale. Values are rem so icons track
150
+ the root font-size. */
139
151
  xsm: {
140
- width: 12,
141
- height: 12,
142
- fontSize: 12,
152
+ width: '0.75rem',
153
+ height: '0.75rem',
154
+ fontSize: '0.75rem',
143
155
  },
144
156
  sm: {
145
- width: 16,
146
- height: 16,
147
- fontSize: 16,
157
+ width: '1rem',
158
+ height: '1rem',
159
+ fontSize: '1rem',
148
160
  },
149
161
  md: {
150
- width: 20,
151
- height: 20,
152
- fontSize: 20,
162
+ width: '1.25rem',
163
+ height: '1.25rem',
164
+ fontSize: '1.25rem',
153
165
  },
154
166
  lg: {
155
- width: 24,
156
- height: 24,
157
- fontSize: 24,
167
+ width: '1.5rem',
168
+ height: '1.5rem',
169
+ fontSize: '1.5rem',
158
170
  },
171
+ /* eslint-enable @astryx/no-hardcoded-styles */
159
172
  });
160
173
 
161
174
  // =============================================================================
@@ -194,10 +207,10 @@ export interface IconProps extends Omit<
194
207
  color?: IconColor;
195
208
  /**
196
209
  * The size of the icon.
197
- * - 'xsm': 12px
198
- * - 'sm': 16px
199
- * - 'md': 20px
200
- * - 'lg': 24px
210
+ * - 'xsm': 0.75rem (12px at a 16px root)
211
+ * - 'sm': 1rem (16px at a 16px root)
212
+ * - 'md': 1.25rem (20px at a 16px root)
213
+ * - 'lg': 1.5rem (24px at a 16px root)
201
214
  * @default 'md'
202
215
  */
203
216
  size?: IconSize;
@@ -76,7 +76,7 @@ export const docs = {
76
76
  },
77
77
  {
78
78
  name: 'status',
79
- type: 'InputStatus',
79
+ type: "{type: 'warning' | 'error' | 'success', message?: string}",
80
80
  description: 'Status indicator applied to the group border.',
81
81
  },
82
82
  {
@@ -119,4 +119,30 @@ describe('Kbd', () => {
119
119
  expect(screen.getByText('⇧')).toBeInTheDocument();
120
120
  expect(screen.getByText('+')).toBeInTheDocument();
121
121
  });
122
+
123
+ it('keeps the computed role and aria-label when unrelated rest props are spread', () => {
124
+ render(<Kbd keys="mod+k" data-testid="kbd" id="shortcut" />);
125
+ const el = screen.getByTestId('kbd');
126
+ // Pass-through attributes land on the wrapper...
127
+ expect(el).toHaveAttribute('id', 'shortcut');
128
+ // ...without disturbing the computed accessibility contract.
129
+ expect(el).toHaveAttribute('role', 'img');
130
+ expect(el).toHaveAttribute('aria-label', 'Control + K');
131
+ });
132
+
133
+ it('computed role and aria-label win over consumer-passed overrides', () => {
134
+ // Contract props the component computes must not be clobbered by
135
+ // pass-through attributes: {...rest} is spread before role/aria-label.
136
+ render(
137
+ <Kbd
138
+ keys="mod+k"
139
+ role="presentation"
140
+ aria-label="custom"
141
+ data-testid="kbd"
142
+ />,
143
+ );
144
+ const el = screen.getByTestId('kbd');
145
+ expect(el).toHaveAttribute('role', 'img');
146
+ expect(el).toHaveAttribute('aria-label', 'Control + K');
147
+ });
122
148
  });
package/src/Kbd/Kbd.tsx CHANGED
@@ -188,10 +188,10 @@ export function Kbd({keys, ref, xstyle, className, style, ...rest}: KbdProps) {
188
188
 
189
189
  return (
190
190
  <span
191
+ {...rest}
191
192
  ref={ref}
192
193
  role="img"
193
194
  aria-label={accessibleName}
194
- {...rest}
195
195
  {...mergeProps(
196
196
  themeProps('kbd'),
197
197
  stylex.props(styles.wrapper, xstyle),
@@ -72,13 +72,14 @@ export const docs = {
72
72
  {
73
73
  name: 'id',
74
74
  type: 'string',
75
- description: 'Unique ID for aria-describedby or other ARIA relationships.',
75
+ description:
76
+ 'Unique ID for aria-describedby or other ARIA relationships.',
76
77
  },
77
78
  {
78
79
  name: 'render',
79
80
  type: '(children: ReactNode, props: ContextRenderProps | FixedRenderProps) => ReactNode',
80
81
  description:
81
- 'Render function for the popover element. Pass placement/alignment in context mode or x/y in fixed mode. Placement/alignment are logical: they map to the self-* position-area keyword family, which resolves against the popover\'s own inherited direction, so RTL contexts mirror automatically in pure CSS. Pass `positioning: "custom"` in context mode to author position styles yourself via `style` (e.g. explicit anchor() insets or an anchor-size() cover): the hook keeps the popover behavior and position-anchor wiring but derives no position styles — including the automatic RTL mirroring, which becomes your responsibility. In context mode, pass `as: "span"` to render an inline-safe layer (e.g. inside a paragraph). The layer renders inline in the React tree; the Popover API promotes it to the top layer when shown, so it escapes ancestor clipping and stacking without a portal.',
82
+ 'Render function for the popover element. Pass placement/alignment in context mode or x/y in fixed mode. Placement/alignment are logical: they map to the self-* position-area keyword family, which resolves against the popover\'s own inherited direction, so RTL contexts mirror automatically in pure CSS. Pass `positioning: "custom"` in context mode to author position styles yourself via `style` (e.g. explicit anchor() insets or an anchor-size() cover): the hook keeps the popover behavior and position-anchor wiring but derives no position styles, including the automatic RTL mirroring, which becomes your responsibility. In context mode, pass `as: "span"` to render an inline-safe layer (e.g. inside a paragraph). The layer renders inline in the React tree; the Popover API promotes it to the top layer when shown, so it escapes ancestor clipping and stacking without a portal. When the layer would overflow the viewport, position-try fallbacks flip it to the opposite side; centered layers additionally slide along the alignment axis (span fallbacks) so they stay on-screen near viewport edges.',
82
83
  },
83
84
  ],
84
85
  usage: {
@@ -130,7 +131,7 @@ export const docsDense = {
130
131
  hide: 'hide layer.',
131
132
  isOpen: 'whether layer is open.',
132
133
  id: 'unique ARIA id.',
133
- render: 'renders popover element; pass placement/alignment or x/y. Placement/alignment logical: mapped to self-* position-area keywords resolved against the popover\'s inherited direction (RTL mirrors in pure CSS). `positioning: "custom"` (context mode) = author position styles yourself via `style`; keeps popover behavior + position-anchor wiring, derives nothing (incl. RTL mirroring — yours). Context mode accepts `as: "span"` for inline-safe layers. Renders inline; the Popover API top layer escapes clipping/stacking without a portal.',
134
+ render: 'renders popover element; pass placement/alignment or x/y. Placement/alignment logical: mapped to self-* position-area keywords resolved against the popover\'s inherited direction (RTL mirrors in pure CSS). `positioning: "custom"` (context mode) = author position styles yourself via `style`; keeps popover behavior + position-anchor wiring, derives nothing (incl. RTL mirroring, which becomes yours). Context mode accepts `as: "span"` for inline-safe layers. Renders inline; the Popover API top layer escapes clipping/stacking without a portal. Viewport overflow: flips to opposite side; centered layers also slide along the alignment axis (span fallbacks).',
134
135
  },
135
136
  usage: {
136
137
  description:
@@ -138,11 +139,13 @@ export const docsDense = {
138
139
  bestPractices: [
139
140
  {
140
141
  guidance: true,
141
- description: 'Use context mode for trigger-anchored overlays; fixed mode for explicit coordinates.',
142
+ description:
143
+ 'Use context mode for trigger-anchored overlays; fixed mode for explicit coordinates.',
142
144
  },
143
145
  {
144
146
  guidance: true,
145
- description: 'Build on Popover/HoverCard/Tooltip for common overlay patterns.',
147
+ description:
148
+ 'Build on Popover/HoverCard/Tooltip for common overlay patterns.',
146
149
  },
147
150
  {
148
151
  guidance: true,
@@ -12,7 +12,7 @@
12
12
  import {describe, it, expect, vi, afterEach} from 'vitest';
13
13
  import {render, act} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
- import {useLayer} from './useLayer';
15
+ import {useLayer, getPositionTryFallbacks} from './useLayer';
16
16
  import type {
17
17
  LayerPlacement,
18
18
  LayerAlignment,
@@ -33,6 +33,141 @@ function LayerHarness({
33
33
  return <>{layer.render(<span>Layer content</span>, {x: 0, y: 0})}</>;
34
34
  }
35
35
 
36
+ /**
37
+ * Context-mode harness: renders a trigger plus the layer so tests can assert
38
+ * on the anchor-positioning styles applied to the popover element.
39
+ */
40
+ function ContextLayerHarness({
41
+ placement,
42
+ alignment,
43
+ }: {
44
+ placement?: LayerPlacement;
45
+ alignment?: LayerAlignment;
46
+ }) {
47
+ const layer = useLayer({mode: 'context'});
48
+ return (
49
+ <>
50
+ <button type="button" ref={layer.ref}>
51
+ Trigger
52
+ </button>
53
+ {layer.render(<span>Layer content</span>, {placement, alignment})}
54
+ </>
55
+ );
56
+ }
57
+
58
+ describe('getPositionTryFallbacks (issue #3671)', () => {
59
+ const FLIPS = 'flip-block, flip-inline, flip-block flip-inline';
60
+
61
+ it('appends inline span fallbacks for centered above/below layers so inline overflow can resolve (flip-inline is a no-op on center)', () => {
62
+ expect(getPositionTryFallbacks('above', 'center')).toBe(
63
+ `${FLIPS}, top span-left, top span-right, bottom span-left, bottom span-right`,
64
+ );
65
+ expect(getPositionTryFallbacks('below', 'center')).toBe(
66
+ `${FLIPS}, bottom span-left, bottom span-right, top span-left, top span-right`,
67
+ );
68
+ });
69
+
70
+ it('appends block span fallbacks for centered start/end layers so block overflow can resolve (flip-block is a no-op on center)', () => {
71
+ expect(getPositionTryFallbacks('start', 'center')).toBe(
72
+ `${FLIPS}, left span-top, left span-bottom, right span-top, right span-bottom`,
73
+ );
74
+ expect(getPositionTryFallbacks('end', 'center')).toBe(
75
+ `${FLIPS}, right span-top, right span-bottom, left span-top, left span-bottom`,
76
+ );
77
+ });
78
+
79
+ it('keeps flip-only fallbacks for non-centered alignments (flips already resolve overflow there)', () => {
80
+ const nonCentered: [LayerPlacement, LayerAlignment][] = [
81
+ ['above', 'start'],
82
+ ['above', 'end'],
83
+ ['below', 'start'],
84
+ ['below', 'end'],
85
+ ['start', 'start'],
86
+ ['start', 'end'],
87
+ ['end', 'start'],
88
+ ['end', 'end'],
89
+ ];
90
+ for (const [placement, alignment] of nonCentered) {
91
+ expect(getPositionTryFallbacks(placement, alignment)).toBe(FLIPS);
92
+ }
93
+ });
94
+
95
+ it('defaults to above/center when called without arguments (matches renderContext defaults)', () => {
96
+ expect(getPositionTryFallbacks()).toBe(
97
+ getPositionTryFallbacks('above', 'center'),
98
+ );
99
+ expect(getPositionTryFallbacks(undefined, undefined)).toBe(
100
+ `${FLIPS}, top span-left, top span-right, bottom span-left, bottom span-right`,
101
+ );
102
+ });
103
+
104
+ it('produces well-formed, duplicate-free lists with flips first and axis-correct spans for every placement/alignment combo', () => {
105
+ const placements: LayerPlacement[] = ['above', 'below', 'start', 'end'];
106
+ const alignments: LayerAlignment[] = ['start', 'center', 'end'];
107
+ const spanPattern: Record<LayerPlacement, RegExp> = {
108
+ above: /^(top|bottom) span-(left|right)$/,
109
+ below: /^(top|bottom) span-(left|right)$/,
110
+ start: /^(left|right) span-(top|bottom)$/,
111
+ end: /^(left|right) span-(top|bottom)$/,
112
+ };
113
+
114
+ for (const placement of placements) {
115
+ for (const alignment of alignments) {
116
+ const list = getPositionTryFallbacks(placement, alignment);
117
+ const items = list.split(', ');
118
+
119
+ expect(items.slice(0, 3)).toEqual([
120
+ 'flip-block',
121
+ 'flip-inline',
122
+ 'flip-block flip-inline',
123
+ ]);
124
+ expect(new Set(items).size).toBe(items.length);
125
+ for (const item of items.slice(3)) {
126
+ expect(item).toMatch(spanPattern[placement]);
127
+ }
128
+ expect(items.length).toBe(alignment === 'center' ? 7 : 3);
129
+ }
130
+ }
131
+ });
132
+
133
+ it('updates the fallback list when placement/alignment props change on re-render', () => {
134
+ const {container, rerender} = render(
135
+ <ContextLayerHarness placement="above" alignment="center" />,
136
+ );
137
+ const layerEl = container.querySelector('[popover]') as HTMLElement;
138
+ expect(layerEl.style.positionTryFallbacks).toContain('top span-left');
139
+
140
+ rerender(<ContextLayerHarness placement="above" alignment="start" />);
141
+ expect(layerEl.style.positionTryFallbacks).toBe(FLIPS);
142
+
143
+ rerender(<ContextLayerHarness placement="start" alignment="center" />);
144
+ expect(layerEl.style.positionTryFallbacks).toBe(
145
+ `${FLIPS}, left span-top, left span-bottom, right span-top, right span-bottom`,
146
+ );
147
+ });
148
+
149
+ it('does not apply anchor fallbacks in fixed mode (manual coordinates)', () => {
150
+ function FixedHarness() {
151
+ const layer = useLayer({mode: 'fixed'});
152
+ return <>{layer.render(<span>Fixed content</span>, {x: 10, y: 20})}</>;
153
+ }
154
+ const {container} = render(<FixedHarness />);
155
+ const layerEl = container.querySelector('[popover]') as HTMLElement;
156
+ expect(layerEl.style.positionTryFallbacks ?? '').toBeFalsy();
157
+ expect(layerEl.style.left).toBe('10px');
158
+ expect(layerEl.style.top).toBe('20px');
159
+ });
160
+
161
+ it('applies span fallbacks to the rendered popover for the default (above/center) layer', () => {
162
+ const {container} = render(<ContextLayerHarness />);
163
+ const layerEl = container.querySelector('[popover]') as HTMLElement;
164
+ expect(layerEl).not.toBeNull();
165
+ expect(layerEl.style.positionTryFallbacks).toBe(
166
+ `${FLIPS}, top span-left, top span-right, bottom span-left, bottom span-right`,
167
+ );
168
+ });
169
+ });
170
+
36
171
  describe('useLayer', () => {
37
172
  const originalShowPopover = HTMLElement.prototype.showPopover;
38
173
  const originalHidePopover = HTMLElement.prototype.hidePopover;
@@ -320,6 +320,36 @@ function getPositionArea(
320
320
  return inline; // center
321
321
  }
322
322
 
323
+ /**
324
+ * Compute the `position-try-fallbacks` list for a placement/alignment pair.
325
+ *
326
+ * Flips alone cannot rescue a centered layer — flipping along the alignment
327
+ * axis maps center → center, so overflow on that axis renders clipped
328
+ * (#3671). Centered alignments therefore append span-based fallbacks letting
329
+ * the browser slide the layer along the alignment axis as a last resort
330
+ * (same-side spans first). Flips already resolve non-centered alignments.
331
+ */
332
+ export function getPositionTryFallbacks(
333
+ placement: LayerPlacement = 'above',
334
+ alignment: LayerAlignment = 'center',
335
+ ): string {
336
+ const flips = 'flip-block, flip-inline, flip-block flip-inline';
337
+
338
+ if (alignment !== 'center') {
339
+ return flips;
340
+ }
341
+
342
+ if (placement === 'above' || placement === 'below') {
343
+ const [same, opposite] =
344
+ placement === 'above' ? ['top', 'bottom'] : ['bottom', 'top'];
345
+ return `${flips}, ${same} span-left, ${same} span-right, ${opposite} span-left, ${opposite} span-right`;
346
+ }
347
+
348
+ const [same, opposite] =
349
+ placement === 'start' ? ['left', 'right'] : ['right', 'left'];
350
+ return `${flips}, ${same} span-top, ${same} span-bottom, ${opposite} span-top, ${opposite} span-bottom`;
351
+ }
352
+
323
353
  /**
324
354
  * Core layer hook that handles popover behavior and positioning.
325
355
  *
@@ -512,8 +542,10 @@ export function useLayer(
512
542
  : {
513
543
  positionAnchor: anchorId,
514
544
  positionArea: getPositionArea(placement, alignment),
515
- positionTryFallbacks:
516
- 'flip-block, flip-inline, flip-block flip-inline',
545
+ positionTryFallbacks: getPositionTryFallbacks(
546
+ placement,
547
+ alignment,
548
+ ),
517
549
  };
518
550
 
519
551
  const stylexResult = stylex.props(styles.base, xstyle);
@@ -22,8 +22,13 @@ export const docs = {
22
22
  },
23
23
  {
24
24
  name: 'media',
25
+ // Left as the named type on purpose: the shape carries `caption?:
26
+ // ReactNode`, so spelling it out makes the docsite playground parse the
27
+ // whole prop as an editable *string*, which would feed the preview text
28
+ // where a media object belongs. The shape and its legal values live in
29
+ // the description instead (#1645).
25
30
  type: 'LightboxMedia | LightboxMedia[]',
26
- description: 'Media to display. Pass a single object for one item, or an array for gallery mode with prev/next navigation. Each item has src, alt, optional caption and type.',
31
+ description: "Media to display. Pass a single object for one item, or an array for gallery mode with prev/next navigation. Each item is {src: string, alt: string, caption?: ReactNode, type?: 'image' | 'video'}; type defaults to 'image', and zoom/pan is disabled for 'video'.",
27
32
  required: true,
28
33
  },
29
34
  {
@@ -100,7 +105,7 @@ export const docsZh = {
100
105
  {
101
106
  name: 'media',
102
107
  type: 'LightboxMedia | LightboxMedia[]',
103
- description: '要显示的媒体。传入单个对象或数组(用于画廊模式的上一张/下一张导航)。',
108
+ description: "要显示的媒体。传入单个对象或数组(用于画廊模式的上一张/下一张导航)。每项为 {src, alt, caption?, type?: 'image' | 'video'};type 默认为 'image','video' 禁用缩放/平移。",
104
109
  required: true,
105
110
  },
106
111
  {
@@ -35,6 +35,7 @@ import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
35
35
  import {mergeProps, mergeRefs} from '../utils';
36
36
  import type {BaseProps} from '../BaseProps';
37
37
  import {themeProps} from '../utils/themeProps';
38
+ import {useTranslator} from '../i18n';
38
39
 
39
40
  /**
40
41
  * Media type for lightbox items.
@@ -282,6 +283,7 @@ export function Lightbox({
282
283
  onKeyDown: onKeyDownProp,
283
284
  ...props
284
285
  }: LightboxProps) {
286
+ const t = useTranslator();
285
287
  const dialogRef = useRef<HTMLDialogElement>(null);
286
288
  const imageWrapperRef = useRef<HTMLDivElement>(null);
287
289
  const triggerElementRef = useRef<Element | null>(null);
@@ -504,7 +506,7 @@ export function Lightbox({
504
506
  handleKeyDown(e);
505
507
  onKeyDownProp?.(e);
506
508
  }}
507
- aria-label={currentItem.alt || 'Media viewer'}
509
+ aria-label={currentItem.alt || t('@astryx.lightbox.mediaViewer')}
508
510
  {...mergeProps(
509
511
  themeProps('lightbox'),
510
512
  stylex.props(styles.dialog, xstyle),
@@ -517,7 +519,7 @@ export function Lightbox({
517
519
  <div {...stylex.props(styles.closeButton)}>
518
520
  <IconButton
519
521
  icon={<Icon icon="close" size="sm" color="inherit" />}
520
- label="Close"
522
+ label={t('@astryx.lightbox.close')}
521
523
  variant="ghost"
522
524
  onClick={handleClose}
523
525
  xstyle={styles.controlButton}
@@ -531,7 +533,7 @@ export function Lightbox({
531
533
  <div {...stylex.props(styles.navButton, styles.navPrev)}>
532
534
  <IconButton
533
535
  icon={<Icon icon="chevronLeft" size="sm" color="inherit" />}
534
- label="Previous"
536
+ label={t('@astryx.lightbox.previous')}
535
537
  variant="ghost"
536
538
  isDisabled={!canPrev}
537
539
  onClick={goToPrev}
@@ -586,7 +588,7 @@ export function Lightbox({
586
588
  <div {...stylex.props(styles.navButton, styles.navNext)}>
587
589
  <IconButton
588
590
  icon={<Icon icon="chevronRight" size="sm" color="inherit" />}
589
- label="Next"
591
+ label={t('@astryx.lightbox.next')}
590
592
  variant="ghost"
591
593
  isDisabled={!canNext}
592
594
  onClick={goToNext}
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen} from '@testing-library/react';
13
+ import {fireEvent, render, screen} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {Link} from './Link';
16
16
  import {LinkProvider} from './LinkProvider';
@@ -153,9 +153,81 @@ describe('Link', () => {
153
153
  Disabled Link
154
154
  </Link>,
155
155
  );
156
- const link = screen.getByRole('link');
156
+ // An href-less anchor has no implicit `link` role, so query by text.
157
+ const link = screen.getByText('Disabled Link').closest('a');
158
+ expect(link).not.toBeNull();
157
159
  expect(link).toHaveAttribute('aria-disabled', 'true');
158
160
  expect(link).toHaveAttribute('tabIndex', '-1');
161
+ // Visual classes are preserved in the disabled state.
162
+ expect(link?.className).toContain('astryx-link');
163
+ });
164
+
165
+ it('disabled link has no href attribute', () => {
166
+ render(
167
+ <Link href="/test" isDisabled>
168
+ Disabled Link
169
+ </Link>,
170
+ );
171
+ const link = screen.getByText('Disabled Link').closest('a');
172
+ expect(link).not.toBeNull();
173
+ expect(link).not.toHaveAttribute('href');
174
+ });
175
+
176
+ it('clicking a disabled link cancels default navigation', () => {
177
+ render(
178
+ <Link href="/test" isDisabled>
179
+ Disabled Link
180
+ </Link>,
181
+ );
182
+ const link = screen.getByText('Disabled Link').closest('a');
183
+ expect(link).not.toBeNull();
184
+ // fireEvent bypasses pointer-events, simulating programmatic/AT
185
+ // activation. It returns false when preventDefault was called, i.e.
186
+ // any default navigation behavior is cancelled.
187
+ const notCancelled = fireEvent.click(link as HTMLAnchorElement);
188
+ expect(notCancelled).toBe(false);
189
+ });
190
+
191
+ it('disabled link with onClick fires neither navigation nor the consumer onClick', () => {
192
+ const handleClick = vi.fn();
193
+ const {container} = render(
194
+ <Link href="/test" isDisabled onClick={handleClick}>
195
+ Disabled Link
196
+ </Link>,
197
+ );
198
+ // With onClick present, a disabled Link renders as a disabled <button>
199
+ // (useInteractiveRole excludes a disabled href). Either way the rendered
200
+ // root must carry no live href and never invoke the consumer handler.
201
+ const el = container.firstElementChild as HTMLElement;
202
+ expect(el).not.toHaveAttribute('href');
203
+ fireEvent.click(el);
204
+ expect(handleClick).not.toHaveBeenCalled();
205
+ });
206
+
207
+ it('disabled link omits target and rel', () => {
208
+ render(
209
+ <Link href="https://example.com" isDisabled isExternalLink>
210
+ Disabled External
211
+ </Link>,
212
+ );
213
+ const link = screen.getByText('Disabled External').closest('a');
214
+ expect(link).not.toBeNull();
215
+ expect(link).not.toHaveAttribute('target');
216
+ expect(link).not.toHaveAttribute('rel');
217
+ });
218
+
219
+ it('disabled link renders a plain anchor, not the custom LinkComponent', () => {
220
+ render(
221
+ <LinkProvider component={CustomLink}>
222
+ <Link href="/custom" as={CustomLink} isDisabled>
223
+ Disabled Custom
224
+ </Link>
225
+ </LinkProvider>,
226
+ );
227
+ const link = screen.getByText('Disabled Custom').closest('a');
228
+ expect(link).not.toBeNull();
229
+ expect(link).not.toHaveAttribute('data-custom-link');
230
+ expect(link).not.toHaveAttribute('href');
159
231
  });
160
232
 
161
233
  it('renders external link with icon and target="_blank"', () => {
package/src/Link/Link.tsx CHANGED
@@ -44,6 +44,7 @@ import {mergeProps} from '../utils';
44
44
  import {computeTargetAndRel} from './computeTargetAndRel';
45
45
  import {useInteractiveRole} from '../hooks/useInteractiveRole';
46
46
  import {themeProps} from '../utils/themeProps';
47
+ import {useTranslator} from '../i18n';
47
48
 
48
49
  /**
49
50
  * Base link styles
@@ -174,6 +175,10 @@ export interface LinkProps extends BaseProps<
174
175
  hasUnderline?: boolean;
175
176
  /**
176
177
  * Whether the link is disabled.
178
+ * A disabled link renders as a plain anchor without an href (and without
179
+ * target/rel/onClick), so it cannot be focused or activated — no
180
+ * navigation and no onClick, even via programmatic focus or assistive
181
+ * technology activation.
177
182
  * @default false
178
183
  */
179
184
  isDisabled?: boolean;
@@ -261,6 +266,15 @@ export interface LinkProps extends BaseProps<
261
266
  children: ReactNode;
262
267
  }
263
268
 
269
+ /**
270
+ * Click handler for disabled links. The disabled anchor renders without an
271
+ * href, so there is no navigation to block in practice; preventDefault is a
272
+ * defensive guard against synthetic/programmatic clicks.
273
+ */
274
+ function preventDefaultClick(event: React.MouseEvent<HTMLAnchorElement>): void {
275
+ event.preventDefault();
276
+ }
277
+
264
278
  /**
265
279
  * A styled anchor link component.
266
280
  *
@@ -286,7 +300,7 @@ export function Link({
286
300
  hasUnderline = false,
287
301
  isDisabled = false,
288
302
  isExternalLink = false,
289
- newTabLabel = '(opens in new tab)',
303
+ newTabLabel: newTabLabelFromProps,
290
304
  target: targetFromProps,
291
305
  onClick,
292
306
  tooltip,
@@ -305,6 +319,8 @@ export function Link({
305
319
  ref,
306
320
  ...props
307
321
  }: LinkProps) {
322
+ const t = useTranslator();
323
+ const newTabLabel = newTabLabelFromProps ?? t('@astryx.link.newTab');
308
324
  const LinkComponent = useLinkComponent(as);
309
325
  const role = useInteractiveRole({href, onClick, isDisabled});
310
326
  // Determine target and rel based on isExternalLink
@@ -368,6 +384,37 @@ export function Link({
368
384
  {sharedContent}
369
385
  </button>
370
386
  );
387
+ } else if (isDisabled) {
388
+ // A disabled link renders as a plain <a> with no href: an href-less
389
+ // anchor is not focusable and exposes no link affordance, so programmatic
390
+ // focus + Enter, AT activation commands, and middle-click cannot navigate
391
+ // or fire the consumer onClick. The router LinkComponent is deliberately
392
+ // skipped — a disabled link performs no navigation, and custom router
393
+ // links may require a live href. target/rel are omitted with the href.
394
+ linkElement = (
395
+ <a
396
+ ref={ref as React.Ref<HTMLAnchorElement>}
397
+ onClick={preventDefaultClick}
398
+ aria-label={label || undefined}
399
+ aria-disabled={true}
400
+ tabIndex={-1}
401
+ {...mergeProps(
402
+ themeProps('link', {color}),
403
+ stylex.props(
404
+ styles.base,
405
+ linkColorStyles[color],
406
+ hasUnderline && styles.hasUnderline,
407
+ isStandalone && styles.standalone,
408
+ styles.disabled,
409
+ xstyle,
410
+ ),
411
+ className,
412
+ style,
413
+ )}
414
+ {...props}>
415
+ {sharedContent}
416
+ </a>
417
+ );
371
418
  } else {
372
419
  linkElement = (
373
420
  <LinkComponent