@astryxdesign/core 0.3.0 → 0.4.0-canary.41f53a0

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 (579) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/AlertDialog/AlertDialog.d.ts +10 -2
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +22 -3
  5. package/dist/AppShell/AppShell.d.ts +2 -2
  6. package/dist/AppShell/AppShell.d.ts.map +1 -1
  7. package/dist/AppShell/AppShell.js +40 -9
  8. package/dist/AspectRatio/AspectRatio.d.ts +13 -0
  9. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  10. package/dist/AspectRatio/AspectRatio.js +9 -0
  11. package/dist/Avatar/Avatar.d.ts.map +1 -1
  12. package/dist/Avatar/Avatar.js +33 -20
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -3
  15. package/dist/Banner/Banner.d.ts.map +1 -1
  16. package/dist/Banner/Banner.js +15 -14
  17. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  18. package/dist/Breadcrumbs/BreadcrumbItem.js +21 -8
  19. package/dist/Breadcrumbs/index.d.ts +2 -1
  20. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  21. package/dist/Breadcrumbs/index.js +1 -0
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +12 -16
  24. package/dist/Calendar/Calendar.d.ts.map +1 -1
  25. package/dist/Calendar/Calendar.js +2 -1
  26. package/dist/Calendar/styles.d.ts +0 -2
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +0 -2
  29. package/dist/Carousel/Carousel.d.ts.map +1 -1
  30. package/dist/Carousel/Carousel.js +8 -12
  31. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  32. package/dist/Chat/ChatDictationButton.js +2 -1
  33. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  34. package/dist/Chat/ChatLayoutScrollButton.js +3 -1
  35. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  36. package/dist/Chat/ChatSendButton.js +4 -3
  37. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  38. package/dist/Chat/ChatToolCalls.js +17 -17
  39. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  40. package/dist/Chat/useTriggerMenu.js +2 -6
  41. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  42. package/dist/CheckboxInput/CheckboxInput.js +36 -125
  43. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  44. package/dist/ClickableCard/ClickableCard.js +3 -11
  45. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  46. package/dist/CodeBlock/CodeBlock.js +84 -67
  47. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  48. package/dist/Collapsible/Collapsible.js +35 -15
  49. package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -1
  50. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  51. package/dist/CommandPalette/CommandPaletteItem.js +6 -3
  52. package/dist/CommandPalette/CommandPaletteList.js +1 -1
  53. package/dist/ComplexSelector/ComplexSelector.d.ts +1 -1
  54. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  55. package/dist/ComplexSelector/ComplexSelector.js +32 -31
  56. package/dist/ContextMenu/ContextMenu.d.ts +2 -2
  57. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  58. package/dist/ContextMenu/ContextMenu.js +1 -1
  59. package/dist/ContextMenu/index.d.ts +2 -1
  60. package/dist/ContextMenu/index.d.ts.map +1 -1
  61. package/dist/ContextMenu/index.js +1 -0
  62. package/dist/DateInput/DateInput.d.ts +9 -2
  63. package/dist/DateInput/DateInput.d.ts.map +1 -1
  64. package/dist/DateInput/DateInput.js +47 -33
  65. package/dist/DateRangeInput/DateRangeInput.d.ts +9 -2
  66. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  67. package/dist/DateRangeInput/DateRangeInput.js +55 -32
  68. package/dist/DateTimeInput/DateTimeInput.d.ts +9 -1
  69. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  70. package/dist/DateTimeInput/DateTimeInput.js +54 -30
  71. package/dist/Dialog/Dialog.d.ts.map +1 -1
  72. package/dist/Dialog/Dialog.js +4 -4
  73. package/dist/DropdownMenu/DropdownMenu.d.ts +33 -8
  74. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  75. package/dist/DropdownMenu/DropdownMenu.js +62 -20
  76. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +8 -11
  77. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +32 -31
  79. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +25 -0
  80. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -0
  81. package/dist/DropdownMenu/DropdownMenuDivider.js +66 -0
  82. package/dist/DropdownMenu/DropdownMenuItem.d.ts +15 -2
  83. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuItem.js +20 -8
  85. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +5 -5
  86. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  87. package/dist/DropdownMenu/DropdownMenuRadioItem.js +24 -67
  88. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  89. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -8
  90. package/dist/DropdownMenu/index.d.ts +2 -1
  91. package/dist/DropdownMenu/index.d.ts.map +1 -1
  92. package/dist/DropdownMenu/index.js +4 -0
  93. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  94. package/dist/DropdownMenu/renderDropdownItems.js +35 -27
  95. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  96. package/dist/EmptyState/EmptyState.js +9 -5
  97. package/dist/Field/InputClearButton.d.ts +16 -3
  98. package/dist/Field/InputClearButton.d.ts.map +1 -1
  99. package/dist/Field/InputClearButton.js +15 -4
  100. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  101. package/dist/HoverCard/useHoverCard.js +3 -19
  102. package/dist/Indicator/CheckIndicator.d.ts +25 -0
  103. package/dist/Indicator/CheckIndicator.d.ts.map +1 -0
  104. package/dist/Indicator/CheckIndicator.js +155 -0
  105. package/dist/Indicator/CheckboxIndicator.d.ts +22 -0
  106. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -0
  107. package/dist/Indicator/CheckboxIndicator.js +173 -0
  108. package/dist/Indicator/RadioIndicator.d.ts +23 -0
  109. package/dist/Indicator/RadioIndicator.d.ts.map +1 -0
  110. package/dist/Indicator/RadioIndicator.js +137 -0
  111. package/dist/Indicator/index.d.ts +24 -0
  112. package/dist/Indicator/index.d.ts.map +1 -0
  113. package/dist/Indicator/index.js +26 -0
  114. package/dist/Indicator/indicator.markers.stylex.d.ts +16 -0
  115. package/dist/Indicator/indicator.markers.stylex.d.ts.map +1 -0
  116. package/dist/Indicator/indicator.markers.stylex.js +21 -0
  117. package/dist/Indicator/indicatorRegistry.d.ts +71 -0
  118. package/dist/Indicator/indicatorRegistry.d.ts.map +1 -0
  119. package/dist/Indicator/indicatorRegistry.js +93 -0
  120. package/dist/Indicator/types.d.ts +177 -0
  121. package/dist/Indicator/types.d.ts.map +1 -0
  122. package/dist/Indicator/types.js +1 -0
  123. package/dist/Indicator/useIndicator.d.ts +18 -0
  124. package/dist/Indicator/useIndicator.d.ts.map +1 -0
  125. package/dist/Indicator/useIndicator.js +30 -0
  126. package/dist/Item/Item.d.ts.map +1 -1
  127. package/dist/Item/Item.js +4 -8
  128. package/dist/Layer/useLayer.d.ts +14 -0
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +25 -2
  131. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  132. package/dist/Lightbox/Lightbox.js +80 -103
  133. package/dist/Link/Link.d.ts.map +1 -1
  134. package/dist/Link/Link.js +4 -5
  135. package/dist/MobileNav/MobileNav.js +8 -8
  136. package/dist/MoreMenu/MoreMenu.d.ts +14 -1
  137. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  138. package/dist/MoreMenu/MoreMenu.js +4 -0
  139. package/dist/MultiSelector/MultiSelector.d.ts +8 -1
  140. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  141. package/dist/MultiSelector/MultiSelector.js +119 -95
  142. package/dist/NumberInput/NumberInput.d.ts +27 -1
  143. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  144. package/dist/NumberInput/NumberInput.js +230 -42
  145. package/dist/Outline/Outline.d.ts.map +1 -1
  146. package/dist/Outline/Outline.js +2 -3
  147. package/dist/Pagination/Pagination.d.ts.map +1 -1
  148. package/dist/Pagination/Pagination.js +61 -63
  149. package/dist/Popover/Popover.d.ts.map +1 -1
  150. package/dist/Popover/Popover.js +6 -8
  151. package/dist/Popover/usePopover.d.ts +15 -0
  152. package/dist/Popover/usePopover.d.ts.map +1 -1
  153. package/dist/Popover/usePopover.js +14 -2
  154. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  155. package/dist/PowerSearch/PowerSearch.js +1 -1
  156. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  157. package/dist/PowerSearch/PowerSearchEditPopover.js +7 -0
  158. package/dist/ProgressBar/ProgressBar.d.ts +11 -4
  159. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  160. package/dist/ProgressBar/ProgressBar.js +95 -6
  161. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  162. package/dist/RadioList/RadioListItem.js +29 -84
  163. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  164. package/dist/Resizable/ResizeHandle.js +2 -3
  165. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  166. package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
  167. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  168. package/dist/SelectableCard/SelectableCard.js +3 -11
  169. package/dist/Selector/Selector.d.ts +12 -1
  170. package/dist/Selector/Selector.d.ts.map +1 -1
  171. package/dist/Selector/Selector.js +222 -111
  172. package/dist/Selector/hooks.d.ts +18 -6
  173. package/dist/Selector/hooks.d.ts.map +1 -1
  174. package/dist/Selector/hooks.js +57 -40
  175. package/dist/SideNav/SideNav.d.ts.map +1 -1
  176. package/dist/SideNav/SideNav.js +1 -1
  177. package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
  178. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  179. package/dist/SideNav/SideNavCollapseButton.js +29 -14
  180. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  181. package/dist/SideNav/SideNavHeading.js +56 -20
  182. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  183. package/dist/SideNav/SideNavItem.js +28 -22
  184. package/dist/Slider/Slider.d.ts.map +1 -1
  185. package/dist/Slider/Slider.js +2 -6
  186. package/dist/Stack/stack.stylex.d.ts +3 -3
  187. package/dist/Switch/Switch.d.ts.map +1 -1
  188. package/dist/Switch/Switch.js +14 -13
  189. package/dist/TabList/Tab.d.ts.map +1 -1
  190. package/dist/TabList/Tab.js +2 -3
  191. package/dist/TabList/TabMenu.d.ts.map +1 -1
  192. package/dist/TabList/TabMenu.js +41 -19
  193. package/dist/Table/BaseTable.d.ts.map +1 -1
  194. package/dist/Table/BaseTable.js +8 -0
  195. package/dist/Table/index.d.ts +1 -1
  196. package/dist/Table/index.d.ts.map +1 -1
  197. package/dist/Table/index.js +1 -1
  198. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +1 -1
  199. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  200. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +22 -18
  201. package/dist/Table/plugins/rowExpansion/index.d.ts +1 -1
  202. package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -1
  203. package/dist/Table/plugins/rowExpansion/index.js +1 -1
  204. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +41 -76
  205. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  206. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +94 -345
  207. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  208. package/dist/Table/plugins/sortable/useTableSortable.js +23 -4
  209. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  210. package/dist/Table/plugins/tree/useTableTreeData.js +29 -33
  211. package/dist/Table/tableContextMenu.d.ts.map +1 -1
  212. package/dist/Table/tableContextMenu.js +2 -1
  213. package/dist/Table/types.d.ts +11 -0
  214. package/dist/Table/types.d.ts.map +1 -1
  215. package/dist/TextArea/TextArea.d.ts +10 -1
  216. package/dist/TextArea/TextArea.d.ts.map +1 -1
  217. package/dist/TextArea/TextArea.js +16 -6
  218. package/dist/TextInput/TextInput.d.ts +10 -1
  219. package/dist/TextInput/TextInput.d.ts.map +1 -1
  220. package/dist/TextInput/TextInput.js +13 -19
  221. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  222. package/dist/Thumbnail/Thumbnail.js +32 -26
  223. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  224. package/dist/TimeInput/TimeInput.js +14 -17
  225. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  226. package/dist/Timestamp/Timestamp.js +14 -3
  227. package/dist/Timestamp/TimestampHoverCard.d.ts.map +1 -1
  228. package/dist/Timestamp/TimestampHoverCard.js +16 -34
  229. package/dist/Timestamp/formatInstant.d.ts +11 -1
  230. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  231. package/dist/Timestamp/formatInstant.js +3 -1
  232. package/dist/Token/Token.d.ts.map +1 -1
  233. package/dist/Token/Token.js +20 -11
  234. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  235. package/dist/Tokenizer/Tokenizer.js +4 -2
  236. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  237. package/dist/Tooltip/useTooltip.js +3 -19
  238. package/dist/TopNav/TopNav.d.ts.map +1 -1
  239. package/dist/TopNav/TopNav.js +6 -5
  240. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  241. package/dist/TopNav/TopNavHeading.js +54 -16
  242. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  243. package/dist/TopNav/TopNavItem.js +3 -9
  244. package/dist/TopNav/TopNavMegaMenu.d.ts +7 -0
  245. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  246. package/dist/TopNav/TopNavMegaMenu.js +146 -60
  247. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  248. package/dist/TopNav/TopNavMegaMenuItem.js +22 -3
  249. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  250. package/dist/TopNav/TopNavMenu.js +83 -38
  251. package/dist/TreeList/TreeList.d.ts.map +1 -1
  252. package/dist/TreeList/TreeList.js +13 -0
  253. package/dist/TreeList/TreeListBranches.d.ts +1 -1
  254. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  255. package/dist/TreeList/TreeListBranches.js +14 -4
  256. package/dist/TreeList/TreeListItem.d.ts +11 -1
  257. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  258. package/dist/TreeList/TreeListItem.js +57 -30
  259. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  260. package/dist/Typeahead/BaseTypeahead.js +16 -8
  261. package/dist/astryx.css +84 -96
  262. package/dist/astryx.umd.js +56 -56
  263. package/dist/astryx.umd.js.map +4 -4
  264. package/dist/hooks/containerReveal.stylex.d.ts +61 -0
  265. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -0
  266. package/dist/hooks/containerReveal.stylex.js +63 -0
  267. package/dist/hooks/index.d.ts +3 -0
  268. package/dist/hooks/index.d.ts.map +1 -1
  269. package/dist/hooks/index.js +2 -0
  270. package/dist/hooks/useClipboard.d.ts +59 -0
  271. package/dist/hooks/useClipboard.d.ts.map +1 -0
  272. package/dist/hooks/useClipboard.js +106 -0
  273. package/dist/hooks/useContainerReveal.d.ts +14 -8
  274. package/dist/hooks/useContainerReveal.d.ts.map +1 -1
  275. package/dist/hooks/useContainerReveal.js +28 -69
  276. package/dist/hooks/useIndicatorFocusRing.d.ts +28 -0
  277. package/dist/hooks/useIndicatorFocusRing.d.ts.map +1 -0
  278. package/dist/hooks/useIndicatorFocusRing.js +96 -0
  279. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  280. package/dist/hooks/useInputStatusIcon.js +17 -4
  281. package/dist/hooks/useLongPress.d.ts.map +1 -1
  282. package/dist/hooks/useLongPress.js +12 -2
  283. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  284. package/dist/hooks/useScrollLock.js +33 -18
  285. package/dist/hooks/useStreamingText.d.ts +18 -3
  286. package/dist/hooks/useStreamingText.d.ts.map +1 -1
  287. package/dist/hooks/useStreamingText.js +47 -7
  288. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  289. package/dist/hooks/useTreeFocus.js +9 -3
  290. package/dist/hooks/useTypeahead.d.ts +3 -2
  291. package/dist/hooks/useTypeahead.d.ts.map +1 -1
  292. package/dist/hooks/useTypeahead.js +19 -7
  293. package/dist/index.d.ts +1 -0
  294. package/dist/index.d.ts.map +1 -1
  295. package/dist/index.js +1 -0
  296. package/dist/theme/defineTheme.d.ts +18 -2
  297. package/dist/theme/defineTheme.d.ts.map +1 -1
  298. package/dist/theme/defineTheme.js +10 -1
  299. package/dist/theme/derivedVarRegistry.d.ts +8 -0
  300. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  301. package/dist/theme/derivedVarRegistry.js +14 -13
  302. package/dist/theme/expandColorScale.d.ts +3 -1
  303. package/dist/theme/expandColorScale.d.ts.map +1 -1
  304. package/dist/theme/expandColorScale.js +17 -7
  305. package/dist/theme/expandTypeScale.d.ts.map +1 -1
  306. package/dist/theme/expandTypeScale.js +17 -0
  307. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  308. package/dist/theme/generateThemeRules.js +11 -0
  309. package/dist/theme/index.d.ts +2 -1
  310. package/dist/theme/index.d.ts.map +1 -1
  311. package/dist/theme/index.js +1 -1
  312. package/dist/theme/tokens.stylex.d.ts +12 -0
  313. package/dist/theme/tokens.stylex.d.ts.map +1 -1
  314. package/dist/theme/tokens.stylex.js +23 -0
  315. package/dist/utils/focusOutline.stylex.d.ts +122 -0
  316. package/dist/utils/focusOutline.stylex.d.ts.map +1 -0
  317. package/dist/utils/focusOutline.stylex.js +143 -0
  318. package/dist/utils/focusReturn.d.ts +24 -0
  319. package/dist/utils/focusReturn.d.ts.map +1 -0
  320. package/dist/utils/focusReturn.js +29 -0
  321. package/dist/utils/index.d.ts +2 -0
  322. package/dist/utils/index.d.ts.map +1 -1
  323. package/dist/utils/index.js +6 -1
  324. package/dist/utils/mergeRefs.d.ts.map +1 -1
  325. package/dist/utils/mergeRefs.js +7 -5
  326. package/dist/utils/rtlStyles.d.ts +2 -2
  327. package/dist/utils/rtlStyles.js +2 -2
  328. package/dist/utils/themeProps.d.ts +20 -1
  329. package/dist/utils/themeProps.d.ts.map +1 -1
  330. package/dist/utils/themeProps.js +8 -2
  331. package/locales/en.json +20 -0
  332. package/locales/pseudo.json +15 -0
  333. package/package.json +8 -3
  334. package/src/AlertDialog/AlertDialog.doc.mjs +16 -3
  335. package/src/AlertDialog/AlertDialog.test.tsx +208 -1
  336. package/src/AlertDialog/AlertDialog.tsx +21 -3
  337. package/src/AppShell/AppShell.doc.mjs +11 -0
  338. package/src/AppShell/AppShell.test.tsx +139 -0
  339. package/src/AppShell/AppShell.tsx +32 -22
  340. package/src/AspectRatio/AspectRatio.doc.mjs +27 -3
  341. package/src/AspectRatio/AspectRatio.test.tsx +30 -0
  342. package/src/AspectRatio/AspectRatio.tsx +13 -0
  343. package/src/Avatar/Avatar.doc.mjs +2 -13
  344. package/src/Avatar/Avatar.test.tsx +33 -8
  345. package/src/Avatar/Avatar.tsx +52 -50
  346. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -9
  347. package/src/Banner/Banner.tsx +9 -6
  348. package/src/Breadcrumbs/BreadcrumbItem.tsx +16 -6
  349. package/src/Breadcrumbs/index.ts +5 -1
  350. package/src/Button/Button.doc.mjs +2 -2
  351. package/src/Button/Button.tsx +22 -41
  352. package/src/Calendar/Calendar.tsx +2 -1
  353. package/src/Calendar/styles.ts +0 -8
  354. package/src/Carousel/Carousel.doc.mjs +3 -0
  355. package/src/Carousel/Carousel.tsx +10 -6
  356. package/src/Chat/Chat.doc.mjs +2 -0
  357. package/src/Chat/ChatDictationButton.tsx +7 -1
  358. package/src/Chat/ChatLayoutScrollButton.test.tsx +22 -0
  359. package/src/Chat/ChatLayoutScrollButton.tsx +8 -1
  360. package/src/Chat/ChatSendButton.tsx +2 -3
  361. package/src/Chat/ChatToolCalls.test.tsx +35 -0
  362. package/src/Chat/ChatToolCalls.tsx +36 -16
  363. package/src/Chat/useTriggerMenu.tsx +2 -5
  364. package/src/CheckboxInput/CheckboxInput.doc.mjs +4 -2
  365. package/src/CheckboxInput/CheckboxInput.test.tsx +120 -0
  366. package/src/CheckboxInput/CheckboxInput.tsx +45 -202
  367. package/src/ClickableCard/ClickableCard.tsx +6 -11
  368. package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
  369. package/src/CodeBlock/CodeBlock.test.tsx +91 -0
  370. package/src/CodeBlock/CodeBlock.tsx +57 -80
  371. package/src/Collapsible/Collapsible.tsx +22 -18
  372. package/src/CommandPalette/CommandPalette.test.tsx +29 -0
  373. package/src/CommandPalette/CommandPaletteItem.tsx +7 -4
  374. package/src/CommandPalette/CommandPaletteList.tsx +2 -2
  375. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  376. package/src/ComplexSelector/ComplexSelector.test.tsx +73 -0
  377. package/src/ComplexSelector/ComplexSelector.tsx +41 -28
  378. package/src/ContextMenu/ContextMenu.doc.mjs +1 -1
  379. package/src/ContextMenu/ContextMenu.test.tsx +41 -0
  380. package/src/ContextMenu/ContextMenu.tsx +3 -3
  381. package/src/ContextMenu/index.ts +5 -1
  382. package/src/DateInput/DateInput.doc.mjs +18 -3
  383. package/src/DateInput/DateInput.test.tsx +112 -11
  384. package/src/DateInput/DateInput.tsx +44 -25
  385. package/src/DateRangeInput/DateRangeInput.doc.mjs +10 -2
  386. package/src/DateRangeInput/DateRangeInput.test.tsx +110 -14
  387. package/src/DateRangeInput/DateRangeInput.tsx +27 -21
  388. package/src/DateTimeInput/DateTimeInput.doc.mjs +16 -2
  389. package/src/DateTimeInput/DateTimeInput.test.tsx +154 -1
  390. package/src/DateTimeInput/DateTimeInput.tsx +58 -17
  391. package/src/Dialog/Dialog.tsx +2 -9
  392. package/src/DropdownMenu/DropdownMenu.doc.mjs +5 -9
  393. package/src/DropdownMenu/DropdownMenu.test.tsx +534 -4
  394. package/src/DropdownMenu/DropdownMenu.tsx +113 -52
  395. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +27 -30
  396. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +34 -0
  397. package/src/DropdownMenu/DropdownMenuDivider.tsx +81 -0
  398. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +39 -4
  399. package/src/DropdownMenu/DropdownMenuItem.tsx +48 -10
  400. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +30 -76
  401. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +25 -19
  402. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +44 -14
  403. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +1 -5
  404. package/src/DropdownMenu/index.ts +8 -1
  405. package/src/DropdownMenu/renderDropdownItems.tsx +33 -34
  406. package/src/EmptyState/EmptyState.doc.mjs +4 -0
  407. package/src/EmptyState/EmptyState.test.tsx +62 -0
  408. package/src/EmptyState/EmptyState.tsx +16 -6
  409. package/src/Field/Field.doc.mjs +1 -0
  410. package/src/Field/InputClearButton.test.tsx +117 -0
  411. package/src/Field/InputClearButton.tsx +30 -3
  412. package/src/HoverCard/useHoverCard.tsx +2 -23
  413. package/src/Icon/Icon.doc.mjs +2 -1
  414. package/src/Indicator/CheckIndicator.tsx +174 -0
  415. package/src/Indicator/CheckboxIndicator.tsx +257 -0
  416. package/src/Indicator/Indicator.doc.mjs +344 -0
  417. package/src/Indicator/Indicator.test.tsx +393 -0
  418. package/src/Indicator/RadioIndicator.tsx +206 -0
  419. package/src/Indicator/index.ts +46 -0
  420. package/src/Indicator/indicator.markers.stylex.ts +19 -0
  421. package/src/Indicator/indicatorRegistry.test.tsx +106 -0
  422. package/src/Indicator/indicatorRegistry.ts +120 -0
  423. package/src/Indicator/types.ts +197 -0
  424. package/src/Indicator/useIndicator.ts +39 -0
  425. package/src/Item/Item.doc.mjs +1 -1
  426. package/src/Item/Item.tsx +7 -14
  427. package/src/Layer/useLayer.doc.mjs +2 -2
  428. package/src/Layer/useLayer.test.tsx +85 -0
  429. package/src/Layer/useLayer.tsx +39 -1
  430. package/src/Lightbox/Lightbox.doc.mjs +1 -1
  431. package/src/Lightbox/Lightbox.tsx +45 -49
  432. package/src/Link/Link.tsx +4 -11
  433. package/src/MobileNav/MobileNav.tsx +3 -3
  434. package/src/MoreMenu/MoreMenu.doc.mjs +31 -0
  435. package/src/MoreMenu/MoreMenu.test.tsx +68 -1
  436. package/src/MoreMenu/MoreMenu.tsx +19 -0
  437. package/src/MultiSelector/MultiSelector.doc.mjs +16 -1
  438. package/src/MultiSelector/MultiSelector.test.tsx +259 -16
  439. package/src/MultiSelector/MultiSelector.tsx +144 -121
  440. package/src/NumberInput/NumberInput.doc.mjs +265 -50
  441. package/src/NumberInput/NumberInput.test.tsx +726 -21
  442. package/src/NumberInput/NumberInput.tsx +353 -59
  443. package/src/Outline/Outline.tsx +2 -5
  444. package/src/Pagination/Pagination.doc.mjs +1 -1
  445. package/src/Pagination/Pagination.test.tsx +22 -7
  446. package/src/Pagination/Pagination.tsx +38 -35
  447. package/src/Popover/Popover.doc.mjs +2 -0
  448. package/src/Popover/Popover.tsx +6 -7
  449. package/src/Popover/usePopover.tsx +37 -4
  450. package/src/PowerSearch/PowerSearch.tsx +1 -1
  451. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +42 -0
  452. package/src/PowerSearch/PowerSearchEditPopover.tsx +7 -0
  453. package/src/ProgressBar/ProgressBar.doc.mjs +27 -5
  454. package/src/ProgressBar/ProgressBar.test.tsx +224 -1
  455. package/src/ProgressBar/ProgressBar.tsx +126 -30
  456. package/src/RadioList/RadioList.doc.mjs +4 -2
  457. package/src/RadioList/RadioListItem.tsx +31 -130
  458. package/src/Resizable/ResizeHandle.tsx +2 -9
  459. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -9
  460. package/src/SelectableCard/SelectableCard.tsx +4 -9
  461. package/src/Selector/Selector.doc.mjs +15 -4
  462. package/src/Selector/Selector.test.tsx +1234 -98
  463. package/src/Selector/Selector.tsx +254 -126
  464. package/src/Selector/hooks.ts +77 -50
  465. package/src/SideNav/SideNav.test.tsx +46 -0
  466. package/src/SideNav/SideNav.tsx +1 -0
  467. package/src/SideNav/SideNavCollapseButton.tsx +15 -10
  468. package/src/SideNav/SideNavHeading.tsx +48 -10
  469. package/src/SideNav/SideNavItem.tsx +20 -12
  470. package/src/Slider/Slider.tsx +2 -11
  471. package/src/Stack/Stack.doc.mjs +110 -0
  472. package/src/StatusDot/StatusDot.doc.mjs +12 -3
  473. package/src/Switch/Switch.test.tsx +32 -0
  474. package/src/Switch/Switch.tsx +18 -10
  475. package/src/TabList/Tab.tsx +2 -9
  476. package/src/TabList/TabMenu.tsx +14 -21
  477. package/src/Table/BaseTable.tsx +11 -0
  478. package/src/Table/index.ts +1 -4
  479. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +30 -14
  480. package/src/Table/plugins/rowExpansion/index.ts +1 -4
  481. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +125 -175
  482. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +115 -458
  483. package/src/Table/plugins/sortable/useTableSortable.tsx +2 -6
  484. package/src/Table/plugins/tree/useTableTreeData.tsx +43 -24
  485. package/src/Table/tableContextMenu.test.tsx +27 -0
  486. package/src/Table/tableContextMenu.tsx +1 -0
  487. package/src/Table/types.ts +11 -0
  488. package/src/Table/useTableGroupedRows.doc.mjs +1 -1
  489. package/src/Table/useTableRowExpansion.doc.mjs +43 -66
  490. package/src/TextArea/TextArea.doc.mjs +42 -2
  491. package/src/TextArea/TextArea.test.tsx +219 -0
  492. package/src/TextArea/TextArea.tsx +34 -7
  493. package/src/TextInput/TextInput.doc.mjs +18 -2
  494. package/src/TextInput/TextInput.test.tsx +189 -0
  495. package/src/TextInput/TextInput.tsx +25 -31
  496. package/src/Thumbnail/Thumbnail.tsx +2 -9
  497. package/src/TimeInput/TimeInput.doc.mjs +2 -2
  498. package/src/TimeInput/TimeInput.test.tsx +84 -3
  499. package/src/TimeInput/TimeInput.tsx +26 -28
  500. package/src/Timestamp/Timestamp.doc.mjs +4 -3
  501. package/src/Timestamp/Timestamp.test.tsx +91 -14
  502. package/src/Timestamp/Timestamp.tsx +14 -2
  503. package/src/Timestamp/TimestampHoverCard.tsx +13 -35
  504. package/src/Timestamp/formatInstant.ts +16 -1
  505. package/src/Token/Token.tsx +5 -28
  506. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  507. package/src/Tokenizer/Tokenizer.test.tsx +45 -3
  508. package/src/Tokenizer/Tokenizer.tsx +10 -2
  509. package/src/Tooltip/useTooltip.tsx +2 -23
  510. package/src/TopNav/TopNav.tsx +1 -3
  511. package/src/TopNav/TopNavHeading.tsx +42 -13
  512. package/src/TopNav/TopNavItem.tsx +3 -21
  513. package/src/TopNav/TopNavMegaMenu.test.tsx +433 -33
  514. package/src/TopNav/TopNavMegaMenu.tsx +118 -53
  515. package/src/TopNav/TopNavMegaMenuItem.tsx +2 -9
  516. package/src/TopNav/TopNavMenu.tsx +34 -37
  517. package/src/TreeList/TreeList.doc.mjs +5 -0
  518. package/src/TreeList/TreeList.test.tsx +208 -8
  519. package/src/TreeList/TreeList.tsx +22 -0
  520. package/src/TreeList/TreeListBranches.tsx +25 -2
  521. package/src/TreeList/TreeListItem.tsx +89 -52
  522. package/src/Typeahead/BaseTypeahead.tsx +19 -6
  523. package/src/Typeahead/Typeahead.doc.mjs +1 -0
  524. package/src/Typeahead/Typeahead.test.tsx +76 -3
  525. package/src/hooks/containerReveal.stylex.ts +102 -0
  526. package/src/hooks/index.ts +4 -0
  527. package/src/hooks/useClipboard.doc.mjs +87 -0
  528. package/src/hooks/useClipboard.test.tsx +151 -0
  529. package/src/hooks/useClipboard.ts +135 -0
  530. package/src/hooks/useContainerReveal.doc.mjs +7 -7
  531. package/src/hooks/useContainerReveal.test.tsx +42 -62
  532. package/src/hooks/useContainerReveal.ts +36 -91
  533. package/src/hooks/useImageMode.test.ts +1 -1
  534. package/src/hooks/useIndicatorFocusRing.tsx +120 -0
  535. package/src/hooks/useInputStatusIcon.tsx +8 -8
  536. package/src/hooks/useLongPress.test.tsx +18 -0
  537. package/src/hooks/useLongPress.ts +12 -2
  538. package/src/hooks/useScrollLock.test.ts +72 -0
  539. package/src/hooks/useScrollLock.ts +51 -19
  540. package/src/hooks/useStreamingText.test.ts +46 -1
  541. package/src/hooks/useStreamingText.ts +49 -7
  542. package/src/hooks/useTreeFocus.test.tsx +50 -0
  543. package/src/hooks/useTreeFocus.ts +14 -3
  544. package/src/hooks/useTypeahead.test.tsx +150 -0
  545. package/src/hooks/useTypeahead.ts +24 -9
  546. package/src/i18n/InternationalizationProvider.doc.mjs +7 -2
  547. package/src/index.ts +1 -0
  548. package/src/theme/defineTheme.ts +27 -0
  549. package/src/theme/derivedVarRegistry.test.ts +21 -1
  550. package/src/theme/derivedVarRegistry.ts +19 -6
  551. package/src/theme/expandColorScale.test.ts +14 -0
  552. package/src/theme/expandColorScale.ts +24 -9
  553. package/src/theme/expandTypeScale.test.ts +43 -0
  554. package/src/theme/expandTypeScale.ts +17 -0
  555. package/src/theme/generateThemeRules.test.ts +78 -20
  556. package/src/theme/generateThemeRules.ts +11 -0
  557. package/src/theme/index.ts +15 -0
  558. package/src/theme/tokens.stylex.ts +18 -0
  559. package/src/theme/tokens.test.ts +37 -0
  560. package/src/utils/focusOutline.stylex.ts +177 -0
  561. package/src/utils/focusReturn.test.ts +42 -0
  562. package/src/utils/focusReturn.ts +31 -0
  563. package/src/utils/index.ts +5 -0
  564. package/src/utils/mergeRefs.test.ts +48 -0
  565. package/src/utils/mergeRefs.ts +12 -6
  566. package/src/utils/rtlStyles.ts +2 -2
  567. package/src/utils/themeProps.ts +26 -4
  568. package/dist/CheckboxInput/checkbox.markers.stylex.d.ts +0 -9
  569. package/dist/CheckboxInput/checkbox.markers.stylex.d.ts.map +0 -1
  570. package/dist/CheckboxInput/checkbox.markers.stylex.js +0 -14
  571. package/dist/RadioList/radio.markers.stylex.d.ts +0 -7
  572. package/dist/RadioList/radio.markers.stylex.d.ts.map +0 -1
  573. package/dist/RadioList/radio.markers.stylex.js +0 -12
  574. package/dist/hooks/containerReveal.pool.stylex.d.ts +0 -303
  575. package/dist/hooks/containerReveal.pool.stylex.d.ts.map +0 -1
  576. package/dist/hooks/containerReveal.pool.stylex.js +0 -292
  577. package/src/CheckboxInput/checkbox.markers.stylex.ts +0 -12
  578. package/src/RadioList/radio.markers.stylex.ts +0 -10
  579. package/src/hooks/containerReveal.pool.stylex.ts +0 -571
