@astryxdesign/core 0.7.1 → 0.7.2-canary.020e1fe

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 (377) hide show
  1. package/dist/AppShell/AppShell.d.ts.map +1 -1
  2. package/dist/AppShell/AppShell.js +40 -1
  3. package/dist/AspectRatio/AspectRatio.d.ts +6 -8
  4. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  5. package/dist/AspectRatio/AspectRatio.js +44 -3
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +38 -14
  10. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  11. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  12. package/dist/CheckboxInput/CheckboxInput.js +3 -1
  13. package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
  14. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  15. package/dist/CheckboxList/CheckboxListItem.js +4 -2
  16. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  17. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteInput.js +37 -5
  19. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  20. package/dist/ContextMenu/ContextMenu.js +5 -2
  21. package/dist/Dialog/Dialog.d.ts.map +1 -1
  22. package/dist/Dialog/Dialog.js +20 -8
  23. package/dist/Drawer/Drawer.d.ts +191 -0
  24. package/dist/Drawer/Drawer.d.ts.map +1 -0
  25. package/dist/Drawer/Drawer.js +382 -0
  26. package/dist/Drawer/DrawerHeader.d.ts +85 -0
  27. package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
  28. package/dist/Drawer/DrawerHeader.js +134 -0
  29. package/dist/Drawer/index.d.ts +11 -0
  30. package/dist/Drawer/index.d.ts.map +1 -0
  31. package/dist/Drawer/index.js +11 -0
  32. package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
  33. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
  34. package/dist/Drawer/useDrawerDialogPresence.js +236 -0
  35. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  36. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  37. package/dist/DropdownMenu/DropdownMenu.js +83 -11
  38. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
  39. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  40. package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
  41. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
  42. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
  43. package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
  44. package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
  45. package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
  46. package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
  47. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  48. package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
  49. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
  50. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
  51. package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
  52. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  53. package/dist/EmptyState/EmptyState.js +4 -10
  54. package/dist/Field/Field.d.ts +1 -1
  55. package/dist/Field/Field.d.ts.map +1 -1
  56. package/dist/Field/Field.js +8 -4
  57. package/dist/Grid/GridSpan.d.ts.map +1 -1
  58. package/dist/Grid/GridSpan.js +1 -0
  59. package/dist/Heading/Heading.d.ts.map +1 -1
  60. package/dist/Heading/Heading.js +4 -2
  61. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  62. package/dist/HoverCard/useHoverCard.js +10 -2
  63. package/dist/Item/Item.d.ts +6 -2
  64. package/dist/Item/Item.d.ts.map +1 -1
  65. package/dist/Item/Item.js +19 -4
  66. package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
  67. package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
  68. package/dist/Layer/layerViewportInset.stylex.js +6 -2
  69. package/dist/Layer/useLayer.d.ts.map +1 -1
  70. package/dist/Layer/useLayer.js +13 -7
  71. package/dist/Layer/useLayerDismissal.d.ts +1 -1
  72. package/dist/Layer/useLayerDismissal.js +1 -1
  73. package/dist/Layout/container.stylex.d.ts +5 -5
  74. package/dist/Layout/container.stylex.js +4 -4
  75. package/dist/List/List.d.ts +46 -19
  76. package/dist/List/List.d.ts.map +1 -1
  77. package/dist/List/List.js +28 -10
  78. package/dist/List/ListContext.d.ts +8 -3
  79. package/dist/List/ListContext.d.ts.map +1 -1
  80. package/dist/List/ListContext.js +7 -2
  81. package/dist/List/ListItem.d.ts +13 -5
  82. package/dist/List/ListItem.d.ts.map +1 -1
  83. package/dist/List/ListItem.js +21 -8
  84. package/dist/List/index.d.ts +1 -1
  85. package/dist/List/index.d.ts.map +1 -1
  86. package/dist/Markdown/Markdown.d.ts.map +1 -1
  87. package/dist/Markdown/Markdown.js +8 -1
  88. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
  89. package/dist/Markdown/plugins/frontmatter.js +7 -3
  90. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  91. package/dist/MetadataList/MetadataList.js +5 -1
  92. package/dist/MetadataList/MetadataListContext.d.ts +1 -1
  93. package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
  94. package/dist/MobileNav/MobileNav.d.ts +9 -0
  95. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  96. package/dist/MobileNav/MobileNav.js +30 -3
  97. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  98. package/dist/MultiSelector/MultiSelector.js +22 -8
  99. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
  100. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  101. package/dist/MultiSelector/hooks.js +3 -1
  102. package/dist/Pagination/Pagination.d.ts +8 -5
  103. package/dist/Pagination/Pagination.d.ts.map +1 -1
  104. package/dist/Pagination/Pagination.js +21 -8
  105. package/dist/Popover/Popover.js +2 -2
  106. package/dist/PowerSearch/PowerSearch.js +1 -1
  107. package/dist/RadioList/RadioList.d.ts +1 -1
  108. package/dist/RadioList/RadioList.d.ts.map +1 -1
  109. package/dist/RadioList/RadioList.js +6 -2
  110. package/dist/RadioList/RadioListItem.d.ts +9 -1
  111. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  112. package/dist/RadioList/RadioListItem.js +2 -0
  113. package/dist/Selector/hooks.d.ts.map +1 -1
  114. package/dist/Selector/hooks.js +6 -2
  115. package/dist/SideNav/SideNavItem.d.ts +8 -1
  116. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  117. package/dist/SideNav/SideNavItem.js +71 -9
  118. package/dist/Switch/Switch.d.ts +1 -1
  119. package/dist/Switch/Switch.d.ts.map +1 -1
  120. package/dist/Switch/Switch.js +13 -1
  121. package/dist/TabList/TabMenu.d.ts +2 -2
  122. package/dist/TabList/TabMenu.d.ts.map +1 -1
  123. package/dist/TabList/TabMenu.js +6 -3
  124. package/dist/Text/Text.d.ts.map +1 -1
  125. package/dist/Text/Text.js +4 -2
  126. package/dist/Text/useTruncation.d.ts +7 -5
  127. package/dist/Text/useTruncation.d.ts.map +1 -1
  128. package/dist/Text/useTruncation.js +16 -5
  129. package/dist/ToggleButton/ToggleButton.d.ts +3 -2
  130. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  131. package/dist/ToggleButton/ToggleButton.js +12 -1
  132. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  133. package/dist/Tooltip/useTooltip.js +11 -0
  134. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  135. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  136. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  137. package/dist/TopNav/TopNavMenu.js +3 -3
  138. package/dist/TreeList/TreeList.d.ts.map +1 -1
  139. package/dist/TreeList/TreeList.js +1 -0
  140. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  141. package/dist/TreeList/TreeListBranches.js +2 -2
  142. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  143. package/dist/TreeList/TreeListItem.js +57 -9
  144. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  145. package/dist/Typeahead/BaseTypeahead.js +26 -0
  146. package/dist/astryx.css +47 -13
  147. package/dist/hooks/useEntryAnimation.d.ts +2 -1
  148. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  149. package/dist/hooks/useEntryAnimation.js +14 -12
  150. package/dist/hooks/useIsHydrating.d.ts +6 -0
  151. package/dist/hooks/useIsHydrating.d.ts.map +1 -0
  152. package/dist/hooks/useIsHydrating.js +30 -0
  153. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  154. package/dist/hooks/useMenuHover.js +18 -13
  155. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  156. package/dist/i18n/generated-locales/en.generated.js +112 -1
  157. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  158. package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
  159. package/dist/index.d.ts +1 -0
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +1 -0
  162. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  163. package/dist/theme/derivedVarRegistry.js +0 -3
  164. package/dist/utils/focusOutline.stylex.d.ts +4 -0
  165. package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
  166. package/dist/utils/focusOutline.stylex.js +4 -0
  167. package/dist/utils/plainDate.d.ts.map +1 -1
  168. package/dist/utils/plainDate.js +15 -9
  169. package/dist/utils/sharedMutationObserver.d.ts +21 -0
  170. package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
  171. package/dist/utils/sharedMutationObserver.js +69 -0
  172. package/locales/en.json +444 -0
  173. package/locales/pseudo.json +333 -0
  174. package/package.json +9 -4
  175. package/scripts/agent-doc-state.mjs +38 -0
  176. package/scripts/postinstall.mjs +1 -1
  177. package/src/AppShell/AppShell.tsx +45 -1
  178. package/src/AspectRatio/AspectRatio.test.tsx +44 -43
  179. package/src/AspectRatio/AspectRatio.tsx +38 -18
  180. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
  181. package/src/Avatar/Avatar.doc.mjs +5 -3
  182. package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
  183. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  184. package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
  185. package/src/Button/Button.doc.mjs +2 -2
  186. package/src/Button/Button.test.tsx +155 -9
  187. package/src/Button/Button.tsx +39 -16
  188. package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
  189. package/src/Button/__tests__/Button.a11y.states.ts +0 -10
  190. package/src/Card/Card.doc.mjs +1 -1
  191. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
  192. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  193. package/src/CheckboxInput/CheckboxInput.tsx +3 -1
  194. package/src/CheckboxList/CheckboxList.test.tsx +22 -0
  195. package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
  196. package/src/CheckboxList/CheckboxListItem.tsx +10 -0
  197. package/src/Code/Code.doc.mjs +1 -0
  198. package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
  199. package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
  200. package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
  201. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
  202. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
  203. package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
  204. package/src/ContextMenu/ContextMenu.tsx +4 -1
  205. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
  206. package/src/Dialog/Dialog.test.tsx +46 -0
  207. package/src/Dialog/Dialog.tsx +20 -7
  208. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
  209. package/src/Drawer/Drawer.doc.mjs +473 -0
  210. package/src/Drawer/Drawer.spec.md +357 -0
  211. package/src/Drawer/Drawer.test.tsx +1300 -0
  212. package/src/Drawer/Drawer.tsx +613 -0
  213. package/src/Drawer/DrawerDirection.test.tsx +127 -0
  214. package/src/Drawer/DrawerHeader.doc.mjs +293 -0
  215. package/src/Drawer/DrawerHeader.test.tsx +102 -0
  216. package/src/Drawer/DrawerHeader.tsx +235 -0
  217. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
  218. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
  219. package/src/Drawer/index.ts +13 -0
  220. package/src/Drawer/useDrawerDialogPresence.ts +284 -0
  221. package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
  222. package/src/DropdownMenu/DropdownMenu.test.tsx +79 -18
  223. package/src/DropdownMenu/DropdownMenu.tsx +83 -12
  224. package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
  225. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
  226. package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
  227. package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
  228. package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
  229. package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
  230. package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
  231. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
  232. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +19 -5
  233. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
  234. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
  235. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
  236. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
  237. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
  238. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
  239. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
  240. package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
  241. package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
  242. package/src/EmptyState/EmptyState.doc.mjs +3 -2
  243. package/src/EmptyState/EmptyState.spec.md +189 -0
  244. package/src/EmptyState/EmptyState.test.tsx +74 -3
  245. package/src/EmptyState/EmptyState.tsx +4 -8
  246. package/src/Field/Field.doc.mjs +14 -8
  247. package/src/Field/Field.spec.md +217 -0
  248. package/src/Field/Field.test.tsx +60 -2
  249. package/src/Field/Field.tsx +17 -8
  250. package/src/Field/FieldLabel.doc.mjs +133 -26
  251. package/src/Field/FieldLabel.spec.md +172 -0
  252. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
  253. package/src/Grid/GridSpan.tsx +3 -0
  254. package/src/Heading/Heading.test.tsx +57 -0
  255. package/src/Heading/Heading.tsx +6 -2
  256. package/src/HoverCard/HoverCard.test.tsx +58 -0
  257. package/src/HoverCard/useHoverCard.tsx +10 -2
  258. package/src/Icon/Icon.doc.mjs +36 -0
  259. package/src/IconButton/IconButton.doc.mjs +2 -2
  260. package/src/IconButton/IconButton.test.tsx +3 -1
  261. package/src/Item/Item.doc.mjs +12 -8
  262. package/src/Item/Item.spec.md +226 -0
  263. package/src/Item/Item.test.tsx +61 -0
  264. package/src/Item/Item.tsx +40 -4
  265. package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
  266. package/src/Layer/layerViewportInset.stylex.ts +6 -2
  267. package/src/Layer/layerViewportInset.test.ts +1 -0
  268. package/src/Layer/useLayer.tsx +24 -13
  269. package/src/Layer/useLayerDismissal.ts +1 -1
  270. package/src/Layout/LayoutHeader.doc.mjs +5 -0
  271. package/src/Layout/container.stylex.ts +6 -6
  272. package/src/List/List.doc.mjs +14 -1
  273. package/src/List/List.spec.md +129 -77
  274. package/src/List/List.test.tsx +163 -94
  275. package/src/List/List.tsx +74 -18
  276. package/src/List/ListContext.tsx +9 -3
  277. package/src/List/ListItem.doc.mjs +24 -5
  278. package/src/List/ListItem.tsx +56 -27
  279. package/src/List/index.ts +1 -1
  280. package/src/Markdown/Markdown.tsx +14 -1
  281. package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
  282. package/src/Markdown/plugins/frontmatter.ts +7 -3
  283. package/src/MetadataList/MetadataList.doc.mjs +3 -3
  284. package/src/MetadataList/MetadataList.test.tsx +38 -0
  285. package/src/MetadataList/MetadataList.tsx +8 -1
  286. package/src/MetadataList/MetadataListContext.tsx +1 -1
  287. package/src/MobileNav/MobileNav.tsx +30 -3
  288. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
  289. package/src/MoreMenu/MoreMenu.test.tsx +5 -1
  290. package/src/MultiSelector/MultiSelector.test.tsx +159 -0
  291. package/src/MultiSelector/MultiSelector.tsx +40 -7
  292. package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
  293. package/src/MultiSelector/hooks.ts +3 -1
  294. package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
  295. package/src/Pagination/Pagination.doc.mjs +7 -7
  296. package/src/Pagination/Pagination.spec.md +9 -9
  297. package/src/Pagination/Pagination.test.tsx +242 -0
  298. package/src/Pagination/Pagination.tsx +27 -11
  299. package/src/Popover/Popover.test.tsx +2 -2
  300. package/src/Popover/Popover.tsx +4 -4
  301. package/src/PowerSearch/PowerSearch.test.tsx +85 -0
  302. package/src/PowerSearch/PowerSearch.tsx +2 -2
  303. package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
  304. package/src/RadioList/RadioList.fr10.test.tsx +45 -2
  305. package/src/RadioList/RadioList.test.tsx +22 -0
  306. package/src/RadioList/RadioList.tsx +4 -1
  307. package/src/RadioList/RadioListItem.doc.mjs +16 -0
  308. package/src/RadioList/RadioListItem.tsx +10 -1
  309. package/src/Resizable/useResizable.doc.mjs +2 -0
  310. package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
  311. package/src/Selector/hooks.ts +6 -2
  312. package/src/Selector/useCombobox.doc.mjs +237 -0
  313. package/src/SideNav/SideNav.spec.md +227 -0
  314. package/src/SideNav/SideNav.test.tsx +179 -0
  315. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  316. package/src/SideNav/SideNavItem.tsx +96 -17
  317. package/src/Slider/Slider.spec.md +39 -18
  318. package/src/Spinner/Spinner.doc.mjs +1 -1
  319. package/src/Switch/Switch.test.tsx +49 -0
  320. package/src/Switch/Switch.tsx +11 -0
  321. package/src/TabList/TabList.spec.md +57 -44
  322. package/src/TabList/TabList.test.tsx +67 -0
  323. package/src/TabList/TabMenu.doc.mjs +4 -3
  324. package/src/TabList/TabMenu.tsx +7 -4
  325. package/src/Text/Text.test.tsx +57 -0
  326. package/src/Text/Text.tsx +6 -2
  327. package/src/Text/useTruncation.test.tsx +69 -0
  328. package/src/Text/useTruncation.ts +16 -5
  329. package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
  330. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  331. package/src/ToggleButton/ToggleButton.test.tsx +95 -0
  332. package/src/ToggleButton/ToggleButton.tsx +12 -1
  333. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
  334. package/src/Tokenizer/Tokenizer.test.tsx +54 -1
  335. package/src/Toolbar/Toolbar.test.tsx +0 -8
  336. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
  337. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
  338. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
  339. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
  340. package/src/Tooltip/Tooltip.test.tsx +31 -0
  341. package/src/Tooltip/useTooltip.test.tsx +13 -0
  342. package/src/Tooltip/useTooltip.tsx +11 -0
  343. package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
  344. package/src/TopNav/TopNavMegaMenu.tsx +7 -3
  345. package/src/TopNav/TopNavMenu.test.tsx +19 -0
  346. package/src/TopNav/TopNavMenu.tsx +11 -3
  347. package/src/TreeList/TreeList.doc.mjs +1 -0
  348. package/src/TreeList/TreeList.spec.md +29 -17
  349. package/src/TreeList/TreeList.test.tsx +327 -6
  350. package/src/TreeList/TreeList.tsx +5 -1
  351. package/src/TreeList/TreeListBranches.tsx +6 -0
  352. package/src/TreeList/TreeListItem.tsx +88 -13
  353. package/src/Typeahead/BaseTypeahead.tsx +26 -0
  354. package/src/Typeahead/Typeahead.test.tsx +260 -2
  355. package/src/docPropLiterals.test.ts +36 -10
  356. package/src/hooks/useEntryAnimation.doc.mjs +4 -4
  357. package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
  358. package/src/hooks/useEntryAnimation.ts +15 -16
  359. package/src/hooks/useIsHydrating.test.tsx +70 -0
  360. package/src/hooks/useIsHydrating.ts +31 -0
  361. package/src/hooks/useMenuHover.ts +18 -13
  362. package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
  363. package/src/i18n/InternationalizationProvider.spec.md +252 -0
  364. package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
  365. package/src/i18n/generated-locales/en.generated.ts +121 -0
  366. package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
  367. package/src/i18n/useTranslator.test.tsx +37 -0
  368. package/src/index.ts +1 -0
  369. package/src/postinstall.test.mjs +46 -0
  370. package/src/reset.css +0 -29
  371. package/src/theme/derivedVarRegistry.test.ts +7 -3
  372. package/src/theme/derivedVarRegistry.ts +0 -3
  373. package/src/utils/focusOutline.stylex.ts +4 -0
  374. package/src/utils/plainDate.test.ts +37 -1
  375. package/src/utils/plainDate.ts +15 -5
  376. package/src/utils/sharedMutationObserver.test.ts +53 -0
  377. package/src/utils/sharedMutationObserver.ts +78 -0
