@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,613 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file Drawer.tsx
7
+ * @input Uses React, StyleX, theme tokens, the shared layer text/structure resets and content boundary, the Dialog purpose type, shared focus/dismissal/depth primitives, container padding lowering, scroll locking/dialog presence, BaseProps, merged refs/props, themeProps
8
+ * @output Exports Drawer component and DrawerProps
9
+ * @position Core implementation; consumed by index.ts, tested by Drawer.test.tsx, demonstrated in Storybook
10
+ *
11
+ * Overlay panel for inspectors and detail views — the "click a table row,
12
+ * see its details" pattern. Slides in from the inline start or end edge and
13
+ * floats above the page content: unlike a docked panel it never reflows the
14
+ * layout underneath, it overlays it (with or without a scrim).
15
+ *
16
+ * Inline axis only (start/end). Block-axis sheets are BottomSheet's job;
17
+ * a drawer is always a full-height side panel.
18
+ *
19
+ * Sizing is viewport-aware: `width` is the desktop budget, and below
20
+ * the mobile breakpoint it preserves a 56px reveal of the page behind, capped
21
+ * by the requested width (or fills the viewport with the experimental
22
+ * `experimental.isFullWidthOnMobile` option).
23
+ *
24
+ * Uses the native `<dialog>` element (same precedent as Dialog/MobileNav):
25
+ * - `showModal()` when `hasScrim` (default) — top-layer rendering, focus
26
+ * trapping, `::backdrop`, no z-index management.
27
+ * - `showPopover()` when `hasScrim={false}` — non-modal top-layer overlay;
28
+ * the page behind stays interactive (e.g. master-detail inspectors).
29
+ *
30
+ * Entry animation uses `@starting-style`; exit slides out before the active
31
+ * modal-dialog or manual-popover host releases the top layer and focus returns
32
+ * to the element that opened the drawer. React owns `display` for both legs
33
+ * rather than a discrete `display` transition, so the panel stops painting in
34
+ * the same commit as the host closes — see the `rendered` style for why.
35
+ *
36
+ * Sibling drawers use the shared layer dismissal stack for topmost-only Escape
37
+ * handling and the browser top layer's chronological paint order.
38
+ *
39
+ * Dismissal follows Dialog: `purpose` decides whether Escape and a scrim click
40
+ * request close, and Drawer renders no close button of its own. Compose
41
+ * `DrawerHeader` with `onOpenChange` for a visible close action, as with
42
+ * `DialogHeader`.
43
+ *
44
+ * The scrolling content area is the drawer's container, like Dialog's inner
45
+ * box: the `padding` prop, else the theme's `padding` on `drawer`, insets it
46
+ * through container tokens, defaulting to `--spacing-4` like Dialog, and the
47
+ * applied inset is published so a lone Section child escapes it and a Layout
48
+ * redistributes it to its regions. Compose `DrawerHeader` inside a `Layout`, as
49
+ * `DialogHeader` is composed in Dialog, so the header is not padded twice. The
50
+ * root dialog stays the overlay boundary (`overlayPaddingReset`).
51
+ *
52
+ * SYNC: When modified, update these files to stay in sync:
53
+ * - /packages/core/src/Drawer/Drawer.doc.mjs (props table, features, usage)
54
+ * - /packages/core/src/Drawer/Drawer.test.tsx (tests for new/changed behavior)
55
+ * - /packages/core/src/Drawer/Drawer.spec.md (component contract)
56
+ * - /packages/core/src/Drawer/index.ts (exports if types change)
57
+ * - /packages/cli/assets/templates/blocks/components/Drawer/ (showcase blocks)
58
+ * - /apps/storybook/stories/Drawer.stories.tsx (examples and visual coverage)
59
+ */
60
+
61
+ import {useCallback, useRef, useState, type ReactNode} from 'react';
62
+ import * as stylex from '@stylexjs/stylex';
63
+ import type {BaseProps} from '../BaseProps';
64
+ import {
65
+ borderVars,
66
+ colorVars,
67
+ durationVars,
68
+ easeVars,
69
+ shadowVars,
70
+ } from '../theme/tokens.stylex';
71
+ import type {DialogPurpose} from '../Dialog';
72
+ import {useFocusTrap, useMergedRefs, useScrollLock} from '../hooks';
73
+ import {LayerDepthProvider, useLayerDismissal} from '../Layer';
74
+ import {layerTextReset} from '../Layer/layerTextReset.stylex';
75
+ import {layerStructureReset} from '../Layer/layerStructureReset.stylex';
76
+ import {LayerContentBoundary} from '../Layer/layerScopedContext';
77
+ import {composeEventHandlers, mergeProps, themeProps} from '../utils';
78
+ import {container, overlayPaddingReset} from '../Layout';
79
+ import {
80
+ containerPaddingBlockEndVarStyles,
81
+ containerPaddingBlockStartVarStyles,
82
+ containerPaddingInlineVarStyles,
83
+ paddingStyles,
84
+ spacingStepToToken,
85
+ } from '../Layout/padding.stylex';
86
+ import type {SpacingStep, SpacingToken} from '../Layout';
87
+ import {useDrawerDialogPresence} from './useDrawerDialogPresence';
88
+
89
+ // =============================================================================
90
+ // Styles
91
+ // =============================================================================
92
+
93
+ // Below this viewport width the drawer preserves a fixed reveal of the page
94
+ // behind instead of growing proportionally with the viewport. 640px is the
95
+ // repo's mobile breakpoint.
96
+ const MOBILE_BREAKPOINT = 640;
97
+
98
+ // Material's established mobile drawer pattern leaves a 56dp reveal. Using
99
+ // the same value in CSS pixels gives the overlay a stable visual relationship
100
+ // to the page behind while the requested width remains an upper bound.
101
+ const MOBILE_PAGE_REVEAL = 56;
102
+ const MOBILE_WIDTH_FULL = '100dvw';
103
+
104
+ const styles = stylex.create({
105
+ dialog: {
106
+ // Reset native <dialog> defaults — the dialog element IS the panel.
107
+ // The layer text baseline comes from the shared `layerTextReset`, applied
108
+ // with the structural reset ahead of these styles on the root below.
109
+ position: 'fixed',
110
+ margin: 0,
111
+ padding: 0,
112
+ borderWidth: 0,
113
+ borderStyle: 'none',
114
+ // Square corners: the drawer is flush with the viewport edge on three
115
+ // sides, so a radius would only ever cut the two edge-adjacent corners.
116
+ borderRadius: 0,
117
+ maxWidth: 'none',
118
+ maxHeight: 'none',
119
+ boxSizing: 'border-box',
120
+ flexDirection: 'column',
121
+ backgroundColor: colorVars['--color-background-surface'],
122
+ boxShadow: shadowVars['--shadow-high'],
123
+ overflow: 'hidden',
124
+ overscrollBehavior: 'contain',
125
+ outline: 'none',
126
+ // Full-height side panel, pinned across the block axis.
127
+ insetBlockStart: 0,
128
+ insetBlockEnd: 0,
129
+ height: '100dvh',
130
+ // Closed state. `display` is owned by React (see `rendered`), not by a
131
+ // discrete `display` transition, so only `transform` animates here.
132
+ display: 'none',
133
+ transitionProperty: 'transform',
134
+ transitionDuration: durationVars['--duration-medium'],
135
+ transitionTimingFunction: easeVars['--ease-standard'],
136
+ '@media (prefers-reduced-motion: reduce)': {
137
+ transitionDuration: '0.01s',
138
+ },
139
+ },
140
+ // Rendered while the drawer is open AND while it slides out. Applied from
141
+ // React state, not from a discrete `display` transition: leaving either
142
+ // native top-layer host drops the panel back into its ordinary containing
143
+ // context, and any ancestor that establishes a containing block for fixed
144
+ // positioning (transform, filter, container-type, contain) then becomes the
145
+ // origin for the panel's `position: fixed`. A panel still painting after host
146
+ // release therefore snaps back INTO the layout and covers the page for the
147
+ // rest of the hold. Owning `display` lets the hide land in the same commit, so
148
+ // no frame is ever painted outside the top layer.
149
+ //
150
+ // Applied via the isOpen/exit state, not :where([open]) — attribute
151
+ // selectors have zero specificity and can lose to default styles
152
+ // depending on CSS source order (same rationale as Dialog/MobileNav).
153
+ rendered: {
154
+ display: 'flex',
155
+ },
156
+ // DOM directionality comes from the nearest effective `dir` attribute. A
157
+ // local override and an inherited region therefore agree with the logical
158
+ // edge the panel is anchored to.
159
+ end: {
160
+ insetInlineEnd: 0,
161
+ insetInlineStart: 'auto',
162
+ borderInlineStartWidth: borderVars['--border-width'],
163
+ borderInlineStartStyle: 'solid',
164
+ borderInlineStartColor: colorVars['--color-border'],
165
+ transform: {
166
+ default: 'translateX(100%)',
167
+ ':dir(rtl)': 'translateX(-100%)',
168
+ },
169
+ },
170
+ endOpen: {
171
+ transform: {
172
+ default: 'translateX(0)',
173
+ '@starting-style': {
174
+ default: 'translateX(100%)',
175
+ ':dir(rtl)': 'translateX(-100%)',
176
+ },
177
+ },
178
+ },
179
+ start: {
180
+ insetInlineStart: 0,
181
+ insetInlineEnd: 'auto',
182
+ borderInlineEndWidth: borderVars['--border-width'],
183
+ borderInlineEndStyle: 'solid',
184
+ borderInlineEndColor: colorVars['--color-border'],
185
+ transform: {
186
+ default: 'translateX(-100%)',
187
+ ':dir(rtl)': 'translateX(100%)',
188
+ },
189
+ },
190
+ startOpen: {
191
+ transform: {
192
+ default: 'translateX(0)',
193
+ '@starting-style': {
194
+ default: 'translateX(-100%)',
195
+ ':dir(rtl)': 'translateX(100%)',
196
+ },
197
+ },
198
+ },
199
+ // Scrim via the browser's ::backdrop pseudo-element (top layer).
200
+ scrim: {
201
+ '::backdrop': {
202
+ backgroundColor: colorVars['--color-overlay'],
203
+ backdropFilter: 'blur(2px)',
204
+ opacity: 0,
205
+ transitionProperty: 'opacity',
206
+ transitionDuration: durationVars['--duration-medium'],
207
+ transitionTimingFunction: easeVars['--ease-standard'],
208
+ },
209
+ '@media (prefers-reduced-motion: reduce)': {
210
+ '::backdrop': {
211
+ transitionDuration: '0.01s',
212
+ },
213
+ },
214
+ },
215
+ scrimOpen: {
216
+ '::backdrop': {
217
+ opacity: {
218
+ default: 1,
219
+ '@starting-style': 0,
220
+ },
221
+ },
222
+ },
223
+ // Scrollable content area, and the drawer's container: it carries the
224
+ // container padding (--spacing-4 unless the `padding` prop or the theme sets
225
+ // it, as in Dialog) and publishes it, so a lone Section child escapes it
226
+ // exactly as in Dialog and a Layout picks it up for its regions.
227
+ // touch-action + overscroll containment keep momentum scrolling inside
228
+ // the panel on touch devices.
229
+ content: {
230
+ flexGrow: 1,
231
+ minHeight: 0,
232
+ width: '100%',
233
+ overflowY: 'auto',
234
+ overflowX: 'hidden',
235
+ overscrollBehavior: 'contain',
236
+ touchAction: 'pan-y',
237
+ outline: 'none',
238
+ // The scrolling area paints the surface itself, covering the panel's whole
239
+ // inner box. Without it the panel's edge is not uniform: a theme that packs
240
+ // an inset ring into --shadow-high (the bundled themes all add one in dark
241
+ // mode) draws that ring just inside the dialog, where an opaque content
242
+ // wrapper such as Section paints over it — so the ring shows only in the
243
+ // gap below where the content ends, and the panel's edges appear to change
244
+ // partway down. Painting the surface here hides the ring evenly, leaving
245
+ // the anchored-side border as the drawer's one edge (same treatment as
246
+ // BottomSheetPanel's body, #5305).
247
+ backgroundColor: colorVars['--color-background-surface'],
248
+ },
249
+ // Block-end inset of the content area = container padding + the home
250
+ // indicator's safe area, so the last row of content stays clear of the
251
+ // indicator in every padding mode. Applied after container()'s own padding so this
252
+ // composed value wins on the block-end edge; the published
253
+ // --container-padding-block-end keeps the safe area OUT, so a bleed child
254
+ // subtracts only the container inset and the safe area survives below it.
255
+ contentSafeArea: {
256
+ paddingBlockEnd:
257
+ 'calc(var(--container-padding-block-end, 0px) + env(safe-area-inset-bottom, 0px))',
258
+ },
259
+ });
260
+
261
+ const dynamicStyles = stylex.create({
262
+ // Width budget: the `width` prop on desktop, a share of the viewport below
263
+ // the mobile breakpoint. maxWidth keeps a large desktop budget from
264
+ // overflowing a narrow window.
265
+ inlineSize: (desktopWidth: string, mobileWidth: string) => ({
266
+ width: {
267
+ default: desktopWidth,
268
+ [`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: mobileWidth,
269
+ },
270
+ maxWidth: '100dvw',
271
+ }),
272
+ });
273
+
274
+ // =============================================================================
275
+ // Types
276
+ // =============================================================================
277
+
278
+ export interface DrawerProps extends BaseProps<HTMLDialogElement> {
279
+ /** Ref forwarded to the root <dialog> element */
280
+ ref?: React.Ref<HTMLDialogElement>;
281
+
282
+ /**
283
+ * Whether the drawer is open. Fully controlled — pair with `onOpenChange`.
284
+ */
285
+ isOpen: boolean;
286
+
287
+ /**
288
+ * Called when the drawer requests an open-state change. Escape and a scrim
289
+ * click call it with `false` as `purpose` allows; a `DrawerHeader` close
290
+ * button calls it when you pass the same callback to the header. The caller
291
+ * owns the open state. When sibling drawers are open, Escape only closes the
292
+ * top (last-opened) drawer.
293
+ */
294
+ onOpenChange: (isOpen: boolean) => void;
295
+
296
+ /**
297
+ * Which edge the drawer slides from.
298
+ * - `'end'` — inline-end edge (right in LTR) — the inspector convention
299
+ * - `'start'` — inline-start edge (left in LTR)
300
+ * @default 'end'
301
+ */
302
+ side?: 'start' | 'end';
303
+
304
+ /**
305
+ * Desktop width budget. A number is pixels; a string is any CSS length
306
+ * (`'50%'`, `'32rem'`). Below the mobile breakpoint (640px), this
307
+ * remains the maximum while the drawer preserves a 56px reveal of the page
308
+ * behind — see `experimental.isFullWidthOnMobile`.
309
+ * @default 400
310
+ */
311
+ width?: number | string;
312
+
313
+ /**
314
+ * Experimental options, outside the stable compatibility promise: anything
315
+ * in this object may change or be removed in a patch release. Omit it for
316
+ * the stable behavior.
317
+ * @experimental
318
+ */
319
+ experimental?: {
320
+ /**
321
+ * Cover the full viewport width on mobile (below 640px) instead of
322
+ * preserving the default 56px reveal of the page behind. The reveal makes
323
+ * the drawer read as an overlay rather than a navigation.
324
+ * @experimental
325
+ * @default false
326
+ */
327
+ isFullWidthOnMobile?: boolean;
328
+ };
329
+
330
+ /**
331
+ * Accessible label for the drawer (required — the drawer has no
332
+ * built-in heading to derive a name from).
333
+ */
334
+ label: string;
335
+
336
+ /**
337
+ * Whether to render a modal scrim behind the drawer.
338
+ * - `true` (default) — `showModal()`: top layer, focus trap, body scroll
339
+ * lock, click-outside-to-close.
340
+ * - `false` — `showPopover()`: non-modal top-layer overlay; the page behind
341
+ * stays interactive. Escape still closes through the shared layer stack.
342
+ * @default true
343
+ */
344
+ hasScrim?: boolean;
345
+
346
+ /**
347
+ * Configures how the drawer enables dismissals, matching Dialog.
348
+ * - required: Disables Escape and scrim click (for mandatory flows); a
349
+ * modal required drawer is exposed as an `alertdialog`
350
+ * - form: Prevents scrim click, allows the Escape key
351
+ * - info: Allows Escape and scrim click
352
+ *
353
+ * A non-modal drawer has no scrim, so `form` and `info` behave the same.
354
+ * @default 'info'
355
+ */
356
+ purpose?: DialogPurpose;
357
+
358
+ /**
359
+ * Internal padding of the drawer content using the spacing scale, matching
360
+ * Dialog. Accepts numeric spacing steps: 0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8,
361
+ * 10. When omitted, uses the theme default for drawers (`--spacing-4`, 16px,
362
+ * unless the theme sets `padding` on `drawer`), as Dialog does. Pass `0` for
363
+ * a full-bleed content area. The content area is a container: a Section that
364
+ * is its only child escapes the padding, bleed children such as Table and
365
+ * Divider compensate against it, and a Layout picks it up for its regions.
366
+ */
367
+ padding?: SpacingStep;
368
+
369
+ /**
370
+ * Drawer content. Rendered inside a full-height scrollable area.
371
+ * Focus the element with `data-autofocus` on open, if present.
372
+ */
373
+ children: ReactNode;
374
+
375
+ /**
376
+ * Test ID for the root element.
377
+ */
378
+ 'data-testid'?: string;
379
+ }
380
+
381
+ // =============================================================================
382
+ // Component
383
+ // =============================================================================
384
+
385
+ /**
386
+ * An overlay panel for inspectors and detail views.
387
+ *
388
+ * Slides in from the logical start or end edge and floats above the page
389
+ * using the native `<dialog>` element: modal with a scrim by default, or a
390
+ * non-modal overlay with `hasScrim={false}` that leaves the page behind
391
+ * interactive. `width` is the desktop budget; below 640px the panel preserves
392
+ * a 56px page reveal without exceeding that budget (or fills the viewport
393
+ * with the experimental `experimental.isFullWidthOnMobile`). Escape
394
+ * closes the top-most open drawer; focus returns to the element that
395
+ * opened it.
396
+ *
397
+ * Dismissal matches Dialog: `purpose` decides whether Escape and a scrim click
398
+ * close the drawer, and `DrawerHeader` renders a close button when given
399
+ * `onOpenChange`.
400
+ *
401
+ * Like Dialog, the drawer is a container: `padding` (or a theme's `padding`
402
+ * on `drawer`) insets the content area, a lone Section child escapes that
403
+ * inset, and a Layout inside picks it up for its header, content, and footer
404
+ * regions. The default inset is `--spacing-4`, matching Dialog.
405
+ *
406
+ * @example
407
+ * ```
408
+ * const [selected, setSelected] = useState(null);
409
+ * const handleOpenChange = isOpen => !isOpen && setSelected(null);
410
+ * <Drawer
411
+ * isOpen={selected != null}
412
+ * onOpenChange={handleOpenChange}
413
+ * label={`Details: ${selected?.name}`}>
414
+ * <Layout
415
+ * header={<DrawerHeader title={selected?.name} onOpenChange={handleOpenChange} />}
416
+ * content={<LayoutContent><HostDetails host={selected} /></LayoutContent>}
417
+ * />
418
+ * </Drawer>
419
+ * ```
420
+ */
421
+ export function Drawer({
422
+ isOpen,
423
+ onOpenChange,
424
+ side = 'end',
425
+ width = 400,
426
+ experimental,
427
+ label,
428
+ hasScrim = true,
429
+ purpose = 'info',
430
+ padding,
431
+ children,
432
+ xstyle,
433
+ className,
434
+ style,
435
+ onClick: onClickProp,
436
+ onKeyDown: onKeyDownProp,
437
+ ref,
438
+ ...props
439
+ }: DrawerProps) {
440
+ const dialogRef = useRef<HTMLDialogElement>(null);
441
+ const {containerRef: focusTrapRef} = useFocusTrap<HTMLDialogElement>({
442
+ isActive: isOpen && hasScrim,
443
+ });
444
+ const mergedDialogRef = useMergedRefs(ref, dialogRef, focusTrapRef);
445
+ // Derive dismissal behavior from purpose, as Dialog does.
446
+ const allowEscape = purpose !== 'required';
447
+ const allowScrimClick = purpose === 'info';
448
+ // Same lowering as Dialog: with no padding prop the content area reads the
449
+ // theme's --astryx-drawer-padding chain (--spacing-4 when unset), else the
450
+ // explicit step.
451
+ const usesThemePadding = padding == null;
452
+ const effectivePadding = padding ?? 4;
453
+ const paddingToken = spacingStepToToken[effectivePadding] as SpacingToken;
454
+ // Whether the panel paints: true while open and for the whole slide-out.
455
+ const [isRendered, setIsRendered] = useState(isOpen);
456
+
457
+ // Adjusted during render, not in an effect: the panel has to be rendered in
458
+ // the same commit that targets the open transform, or @starting-style has
459
+ // nothing to animate from.
460
+ if (isOpen && !isRendered) {
461
+ setIsRendered(true);
462
+ }
463
+
464
+ useDrawerDialogPresence({
465
+ dialogRef,
466
+ isOpen,
467
+ isModal: hasScrim,
468
+ setIsRendered,
469
+ });
470
+
471
+ const handleDismiss = useCallback(() => {
472
+ onOpenChange(false);
473
+ }, [onOpenChange]);
474
+
475
+ // The shared stack owns Escape delivery across every overlay family. The
476
+ // provider below gives layers opened from Drawer content a greater logical
477
+ // depth even when they render elsewhere in the DOM or browser top layer.
478
+ const {shouldDismissOnCloseRequest} = useLayerDismissal({
479
+ // The native host stays present through the slide-out. Keep this entry on
480
+ // the stack until the same commit that hides the host, so a second Escape
481
+ // cannot fall through to a lower layer during the exit animation.
482
+ isActive: isRendered,
483
+ // A `required` drawer consumes Escape without closing, so the press cannot
484
+ // fall through and dismiss a layer behind it either.
485
+ escapeBehavior: allowEscape ? 'close' : 'block',
486
+ onDismiss: handleDismiss,
487
+ getContainer: () => dialogRef.current,
488
+ });
489
+
490
+ // Lock body scroll while a modal drawer is open (iOS Safari workaround).
491
+ useScrollLock(isOpen && hasScrim);
492
+
493
+ // A modal dialog's native cancel event is a platform close request (for
494
+ // example Android Back). Keep controlled state authoritative and apply the
495
+ // same top-most/IME rules as the shared Escape listener.
496
+ const handleCancel = useCallback(
497
+ (event: React.SyntheticEvent<HTMLDialogElement>) => {
498
+ event.preventDefault();
499
+ if (shouldDismissOnCloseRequest() && allowEscape) {
500
+ handleDismiss();
501
+ }
502
+ },
503
+ [allowEscape, handleDismiss, shouldDismissOnCloseRequest],
504
+ );
505
+
506
+ // Clicks on the ::backdrop target the <dialog> element itself; clicks on
507
+ // drawer content always target a child (the content area fills the panel).
508
+ const handleClick = useCallback(
509
+ (event: React.MouseEvent<HTMLDialogElement>) => {
510
+ if (event.target === event.currentTarget && hasScrim && allowScrimClick) {
511
+ onOpenChange(false);
512
+ }
513
+ },
514
+ [allowScrimClick, hasScrim, onOpenChange],
515
+ );
516
+
517
+ const widthValue = typeof width === 'number' ? `${width}px` : width;
518
+ const isFullWidthOnMobile = experimental?.isFullWidthOnMobile ?? false;
519
+ const mobileWidth = isFullWidthOnMobile
520
+ ? MOBILE_WIDTH_FULL
521
+ : `min(${widthValue}, calc(100dvw - ${MOBILE_PAGE_REVEAL}px))`;
522
+
523
+ // The side the panel is ANCHORED to, which is the side it must slide back
524
+ // out to. Latched at open, because a consumer commonly derives `side` from
525
+ // the same state that drives `isOpen` (`side={selected?.side ?? 'end'}`):
526
+ // that state clears on close, so the live prop flips mid-exit and the panel
527
+ // teleports to the other edge and slides out the wrong way. Children stay
528
+ // mounted for the exit for the same reason; so does the anchor.
529
+ const exitSideRef = useRef(side);
530
+ if (isOpen) {
531
+ exitSideRef.current = side;
532
+ }
533
+ const anchoredSide = isOpen ? side : exitSideRef.current;
534
+
535
+ const sideStyle = anchoredSide === 'start' ? styles.start : styles.end;
536
+ const sideOpenStyle =
537
+ anchoredSide === 'start' ? styles.startOpen : styles.endOpen;
538
+
539
+ // Filter out native `open` to prevent InvalidStateError when passed
540
+ const {open: _open, ...safeProps} = props as Record<string, unknown>;
541
+
542
+ return (
543
+ <dialog
544
+ ref={mergedDialogRef}
545
+ {...mergeProps(
546
+ themeProps('drawer', {side: anchoredSide}),
547
+ stylex.props(
548
+ layerTextReset.reset,
549
+ layerStructureReset.reset,
550
+ styles.dialog,
551
+ overlayPaddingReset.reset,
552
+ sideStyle,
553
+ dynamicStyles.inlineSize(widthValue, mobileWidth),
554
+ isRendered && styles.rendered,
555
+ isOpen && sideOpenStyle,
556
+ hasScrim && styles.scrim,
557
+ hasScrim && isOpen && styles.scrimOpen,
558
+ xstyle,
559
+ ),
560
+ className,
561
+ style,
562
+ )}
563
+ {...safeProps}
564
+ popover={hasScrim ? undefined : 'manual'}
565
+ aria-label={label}
566
+ aria-modal={hasScrim ? 'true' : undefined}
567
+ {...(purpose === 'required' && hasScrim
568
+ ? {role: 'alertdialog'}
569
+ : undefined)}
570
+ onClick={composeEventHandlers(onClickProp, handleClick)}
571
+ onKeyDown={onKeyDownProp}
572
+ onCancel={handleCancel}>
573
+ <LayerDepthProvider>
574
+ <LayerContentBoundary>
575
+ {/* Scrollable content area — tabIndex so the dialog's focusing steps
576
+ land on the panel body rather than the first button inside. */}
577
+ <div
578
+ tabIndex={-1}
579
+ {...stylex.props(
580
+ styles.content,
581
+ ...container(
582
+ usesThemePadding
583
+ ? {useThemeDefault: 'drawer'}
584
+ : {
585
+ paddingInnerX: paddingToken,
586
+ paddingInnerY: paddingToken,
587
+ paddingOuterX: paddingToken,
588
+ paddingOuterY: paddingToken,
589
+ },
590
+ ),
591
+ !usesThemePadding &&
592
+ effectivePadding !== 4 &&
593
+ paddingStyles[effectivePadding],
594
+ !usesThemePadding &&
595
+ effectivePadding !== 4 &&
596
+ containerPaddingInlineVarStyles[effectivePadding],
597
+ !usesThemePadding &&
598
+ effectivePadding !== 4 &&
599
+ containerPaddingBlockStartVarStyles[effectivePadding],
600
+ !usesThemePadding &&
601
+ effectivePadding !== 4 &&
602
+ containerPaddingBlockEndVarStyles[effectivePadding],
603
+ styles.contentSafeArea,
604
+ )}>
605
+ {children}
606
+ </div>
607
+ </LayerContentBoundary>
608
+ </LayerDepthProvider>
609
+ </dialog>
610
+ );
611
+ }
612
+
613
+ Drawer.displayName = 'Drawer';