@@ -19,11 +19,13 @@ import {
19
19
  afterEach,
20
20
  } from 'vitest';
21
21
  import {render, screen, fireEvent} from '@testing-library/react';
22
+ import userEvent from '@testing-library/user-event';
22
23
  import {AppShell} from './AppShell';
23
24
  import {InternationalizationProvider} from '../i18n';
24
25
  import {MobileNav} from '../MobileNav';
25
26
  import {SideNav, SideNavItem, SideNavSection} from '../SideNav';
26
27
  import {TopNav, TopNavHeading, TopNavItem} from '../TopNav';
28
+ import {useAppShellMobile} from './AppShellMobileContext';
27
29
 
28
30
  // jsdom doesn't implement showModal/close on <dialog>, so we mock them
29
31
  beforeAll(() => {
@@ -778,4 +780,141 @@ describe('AppShell', () => {
778
780
  expect(ref).toHaveBeenCalled();
779
781
  expect(ref.mock.calls[0][0]).toBe(screen.getByTestId('shell'));
780
782
  });
783
+ // ===========================================================================
784
+ // Keyboard operation
785
+ //
786
+ // The focus TRAP and focus RESTORE contracts of the drawer are native
787
+ // <dialog> behaviour and cannot be asserted here: jsdom has no dialog
788
+ // implementation, so this file shims showModal()/close() with an `open`
789
+ // attribute. Those two were verified in Chromium instead. What is asserted
790
+ // here is everything the shim can still prove: the keyboard path to each
791
+ // control, and the ARIA wiring between the toggle and the drawer.
792
+ // ===========================================================================
793
+
794
+ it('reaches the skip link with Tab and moves focus to main with Enter', async () => {
795
+ const user = userEvent.setup();
796
+ render(
797
+ <AppShell topNav={<TopNav label="Main navigation" />}>
798
+ <div>Content</div>
799
+ </AppShell>,
800
+ );
801
+
802
+ await user.tab();
803
+ const skipLink = screen.getByTestId('skip-to-content');
804
+ expect(skipLink).toHaveFocus();
805
+
806
+ await user.keyboard('{Enter}');
807
+ expect(screen.getByRole('main')).toHaveFocus();
808
+ });
809
+
810
+ it('opens the mobile drawer from the keyboard and points the toggle at it', async () => {
811
+ mockMql = createMockMatchMedia(true);
812
+ vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
813
+ const user = userEvent.setup();
814
+
815
+ render(
816
+ <AppShell sideNav={<TestSideNav />}>
817
+ <div>Content</div>
818
+ </AppShell>,
819
+ );
820
+
821
+ const toggle = screen.getByRole('button', {name: 'Open navigation'});
822
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
823
+
824
+ toggle.focus();
825
+ await user.keyboard('{Enter}');
826
+
827
+ expect(toggle).toHaveAttribute('aria-expanded', 'true');
828
+ const drawer = screen.getByRole('dialog', {hidden: true});
829
+ // aria-controls has to name the drawer that actually opened, or a screen
830
+ // reader cannot follow the toggle to it.
831
+ expect(toggle.getAttribute('aria-controls')).toBe(drawer.id);
832
+ expect(drawer.id).toBeTruthy();
833
+ });
834
+
835
+ // ===========================================================================
836
+ // Banner landmark on the mobile top bar
837
+ // ===========================================================================
838
+
839
+ it('exposes the mobile top bar as a banner landmark in a sidenav-only layout', () => {
840
+ mockMql = createMockMatchMedia(true);
841
+ vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
842
+
843
+ render(
844
+ <AppShell sideNav={<TestSideNav />}>
845
+ <div>Content</div>
846
+ </AppShell>,
847
+ );
848
+
849
+ // Same landmark structure as the topNav layout: one banner region holding
850
+ // the top bar, whichever nav slots the page happens to fill.
851
+ expect(screen.getByRole('banner')).toBeInTheDocument();
852
+ });
853
+
854
+ it('renders exactly one banner landmark when a banner slot joins the mobile top bar', () => {
855
+ mockMql = createMockMatchMedia(true);
856
+ vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
857
+
858
+ render(
859
+ <AppShell banner={<div>Announcement</div>} sideNav={<TestSideNav />}>
860
+ <div>Content</div>
861
+ </AppShell>,
862
+ );
863
+
864
+ expect(screen.getAllByRole('banner')).toHaveLength(1);
865
+ });
866
+
867
+ // ===========================================================================
868
+ // useAppShellMobile
869
+ // ===========================================================================
870
+
871
+ function MobileProbe() {
872
+ const {isMobile, isMobileNavOpen, isMobileNavEnabled, mobileNavId} =
873
+ useAppShellMobile();
874
+ return (
875
+ <span data-testid="probe">
876
+ {JSON.stringify({
877
+ isMobile,
878
+ isMobileNavOpen,
879
+ isMobileNavEnabled,
880
+ hasId: mobileNavId != null,
881
+ })}
882
+ </span>
883
+ );
884
+ }
885
+
886
+ it('useAppShellMobile is inert outside an AppShell', () => {
887
+ render(<MobileProbe />);
888
+ expect(JSON.parse(screen.getByTestId('probe').textContent)).toEqual({
889
+ isMobile: false,
890
+ isMobileNavOpen: false,
891
+ isMobileNavEnabled: false,
892
+ hasId: false,
893
+ });
894
+ });
895
+
896
+ it('useAppShellMobile reports the shell mobile state and drawer id', async () => {
897
+ mockMql = createMockMatchMedia(true);
898
+ vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
899
+ const user = userEvent.setup();
900
+
901
+ render(
902
+ <AppShell sideNav={<TestSideNav />}>
903
+ <MobileProbe />
904
+ </AppShell>,
905
+ );
906
+
907
+ expect(JSON.parse(screen.getByTestId('probe').textContent)).toEqual({
908
+ isMobile: true,
909
+ isMobileNavOpen: false,
910
+ isMobileNavEnabled: true,
911
+ hasId: true,
912
+ });
913
+
914
+ await user.click(screen.getByRole('button', {name: 'Open navigation'}));
915
+
916
+ expect(
917
+ JSON.parse(screen.getByTestId('probe').textContent).isMobileNavOpen,
918
+ ).toBe(true);
919
+ });
781
920
  });