@@ -0,0 +1,296 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FieldLabelAudit.a11y.chromium.spec.ts
5
+ * @input Exact-head FieldLabel story across semantics, themes, direction, and viewport sizes
6
+ * @output Screenshots and fail-closed sensor receipts for the FieldLabel whole-component audit
7
+ * @position Component-owned Night Watch browser evidence; CI runs it only for a FieldLabel audit pull request
8
+ */
9
+
10
+ import {createHash} from 'node:crypto';
11
+ import {execFileSync} from 'node:child_process';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import {expect, test, type Page} from '@playwright/test';
15
+ import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
16
+ import {
17
+ DEFAULT_STORYBOOK_DIR,
18
+ serveStorybook,
19
+ type StaticServer,
20
+ } from '@astryxdesign/a11y-spec/storybook';
21
+
22
+ const OUTPUT = path.resolve('field-label-audit-evidence');
23
+ const WIDE = {width: 1024, height: 900};
24
+ const NARROW = {width: 320, height: 900};
25
+
26
+ type Mode = 'light' | 'dark';
27
+ type Direction = 'ltr' | 'rtl';
28
+ type Theme = 'neutral' | 'butter';
29
+
30
+ type AuditCase = {
31
+ key: string;
32
+ mode: Mode;
33
+ direction: Direction;
34
+ theme: Theme;
35
+ viewport: {width: number; height: number};
36
+ };
37
+
38
+ type AuditReceipt = {
39
+ passed: boolean;
40
+ expected: Record<string, unknown>;
41
+ };
42
+
43
+ const cases: AuditCase[] = [];
44
+ function addPair(
45
+ key: string,
46
+ options: Partial<Omit<AuditCase, 'key' | 'mode'>> = {},
47
+ ) {
48
+ for (const mode of ['light', 'dark'] as const) {
49
+ cases.push({
50
+ key: `${key}-${mode}`,
51
+ mode,
52
+ direction: options.direction ?? 'ltr',
53
+ theme: options.theme ?? 'neutral',
54
+ viewport: options.viewport ?? WIDE,
55
+ });
56
+ }
57
+ }
58
+
59
+ addPair('default');
60
+ addPair('custom-theme', {theme: 'butter'});
61
+ addPair('rtl', {direction: 'rtl'});
62
+ addPair('narrow', {viewport: NARROW});
63
+
64
+ test.describe.configure({mode: 'serial', retries: 0});
65
+ let server: StaticServer;
66
+ let checkoutSha: string;
67
+ let storybookSha: string;
68
+
69
+ function storyUrl(item: AuditCase) {
70
+ return `${server.origin}/iframe.html?id=core-fieldlabel--states&viewMode=story&globals=colorMode:${item.mode};astryxTheme:${item.theme};direction:${item.direction}`;
71
+ }
72
+
73
+ async function settle(page: Page) {
74
+ await holdMotionStill(page);
75
+ await page.evaluate(async () => document.fonts.ready);
76
+ await page.waitForTimeout(40);
77
+ }
78
+
79
+ test.beforeAll(async () => {
80
+ fs.rmSync(OUTPUT, {recursive: true, force: true});
81
+ fs.mkdirSync(OUTPUT, {recursive: true});
82
+ checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
83
+ encoding: 'utf8',
84
+ }).trim();
85
+ const expected = process.env.ASTRYX_HEAD_SHA;
86
+ if (expected && expected !== checkoutSha) {
87
+ throw new Error(
88
+ `checkout ${checkoutSha} does not match expected head ${expected}`,
89
+ );
90
+ }
91
+ server = await serveStorybook(
92
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
93
+ );
94
+ const response = await fetch(`${server.origin}/astryx-build-sha.txt`);
95
+ if (!response.ok) {
96
+ throw new Error(`Storybook build has no SHA stamp (${response.status})`);
97
+ }
98
+ storybookSha = (await response.text()).trim();
99
+ if (storybookSha !== checkoutSha) {
100
+ throw new Error(
101
+ `Storybook ${storybookSha} does not match checkout ${checkoutSha}`,
102
+ );
103
+ }
104
+ });
105
+
106
+ test.afterAll(async () => {
107
+ await server?.close();
108
+ });
109
+
110
+ for (const item of cases) {
111
+ test(item.key, async ({browser}) => {
112
+ const context = await browser.newContext({
113
+ viewport: item.viewport,
114
+ colorScheme: item.mode,
115
+ reducedMotion: 'reduce',
116
+ });
117
+ const page = await context.newPage();
118
+ const pageErrors: string[] = [];
119
+ page.on('pageerror', error => pageErrors.push(String(error)));
120
+ await page.goto(storyUrl(item), {waitUntil: 'load'});
121
+ await page.locator('#storybook-root').waitFor();
122
+
123
+ const targets = page.locator('.astryx-field-label');
124
+ await expect(targets).toHaveCount(7);
125
+ await expect(page.locator('label[for="field-label-default"]')).toHaveText(
126
+ 'Email',
127
+ );
128
+ const description = page.locator('#field-label-description-help');
129
+ await expect(description).toHaveText(
130
+ 'Shown in navigation and invitations.',
131
+ );
132
+ await expect(description.locator('xpath=ancestor::label')).toHaveCount(0);
133
+ await expect(page.locator('#field-label-group-label')).toHaveJSProperty(
134
+ 'tagName',
135
+ 'SPAN',
136
+ );
137
+ await expect(
138
+ page.getByRole('radiogroup', {name: 'Visibility'}),
139
+ ).toBeVisible();
140
+ await expect(page.getByText(/Required/)).toBeVisible();
141
+ await expect(page.getByText(/Optional/)).toBeVisible();
142
+ await expect(page.getByLabel('Search')).toBeVisible();
143
+ const tooltipAnchor = page
144
+ .locator('label[for="field-label-optional"] [aria-describedby]')
145
+ .first();
146
+ await expect(tooltipAnchor).toHaveCount(1);
147
+
148
+ await settle(page);
149
+ const observed = await page.evaluate(() => {
150
+ const labels = Array.from(
151
+ document.querySelectorAll<HTMLElement>('.astryx-field-label'),
152
+ ).map(label => {
153
+ const style = getComputedStyle(label);
154
+ const box = label.getBoundingClientRect();
155
+ return {
156
+ text: label.textContent?.replace(/\s+/g, ' ').trim(),
157
+ tagName: label.tagName,
158
+ htmlFor: label.getAttribute('for'),
159
+ color: style.color,
160
+ cursor: style.cursor,
161
+ geometry: {x: box.x, y: box.y, width: box.width, height: box.height},
162
+ };
163
+ });
164
+ const hidden = document.querySelector<HTMLElement>(
165
+ 'label[for="field-label-hidden"]',
166
+ );
167
+ const hiddenBox = hidden?.getBoundingClientRect();
168
+ const firstLabel = document.querySelector<HTMLElement>(
169
+ '.astryx-field-label',
170
+ );
171
+ const currentTooltipAnchor = document.querySelector<HTMLElement>(
172
+ 'label[for="field-label-optional"] [aria-describedby]',
173
+ );
174
+ return {
175
+ theme: document
176
+ .querySelector('[data-astryx-theme]')
177
+ ?.getAttribute('data-astryx-theme'),
178
+ mode: document.documentElement.getAttribute('data-theme'),
179
+ direction: firstLabel ? getComputedStyle(firstLabel).direction : null,
180
+ viewport: {width: innerWidth, height: innerHeight},
181
+ reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
182
+ horizontalOverflow:
183
+ document.documentElement.scrollWidth > innerWidth + 1,
184
+ fonts: document.fonts.status,
185
+ labels,
186
+ hiddenGeometry: hiddenBox
187
+ ? {width: hiddenBox.width, height: hiddenBox.height}
188
+ : null,
189
+ tooltipAnchor: currentTooltipAnchor
190
+ ? {
191
+ ariaHidden: currentTooltipAnchor.getAttribute('aria-hidden'),
192
+ ariaLabel: currentTooltipAnchor.getAttribute('aria-label'),
193
+ describedBy:
194
+ currentTooltipAnchor.getAttribute('aria-describedby'),
195
+ tabIndex: currentTooltipAnchor.getAttribute('tabindex'),
196
+ }
197
+ : null,
198
+ };
199
+ });
200
+
201
+ const semanticsPass =
202
+ observed.labels.length === 7 &&
203
+ observed.labels.filter(label => label.tagName === 'SPAN').length === 1 &&
204
+ observed.labels.every(label => label.geometry.width > 0) &&
205
+ observed.hiddenGeometry?.width === 1 &&
206
+ observed.hiddenGeometry.height === 1;
207
+ const knownTooltipGapObserved =
208
+ observed.tooltipAnchor?.ariaHidden === 'true' &&
209
+ observed.tooltipAnchor.ariaLabel == null &&
210
+ observed.tooltipAnchor.tabIndex == null;
211
+
212
+ const png = await page.screenshot({animations: 'disabled', fullPage: true});
213
+ const image = `${item.key}.png`;
214
+ const receipt = {
215
+ schemaVersion: 1,
216
+ expected: {
217
+ build: checkoutSha,
218
+ storyId: 'core-fieldlabel--states',
219
+ mode: item.mode,
220
+ direction: item.direction,
221
+ theme: item.theme,
222
+ viewport: item.viewport,
223
+ targetCount: 7,
224
+ groupLabelTag: 'SPAN',
225
+ hiddenGeometry: {width: 1, height: 1},
226
+ knownGap:
227
+ 'the tooltip anchor is anonymous and nonfocusable on this audited head',
228
+ },
229
+ observed: {
230
+ build: storybookSha,
231
+ ...observed,
232
+ pageErrors,
233
+ },
234
+ image: {
235
+ file: image,
236
+ sha256: createHash('sha256').update(png).digest('hex'),
237
+ width: png.readUInt32BE(16),
238
+ height: png.readUInt32BE(20),
239
+ },
240
+ checks: {semanticsPass, knownTooltipGapObserved},
241
+ passed:
242
+ storybookSha === checkoutSha &&
243
+ pageErrors.length === 0 &&
244
+ observed.theme === item.theme &&
245
+ observed.mode === item.mode &&
246
+ observed.direction === item.direction &&
247
+ observed.viewport.width === item.viewport.width &&
248
+ observed.viewport.height === item.viewport.height &&
249
+ observed.reducedMotion &&
250
+ !observed.horizontalOverflow &&
251
+ observed.fonts === 'loaded' &&
252
+ semanticsPass,
253
+ };
254
+ fs.writeFileSync(path.join(OUTPUT, image), png);
255
+ fs.writeFileSync(
256
+ path.join(OUTPUT, `${item.key}.sensors.json`),
257
+ `${JSON.stringify(receipt, null, 2)}\n`,
258
+ );
259
+ await context.close();
260
+ });
261
+ }
262
+
263
+ test('manifest is complete', async () => {
264
+ const receiptFiles = fs
265
+ .readdirSync(OUTPUT)
266
+ .filter(file => file.endsWith('.sensors.json'));
267
+ const receipts = receiptFiles.map(
268
+ (file): AuditReceipt =>
269
+ JSON.parse(
270
+ fs.readFileSync(path.join(OUTPUT, file), 'utf8'),
271
+ ) as AuditReceipt,
272
+ );
273
+ fs.writeFileSync(
274
+ path.join(OUTPUT, 'manifest.json'),
275
+ `${JSON.stringify(
276
+ {
277
+ schemaVersion: 1,
278
+ component: 'core/FieldLabel',
279
+ build: checkoutSha,
280
+ frames: cases.map(item => ({
281
+ key: item.key,
282
+ image: `${item.key}.png`,
283
+ receipt: `${item.key}.sensors.json`,
284
+ })),
285
+ },
286
+ null,
287
+ 2,
288
+ )}\n`,
289
+ );
290
+ expect(receiptFiles).toHaveLength(cases.length);
291
+ expect(
292
+ receipts
293
+ .filter(receipt => !receipt.passed)
294
+ .map(receipt => receipt.expected),
295
+ ).toEqual([]);
296
+ });
@@ -47,6 +47,9 @@ const baseStyles = stylex.create({
47
47
  minWidth: 0, // Prevent overflow in grid
48
48
  // Make span fill grid cell and stretch children
49
49
  display: 'grid',
50
+ // The implicit `auto` column would grow to a child's min-content width;
51
+ // a zero minimum keeps it inside the span.
52
+ gridTemplateColumns: 'minmax(0, 1fr)',
50
53
  height: '100%',
51
54
  },
52
55
  });
