@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.
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -1
- package/dist/AspectRatio/AspectRatio.d.ts +6 -8
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +44 -3
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +38 -14
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +4 -2
- package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +37 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +5 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +20 -8
- package/dist/Drawer/Drawer.d.ts +191 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -0
- package/dist/Drawer/Drawer.js +382 -0
- package/dist/Drawer/DrawerHeader.d.ts +85 -0
- package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
- package/dist/Drawer/DrawerHeader.js +134 -0
- package/dist/Drawer/index.d.ts +11 -0
- package/dist/Drawer/index.d.ts.map +1 -0
- package/dist/Drawer/index.js +11 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
- package/dist/Drawer/useDrawerDialogPresence.js +236 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +83 -11
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts +6 -4
- package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuGroup.js +4 -3
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +24 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +46 -0
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +4 -10
- package/dist/Field/Field.d.ts +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +8 -4
- package/dist/Grid/GridSpan.d.ts.map +1 -1
- package/dist/Grid/GridSpan.js +1 -0
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +4 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -2
- package/dist/Item/Item.d.ts +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +19 -4
- package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
- package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
- package/dist/Layer/layerViewportInset.stylex.js +6 -2
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +13 -7
- package/dist/Layer/useLayerDismissal.d.ts +1 -1
- package/dist/Layer/useLayerDismissal.js +1 -1
- package/dist/Layout/container.stylex.d.ts +5 -5
- package/dist/Layout/container.stylex.js +4 -4
- package/dist/List/List.d.ts +46 -19
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +28 -10
- package/dist/List/ListContext.d.ts +8 -3
- package/dist/List/ListContext.d.ts.map +1 -1
- package/dist/List/ListContext.js +7 -2
- package/dist/List/ListItem.d.ts +13 -5
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +21 -8
- package/dist/List/index.d.ts +1 -1
- package/dist/List/index.d.ts.map +1 -1
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +8 -1
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
- package/dist/Markdown/plugins/frontmatter.js +7 -3
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +5 -1
- package/dist/MetadataList/MetadataListContext.d.ts +1 -1
- package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.d.ts +9 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +30 -3
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +22 -8
- package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +3 -1
- package/dist/Pagination/Pagination.d.ts +8 -5
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +21 -8
- package/dist/Popover/Popover.js +2 -2
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/RadioList/RadioList.d.ts +1 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +6 -2
- package/dist/RadioList/RadioListItem.d.ts +9 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +2 -0
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +6 -2
- package/dist/SideNav/SideNavItem.d.ts +8 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +71 -9
- package/dist/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +13 -1
- package/dist/TabList/TabMenu.d.ts +2 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -3
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -2
- package/dist/Text/useTruncation.d.ts +7 -5
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +16 -5
- package/dist/ToggleButton/ToggleButton.d.ts +3 -2
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +12 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -0
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +3 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +2 -2
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +57 -9
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +26 -0
- package/dist/astryx.css +47 -13
- package/dist/hooks/useEntryAnimation.d.ts +2 -1
- package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +14 -12
- package/dist/hooks/useIsHydrating.d.ts +6 -0
- package/dist/hooks/useIsHydrating.d.ts.map +1 -0
- package/dist/hooks/useIsHydrating.js +30 -0
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +18 -13
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +112 -1
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +0 -3
- package/dist/utils/focusOutline.stylex.d.ts +4 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
- package/dist/utils/focusOutline.stylex.js +4 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +15 -9
- package/dist/utils/sharedMutationObserver.d.ts +21 -0
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
- package/dist/utils/sharedMutationObserver.js +69 -0
- package/locales/en.json +444 -0
- package/locales/pseudo.json +333 -0
- package/package.json +9 -4
- package/scripts/agent-doc-state.mjs +38 -0
- package/scripts/postinstall.mjs +1 -1
- package/src/AppShell/AppShell.tsx +45 -1
- package/src/AspectRatio/AspectRatio.test.tsx +44 -43
- package/src/AspectRatio/AspectRatio.tsx +38 -18
- package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
- package/src/Avatar/Avatar.doc.mjs +5 -3
- package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.test.tsx +155 -9
- package/src/Button/Button.tsx +39 -16
- package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
- package/src/Button/__tests__/Button.a11y.states.ts +0 -10
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +3 -1
- package/src/CheckboxList/CheckboxList.test.tsx +22 -0
- package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
- package/src/CheckboxList/CheckboxListItem.tsx +10 -0
- package/src/Code/Code.doc.mjs +1 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +2 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
- package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
- package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
- package/src/ContextMenu/ContextMenu.tsx +4 -1
- package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
- package/src/Dialog/Dialog.test.tsx +46 -0
- package/src/Dialog/Dialog.tsx +20 -7
- package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
- package/src/Drawer/Drawer.doc.mjs +473 -0
- package/src/Drawer/Drawer.spec.md +357 -0
- package/src/Drawer/Drawer.test.tsx +1300 -0
- package/src/Drawer/Drawer.tsx +613 -0
- package/src/Drawer/DrawerDirection.test.tsx +127 -0
- package/src/Drawer/DrawerHeader.doc.mjs +293 -0
- package/src/Drawer/DrawerHeader.test.tsx +102 -0
- package/src/Drawer/DrawerHeader.tsx +235 -0
- package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
- package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
- package/src/Drawer/index.ts +13 -0
- package/src/Drawer/useDrawerDialogPresence.ts +284 -0
- package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +57 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +79 -18
- package/src/DropdownMenu/DropdownMenu.tsx +83 -12
- package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
- package/src/DropdownMenu/DropdownMenuGroup.doc.mjs +169 -10
- package/src/DropdownMenu/DropdownMenuGroup.tsx +6 -4
- package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +126 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +19 -5
- package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
- package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +334 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
- package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +43 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +71 -0
- package/src/EmptyState/EmptyState.doc.mjs +3 -2
- package/src/EmptyState/EmptyState.spec.md +189 -0
- package/src/EmptyState/EmptyState.test.tsx +74 -3
- package/src/EmptyState/EmptyState.tsx +4 -8
- package/src/Field/Field.doc.mjs +14 -8
- package/src/Field/Field.spec.md +217 -0
- package/src/Field/Field.test.tsx +60 -2
- package/src/Field/Field.tsx +17 -8
- package/src/Field/FieldLabel.doc.mjs +133 -26
- package/src/Field/FieldLabel.spec.md +172 -0
- package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +296 -0
- package/src/Grid/GridSpan.tsx +3 -0
- package/src/Heading/Heading.test.tsx +57 -0
- package/src/Heading/Heading.tsx +6 -2
- package/src/HoverCard/HoverCard.test.tsx +58 -0
- package/src/HoverCard/useHoverCard.tsx +10 -2
- package/src/Icon/Icon.doc.mjs +36 -0
- package/src/IconButton/IconButton.doc.mjs +2 -2
- package/src/IconButton/IconButton.test.tsx +3 -1
- package/src/Item/Item.doc.mjs +12 -8
- package/src/Item/Item.spec.md +226 -0
- package/src/Item/Item.test.tsx +61 -0
- package/src/Item/Item.tsx +40 -4
- package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
- package/src/Layer/layerViewportInset.stylex.ts +6 -2
- package/src/Layer/layerViewportInset.test.ts +1 -0
- package/src/Layer/useLayer.tsx +24 -13
- package/src/Layer/useLayerDismissal.ts +1 -1
- package/src/Layout/LayoutHeader.doc.mjs +5 -0
- package/src/Layout/container.stylex.ts +6 -6
- package/src/List/List.doc.mjs +14 -1
- package/src/List/List.spec.md +129 -77
- package/src/List/List.test.tsx +163 -94
- package/src/List/List.tsx +74 -18
- package/src/List/ListContext.tsx +9 -3
- package/src/List/ListItem.doc.mjs +24 -5
- package/src/List/ListItem.tsx +56 -27
- package/src/List/index.ts +1 -1
- package/src/Markdown/Markdown.tsx +14 -1
- package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
- package/src/Markdown/plugins/frontmatter.ts +7 -3
- package/src/MetadataList/MetadataList.doc.mjs +3 -3
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -1
- package/src/MobileNav/MobileNav.tsx +30 -3
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
- package/src/MoreMenu/MoreMenu.test.tsx +5 -1
- package/src/MultiSelector/MultiSelector.test.tsx +159 -0
- package/src/MultiSelector/MultiSelector.tsx +40 -7
- package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
- package/src/MultiSelector/hooks.ts +3 -1
- package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
- package/src/Pagination/Pagination.doc.mjs +7 -7
- package/src/Pagination/Pagination.spec.md +9 -9
- package/src/Pagination/Pagination.test.tsx +242 -0
- package/src/Pagination/Pagination.tsx +27 -11
- package/src/Popover/Popover.test.tsx +2 -2
- package/src/Popover/Popover.tsx +4 -4
- package/src/PowerSearch/PowerSearch.test.tsx +85 -0
- package/src/PowerSearch/PowerSearch.tsx +2 -2
- package/src/ProgressBar/ProgressBar.doc.mjs +1 -0
- package/src/RadioList/RadioList.fr10.test.tsx +45 -2
- package/src/RadioList/RadioList.test.tsx +22 -0
- package/src/RadioList/RadioList.tsx +4 -1
- package/src/RadioList/RadioListItem.doc.mjs +16 -0
- package/src/RadioList/RadioListItem.tsx +10 -1
- package/src/Resizable/useResizable.doc.mjs +2 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +1 -1
- package/src/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -0
- package/src/SideNav/SideNav.spec.md +227 -0
- package/src/SideNav/SideNav.test.tsx +179 -0
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +96 -17
- package/src/Slider/Slider.spec.md +39 -18
- package/src/Spinner/Spinner.doc.mjs +1 -1
- package/src/Switch/Switch.test.tsx +49 -0
- package/src/Switch/Switch.tsx +11 -0
- package/src/TabList/TabList.spec.md +57 -44
- package/src/TabList/TabList.test.tsx +67 -0
- package/src/TabList/TabMenu.doc.mjs +4 -3
- package/src/TabList/TabMenu.tsx +7 -4
- package/src/Text/Text.test.tsx +57 -0
- package/src/Text/Text.tsx +6 -2
- package/src/Text/useTruncation.test.tsx +69 -0
- package/src/Text/useTruncation.ts +16 -5
- package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +4 -0
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- package/src/Toolbar/Toolbar.test.tsx +0 -8
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +163 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +63 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +142 -0
- package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +126 -0
- package/src/Tooltip/Tooltip.test.tsx +31 -0
- package/src/Tooltip/useTooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +11 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
- package/src/TopNav/TopNavMegaMenu.tsx +7 -3
- package/src/TopNav/TopNavMenu.test.tsx +19 -0
- package/src/TopNav/TopNavMenu.tsx +11 -3
- package/src/TreeList/TreeList.doc.mjs +1 -0
- package/src/TreeList/TreeList.spec.md +29 -17
- package/src/TreeList/TreeList.test.tsx +327 -6
- package/src/TreeList/TreeList.tsx +5 -1
- package/src/TreeList/TreeListBranches.tsx +6 -0
- package/src/TreeList/TreeListItem.tsx +88 -13
- package/src/Typeahead/BaseTypeahead.tsx +26 -0
- package/src/Typeahead/Typeahead.test.tsx +260 -2
- package/src/docPropLiterals.test.ts +36 -10
- package/src/hooks/useEntryAnimation.doc.mjs +4 -4
- package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
- package/src/hooks/useEntryAnimation.ts +15 -16
- package/src/hooks/useIsHydrating.test.tsx +70 -0
- package/src/hooks/useIsHydrating.ts +31 -0
- package/src/hooks/useMenuHover.ts +18 -13
- package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
- package/src/i18n/InternationalizationProvider.spec.md +252 -0
- package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
- package/src/i18n/generated-locales/en.generated.ts +121 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
- package/src/i18n/useTranslator.test.tsx +37 -0
- package/src/index.ts +1 -0
- package/src/postinstall.test.mjs +46 -0
- package/src/reset.css +0 -29
- package/src/theme/derivedVarRegistry.test.ts +7 -3
- package/src/theme/derivedVarRegistry.ts +0 -3
- package/src/utils/focusOutline.stylex.ts +4 -0
- package/src/utils/plainDate.test.ts +37 -1
- package/src/utils/plainDate.ts +15 -5
- package/src/utils/sharedMutationObserver.test.ts +53 -0
- 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';
|