@astryxdesign/lab 0.4.4-canary.eb069fd → 0.4.5-canary.013e728

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.
@@ -106,7 +106,7 @@ const styles = {
106
106
  kaIpWk: "xx3sua9",
107
107
  ksu8eU: "xng3xce",
108
108
  kmVPX3: "x1717udv",
109
- kWkggS: "xjbqb8w x1n5bzlp xe9uy6x",
109
+ kWkggS: "xjbqb8w x1iutq3a xoevpu5",
110
110
  kkrTdU: "x1ypdohk",
111
111
  kGuDYH: "xosj86m",
112
112
  kLWn49: "xw6l6zx",
@@ -99,10 +99,10 @@ export function ChatReactionBar({
99
99
  onClick: () => onToggle?.(reaction.emoji),
100
100
  ...{
101
101
  0: {
102
- className: "x3nfvp2 x6s0dn4 xzye2dw xxk0z11 xf314gf xjspbzw xmkeg23 x1y0btm7 x9r1u3d x1xfd4ba x1ww4t2b xwmxj5m x1ypdohk x9ynric x9f619 xq90yva xuedmi6 x12w9bfk xlr8y92"
102
+ className: "x3nfvp2 x6s0dn4 xzye2dw xxk0z11 xf314gf xjspbzw xmkeg23 x1y0btm7 x9r1u3d xs8to0k x1xwh4np xwmxj5m x1ypdohk x9ynric x9f619 xq90yva xuedmi6 x12w9bfk xlr8y92"
103
103
  },
104
104
  1: {
105
- className: "x3nfvp2 x6s0dn4 xzye2dw xxk0z11 xf314gf xjspbzw xmkeg23 x1y0btm7 x1ypdohk x9ynric x9f619 xq90yva xuedmi6 x12w9bfk xlr8y92 xgcxg3y xad5do xn21ew4 xhggfp0"
105
+ className: "x3nfvp2 x6s0dn4 xzye2dw xxk0z11 xf314gf xjspbzw xmkeg23 x1y0btm7 x1ypdohk x9ynric x9f619 xq90yva xuedmi6 x12w9bfk xlr8y92 xgcxg3y xad5do xz7zccy xmhmdd9"
106
106
  }
107
107
  }[!!isSelected << 0],
108
108
  children: [/*#__PURE__*/_jsx("span", {
@@ -137,7 +137,7 @@ export function ChatReactionBar({
137
137
  type: "button",
138
138
  "aria-label": addLabel,
139
139
  ...{
140
- className: "x3nfvp2 x6s0dn4 xl56j7k x1td3qas xxk0z11 xjspbzw xmkeg23 x1y0btm7 x9r1u3d x1xfd4ba x1ww4t2b xwmxj5m xv9yike x1ypdohk x9f619 x1jvydc1 x1ltkj2j x1717udv"
140
+ className: "x3nfvp2 x6s0dn4 xl56j7k x1td3qas xxk0z11 xjspbzw xmkeg23 x1y0btm7 x9r1u3d xs8to0k x1xwh4np xwmxj5m xv9yike x1ypdohk x9f619 x1jvydc1 x1ltkj2j x1717udv"
141
141
  },
142
142
  children: "+"
143
143
  })
@@ -4,10 +4,17 @@
4
4
  * @output Exports Drawer component and DrawerProps
5
5
  * @position Lab implementation; consumed by index.ts, tested by Drawer.test.tsx, demonstrated in Storybook
6
6
  *
7
- * Edge-anchored overlay panel for inspectors, detail views, and sheets —
8
- * the "click a table row, see its details" pattern. Slides in from any of
9
- * the four edges: inline start/end (side panels) or block top/bottom
10
- * (full-width sheets).
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 `isFullWidthOnMobile`).
11
18
  *
12
19
  * Uses the native `<dialog>` element (same precedent as Dialog/MobileNav):
13
20
  * - `showModal()` when `hasScrim` (default) — top-layer rendering, focus
@@ -36,36 +43,42 @@ export interface DrawerProps extends BaseProps<HTMLDialogElement> {
36
43
  /** Ref forwarded to the root <dialog> element */
37
44
  ref?: React.Ref<HTMLDialogElement>;
38
45
  /**
39
- * Whether the drawer is open. Fully controlled — pair with `onClose`.
46
+ * Whether the drawer is open. Fully controlled — pair with `onOpenChange`.
40
47
  */
41
48
  isOpen: boolean;
42
49
  /**
43
- * Called when the drawer requests to be closed
44
- * (Escape key, scrim click, built-in close button). The caller owns the
45
- * open state. When sibling drawers are open, Escape only closes the
46
- * top (last-opened) drawer.
50
+ * Called when the drawer requests an open-state change. Escape, scrim
51
+ * click, and the built-in close button call it with `false`. The caller owns
52
+ * the open state. When sibling drawers are open, Escape only closes the top
53
+ * (last-opened) drawer.
47
54
  */
48
- onClose: () => void;
55
+ onOpenChange: (isOpen: boolean) => void;
49
56
  /**
50
57
  * Which edge the drawer slides from.
51
58
  * - `'end'` — inline-end edge (right in LTR) — the inspector convention
52
59
  * - `'start'` — inline-start edge (left in LTR)
53
- * - `'top'` / `'bottom'` — full-width sheets on the block axis
54
60
  * @default 'end'
55
61
  */
56
- side?: 'start' | 'end' | 'top' | 'bottom';
62
+ side?: 'start' | 'end';
57
63
  /**
58
- * Size budget of the panel along its slide axis: width for
59
- * `side="start"/"end"`, height for `side="top"/"bottom"`. A number is
60
- * pixels; a string is any CSS length (`'50%'`, `'40dvh'`). On viewports
61
- * smaller than the budget the drawer fills the axis.
64
+ * Desktop width budget. A number is pixels; a string is any CSS length
65
+ * (`'50%'`, `'32rem'`). Below the mobile breakpoint (640px), this
66
+ * remains the maximum while the drawer preserves a 56px reveal of the page
67
+ * behind — see `isFullWidthOnMobile`.
62
68
  * @default 400
63
69
  */
64
- size?: number | string;
70
+ width?: number | string;
71
+ /**
72
+ * Whether the drawer covers the full viewport width on mobile
73
+ * (below 640px) instead of preserving the default 56px reveal of the page
74
+ * behind. The reveal makes the drawer read as an overlay rather than a
75
+ * navigation.
76
+ * @default false
77
+ */
78
+ isFullWidthOnMobile?: boolean;
65
79
  /**
66
80
  * Accessible label for the drawer (required — the drawer has no
67
- * built-in heading to derive a name from). Also names the built-in
68
- * collapse/expand affordances.
81
+ * built-in heading to derive a name from).
69
82
  */
70
83
  label: string;
71
84
  /**
@@ -79,24 +92,11 @@ export interface DrawerProps extends BaseProps<HTMLDialogElement> {
79
92
  hasScrim?: boolean;
80
93
  /**
81
94
  * Whether to render the built-in close button in the top-trailing
82
- * corner. Defaults to the `hasScrim` value: modal drawers get a close
83
- * button, non-modal drawers don't.
84
- * @default hasScrim
95
+ * corner. Enabled by default for both modal and non-modal drawers so every
96
+ * overlay has an obvious dismissal affordance.
97
+ * @default true
85
98
  */
86
99
  hasCloseButton?: boolean;
87
- /**
88
- * Collapse the drawer to a narrow click-to-expand rail. Only supported
89
- * for non-modal (`hasScrim={false}`) drawers with `side="start"/"end"`;
90
- * ignored (with a dev warning) otherwise. Controlled — pair with
91
- * `onCollapsedChange`.
92
- */
93
- isCollapsed?: boolean;
94
- /**
95
- * Called when the built-in collapse/expand affordances are used.
96
- * Providing it renders a collapse toggle next to the close button while
97
- * expanded; the collapsed rail always expands on click.
98
- */
99
- onCollapsedChange?: (collapsed: boolean) => void;
100
100
  /**
101
101
  * Drawer content. Rendered inside a full-height scrollable area.
102
102
  * Focus the element with `data-autofocus` on open, if present.
@@ -108,27 +108,29 @@ export interface DrawerProps extends BaseProps<HTMLDialogElement> {
108
108
  'data-testid'?: string;
109
109
  }
110
110
  /**
111
- * An edge-anchored overlay panel for inspectors, detail views, and sheets.
111
+ * An overlay panel for inspectors and detail views.
112
112
  *
113
- * Slides in from the logical start/end edge (side panel) or the top/bottom
114
- * edge (full-width sheet) using the native `<dialog>` element: modal with a
115
- * scrim by default, or a non-modal inline overlay with `hasScrim={false}`.
116
- * Escape closes the top-most open drawer; focus returns to the element that
117
- * opened the drawer. Non-modal side drawers can collapse to a rail via
118
- * `isCollapsed`/`onCollapsedChange`.
113
+ * Slides in from the logical start or end edge and floats above the page
114
+ * using the native `<dialog>` element: modal with a scrim by default, or a
115
+ * non-modal overlay with `hasScrim={false}` that leaves the page behind
116
+ * interactive. `width` is the desktop budget; below 640px the panel preserves
117
+ * a 56px page reveal without exceeding that budget (or fills the viewport
118
+ * with `isFullWidthOnMobile`). Escape
119
+ * closes the top-most open drawer; focus returns to the element that
120
+ * opened it.
119
121
  *
120
122
  * @example
121
123
  * ```
122
124
  * const [selected, setSelected] = useState(null);
123
125
  * <Drawer
124
126
  * isOpen={selected != null}
125
- * onClose={() => setSelected(null)}
127
+ * onOpenChange={isOpen => !isOpen && setSelected(null)}
126
128
  * label={`Details: ${selected?.name}`}>
127
129
  * <HostDetails host={selected} />
128
130
  * </Drawer>
129
131
  * ```
130
132
  */
131
- export declare function Drawer({ isOpen, onClose, side, size, label, hasScrim, hasCloseButton, isCollapsed, onCollapsedChange, children, xstyle, className, style, ref, ...props }: DrawerProps): import("react").JSX.Element;
133
+ export declare function Drawer({ isOpen, onOpenChange, side, width, isFullWidthOnMobile, label, hasScrim, hasCloseButton, children, xstyle, className, style, onClick: onClickProp, onKeyDown: onKeyDownProp, ref, ...props }: DrawerProps): import("react").JSX.Element;
132
134
  export declare namespace Drawer {
133
135
  var displayName: string;
134
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../src/Drawer/Drawer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AA6SlD,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;;;;;OAKG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;IAE1C;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEvB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAEjD;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,OAAO,EACP,IAAY,EACZ,IAAU,EACV,KAAK,EACL,QAAe,EACf,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAmQb;yBAnRe,MAAM"}
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../src/Drawer/Drawer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAsOlD,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;;;;;OAKG;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;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,YAAY,EACZ,IAAY,EACZ,KAAW,EACX,mBAA2B,EAC3B,KAAK,EACL,QAAe,EACf,cAAqB,EACrB,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAmMb;yBApNe,MAAM"}
@@ -8,10 +8,17 @@
8
8
  * @output Exports Drawer component and DrawerProps
9
9
  * @position Lab implementation; consumed by index.ts, tested by Drawer.test.tsx, demonstrated in Storybook
10
10
  *
11
- * Edge-anchored overlay panel for inspectors, detail views, and sheets —
12
- * the "click a table row, see its details" pattern. Slides in from any of
13
- * the four edges: inline start/end (side panels) or block top/bottom
14
- * (full-width sheets).
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 `isFullWidthOnMobile`).
15
22
  *
16
23
  * Uses the native `<dialog>` element (same precedent as Dialog/MobileNav):
17
24
  * - `showModal()` when `hasScrim` (default) — top-layer rendering, focus
@@ -37,11 +44,12 @@
37
44
  import { useCallback, useEffect, useId, useRef, useState } from 'react';
38
45
  import * as stylex from '@stylexjs/stylex';
39
46
  import '@astryxdesign/core/theme/tokens.stylex';
40
- import { borderVars, colorVars, durationVars, easeVars, shadowVars, spacingVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
47
+ import { borderVars, colorVars, durationVars, easeVars, shadowVars, spacingVars } from '@astryxdesign/core/theme/tokens.stylex';
41
48
  import { Icon } from '@astryxdesign/core/Icon';
42
49
  import { IconButton } from '@astryxdesign/core/IconButton';
43
- import { useScrollLock, useDevWarning } from '@astryxdesign/core/hooks';
44
- import { mergeProps, mergeRefs, themeProps } from '@astryxdesign/core/utils';
50
+ import { useScrollLock } from '@astryxdesign/core/hooks';
51
+ import { composeEventHandlers, mergeProps, mergeRefs, themeProps } from '@astryxdesign/core/utils';
52
+ import { overlayPaddingReset } from '@astryxdesign/core/Layout';
45
53
 
46
54
  // =============================================================================
47
55
  // LIFO stacking registry (internal)
@@ -85,14 +93,22 @@ function isTopDrawer(id) {
85
93
  // Styles
86
94
  // =============================================================================
87
95
 
88
- // Structural rail width when collapsed — wide enough for a 44px touch
89
- // target, matching the raw-number convention used for the size default.
90
- const RAIL_WIDTH = 44;
96
+ // Below this viewport width the drawer preserves a fixed reveal of the page
97
+ // behind instead of growing proportionally with the viewport. 640px is the
98
+ // repo's mobile breakpoint.
99
+ const MOBILE_BREAKPOINT = 640;
100
+
101
+ // Material's established mobile drawer pattern leaves a 56dp reveal. Using
102
+ // the same value in CSS pixels gives the overlay a stable visual relationship
103
+ // to the page behind while the requested width remains an upper bound.
104
+ const MOBILE_PAGE_REVEAL = 56;
105
+ const MOBILE_WIDTH_FULL = '100dvw';
91
106
  const styles = {
92
107
  dialog: {
93
108
  kVAEAm: "xixxii4",
94
109
  kogj98: "x1ghz6dp",
95
110
  kmVPX3: "x1717udv",
111
+ kaIpWk: "x2u8bby",
96
112
  ks0D6T: "x1x1rfll",
97
113
  kskxy: "x7ab17h",
98
114
  kB7OPa: "x9f619",
@@ -102,8 +118,11 @@ const styles = {
102
118
  kVQacm: "xb3r6kr",
103
119
  kZeWKH: "xish69e",
104
120
  kI3sdo: "x1a2a7pz",
121
+ k87sOh: "x13vifvy",
122
+ krVfgx: "x1ey2m1c",
123
+ kZKoxP: "xtdtrs8",
105
124
  k1xSpc: "x1s85apg",
106
- k1ekBW: "x1yw18vd",
125
+ k1ekBW: "xayqnl7",
107
126
  kIyJzY: "x80gvsz",
108
127
  kAMwcw: "xlr8y92",
109
128
  kzIqYQ: "xd00j3c",
@@ -115,11 +134,8 @@ const styles = {
115
134
  $$css: true
116
135
  },
117
136
  end: {
118
- k87sOh: "x13vifvy",
119
- krVfgx: "x1ey2m1c",
120
137
  kt4wiu: "xtijo5x",
121
138
  kLqNvP: "xhi6v0a",
122
- kZKoxP: "xtdtrs8",
123
139
  k2ei4v: "xgbv0en",
124
140
  kVhnKS: "x1t7ytsu",
125
141
  kGJrpR: "x1j92z86",
@@ -131,11 +147,8 @@ const styles = {
131
147
  $$css: true
132
148
  },
133
149
  start: {
134
- k87sOh: "x13vifvy",
135
- krVfgx: "x1ey2m1c",
136
150
  kLqNvP: "x1o0tod",
137
151
  kt4wiu: "x1woyocn",
138
- kZKoxP: "xtdtrs8",
139
152
  ke9TFa: "xw8tdv1",
140
153
  k8ry5P: "x18b5jzi",
141
154
  kBCPoo: "x1gejf6u",
@@ -146,38 +159,6 @@ const styles = {
146
159
  k3aq6I: "xbryuvx x6mt36l x14gflnl",
147
160
  $$css: true
148
161
  },
149
- top: {
150
- kzqmXN: "x1o6l61p",
151
- kLqNvP: "x1o0tod",
152
- kt4wiu: "xtijo5x",
153
- k87sOh: "x13vifvy",
154
- krVfgx: "xdd4er5",
155
- kt9PQ7: "x92x3c3",
156
- kfdmCh: "x1q0q8m5",
157
- kL6WhQ: "xw8gpjh",
158
- k3aq6I: "x105ttfm",
159
- $$css: true
160
- },
161
- topOpen: {
162
- k3aq6I: "xnn1q72 xub2912",
163
- $$css: true
164
- },
165
- bottom: {
166
- kzqmXN: "x1o6l61p",
167
- kLqNvP: "x1o0tod",
168
- kt4wiu: "xtijo5x",
169
- krVfgx: "x1ey2m1c",
170
- k87sOh: "x80663w",
171
- kEafiO: "x11xkdxz",
172
- kPef9Z: "x13fuv20",
173
- kLZC3w: "x1pc3f07",
174
- k3aq6I: "x1weeur4",
175
- $$css: true
176
- },
177
- bottomOpen: {
178
- k3aq6I: "xnn1q72 xhbqy3z",
179
- $$css: true
180
- },
181
162
  scrim: {
182
163
  kGyWv1: "xnixb3f",
183
164
  kba3nw: "x1abwkk1",
@@ -191,32 +172,19 @@ const styles = {
191
172
  scrimOpen: {
192
173
  k5sjJv: "xb3n6bw xxiuuzi",
193
174
  $$css: true
194
- },
195
- collapsedRail: {
196
- ks0D6T: "x1k2d6hx",
197
- $$css: true
198
175
  }
199
176
  };
200
177
  const _temp = {
201
- kzqmXN: "x1o6l61p",
202
- "$$css": true
203
- };
204
- const _temp2 = {
205
- kZKoxP: "xtdtrs8",
178
+ ks0D6T: "xlbgzzq",
206
179
  "$$css": true
207
180
  };
208
181
  const dynamicStyles = {
209
- inlineSize: s => [_temp, {
210
- ks0D6T: s != null ? "xf68679" : s,
182
+ inlineSize: (desktopWidth, mobileWidth) => [_temp, {
183
+ kzqmXN: (desktopWidth != null ? "x11ymkkh " : desktopWidth) + (mobileWidth != null ? "xci7e5a" : mobileWidth),
211
184
  $$css: true
212
185
  }, {
213
- "--x-maxWidth": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(s)
214
- }],
215
- blockSize: s => [_temp2, {
216
- kskxy: s != null ? "x1jols5v" : s,
217
- $$css: true
218
- }, {
219
- "--x-maxHeight": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(s)
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)
220
188
  }],
221
189
  stackZ: z => [{
222
190
  kY2c9j: z != null ? "xr3buco" : z,
@@ -235,21 +203,23 @@ const dynamicStyles = {
235
203
  // =============================================================================
236
204
 
237
205
  /**
238
- * An edge-anchored overlay panel for inspectors, detail views, and sheets.
206
+ * An overlay panel for inspectors and detail views.
239
207
  *
240
- * Slides in from the logical start/end edge (side panel) or the top/bottom
241
- * edge (full-width sheet) using the native `<dialog>` element: modal with a
242
- * scrim by default, or a non-modal inline overlay with `hasScrim={false}`.
243
- * Escape closes the top-most open drawer; focus returns to the element that
244
- * opened the drawer. Non-modal side drawers can collapse to a rail via
245
- * `isCollapsed`/`onCollapsedChange`.
208
+ * Slides in from the logical start or end edge and floats above the page
209
+ * using the native `<dialog>` element: modal with a scrim by default, or a
210
+ * non-modal overlay with `hasScrim={false}` that leaves the page behind
211
+ * interactive. `width` is the desktop budget; below 640px the panel preserves
212
+ * a 56px page reveal without exceeding that budget (or fills the viewport
213
+ * with `isFullWidthOnMobile`). Escape
214
+ * closes the top-most open drawer; focus returns to the element that
215
+ * opened it.
246
216
  *
247
217
  * @example
248
218
  * ```
249
219
  * const [selected, setSelected] = useState(null);
250
220
  * <Drawer
251
221
  * isOpen={selected != null}
252
- * onClose={() => setSelected(null)}
222
+ * onOpenChange={isOpen => !isOpen && setSelected(null)}
253
223
  * label={`Details: ${selected?.name}`}>
254
224
  * <HostDetails host={selected} />
255
225
  * </Drawer>
@@ -257,18 +227,19 @@ const dynamicStyles = {
257
227
  */
258
228
  export function Drawer({
259
229
  isOpen,
260
- onClose,
230
+ onOpenChange,
261
231
  side = 'end',
262
- size = 400,
232
+ width = 400,
233
+ isFullWidthOnMobile = false,
263
234
  label,
264
235
  hasScrim = true,
265
- hasCloseButton,
266
- isCollapsed,
267
- onCollapsedChange,
236
+ hasCloseButton = true,
268
237
  children,
269
238
  xstyle,
270
239
  className,
271
240
  style,
241
+ onClick: onClickProp,
242
+ onKeyDown: onKeyDownProp,
272
243
  ref,
273
244
  ...props
274
245
  }) {
@@ -276,23 +247,15 @@ export function Drawer({
276
247
  const closeTimeoutRef = useRef(null);
277
248
  // Element focused when the drawer opened — restored on close.
278
249
  const triggerElementRef = useRef(null);
279
- // Registry identity + latest onClose (stable across re-renders so the
280
- // registration effect doesn't churn on every onClose identity change).
250
+ // Registry identity + latest onOpenChange (stable across re-renders so the
251
+ // registration effect doesn't churn on every onOpenChange identity change).
281
252
  const drawerId = useId();
282
- const onCloseRef = useRef(onClose);
253
+ const onOpenChangeRef = useRef(onOpenChange);
283
254
  useEffect(() => {
284
- onCloseRef.current = onClose;
285
- }, [onClose]);
255
+ onOpenChangeRef.current = onOpenChange;
256
+ }, [onOpenChange]);
286
257
  // z-index assigned by the registry on open (non-modal stacking only).
287
258
  const [stackZ, setStackZ] = useState(NON_MODAL_BASE_Z);
288
- const isSheet = side === 'top' || side === 'bottom';
289
- // Collapse only makes sense for a persistent (non-modal) side panel.
290
- const canCollapse = !hasScrim && !isSheet;
291
- const collapsed = canCollapse && isCollapsed === true;
292
- const showCloseButton = hasCloseButton ?? hasScrim;
293
-
294
- // Dev warning for unsupported collapse combinations.
295
- useDevWarning('Drawer', '`isCollapsed` is only supported for non-modal drawers ' + '(hasScrim={false}) with side="start" or side="end". The prop is ' + 'ignored.', isCollapsed != null && !canCollapse);
296
259
 
297
260
  // Open/close the native dialog. close() is delayed so the slide-out
298
261
  // transition can play; focus restore happens after close() because a
@@ -364,7 +327,7 @@ export function Drawer({
364
327
  if (!isOpen) {
365
328
  return;
366
329
  }
367
- const z = registerDrawer(drawerId, () => onCloseRef.current());
330
+ const z = registerDrawer(drawerId, () => onOpenChangeRef.current(false));
368
331
  setStackZ(z);
369
332
  return () => unregisterDrawer(drawerId);
370
333
  }, [isOpen, drawerId]);
@@ -373,57 +336,40 @@ export function Drawer({
373
336
  useScrollLock(isOpen && hasScrim);
374
337
 
375
338
  // Escape closes. The native `cancel` event only fires for showModal();
376
- // this keydown handler covers the non-modal show() path too. Only the
339
+ // this React keydown handler covers the non-modal show() path too. Only the
377
340
  // top of the drawer stack closes, so stacked siblings peel off
378
341
  // innermost-first.
379
- useEffect(() => {
380
- const dialog = dialogRef.current;
381
- if (!dialog || !isOpen) {
382
- return;
383
- }
384
- const handleKeyDown = event => {
385
- if (event.key === 'Escape') {
386
- event.preventDefault();
387
- if (isTopDrawer(drawerId)) {
388
- onClose();
389
- }
342
+ const handleKeyDown = useCallback(event => {
343
+ if (event.key === 'Escape') {
344
+ event.preventDefault();
345
+ if (isTopDrawer(drawerId)) {
346
+ onOpenChange(false);
390
347
  }
391
- };
392
- dialog.addEventListener('keydown', handleKeyDown);
393
- return () => dialog.removeEventListener('keydown', handleKeyDown);
394
- }, [isOpen, onClose, drawerId]);
348
+ }
349
+ }, [onOpenChange, drawerId]);
395
350
 
396
351
  // Native cancel event (browser Escape handling) — prevent the browser
397
- // from closing the dialog directly and route through onClose so the
352
+ // from closing the dialog directly and route through onOpenChange so the
398
353
  // caller's state stays the source of truth. Same top-of-stack rule as
399
354
  // the keydown path.
400
355
  const handleCancel = useCallback(event => {
401
356
  event.preventDefault();
402
357
  if (isTopDrawer(drawerId)) {
403
- onClose();
358
+ onOpenChange(false);
404
359
  }
405
- }, [onClose, drawerId]);
360
+ }, [onOpenChange, drawerId]);
406
361
 
407
362
  // Clicks on the ::backdrop target the <dialog> element itself; clicks on
408
363
  // drawer content always target a child (the content area fills the panel).
409
364
  const handleClick = useCallback(event => {
410
365
  if (event.target === event.currentTarget && hasScrim) {
411
- onClose();
366
+ onOpenChange(false);
412
367
  }
413
- }, [hasScrim, onClose]);
414
- const sizeValue = typeof size === 'number' ? `${size}px` : size;
415
- const sideStyle = {
416
- start: styles.start,
417
- end: styles.end,
418
- top: styles.top,
419
- bottom: styles.bottom
420
- }[side];
421
- const sideOpenStyle = {
422
- start: styles.startOpen,
423
- end: styles.endOpen,
424
- top: styles.topOpen,
425
- bottom: styles.bottomOpen
426
- }[side];
368
+ }, [hasScrim, onOpenChange]);
369
+ const widthValue = typeof width === 'number' ? `${width}px` : width;
370
+ const mobileWidth = isFullWidthOnMobile ? MOBILE_WIDTH_FULL : `min(${widthValue}, calc(100dvw - ${MOBILE_PAGE_REVEAL}px))`;
371
+ const sideStyle = side === 'start' ? styles.start : styles.end;
372
+ const sideOpenStyle = side === 'start' ? styles.startOpen : styles.endOpen;
427
373
 
428
374
  // Filter out native `open` to prevent InvalidStateError when passed
429
375
  const {
@@ -432,56 +378,26 @@ export function Drawer({
432
378
  } = props;
433
379
  return /*#__PURE__*/_jsxs("dialog", {
434
380
  ref: mergeRefs(ref, dialogRef),
435
- "aria-label": label,
436
- "aria-modal": hasScrim ? 'true' : undefined,
437
- onClick: handleClick,
438
- onCancel: handleCancel,
439
381
  ...mergeProps(themeProps('drawer', {
440
382
  side
441
- }), stylex.props(styles.dialog, sideStyle, isSheet ? dynamicStyles.blockSize(sizeValue) : dynamicStyles.inlineSize(sizeValue), isOpen && styles.open, isOpen && sideOpenStyle, hasScrim ? styles.scrim : dynamicStyles.stackZ(stackZ), hasScrim && isOpen && styles.scrimOpen, collapsed && styles.collapsedRail, xstyle), className, style),
383
+ }), stylex.props(styles.dialog, overlayPaddingReset.reset, sideStyle, dynamicStyles.inlineSize(widthValue, mobileWidth), isOpen && styles.open, isOpen && sideOpenStyle, hasScrim ? styles.scrim : dynamicStyles.stackZ(stackZ), hasScrim && isOpen && styles.scrimOpen, xstyle), className, style),
442
384
  ...safeProps,
385
+ "aria-label": label,
386
+ "aria-modal": hasScrim ? 'true' : undefined,
387
+ onClick: composeEventHandlers(onClickProp, handleClick),
388
+ onKeyDown: composeEventHandlers(onKeyDownProp, handleKeyDown),
389
+ onCancel: handleCancel,
443
390
  children: [/*#__PURE__*/_jsx("div", {
444
391
  tabIndex: -1,
445
392
  ...{
446
- 0: {
447
- className: "x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a2a7pz"
448
- },
449
- 1: {
450
- className: "x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a2a7pz x1s85apg"
451
- }
452
- }[!!collapsed << 0],
393
+ className: "x1iyjqo2 x2lwn1j xh8yej3 x1odjw0f x6ikm8r xish69e xx69xxh x1a148e8 x1a2a7pz"
394
+ },
453
395
  children: children
454
- }), collapsed ?
455
- /*#__PURE__*/
456
- // Collapsed rail: one full-size expand button with the label
457
- // reading vertically.
458
- _jsx("button", {
459
- type: "button",
460
- "aria-label": `Expand ${label}`,
461
- onClick: () => onCollapsedChange?.(false),
462
- ...{
463
- 0: {
464
- className: "xjyslct xng3xce x1ghz6dp x8o8v82 x7a5moj x1iyjqo2 xh8yej3 x2lwn1j x78zum5 x6s0dn4 xl56j7k x1ypdohk xjbqb8w xtao9ic xv1l7n4 xjb2p0i xcr08ib xmhvcl5 x1kq96og xuxw1ft xb3r6kr xlyipyv x98t3bc x1a2a7pz"
465
- },
466
- 1: {
467
- className: "xjyslct xng3xce x1ghz6dp x8o8v82 x7a5moj x1iyjqo2 xh8yej3 x2lwn1j x78zum5 x6s0dn4 xl56j7k x1ypdohk xjbqb8w xtao9ic xv1l7n4 xjb2p0i xcr08ib xmhvcl5 x1kq96og xuxw1ft xb3r6kr xlyipyv x98t3bc x1a2a7pz x19jd1h0"
468
- }
469
- }[!!(side === 'start') << 0],
470
- children: label
471
- }) : (showCloseButton || canCollapse && onCollapsedChange != null) && /*#__PURE__*/_jsxs("div", {
396
+ }), hasCloseButton && /*#__PURE__*/_jsx("div", {
472
397
  ...{
473
398
  className: "x10l6tqk xctzyg x72tfeb x78zum5 xzye2dw x1vjfegm"
474
399
  },
475
- children: [canCollapse && onCollapsedChange != null && /*#__PURE__*/_jsx(IconButton, {
476
- icon: /*#__PURE__*/_jsx(Icon, {
477
- icon: side === 'start' ? 'chevronLeft' : 'chevronRight',
478
- size: "sm",
479
- color: "inherit"
480
- }),
481
- label: `Collapse ${label}`,
482
- variant: "ghost",
483
- onClick: () => onCollapsedChange(true)
484
- }), showCloseButton && /*#__PURE__*/_jsx(IconButton, {
400
+ children: /*#__PURE__*/_jsx(IconButton, {
485
401
  icon: /*#__PURE__*/_jsx(Icon, {
486
402
  icon: "close",
487
403
  size: "sm",
@@ -489,8 +405,8 @@ export function Drawer({
489
405
  }),
490
406
  label: "Close",
491
407
  variant: "ghost",
492
- onClick: onClose
493
- })]
408
+ onClick: () => onOpenChange(false)
409
+ })
494
410
  })]
495
411
  });
496
412
  }
@@ -35,8 +35,8 @@ export interface InfoTipProps {
35
35
  }
36
36
  /**
37
37
  * An inline info-icon help affordance: a small "i" button that reveals a
38
- * tooltip on hover and keyboard focus. Use it next to labels, values, and
39
- * metrics for permission notes, metric definitions, and field help.
38
+ * tooltip on hover, keyboard focus, and tap. Use it next to labels, values,
39
+ * and metrics for permission notes, metric definitions, and field help.
40
40
  *
41
41
  * The value over hand-composing Icon inside Tooltip is the pre-wired
42
42
  * accessible trigger: a real button with an aria-label, Tab-reachable,
@@ -1 +1 @@
1
- {"version":3,"file":"InfoTip.d.ts","sourceRoot":"","sources":["../../src/InfoTip/InfoTip.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAgC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAGpE,OAAO,EAAO,KAAK,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAW5D;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEnC,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AA2BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,KAA0B,EAC1B,IAAW,GACZ,EAAE,YAAY,GAAG,SAAS,CA4C1B;yBAhDe,OAAO"}
1
+ {"version":3,"file":"InfoTip.d.ts","sourceRoot":"","sources":["../../src/InfoTip/InfoTip.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAgC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAGpE,OAAO,EAAO,KAAK,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAW5D;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEnC,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AA2BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,KAA0B,EAC1B,IAAW,GACZ,EAAE,YAAY,GAAG,SAAS,CAiD1B;yBArDe,OAAO"}