@astryxdesign/core 0.7.2-canary.c68f5e8 → 0.7.2-canary.ccf1a1c
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/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.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +4 -4
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -2
- 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/Markdown/plugins/frontmatter.d.ts.map +1 -1
- package/dist/Markdown/plugins/frontmatter.js +7 -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/Popover/Popover.js +2 -2
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +6 -2
- package/dist/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +7 -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/useTruncation.d.ts +7 -5
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +16 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +26 -0
- package/dist/astryx.css +20 -10
- 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/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +93 -1
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +93 -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/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 +368 -0
- package/locales/pseudo.json +276 -0
- package/package.json +8 -3
- package/src/Drawer/Drawer.doc.mjs +472 -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.tsx +2 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +2 -2
- package/src/HoverCard/HoverCard.test.tsx +58 -0
- package/src/HoverCard/useHoverCard.tsx +10 -2
- 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/container.stylex.ts +6 -6
- package/src/List/ListItem.doc.mjs +8 -5
- package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
- package/src/Markdown/plugins/frontmatter.ts +7 -3
- 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/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/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -0
- package/src/Switch/Switch.test.tsx +21 -0
- package/src/Switch/Switch.tsx +5 -0
- 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/useTruncation.test.tsx +69 -0
- package/src/Text/useTruncation.ts +16 -5
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- 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/Typeahead/BaseTypeahead.tsx +26 -0
- package/src/Typeahead/Typeahead.test.tsx +260 -2
- 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/i18n/generated-locales/en.generated.ts +100 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +100 -0
- package/src/index.ts +1 -0
- package/src/theme/derivedVarRegistry.test.ts +0 -3
- package/src/theme/derivedVarRegistry.ts +0 -3
- package/src/utils/focusOutline.stylex.ts +4 -0
- package/src/utils/sharedMutationObserver.test.ts +53 -0
- package/src/utils/sharedMutationObserver.ts +78 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Drawer.tsx
|
|
3
|
+
* @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
|
|
4
|
+
* @output Exports Drawer component and DrawerProps
|
|
5
|
+
* @position Core implementation; consumed by index.ts, tested by Drawer.test.tsx, demonstrated in Storybook
|
|
6
|
+
*
|
|
7
|
+
* Overlay panel for inspectors and detail views — the "click a table row,
|
|
8
|
+
* see its details" pattern. Slides in from the inline start or end edge and
|
|
9
|
+
* floats above the page content: unlike a docked panel it never reflows the
|
|
10
|
+
* layout underneath, it overlays it (with or without a scrim).
|
|
11
|
+
*
|
|
12
|
+
* Inline axis only (start/end). Block-axis sheets are BottomSheet's job;
|
|
13
|
+
* a drawer is always a full-height side panel.
|
|
14
|
+
*
|
|
15
|
+
* Sizing is viewport-aware: `width` is the desktop budget, and below
|
|
16
|
+
* the mobile breakpoint it preserves a 56px reveal of the page behind, capped
|
|
17
|
+
* by the requested width (or fills the viewport with the experimental
|
|
18
|
+
* `experimental.isFullWidthOnMobile` option).
|
|
19
|
+
*
|
|
20
|
+
* Uses the native `<dialog>` element (same precedent as Dialog/MobileNav):
|
|
21
|
+
* - `showModal()` when `hasScrim` (default) — top-layer rendering, focus
|
|
22
|
+
* trapping, `::backdrop`, no z-index management.
|
|
23
|
+
* - `showPopover()` when `hasScrim={false}` — non-modal top-layer overlay;
|
|
24
|
+
* the page behind stays interactive (e.g. master-detail inspectors).
|
|
25
|
+
*
|
|
26
|
+
* Entry animation uses `@starting-style`; exit slides out before the active
|
|
27
|
+
* modal-dialog or manual-popover host releases the top layer and focus returns
|
|
28
|
+
* to the element that opened the drawer. React owns `display` for both legs
|
|
29
|
+
* rather than a discrete `display` transition, so the panel stops painting in
|
|
30
|
+
* the same commit as the host closes — see the `rendered` style for why.
|
|
31
|
+
*
|
|
32
|
+
* Sibling drawers use the shared layer dismissal stack for topmost-only Escape
|
|
33
|
+
* handling and the browser top layer's chronological paint order.
|
|
34
|
+
*
|
|
35
|
+
* Dismissal follows Dialog: `purpose` decides whether Escape and a scrim click
|
|
36
|
+
* request close, and Drawer renders no close button of its own. Compose
|
|
37
|
+
* `DrawerHeader` with `onOpenChange` for a visible close action, as with
|
|
38
|
+
* `DialogHeader`.
|
|
39
|
+
*
|
|
40
|
+
* The scrolling content area is the drawer's container, like Dialog's inner
|
|
41
|
+
* box: the `padding` prop, else the theme's `padding` on `drawer`, insets it
|
|
42
|
+
* through container tokens, defaulting to `--spacing-4` like Dialog, and the
|
|
43
|
+
* applied inset is published so a lone Section child escapes it and a Layout
|
|
44
|
+
* redistributes it to its regions. Compose `DrawerHeader` inside a `Layout`, as
|
|
45
|
+
* `DialogHeader` is composed in Dialog, so the header is not padded twice. The
|
|
46
|
+
* root dialog stays the overlay boundary (`overlayPaddingReset`).
|
|
47
|
+
*
|
|
48
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
49
|
+
* - /packages/core/src/Drawer/Drawer.doc.mjs (props table, features, usage)
|
|
50
|
+
* - /packages/core/src/Drawer/Drawer.test.tsx (tests for new/changed behavior)
|
|
51
|
+
* - /packages/core/src/Drawer/Drawer.spec.md (component contract)
|
|
52
|
+
* - /packages/core/src/Drawer/index.ts (exports if types change)
|
|
53
|
+
* - /packages/cli/assets/templates/blocks/components/Drawer/ (showcase blocks)
|
|
54
|
+
* - /apps/storybook/stories/Drawer.stories.tsx (examples and visual coverage)
|
|
55
|
+
*/
|
|
56
|
+
import { type ReactNode } from 'react';
|
|
57
|
+
import type { BaseProps } from '../BaseProps';
|
|
58
|
+
import type { DialogPurpose } from '../Dialog';
|
|
59
|
+
import type { SpacingStep } from '../Layout';
|
|
60
|
+
export interface DrawerProps extends BaseProps<HTMLDialogElement> {
|
|
61
|
+
/** Ref forwarded to the root <dialog> element */
|
|
62
|
+
ref?: React.Ref<HTMLDialogElement>;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the drawer is open. Fully controlled — pair with `onOpenChange`.
|
|
65
|
+
*/
|
|
66
|
+
isOpen: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Called when the drawer requests an open-state change. Escape and a scrim
|
|
69
|
+
* click call it with `false` as `purpose` allows; a `DrawerHeader` close
|
|
70
|
+
* button calls it when you pass the same callback to the header. The caller
|
|
71
|
+
* owns the open state. When sibling drawers are open, Escape only closes the
|
|
72
|
+
* top (last-opened) drawer.
|
|
73
|
+
*/
|
|
74
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
75
|
+
/**
|
|
76
|
+
* Which edge the drawer slides from.
|
|
77
|
+
* - `'end'` — inline-end edge (right in LTR) — the inspector convention
|
|
78
|
+
* - `'start'` — inline-start edge (left in LTR)
|
|
79
|
+
* @default 'end'
|
|
80
|
+
*/
|
|
81
|
+
side?: 'start' | 'end';
|
|
82
|
+
/**
|
|
83
|
+
* Desktop width budget. A number is pixels; a string is any CSS length
|
|
84
|
+
* (`'50%'`, `'32rem'`). Below the mobile breakpoint (640px), this
|
|
85
|
+
* remains the maximum while the drawer preserves a 56px reveal of the page
|
|
86
|
+
* behind — see `experimental.isFullWidthOnMobile`.
|
|
87
|
+
* @default 400
|
|
88
|
+
*/
|
|
89
|
+
width?: number | string;
|
|
90
|
+
/**
|
|
91
|
+
* Experimental options, outside the stable compatibility promise: anything
|
|
92
|
+
* in this object may change or be removed in a patch release. Omit it for
|
|
93
|
+
* the stable behavior.
|
|
94
|
+
* @experimental
|
|
95
|
+
*/
|
|
96
|
+
experimental?: {
|
|
97
|
+
/**
|
|
98
|
+
* Cover the full viewport width on mobile (below 640px) instead of
|
|
99
|
+
* preserving the default 56px reveal of the page behind. The reveal makes
|
|
100
|
+
* the drawer read as an overlay rather than a navigation.
|
|
101
|
+
* @experimental
|
|
102
|
+
* @default false
|
|
103
|
+
*/
|
|
104
|
+
isFullWidthOnMobile?: boolean;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Accessible label for the drawer (required — the drawer has no
|
|
108
|
+
* built-in heading to derive a name from).
|
|
109
|
+
*/
|
|
110
|
+
label: string;
|
|
111
|
+
/**
|
|
112
|
+
* Whether to render a modal scrim behind the drawer.
|
|
113
|
+
* - `true` (default) — `showModal()`: top layer, focus trap, body scroll
|
|
114
|
+
* lock, click-outside-to-close.
|
|
115
|
+
* - `false` — `showPopover()`: non-modal top-layer overlay; the page behind
|
|
116
|
+
* stays interactive. Escape still closes through the shared layer stack.
|
|
117
|
+
* @default true
|
|
118
|
+
*/
|
|
119
|
+
hasScrim?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Configures how the drawer enables dismissals, matching Dialog.
|
|
122
|
+
* - required: Disables Escape and scrim click (for mandatory flows); a
|
|
123
|
+
* modal required drawer is exposed as an `alertdialog`
|
|
124
|
+
* - form: Prevents scrim click, allows the Escape key
|
|
125
|
+
* - info: Allows Escape and scrim click
|
|
126
|
+
*
|
|
127
|
+
* A non-modal drawer has no scrim, so `form` and `info` behave the same.
|
|
128
|
+
* @default 'info'
|
|
129
|
+
*/
|
|
130
|
+
purpose?: DialogPurpose;
|
|
131
|
+
/**
|
|
132
|
+
* Internal padding of the drawer content using the spacing scale, matching
|
|
133
|
+
* Dialog. Accepts numeric spacing steps: 0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8,
|
|
134
|
+
* 10. When omitted, uses the theme default for drawers (`--spacing-4`, 16px,
|
|
135
|
+
* unless the theme sets `padding` on `drawer`), as Dialog does. Pass `0` for
|
|
136
|
+
* a full-bleed content area. The content area is a container: a Section that
|
|
137
|
+
* is its only child escapes the padding, bleed children such as Table and
|
|
138
|
+
* Divider compensate against it, and a Layout picks it up for its regions.
|
|
139
|
+
*/
|
|
140
|
+
padding?: SpacingStep;
|
|
141
|
+
/**
|
|
142
|
+
* Drawer content. Rendered inside a full-height scrollable area.
|
|
143
|
+
* Focus the element with `data-autofocus` on open, if present.
|
|
144
|
+
*/
|
|
145
|
+
children: ReactNode;
|
|
146
|
+
/**
|
|
147
|
+
* Test ID for the root element.
|
|
148
|
+
*/
|
|
149
|
+
'data-testid'?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* An overlay panel for inspectors and detail views.
|
|
153
|
+
*
|
|
154
|
+
* Slides in from the logical start or end edge and floats above the page
|
|
155
|
+
* using the native `<dialog>` element: modal with a scrim by default, or a
|
|
156
|
+
* non-modal overlay with `hasScrim={false}` that leaves the page behind
|
|
157
|
+
* interactive. `width` is the desktop budget; below 640px the panel preserves
|
|
158
|
+
* a 56px page reveal without exceeding that budget (or fills the viewport
|
|
159
|
+
* with the experimental `experimental.isFullWidthOnMobile`). Escape
|
|
160
|
+
* closes the top-most open drawer; focus returns to the element that
|
|
161
|
+
* opened it.
|
|
162
|
+
*
|
|
163
|
+
* Dismissal matches Dialog: `purpose` decides whether Escape and a scrim click
|
|
164
|
+
* close the drawer, and `DrawerHeader` renders a close button when given
|
|
165
|
+
* `onOpenChange`.
|
|
166
|
+
*
|
|
167
|
+
* Like Dialog, the drawer is a container: `padding` (or a theme's `padding`
|
|
168
|
+
* on `drawer`) insets the content area, a lone Section child escapes that
|
|
169
|
+
* inset, and a Layout inside picks it up for its header, content, and footer
|
|
170
|
+
* regions. The default inset is `--spacing-4`, matching Dialog.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```
|
|
174
|
+
* const [selected, setSelected] = useState(null);
|
|
175
|
+
* const handleOpenChange = isOpen => !isOpen && setSelected(null);
|
|
176
|
+
* <Drawer
|
|
177
|
+
* isOpen={selected != null}
|
|
178
|
+
* onOpenChange={handleOpenChange}
|
|
179
|
+
* label={`Details: ${selected?.name}`}>
|
|
180
|
+
* <Layout
|
|
181
|
+
* header={<DrawerHeader title={selected?.name} onOpenChange={handleOpenChange} />}
|
|
182
|
+
* content={<LayoutContent><HostDetails host={selected} /></LayoutContent>}
|
|
183
|
+
* />
|
|
184
|
+
* </Drawer>
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export declare function Drawer({ isOpen, onOpenChange, side, width, experimental, label, hasScrim, purpose, padding, children, xstyle, className, style, onClick: onClickProp, onKeyDown: onKeyDownProp, ref, ...props }: DrawerProps): import("react").JSX.Element;
|
|
188
|
+
export declare namespace Drawer {
|
|
189
|
+
var displayName: string;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=Drawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../src/Drawer/Drawer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,EAAgC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAQ5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAe7C,OAAO,KAAK,EAAC,WAAW,EAAe,MAAM,WAAW,CAAC;AAgMzD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,iDAAiD;IACjD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAEvB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QACb;;;;;;WAMG;QACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;KAC/B,CAAC;IAEF;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,YAAY,EACZ,IAAY,EACZ,KAAW,EACX,YAAY,EACZ,KAAK,EACL,QAAe,EACf,OAAgB,EAChB,OAAO,EACP,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BA4Kb;yBA9Le,MAAM"}
|
|
@@ -0,0 +1,382 @@
|
|
|
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
|
+
import { useCallback, useRef, useState } from 'react';
|
|
61
|
+
import * as stylex from '@stylexjs/stylex';
|
|
62
|
+
import "../theme/tokens.stylex.js";
|
|
63
|
+
import { borderVars, colorVars, durationVars, easeVars, shadowVars } from "../theme/tokens.stylex.js";
|
|
64
|
+
import { useFocusTrap, useMergedRefs, useScrollLock } from "../hooks/index.js";
|
|
65
|
+
import { LayerDepthProvider, useLayerDismissal } from "../Layer/index.js";
|
|
66
|
+
import { layerTextReset } from "../Layer/layerTextReset.stylex.js";
|
|
67
|
+
import { layerStructureReset } from "../Layer/layerStructureReset.stylex.js";
|
|
68
|
+
import { LayerContentBoundary } from "../Layer/layerScopedContext.js";
|
|
69
|
+
import { composeEventHandlers, mergeProps, themeProps } from "../utils/index.js";
|
|
70
|
+
import { container, overlayPaddingReset } from "../Layout/index.js";
|
|
71
|
+
import { containerPaddingBlockEndVarStyles, containerPaddingBlockStartVarStyles, containerPaddingInlineVarStyles, paddingStyles, spacingStepToToken } from "../Layout/padding.stylex.js";
|
|
72
|
+
import { useDrawerDialogPresence } from "./useDrawerDialogPresence.js";
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// Styles
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
// Below this viewport width the drawer preserves a fixed reveal of the page
|
|
79
|
+
// behind instead of growing proportionally with the viewport. 640px is the
|
|
80
|
+
// repo's mobile breakpoint.
|
|
81
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
82
|
+
const MOBILE_BREAKPOINT = 640;
|
|
83
|
+
|
|
84
|
+
// Material's established mobile drawer pattern leaves a 56dp reveal. Using
|
|
85
|
+
// the same value in CSS pixels gives the overlay a stable visual relationship
|
|
86
|
+
// to the page behind while the requested width remains an upper bound.
|
|
87
|
+
const MOBILE_PAGE_REVEAL = 56;
|
|
88
|
+
const MOBILE_WIDTH_FULL = '100dvw';
|
|
89
|
+
const styles = {
|
|
90
|
+
dialog: {
|
|
91
|
+
kVAEAm: "xixxii4",
|
|
92
|
+
kogj98: "x1ghz6dp",
|
|
93
|
+
kmVPX3: "x1717udv",
|
|
94
|
+
kMzoRj: "xc342km",
|
|
95
|
+
ksu8eU: "xng3xce",
|
|
96
|
+
kaIpWk: "x2u8bby",
|
|
97
|
+
ks0D6T: "x1x1rfll",
|
|
98
|
+
kskxy: "x7ab17h",
|
|
99
|
+
kB7OPa: "x9f619",
|
|
100
|
+
kXwgrk: "xdt5ytf",
|
|
101
|
+
kWkggS: "x10xzikg",
|
|
102
|
+
kGVxlE: "x1kcpxr7",
|
|
103
|
+
kVQacm: "xb3r6kr",
|
|
104
|
+
kZeWKH: "xish69e",
|
|
105
|
+
kI3sdo: "x1a2a7pz",
|
|
106
|
+
k87sOh: "x13vifvy",
|
|
107
|
+
krVfgx: "x1ey2m1c",
|
|
108
|
+
kZKoxP: "xtdtrs8",
|
|
109
|
+
k1xSpc: "x1s85apg",
|
|
110
|
+
k1ekBW: "x11xpdln",
|
|
111
|
+
kIyJzY: "x80gvsz",
|
|
112
|
+
kAMwcw: "xlr8y92",
|
|
113
|
+
k6CgDc: "xzg1mie",
|
|
114
|
+
$$css: true
|
|
115
|
+
},
|
|
116
|
+
rendered: {
|
|
117
|
+
k1xSpc: "x78zum5",
|
|
118
|
+
$$css: true
|
|
119
|
+
},
|
|
120
|
+
end: {
|
|
121
|
+
kt4wiu: "xtijo5x",
|
|
122
|
+
kLqNvP: "xhi6v0a",
|
|
123
|
+
k2ei4v: "xgbv0en",
|
|
124
|
+
kVhnKS: "x1t7ytsu",
|
|
125
|
+
kGJrpR: "x1j92z86",
|
|
126
|
+
k3aq6I: "xumwmo6 xbw113y",
|
|
127
|
+
$$css: true
|
|
128
|
+
},
|
|
129
|
+
endOpen: {
|
|
130
|
+
k3aq6I: "xbryuvx x1yqmsfc xrxqmjf",
|
|
131
|
+
$$css: true
|
|
132
|
+
},
|
|
133
|
+
start: {
|
|
134
|
+
kLqNvP: "x1o0tod",
|
|
135
|
+
kt4wiu: "x1woyocn",
|
|
136
|
+
ke9TFa: "xw8tdv1",
|
|
137
|
+
k8ry5P: "x18b5jzi",
|
|
138
|
+
kBCPoo: "x1gejf6u",
|
|
139
|
+
k3aq6I: "x5i6ehr x1eqdj8u",
|
|
140
|
+
$$css: true
|
|
141
|
+
},
|
|
142
|
+
startOpen: {
|
|
143
|
+
k3aq6I: "xbryuvx x6mt36l x1r0dqvm",
|
|
144
|
+
$$css: true
|
|
145
|
+
},
|
|
146
|
+
scrim: {
|
|
147
|
+
kGyWv1: "xnixb3f",
|
|
148
|
+
kba3nw: "x1abwkk1",
|
|
149
|
+
k5sjJv: "xph5o2a",
|
|
150
|
+
kND0Po: "x167zut7",
|
|
151
|
+
k9an0g: "xft5bk6",
|
|
152
|
+
kb4ib: "x15h3t91",
|
|
153
|
+
kA5Tbj: "x1viac0w",
|
|
154
|
+
$$css: true
|
|
155
|
+
},
|
|
156
|
+
scrimOpen: {
|
|
157
|
+
k5sjJv: "xb3n6bw xxiuuzi",
|
|
158
|
+
$$css: true
|
|
159
|
+
},
|
|
160
|
+
content: {
|
|
161
|
+
kzQI83: "x1iyjqo2",
|
|
162
|
+
kAzted: "x2lwn1j",
|
|
163
|
+
kzqmXN: "xh8yej3",
|
|
164
|
+
kORKVm: "x1odjw0f",
|
|
165
|
+
kXHlph: "x6ikm8r",
|
|
166
|
+
kZeWKH: "xish69e",
|
|
167
|
+
kFalU9: "xx69xxh",
|
|
168
|
+
kI3sdo: "x1a2a7pz",
|
|
169
|
+
kWkggS: "x10xzikg",
|
|
170
|
+
$$css: true
|
|
171
|
+
},
|
|
172
|
+
contentSafeArea: {
|
|
173
|
+
kGO01o: "xvmia7e",
|
|
174
|
+
$$css: true
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const _temp = {
|
|
178
|
+
ks0D6T: "xlbgzzq",
|
|
179
|
+
"$$css": true
|
|
180
|
+
};
|
|
181
|
+
const dynamicStyles = {
|
|
182
|
+
inlineSize: (desktopWidth, mobileWidth) => [_temp, {
|
|
183
|
+
kzqmXN: (desktopWidth != null ? "x11ymkkh " : desktopWidth) + (mobileWidth != null ? "xci7e5a" : mobileWidth),
|
|
184
|
+
$$css: true
|
|
185
|
+
}, {
|
|
186
|
+
"--x-1xmrurk": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(desktopWidth),
|
|
187
|
+
"--x-fqzwsx": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(mobileWidth)
|
|
188
|
+
}]
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// Types
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Component
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* An overlay panel for inspectors and detail views.
|
|
201
|
+
*
|
|
202
|
+
* Slides in from the logical start or end edge and floats above the page
|
|
203
|
+
* using the native `<dialog>` element: modal with a scrim by default, or a
|
|
204
|
+
* non-modal overlay with `hasScrim={false}` that leaves the page behind
|
|
205
|
+
* interactive. `width` is the desktop budget; below 640px the panel preserves
|
|
206
|
+
* a 56px page reveal without exceeding that budget (or fills the viewport
|
|
207
|
+
* with the experimental `experimental.isFullWidthOnMobile`). Escape
|
|
208
|
+
* closes the top-most open drawer; focus returns to the element that
|
|
209
|
+
* opened it.
|
|
210
|
+
*
|
|
211
|
+
* Dismissal matches Dialog: `purpose` decides whether Escape and a scrim click
|
|
212
|
+
* close the drawer, and `DrawerHeader` renders a close button when given
|
|
213
|
+
* `onOpenChange`.
|
|
214
|
+
*
|
|
215
|
+
* Like Dialog, the drawer is a container: `padding` (or a theme's `padding`
|
|
216
|
+
* on `drawer`) insets the content area, a lone Section child escapes that
|
|
217
|
+
* inset, and a Layout inside picks it up for its header, content, and footer
|
|
218
|
+
* regions. The default inset is `--spacing-4`, matching Dialog.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```
|
|
222
|
+
* const [selected, setSelected] = useState(null);
|
|
223
|
+
* const handleOpenChange = isOpen => !isOpen && setSelected(null);
|
|
224
|
+
* <Drawer
|
|
225
|
+
* isOpen={selected != null}
|
|
226
|
+
* onOpenChange={handleOpenChange}
|
|
227
|
+
* label={`Details: ${selected?.name}`}>
|
|
228
|
+
* <Layout
|
|
229
|
+
* header={<DrawerHeader title={selected?.name} onOpenChange={handleOpenChange} />}
|
|
230
|
+
* content={<LayoutContent><HostDetails host={selected} /></LayoutContent>}
|
|
231
|
+
* />
|
|
232
|
+
* </Drawer>
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
export function Drawer({
|
|
236
|
+
isOpen,
|
|
237
|
+
onOpenChange,
|
|
238
|
+
side = 'end',
|
|
239
|
+
width = 400,
|
|
240
|
+
experimental,
|
|
241
|
+
label,
|
|
242
|
+
hasScrim = true,
|
|
243
|
+
purpose = 'info',
|
|
244
|
+
padding,
|
|
245
|
+
children,
|
|
246
|
+
xstyle,
|
|
247
|
+
className,
|
|
248
|
+
style,
|
|
249
|
+
onClick: onClickProp,
|
|
250
|
+
onKeyDown: onKeyDownProp,
|
|
251
|
+
ref,
|
|
252
|
+
...props
|
|
253
|
+
}) {
|
|
254
|
+
const dialogRef = useRef(null);
|
|
255
|
+
const {
|
|
256
|
+
containerRef: focusTrapRef
|
|
257
|
+
} = useFocusTrap({
|
|
258
|
+
isActive: isOpen && hasScrim
|
|
259
|
+
});
|
|
260
|
+
const mergedDialogRef = useMergedRefs(ref, dialogRef, focusTrapRef);
|
|
261
|
+
// Derive dismissal behavior from purpose, as Dialog does.
|
|
262
|
+
const allowEscape = purpose !== 'required';
|
|
263
|
+
const allowScrimClick = purpose === 'info';
|
|
264
|
+
// Same lowering as Dialog: with no padding prop the content area reads the
|
|
265
|
+
// theme's --astryx-drawer-padding chain (--spacing-4 when unset), else the
|
|
266
|
+
// explicit step.
|
|
267
|
+
const usesThemePadding = padding == null;
|
|
268
|
+
const effectivePadding = padding ?? 4;
|
|
269
|
+
const paddingToken = spacingStepToToken[effectivePadding];
|
|
270
|
+
// Whether the panel paints: true while open and for the whole slide-out.
|
|
271
|
+
const [isRendered, setIsRendered] = useState(isOpen);
|
|
272
|
+
|
|
273
|
+
// Adjusted during render, not in an effect: the panel has to be rendered in
|
|
274
|
+
// the same commit that targets the open transform, or @starting-style has
|
|
275
|
+
// nothing to animate from.
|
|
276
|
+
if (isOpen && !isRendered) {
|
|
277
|
+
setIsRendered(true);
|
|
278
|
+
}
|
|
279
|
+
useDrawerDialogPresence({
|
|
280
|
+
dialogRef,
|
|
281
|
+
isOpen,
|
|
282
|
+
isModal: hasScrim,
|
|
283
|
+
setIsRendered
|
|
284
|
+
});
|
|
285
|
+
const handleDismiss = useCallback(() => {
|
|
286
|
+
onOpenChange(false);
|
|
287
|
+
}, [onOpenChange]);
|
|
288
|
+
|
|
289
|
+
// The shared stack owns Escape delivery across every overlay family. The
|
|
290
|
+
// provider below gives layers opened from Drawer content a greater logical
|
|
291
|
+
// depth even when they render elsewhere in the DOM or browser top layer.
|
|
292
|
+
const {
|
|
293
|
+
shouldDismissOnCloseRequest
|
|
294
|
+
} = useLayerDismissal({
|
|
295
|
+
// The native host stays present through the slide-out. Keep this entry on
|
|
296
|
+
// the stack until the same commit that hides the host, so a second Escape
|
|
297
|
+
// cannot fall through to a lower layer during the exit animation.
|
|
298
|
+
isActive: isRendered,
|
|
299
|
+
// A `required` drawer consumes Escape without closing, so the press cannot
|
|
300
|
+
// fall through and dismiss a layer behind it either.
|
|
301
|
+
escapeBehavior: allowEscape ? 'close' : 'block',
|
|
302
|
+
onDismiss: handleDismiss,
|
|
303
|
+
getContainer: () => dialogRef.current
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Lock body scroll while a modal drawer is open (iOS Safari workaround).
|
|
307
|
+
useScrollLock(isOpen && hasScrim);
|
|
308
|
+
|
|
309
|
+
// A modal dialog's native cancel event is a platform close request (for
|
|
310
|
+
// example Android Back). Keep controlled state authoritative and apply the
|
|
311
|
+
// same top-most/IME rules as the shared Escape listener.
|
|
312
|
+
const handleCancel = useCallback(event => {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
if (shouldDismissOnCloseRequest() && allowEscape) {
|
|
315
|
+
handleDismiss();
|
|
316
|
+
}
|
|
317
|
+
}, [allowEscape, handleDismiss, shouldDismissOnCloseRequest]);
|
|
318
|
+
|
|
319
|
+
// Clicks on the ::backdrop target the <dialog> element itself; clicks on
|
|
320
|
+
// drawer content always target a child (the content area fills the panel).
|
|
321
|
+
const handleClick = useCallback(event => {
|
|
322
|
+
if (event.target === event.currentTarget && hasScrim && allowScrimClick) {
|
|
323
|
+
onOpenChange(false);
|
|
324
|
+
}
|
|
325
|
+
}, [allowScrimClick, hasScrim, onOpenChange]);
|
|
326
|
+
const widthValue = typeof width === 'number' ? `${width}px` : width;
|
|
327
|
+
const isFullWidthOnMobile = experimental?.isFullWidthOnMobile ?? false;
|
|
328
|
+
const mobileWidth = isFullWidthOnMobile ? MOBILE_WIDTH_FULL : `min(${widthValue}, calc(100dvw - ${MOBILE_PAGE_REVEAL}px))`;
|
|
329
|
+
|
|
330
|
+
// The side the panel is ANCHORED to, which is the side it must slide back
|
|
331
|
+
// out to. Latched at open, because a consumer commonly derives `side` from
|
|
332
|
+
// the same state that drives `isOpen` (`side={selected?.side ?? 'end'}`):
|
|
333
|
+
// that state clears on close, so the live prop flips mid-exit and the panel
|
|
334
|
+
// teleports to the other edge and slides out the wrong way. Children stay
|
|
335
|
+
// mounted for the exit for the same reason; so does the anchor.
|
|
336
|
+
const exitSideRef = useRef(side);
|
|
337
|
+
if (isOpen) {
|
|
338
|
+
exitSideRef.current = side;
|
|
339
|
+
}
|
|
340
|
+
const anchoredSide = isOpen ? side : exitSideRef.current;
|
|
341
|
+
const sideStyle = anchoredSide === 'start' ? styles.start : styles.end;
|
|
342
|
+
const sideOpenStyle = anchoredSide === 'start' ? styles.startOpen : styles.endOpen;
|
|
343
|
+
|
|
344
|
+
// Filter out native `open` to prevent InvalidStateError when passed
|
|
345
|
+
const {
|
|
346
|
+
open: _open,
|
|
347
|
+
...safeProps
|
|
348
|
+
} = props;
|
|
349
|
+
return /*#__PURE__*/_jsx("dialog", {
|
|
350
|
+
ref: mergedDialogRef,
|
|
351
|
+
...mergeProps(themeProps('drawer', {
|
|
352
|
+
side: anchoredSide
|
|
353
|
+
}), stylex.props(layerTextReset.reset, layerStructureReset.reset, styles.dialog, overlayPaddingReset.reset, sideStyle, dynamicStyles.inlineSize(widthValue, mobileWidth), isRendered && styles.rendered, isOpen && sideOpenStyle, hasScrim && styles.scrim, hasScrim && isOpen && styles.scrimOpen, xstyle), className, style),
|
|
354
|
+
...safeProps,
|
|
355
|
+
popover: hasScrim ? undefined : 'manual',
|
|
356
|
+
"aria-label": label,
|
|
357
|
+
"aria-modal": hasScrim ? 'true' : undefined,
|
|
358
|
+
...(purpose === 'required' && hasScrim ? {
|
|
359
|
+
role: 'alertdialog'
|
|
360
|
+
} : undefined),
|
|
361
|
+
onClick: composeEventHandlers(onClickProp, handleClick),
|
|
362
|
+
onKeyDown: onKeyDownProp,
|
|
363
|
+
onCancel: handleCancel,
|
|
364
|
+
children: /*#__PURE__*/_jsx(LayerDepthProvider, {
|
|
365
|
+
children: /*#__PURE__*/_jsx(LayerContentBoundary, {
|
|
366
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
367
|
+
tabIndex: -1,
|
|
368
|
+
...stylex.props(styles.content, ...container(usesThemePadding ? {
|
|
369
|
+
useThemeDefault: 'drawer'
|
|
370
|
+
} : {
|
|
371
|
+
paddingInnerX: paddingToken,
|
|
372
|
+
paddingInnerY: paddingToken,
|
|
373
|
+
paddingOuterX: paddingToken,
|
|
374
|
+
paddingOuterY: paddingToken
|
|
375
|
+
}), !usesThemePadding && effectivePadding !== 4 && paddingStyles[effectivePadding], !usesThemePadding && effectivePadding !== 4 && containerPaddingInlineVarStyles[effectivePadding], !usesThemePadding && effectivePadding !== 4 && containerPaddingBlockStartVarStyles[effectivePadding], !usesThemePadding && effectivePadding !== 4 && containerPaddingBlockEndVarStyles[effectivePadding], styles.contentSafeArea),
|
|
376
|
+
children: children
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
Drawer.displayName = 'Drawer';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DrawerHeader.tsx
|
|
3
|
+
* @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, i18n, BaseProps, isRenderable, mergeProps, themeProps
|
|
4
|
+
* @output Exports DrawerHeader component and DrawerHeaderProps
|
|
5
|
+
* @position Drawer header; composed in a Layout header slot inside Drawer, tested by DrawerHeader.test.tsx
|
|
6
|
+
*
|
|
7
|
+
* Mirrors DialogHeader's API: a title, an optional subtitle, start/end content
|
|
8
|
+
* slots, and a close button that renders only when `onOpenChange` is passed.
|
|
9
|
+
* Two differences follow from Drawer's contract:
|
|
10
|
+
* - It does not move focus on mount. Drawer owns focus entry through
|
|
11
|
+
* `data-autofocus` once its native host is presented.
|
|
12
|
+
* - It does not name the drawer. Drawer's required `label` does.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
15
|
+
* - /packages/core/src/Drawer/DrawerHeader.doc.mjs (props, anatomy, theming)
|
|
16
|
+
* - /packages/core/src/Drawer/DrawerHeader.test.tsx (tests for new/changed behavior)
|
|
17
|
+
* - /packages/core/src/Drawer/index.ts (exports if types change)
|
|
18
|
+
* - /packages/cli/assets/templates/blocks/components/Drawer/ (showcase blocks)
|
|
19
|
+
* - /packages/cli/assets/templates/blocks/components/DrawerHeader/ (showcase blocks)
|
|
20
|
+
* - /apps/storybook/stories/Drawer.stories.tsx (examples and visual coverage)
|
|
21
|
+
* - /apps/storybook/stories/DrawerHeader.stories.tsx (component audit fixture)
|
|
22
|
+
*/
|
|
23
|
+
import type { ReactNode } from 'react';
|
|
24
|
+
import type { BaseProps } from '../BaseProps';
|
|
25
|
+
export interface DrawerHeaderProps extends BaseProps<HTMLDivElement> {
|
|
26
|
+
/** Ref forwarded to the root element */
|
|
27
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
28
|
+
/**
|
|
29
|
+
* The title of the drawer, rendered as an h2. Keep it inline and
|
|
30
|
+
* non-interactive. It does not name the drawer; Drawer's `label` does.
|
|
31
|
+
*/
|
|
32
|
+
title: ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* Optional subtitle displayed below the title in smaller, secondary text.
|
|
35
|
+
* Accepts inline content such as a Link; it renders inside a span, so
|
|
36
|
+
* avoid block elements. Nothing renders for `null`, `undefined`, booleans,
|
|
37
|
+
* or an empty string.
|
|
38
|
+
*/
|
|
39
|
+
subtitle?: ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* Called with `false` when the close button is clicked. Pass the Drawer's
|
|
42
|
+
* own `onOpenChange`. If not provided, no close button is rendered.
|
|
43
|
+
*/
|
|
44
|
+
onOpenChange?: (isOpen: boolean) => unknown;
|
|
45
|
+
/**
|
|
46
|
+
* Content to render before the title (e.g., a back button).
|
|
47
|
+
*/
|
|
48
|
+
startContent?: ReactNode;
|
|
49
|
+
/**
|
|
50
|
+
* Content to render after the title, before the close button (e.g., action
|
|
51
|
+
* buttons).
|
|
52
|
+
*/
|
|
53
|
+
endContent?: ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Overrides automatic end-slot compensation. When omitted, rendering the
|
|
56
|
+
* close action applies block and logical inline-end compensation. Use
|
|
57
|
+
* `inline`, `block`, or `all` to select the axes explicitly.
|
|
58
|
+
*/
|
|
59
|
+
endContentEdgeCompensation?: 'inline' | 'block' | 'all';
|
|
60
|
+
/**
|
|
61
|
+
* Adds a themed border at the bottom edge.
|
|
62
|
+
* When false, spacing collapse is applied automatically for seamless visual flow.
|
|
63
|
+
* Defaults to the parent Layout's `defaultHasDividers` context value.
|
|
64
|
+
*/
|
|
65
|
+
hasDivider?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Header for a Drawer: a title, optional subtitle and start/end content, and a
|
|
69
|
+
* close button when `onOpenChange` is passed. Same API as DialogHeader.
|
|
70
|
+
*
|
|
71
|
+
* Uses LayoutHeader internally for consistent styling with other layout headers.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```
|
|
75
|
+
* <Drawer isOpen={isOpen} onOpenChange={setIsOpen} label="Details">
|
|
76
|
+
* <DrawerHeader title="Details" onOpenChange={setIsOpen} />
|
|
77
|
+
* <Section padding={4}>Content</Section>
|
|
78
|
+
* </Drawer>
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function DrawerHeader({ title, subtitle, onOpenChange, startContent, endContent, endContentEdgeCompensation, hasDivider, xstyle, className, style, ref, ...rest }: DrawerHeaderProps): import("react").JSX.Element;
|
|
82
|
+
export declare namespace DrawerHeader {
|
|
83
|
+
var displayName: string;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=DrawerHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DrawerHeader.d.ts","sourceRoot":"","sources":["../../src/Drawer/DrawerHeader.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA0C5C,MAAM,WAAW,iBAAkB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAClE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAE5C;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IAExD;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,IAAI,EACR,EAAE,iBAAiB,+BAoFnB;yBAjGe,YAAY"}
|