@@ -269,3 +269,60 @@ describe('Heading', () => {
269
269
  expect(element).not.toHaveAttribute('data-type', 'editorial');
270
270
  });
271
271
  });
272
+
273
+ // With hasTruncateTooltip={false} nothing reads the measured overflow, so a
274
+ // long table of truncated cells must not pay a layout read per cell.
275
+ function countScrollWidthReads(run: () => void): number {
276
+ const proto = window.HTMLElement.prototype;
277
+ const original = Object.getOwnPropertyDescriptor(proto, 'scrollWidth');
278
+ let reads = 0;
279
+ Object.defineProperty(proto, 'scrollWidth', {
280
+ configurable: true,
281
+ get() {
282
+ reads += 1;
283
+ return 0;
284
+ },
285
+ });
286
+ try {
287
+ run();
288
+ } finally {
289
+ if (original) {
290
+ Object.defineProperty(proto, 'scrollWidth', original);
291
+ } else {
292
+ delete (proto as {scrollWidth?: number}).scrollWidth;
293
+ }
294
+ }
295
+ return reads;
296
+ }
297
+
298
+ describe('Heading maxLines without a truncation tooltip', () => {
299
+ it('takes no truncation measurement when hasTruncateTooltip is false', () => {
300
+ const reads = countScrollWidthReads(() => {
301
+ render(
302
+ <>
303
+ {Array.from({length: 20}, (_, index) => (
304
+ <Heading
305
+ level={3}
306
+ maxLines={1}
307
+ hasTruncateTooltip={false}
308
+ key={index}>
309
+ Cell {index} with text far wider than its column
310
+ </Heading>
311
+ ))}
312
+ </>,
313
+ );
314
+ });
315
+ expect(reads).toBe(0);
316
+ });
317
+
318
+ it('still measures when the tooltip is enabled', () => {
319
+ const reads = countScrollWidthReads(() => {
320
+ render(
321
+ <Heading level={3} maxLines={1}>
322
+ Text far wider than its column
323
+ </Heading>,
324
+ );
325
+ });
326
+ expect(reads).toBeGreaterThan(0);
327
+ });
328
+ });
@@ -253,8 +253,12 @@ export function Heading({
253
253
  // the closed built-in StyleX maps with an unknown name.
254
254
  const builtinType = type && isBuiltinHeadingType(type) ? type : undefined;
255
255
 
256
- // Truncation detection
257
- const truncation = useTruncation({maxLines});
256
+ // Truncation detection. Only the tooltip reads the measured overflow, so
257
+ // with the tooltip off the text is clipped by CSS alone and nothing is
258
+ // measured or observed.
259
+ const truncation = useTruncation({
260
+ maxLines: hasTruncateTooltip === false ? 0 : maxLines,
261
+ });
258
262
 
259
263
  // Tooltip for truncated text
260
264
  const tooltipPlacement: LayerPlacement =
@@ -109,6 +109,64 @@ describe('useHoverCard identity', () => {
109
109
  });
110
110
  });