@@ -51,6 +51,7 @@ import type {AppShellMobileContextValue} from './AppShellMobileContext';
51
51
  import type {SpacingStep} from '../utils/types';
52
52
  import type {BaseProps} from '../BaseProps';
53
53
  import {mergeProps, mergeRefs, isRenderable} from '../utils';
54
+ import {focusOutlineProps} from '../utils/focusOutline.stylex';
54
55
  import {useMediaQuery} from '../hooks/useMediaQuery';
55
56
  import {observeResize, unobserveResize} from '../utils/sharedResizeObserver';
56
57
  import {themeProps} from '../utils/themeProps';
@@ -217,7 +218,7 @@ export interface AppShellProps extends BaseProps<HTMLDivElement> {
217
218
  * <AppShell mobileNav={{ hasToggle: false }}>
218
219
  * <MobileNavToggle />
219
220
  * </AppShell>
220
- * <AppShell mobileNav={<MobileNav title="Menu">...</MobileNav>} />
221
+ * <AppShell mobileNav={<MobileNav header="Menu">...</MobileNav>} />
221
222
  * <AppShell mobileNav={false} />
222
223
  * ```
223
224
  */
@@ -409,8 +410,8 @@ const styles = stylex.create({
409
410
  flexShrink: 0,
410
411
  overflow: 'clip',
411
412
  position: 'sticky',
412
- top: 'var(--appshell-header-height, 0px)',
413
- height: 'calc(100dvh - var(--appshell-header-height, 0px))',
413
+ top: 'var(--_app-shell-header-height, 0px)',
414
+ height: 'calc(100dvh - var(--_app-shell-header-height, 0px))',
414
415
  // Ensure children (LayoutPanel → SideNav) fill the sticky container
415
416
  display: 'flex',
416
417
  flexDirection: 'column',
@@ -441,7 +442,7 @@ const styles = stylex.create({
441
442
  * topNav={<TopNav label="Navigation" heading={<TopNavHeading heading="My App" />} />}
442
443
  * sideNav={<SideNav>{navSections}</SideNav>}
443
444
  * mobileNav={
444
- * <MobileNav isOpen={mobileOpen} onOpenChange={(open) => setMobileOpen(open)} title="My App">
445
+ * <MobileNav isOpen={mobileOpen} onOpenChange={(open) => setMobileOpen(open)} header="My App">
445
446
  * {navSections}
446
447
  * </MobileNav>
447
448
  * }>
@@ -512,14 +513,19 @@ export function AppShell({
512
513
  const [uncontrolledMobileOpen, setUncontrolledMobileOpen] = useState(false);
513
514
  const isMobileNavOpen = mobileNavConfig?.isOpen ?? uncontrolledMobileOpen;
514
515
 
516
+ const mobileNavOnOpenChange = mobileNavConfig?.onOpenChange;
515
517
  const setMobileNavOpen = useCallback(
516
518
  (open: boolean) => {
517
519
  if (!mobileNavIsControlled) {
518
520
  setUncontrolledMobileOpen(open);
519
521
  }
520
- mobileNavConfig?.onOpenChange?.(open);
522
+ mobileNavOnOpenChange?.(open);
521
523
  },
522
- [mobileNavIsControlled, mobileNavConfig],
524
+ // Depend on the callback itself, not on the config object: the documented
525
+ // usage is an inline `mobileNav={{isOpen, onOpenChange}}` literal, which is
526
+ // a new object every render and would otherwise churn this callback and the
527
+ // context value memo built from it on every render of the shell.
528
+ [mobileNavIsControlled, mobileNavOnOpenChange],
523
529
  );
524
530
 
525
531
  // Move focus to the main content container when the skip link is activated.
@@ -577,7 +583,7 @@ export function AppShell({
577
583
 
578
584
  const updateHeight = () => {
579
585
  const height = headerEl.getBoundingClientRect().height;
580
- shellEl.style.setProperty('--appshell-header-height', `${height}px`);
586
+ shellEl.style.setProperty('--_app-shell-header-height', `${height}px`);
581
587
  };
582
588
 
583
589
  observeResize(headerEl, () => updateHeight());
@@ -669,20 +675,19 @@ export function AppShell({
669
675
  </LayoutHeader>
670
676
  ) : undefined;
671
677
 
672
- const headerContent =
673
- headerInner != null ? (
674
- <div
675
- ref={headerRef}
676
- // Top-level banner landmark for the header region (topNav + banner).
677
- // Safe here: the wrapper is never nested inside main/nav/other landmarks.
678
- role="banner"
679
- {...mergeProps(
680
- themeProps('app-shell-header', {variant}),
681
- stylex.props(navAreaStyle, isAuto && styles.headerSticky),
682
- )}>
683
- {headerInner}
684
- </div>
685
- ) : undefined;
678
+ const headerContent = isRenderable(headerInner) ? (
679
+ <div
680
+ ref={headerRef}
681
+ // Top-level banner landmark for the header region (topNav + banner).
682
+ // Safe here: the wrapper is never nested inside main/nav/other landmarks.
683
+ role="banner"
684
+ {...mergeProps(
685
+ themeProps('app-shell-header', {variant}),
686
+ stylex.props(navAreaStyle, isAuto && styles.headerSticky),
687
+ )}>
688
+ {headerInner}
689
+ </div>
690
+ ) : undefined;
686
691
 
687
692
  // =========================================================================
688
693
  // Build sideNav content
@@ -760,6 +765,11 @@ export function AppShell({
760
765
  const autoMobileTopBar =
761
766
  shouldShowAutoToggle && !hasTopNav && hasSideNav ? (
762
767
  <div
768
+ // Banner landmark for the mobile top bar, so the sidenav-only layout
769
+ // exposes the same landmark structure as the topNav one. Only when
770
+ // there is no headerContent: a banner slot with no topNav renders both,
771
+ // and two sibling banner regions is worse than the one this restores.
772
+ role={headerContent == null ? 'banner' : undefined}
763
773
  {...mergeProps(
764
774
  themeProps('app-shell-header', {variant}),
765
775
  stylex.props(navAreaStyle, isAuto && styles.headerSticky),
@@ -805,7 +815,7 @@ export function AppShell({
805
815
  <a
806
816
  href={`#${MAIN_CONTENT_ID}`}
807
817
  onClick={handleSkipLinkClick}
808
- {...stylex.props(styles.skipLink)}
818
+ {...focusOutlineProps.focusVisible(styles.skipLink)}
809
819
  data-testid="skip-to-content">
810
820
  {t('@astryx.appShell.skipToContent')}
811
821
  </a>
@@ -9,13 +9,21 @@ export const docs = {
9
9
  keywords: ["aspect-ratio","ratio","proportion","responsive","embed","container","widescreen","thumbnail","letterbox","crop"],
10
10
  usage: {
11
11
  description:
12
- 'Maintains a fixed width-to-height ratio for its children, regardless of screen size. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions.',
12
+ 'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
13
13
  bestPractices: [
14
14
  {guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
15
15
  {guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
16
16
  {guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
17
+ {guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
18
+ {guidance: true, description: 'Describe media children with `alt`, or `alt=""` when the image is decorative. AspectRatio adds no role and no accessible name of its own, so the child carries the whole accessible description.'},
17
19
  {guidance: false, description: 'Use for general layout containers; use standard layout components instead.'},
18
20
  {guidance: false, description: 'Nest AspectRatio containers; one level is sufficient.'},
21
+ {guidance: false, description: 'Constrain the height on its own. The width comes from the container, so a `height` or `maxHeight` by itself clamps the box off ratio; pair it with `width: "auto"` to size from the height instead.'},
22
+ {guidance: false, description: 'Place it in a shrink-to-fit parent such as `inline-flex`, `width: fit-content` or a floated box. It contributes no intrinsic width there and collapses to zero.'},
23
+ ],
24
+ anatomy: [
25
+ {name: 'Ratio box', required: true, description: 'The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.'},
26
+ {name: 'Content slot', required: true, description: 'A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.'},
19
27
  ],
20
28
  },
21
29
  props: [
@@ -68,13 +76,21 @@ export const docsZh = {
68
76
  displayName: 'Aspect Ratio',
69
77
  usage: {
70
78
  description:
71
- 'Maintains a fixed width-to-height ratio for its children, regardless of screen size. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions.',
79
+ 'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
72
80
  bestPractices: [
73
81
  {guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
74
82
  {guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
75
83
  {guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
84
+ {guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
85
+ {guidance: true, description: 'Describe media children with `alt`, or `alt=""` when the image is decorative. AspectRatio adds no role and no accessible name of its own, so the child carries the whole accessible description.'},
76
86
  {guidance: false, description: 'Use for general layout containers; use standard layout components instead.'},
77
87
  {guidance: false, description: 'Nest AspectRatio containers; one level is sufficient.'},
88
+ {guidance: false, description: 'Constrain the height on its own. The width comes from the container, so a `height` or `maxHeight` by itself clamps the box off ratio; pair it with `width: "auto"` to size from the height instead.'},
89
+ {guidance: false, description: 'Place it in a shrink-to-fit parent such as `inline-flex`, `width: fit-content` or a floated box. It contributes no intrinsic width there and collapses to zero.'},
90
+ ],
91
+ anatomy: [
92
+ {name: 'Ratio box', required: true, description: 'The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.'},
93
+ {name: 'Content slot', required: true, description: 'A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.'},
78
94
  ],
79
95
  },
80
96
  props: [
@@ -110,13 +126,21 @@ export const docsDense = {
110
126
  description: 'maintains specific aspect ratio for children',
111
127
  usage: {
112
128
  description:
113
- 'Maintains a fixed width-to-height ratio for its children, regardless of screen size. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions.',
129
+ 'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
114
130
  bestPractices: [
115
131
  {guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
116
132
  {guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
117
133
  {guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
134
+ {guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
135
+ {guidance: true, description: 'Describe media children with `alt`, or `alt=""` when the image is decorative. AspectRatio adds no role and no accessible name of its own, so the child carries the whole accessible description.'},
118
136
  {guidance: false, description: 'Use for general layout containers; use standard layout components instead.'},
119
137
  {guidance: false, description: 'Nest AspectRatio containers; one level is sufficient.'},
138
+ {guidance: false, description: 'Constrain the height on its own. The width comes from the container, so a `height` or `maxHeight` by itself clamps the box off ratio; pair it with `width: "auto"` to size from the height instead.'},
139
+ {guidance: false, description: 'Place it in a shrink-to-fit parent such as `inline-flex`, `width: fit-content` or a floated box. It contributes no intrinsic width there and collapses to zero.'},
140
+ ],
141
+ anatomy: [
142
+ {name: 'Ratio box', required: true, description: 'The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.'},
143
+ {name: 'Content slot', required: true, description: 'A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.'},
120
144
  ],
121
145
  },
122
146
  propDescriptions: {
@@ -276,6 +276,36 @@ describe('AspectRatio', () => {
276
276
  });
277
277
  });
278
278
 
279
+ describe('style merging', () => {
280
+ it('keeps the consumer style and applies the ratio over it', () => {
281
+ render(
282
+ <AspectRatio
283
+ ratio={16 / 9}
284
+ data-testid="aspect-ratio"
285
+ style={{opacity: 0.5, aspectRatio: '3 / 1'}}>
286
+ <div>Content</div>
287
+ </AspectRatio>,
288
+ );
289
+ const element = screen.getByTestId('aspect-ratio');
290
+ expect(element.style.opacity).toBe('0.5');
291
+ expect(element.style.aspectRatio).toBe(String(16 / 9));
292
+ });
293
+
294
+ it('keeps a consumer className beside the theme target', () => {
295
+ render(
296
+ <AspectRatio
297
+ ratio={1}
298
+ data-testid="aspect-ratio"
299
+ className="consumer-class">
300
+ <div>Content</div>
301
+ </AspectRatio>,
302
+ );
303
+ const element = screen.getByTestId('aspect-ratio');
304
+ expect(element).toHaveClass('consumer-class');
305
+ expect(element).toHaveClass('astryx-aspect-ratio');
306
+ });
307
+ });
308
+
279
309
  describe('reset.css fit baseline rules', () => {
280
310
  // The cover/contain child sizing ships as zero-specificity baseline
281
311
  // rules in reset.css keyed on the data-astryx-aspect-ratio-override
@@ -94,6 +94,10 @@ export interface AspectRatioProps extends BaseProps<HTMLDivElement> {
94
94
  /**
95
95
  * Content to render inside the aspect ratio container.
96
96
  * The child element will be positioned absolutely to fill the container.
97
+ *
98
+ * Pass a single child. With `fit` set, every direct child is stretched to
99
+ * fill the box, so a second child is laid out below the first and clipped
100
+ * out of view; compose an overlay or caption inside one wrapper child.
97
101
  */
98
102
  children: ReactNode;
99
103
  }
@@ -145,6 +149,15 @@ const styles = stylex.create({
145
149
  * `ratio={1}` or an oval at other ratios. Both shapes respect the provided
146
150
  * `ratio`.
147
151
  *
152
+ * Sizing: the box takes its width from its container and derives its height
153
+ * from the ratio, so it needs an ancestor with a definite width. Two
154
+ * consequences are worth knowing. Constraining only the height (`height` or
155
+ * `maxHeight`) clamps the box without releasing the width, so the rendered
156
+ * ratio no longer matches `ratio`; pass `width: 'auto'` alongside to size from
157
+ * the height instead. And in a shrink-to-fit parent (`inline-flex`,
158
+ * `width: fit-content`, a floated box) it contributes no intrinsic width and
159
+ * collapses to zero.
160
+ *
148
161
  * @example
149
162
  * ```
150
163
  * <AspectRatio ratio={16 / 9} fit="cover">
@@ -29,21 +29,10 @@ export const docs = {
29
29
  theming: {
30
30
  targets: [
31
31
  {className: 'astryx-avatar', visualProps: ['size']},
32
+ {className: 'astryx-avatar-fallback', visualProps: ['size']},
32
33
  {className: 'astryx-avatar-status-dot', visualProps: ['variant']},
33
34
  {className: 'astryx-avatar-status-dot-glyph', visualProps: ['shape']},
34
35
  ],
35
- vars: [
36
- {name: '--_avatar-fallback-font-size', description: 'Initials font size; default is proportional to the avatar size (size × 0.4). Override per size tier (e.g. size:sm) for a custom type scale.', default: 'calc(avatar-size × 0.4)', private: true},
37
- {name: '--_avatar-fallback-font-weight', description: 'Initials font weight', default: 'var(--font-weight-medium)', private: true},
38
- {name: '--_avatar-fallback-color', description: 'Fallback text and default-icon color', default: 'var(--color-text-secondary)', private: true},
39
- {name: '--_avatar-fallback-background', description: 'Fallback wash background fill', default: 'var(--color-neutral)', private: true},
40
- ],
41
- derived: [
42
- {property: 'fontSize', vars: ['--_avatar-fallback-font-size']},
43
- {property: 'fontWeight', vars: ['--_avatar-fallback-font-weight']},
44
- {property: 'color', vars: ['--_avatar-fallback-color']},
45
- {property: 'backgroundColor', vars: ['--_avatar-fallback-background']},
46
- ],
47
36
  },
48
37
  description: 'Displays a user avatar with image, initials fallback, and optional status indicator.',
49
38
  props: [
@@ -104,7 +93,7 @@ export const docs = {
104
93
  name: 'as',
105
94
  type: 'ElementType',
106
95
  description:
107
- 'Custom link component used when `href` is set (e.g. Next.js `Link`). Overrides the provider-level LinkProvider default. Only applies with `href`.',
96
+ 'Custom link component used when `href` is set (e.g. `next/link`). Overrides the provider-level LinkProvider default. Only applies with `href`.',
108
97
  },
109
98
  {
110
99
  name: 'target',
@@ -37,17 +37,42 @@ describe('Avatar', () => {
37
37
  expect(innerImg).toHaveAttribute('alt', '');
38
38
  });
39
39
 
40
- it('renders fallback initials through the themeable font-size var, not a bare px literal', () => {
40
+ it('renders fallback initials at the proportional size via a StyleX class, not an inline property', () => {
41
41
  render(<Avatar name="Ada Lovelace" size="sm" data-testid="a" />);
42
42
  const initials = screen.getByText('AL');
43
- // The seam: the dynamic font size resolves to the Avatar-scoped var (with
44
- // the proportional `size × 0.4` default baked in as the fallback), so a
45
- // theme can re-scope it per size. A regression to a bare px literal would
46
- // break theming.
43
+ // The default proportional size (sm = 24 × 0.4 = 9.6px) is fed to StyleX as
44
+ // a dynamic value: StyleX applies `font-size` through a class and sets only
45
+ // the computed value inline (as a custom property). Because the property
46
+ // lands via a class, a theme's `.astryx-avatar-fallback.<size>` rule in the
47
+ // theme layer overrides it per size tier — no internal var seam needed.
47
48
  const style = initials.getAttribute('style') ?? '';
48
- expect(style).toContain('var(--_avatar-fallback-font-size,');
49
- // Default still reproduces the proportional scale (sm = 24 × 0.4 = 9.6px).
50
- expect(style).toMatch(/var\(--_avatar-fallback-font-size,\s*9\.6\d*px\)/);
49
+ expect(style).toMatch(/9\.6\d*px/);
50
+ // Regression guard: the seam must NOT reintroduce the removed internal var.
51
+ expect(style).not.toContain('--_avatar-fallback-font-size');
52
+ });
53
+
54
+ it('marks the fallback surface with the stable theming class (initials and icon)', () => {
55
+ // The background is themed directly on `.astryx-avatar-fallback`, so both
56
+ // the initials and the default-icon fallback must carry the class.
57
+ const {rerender} = render(<Avatar name="Ada Lovelace" />);
58
+ expect(screen.getByText('AL').className).toContain(
59
+ 'astryx-avatar-fallback',
60
+ );
61
+
62
+ rerender(<Avatar />);
63
+ const icon = document.querySelector('.astryx-avatar-fallback');
64
+ expect(icon).not.toBeNull();
65
+ expect(icon?.querySelector('svg')).not.toBeNull();
66
+ });
67
+
68
+ it('does not split an emoji surrogate pair when generating initials', () => {
69
+ render(<Avatar name="😀 Ada" data-testid="avatar" />);
70
+ expect(screen.getByTestId('avatar')).toHaveTextContent('😀A');
71
+ });
72
+
73
+ it('preserves a complete grapheme when generating initials', () => {
74
+ render(<Avatar name="🇬🇧 Ada" data-testid="avatar" />);
75
+ expect(screen.getByTestId('avatar')).toHaveTextContent('🇬🇧A');
51
76
  });
52
77
 
53
78
  it('retries a new src after a previous src failed to load', () => {