111
111
 
112
+ // A card closed while the pointer rests on it unmounts without a mouseleave,
113
+ // so the hook must not keep believing the pointer is over the card: if it
114
+ // does, the next open of the same trigger never closes on leave.
115
+ describe('useHoverCard closing while the pointer is on the card', () => {
116
+ function Harness() {
117
+ const card = useHoverCard({delay: 0, hideDelay: 0});
118
+ return (
119
+ <div>
120
+ <span ref={card.ref}>Trigger</span>
121
+ <output data-testid="state">{card.isOpen ? 'open' : 'closed'}</output>
122
+ {card.renderHoverCard(
123
+ <button type="button" onClick={card.hide}>
124
+ Pick
125
+ </button>,
126
+ )}
127
+ </div>
128
+ );
129
+ }
130
+
131
+ async function openThenRestOnCard() {
132
+ const state = screen.getByTestId('state');
133
+ fireEvent.mouseEnter(screen.getByText('Trigger'));
134
+ await waitFor(() => expect(state).toHaveTextContent('open'));
135
+ const pick = screen.getByRole('button', {name: 'Pick', hidden: true});
136
+ fireEvent.mouseLeave(screen.getByText('Trigger'));
137
+ fireEvent.mouseEnter(pick.parentElement!);
138
+ return {state, pick};
139
+ }
140
+
141
+ async function expectNextOpenClosesOnLeave(state: HTMLElement) {
142
+ const trigger = screen.getByText('Trigger');
143
+ fireEvent.mouseEnter(trigger);
144
+ await waitFor(() => expect(state).toHaveTextContent('open'));
145
+ fireEvent.mouseLeave(trigger);
146
+ await waitFor(() => expect(state).toHaveTextContent('closed'));
147
+ }
148
+
149
+ it('closes the next open on leave after hide() from inside the card', async () => {
150
+ render(<Harness />);
151
+ const {state, pick} = await openThenRestOnCard();
152
+
153
+ fireEvent.click(pick);
154
+ await waitFor(() => expect(state).toHaveTextContent('closed'));
155
+
156
+ await expectNextOpenClosesOnLeave(state);
157
+ });
158
+
159
+ it('closes the next open on leave after Escape dismisses the card', async () => {
160
+ render(<Harness />);
161
+ const {state} = await openThenRestOnCard();
162
+
163
+ fireEvent.keyDown(document.body, {key: 'Escape'});
164
+ await waitFor(() => expect(state).toHaveTextContent('closed'));
165
+
166
+ await expectNextOpenClosesOnLeave(state);
167
+ });
168
+ });
169
+
112
170
  describe('HoverCard', () => {
113
171
  it('renders trigger element', () => {
114
172
  render(
@@ -345,6 +345,14 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
345
345
  hideLayer();
346
346
  }, [clearTimeouts, hideLayer]);
347
347
 
348
+ // The imperative hide. A card closed while the pointer rests on it unmounts
349
+ // without a mouseleave, so the content-hover latch is cleared here; left set,
350
+ // it would keep the trigger's next hover-open from closing on leave.
351
+ const hide = useCallback(() => {
352
+ isHoveringContentRef.current = false;
353
+ hideLayer();
354
+ }, [hideLayer]);
355
+
348
356
  const {
349
357
  isTouchPointerRef,
350
358
  isTouchInteraction,
@@ -508,7 +516,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
508
516
  ? null
509
517
  : document.getElementById(layerId);
510
518
  const hadFocus = card?.contains(document.activeElement) ?? false;
511
- hideLayer();
519
+ hide();
512
520
  if (hadFocus) {
513
521
  isEscapeDismissingRef.current = true;
514
522
  triggerRef.current?.focus();
@@ -720,6 +728,6 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
720
728
  isOpen: isLayerOpen,
721
729
  renderHoverCard,
722
730
  show: showLayer,
723
- hide: hideLayer,
731
+ hide,
724
732
  };
725
733
  }
@@ -23,6 +23,42 @@ export const docs = {
23
23
  'pictogram',
24
24
  'graphic',
25
25
  'vector',
26
+ 'arrow',
27
+ 'chevron',
28
+ 'caret',
29
+ 'checkmark',
30
+ 'cross',
31
+ 'gear',
32
+ 'cog',
33
+ 'broom',
34
+ 'sparkle',
35
+ 'sparkles',
36
+ 'star',
37
+ 'heart',
38
+ 'bell',
39
+ 'trash',
40
+ 'pencil',
41
+ 'magnifyingglass',
42
+ 'paperclip',
43
+ 'house',
44
+ 'flame',
45
+ 'rocket',
46
+ 'lightning',
47
+ 'bolt',
48
+ 'globe',
49
+ 'sun',
50
+ 'moon',
51
+ 'bookmark',
52
+ 'thumbsup',
53
+ 'smiley',
54
+ 'emoji',
55
+ 'lock',
56
+ 'eye',
57
+ 'cloud',
58
+ 'hourglass',
59
+ 'trophy',
60
+ 'gift',
61
+ 'camera',
26
62
  ],
27
63
  playground: {
28
64
  // `icon` is required and its type can't be auto-generated, so the
@@ -46,7 +46,7 @@ export const docs = {
46
46
  {
47
47
  name: 'isLoading',
48
48
  type: 'boolean',
49
- description: 'Shows a loading spinner and disables interaction.',
49
+ description: 'Shows a loading spinner and blocks re-activation while staying focusable (aria-busy + aria-disabled, not native disabled).',
50
50
  default: 'false',
51
51
  },
52
52
  {
@@ -141,7 +141,7 @@ export const docsDense = {
141
141
  variant: 'visual style variant',
142
142
  size: 'size variant',
143
143
  elevation: 'resting shadow depth: none|low|med|high; raise for a floating action button (FAB)',
144
- isLoading: 'shows loading spinner + disables interaction',
144
+ isLoading: 'shows loading spinner, blocks re-activation but stays focusable (aria-disabled, not native disabled)',
145
145
  isDisabled: 'disables button',
146
146
  tooltip: 'tooltip text shown on hover',
147
147
  onClick: 'standard click handler',
@@ -124,7 +124,9 @@ describe('IconButton', () => {
124
124
  it('shows loading state', () => {
125
125
  render(<IconButton label="Save" icon={<span>💾</span>} isLoading />);
126
126
  const button = screen.getByRole('button');
127
- expect(button).toBeDisabled();
127
+ // Busy is aria-disabled (focusable), not native disabled — see #4871.
128
+ expect(button).not.toBeDisabled();
129
+ expect(button).toHaveAttribute('aria-disabled', 'true');
128
130
  });
129
131
 
130
132
  it('forwards ref correctly', () => {
@@ -49,7 +49,7 @@ export const docs = {
49
49
  {
50
50
  name: '--_item-inset-inline',
51
51
  description:
52
- 'Inline inset of the row. Item derives its paddingInline from this variable, and List reads it to cancel the inset when edgeCompensation="inline". Set paddingInline on `item` in a theme and both stay in sync.',
52
+ 'Inline inset of the row. Item derives its paddingInline from this variable, and List reads it to place rows for its edge treatments (edge="flush", "outset", and "bleed"). Set paddingInline on `item` in a theme and both stay in sync.',
53
53
  default: 'var(--spacing-2) (var(--spacing-3) for density="spacious")',
54
54
  private: true,
55
55
  },
@@ -118,8 +118,9 @@ export const docs = {
118
118
  },
119
119
  {
120
120
  name: 'align',
121
- type: "'center' | 'start'",
122
- description: 'Vertical alignment of start/end content slots.',
121
+ type: "'center' | 'start' | 'first-line'",
122
+ description:
123
+ "Vertical alignment of start/end content slots. 'center' centers each slot on the whole text block; 'start' lines up slot tops with the text's top; 'first-line' centers each slot on the label's first line, so a small mark stays level with the first line of a wrapped label. A slot taller than that line moves the text down. The `item` theme target reflects the value as `align`.",
123
124
  default: "'center'",
124
125
  },
125
126
  {
@@ -248,7 +249,7 @@ export const docs = {
248
249
  {
249
250
  guidance: true,
250
251
  description:
251
- 'Use align="start" when start or end content is taller than a single line of text.',
252
+ 'Use align="first-line" when a wrapping label sits beside a small mark (a status dot, a radio, a timestamp) that belongs to its first line. Use align="start" to line up the tops of a tall slot and the text.',
252
253
  },
253
254
  {
254
255
  guidance: true,
@@ -321,7 +322,8 @@ export const docsZh = {
321
322
  endContent:
322
323
  '在标签/描述区域之后渲染的内容,例如徽章、元数据、时间戳或操作按钮。',
323
324
  as: '根元素的 HTML 元素。',
324
- align: '起始/结束内容插槽的垂直对齐方式。',
325
+ align:
326
+ "起始/结束内容插槽的垂直对齐方式:'center'(默认)、'start'(顶部对齐)或 'first-line'(以标签第一行为中心)。",
325
327
  density:
326
328
  '间距密度。"compact" 使用 4px 块内距,"balanced" 使用 8px,"spacious" 使用 12px 块内距和内联内距。',
327
329
  labelLines: '标签截断前的最大行数。',
@@ -363,7 +365,8 @@ export const docsZh = {
363
365
  },
364
366
  {
365
367
  guidance: true,
366
- description: '当起始或结束内容高于单行文本时使用 align="start"。',
368
+ description:
369
+ '当换行标签旁边是属于其第一行的小标记(状态点、单选按钮、时间戳)时,使用 align="first-line"。使用 align="start" 让较高插槽与文本顶部对齐。',
367
370
  },
368
371
  {
369
372
  guidance: false,
@@ -426,7 +429,7 @@ export const docsDense = {
426
429
  {
427
430
  guidance: true,
428
431
  description:
429
- 'align="start" when start/end content is taller than one text line.',
432
+ 'align="first-line" for a small mark beside a wrapping label; align="start" to top-align a tall slot with the text.',
430
433
  },
431
434
  {
432
435
  guidance: false,
@@ -458,7 +461,8 @@ export const docsDense = {
458
461
  endContent:
459
462
  'Content after label/description: badges, timestamps, actions.',
460
463
  as: 'Root HTML element.',
461
- align: 'Vertical alignment of start/end content slots.',
464
+ align:
465
+ "Start/end slot alignment: 'center' (default), 'start' (tops), or 'first-line' (centered on the label's first line).",
462
466
  density:
463
467
  'Spacing: "compact" (4px), "balanced" (8px), or "spacious" (12px).',
464
468
  labelLines: 'Max label lines before truncation.',