asterui 0.12.32 → 0.12.33

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.
@@ -1,13 +1,19 @@
1
1
  import { default as React } from 'react';
2
2
  export type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left';
3
3
  export type DrawerSize = 'default' | 'large' | number;
4
- export interface DrawerProps {
4
+ export interface DrawerPushConfig {
5
+ /** Distance to push parent drawer (default: 180) */
6
+ distance?: number;
7
+ }
8
+ export interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
5
9
  /** Drawer content */
6
10
  children: React.ReactNode;
7
11
  /** Whether the drawer is visible */
8
12
  open?: boolean;
9
13
  /** Callback when drawer is closed */
10
- onClose?: () => void;
14
+ onClose?: (e?: React.MouseEvent | React.KeyboardEvent) => void;
15
+ /** Callback after open/close animation completes */
16
+ afterOpenChange?: (open: boolean) => void;
11
17
  /** Drawer title */
12
18
  title?: React.ReactNode;
13
19
  /** Direction drawer slides from */
@@ -36,16 +42,40 @@ export interface DrawerProps {
36
42
  rootClassName?: string;
37
43
  /** Style for drawer panel */
38
44
  style?: React.CSSProperties;
45
+ /** Style for drawer header */
46
+ headerStyle?: React.CSSProperties;
47
+ /** Style for drawer body/content area */
48
+ bodyStyle?: React.CSSProperties;
49
+ /** Style for drawer footer */
50
+ footerStyle?: React.CSSProperties;
51
+ /** Style for drawer wrapper (includes mask) */
52
+ rootStyle?: React.CSSProperties;
53
+ /** Style for mask/backdrop */
54
+ maskStyle?: React.CSSProperties;
39
55
  /** z-index of drawer */
40
56
  zIndex?: number;
41
57
  /** Destroy content when closed */
42
58
  destroyOnClose?: boolean;
59
+ /** Pre-render drawer content (keep in DOM even when closed) */
60
+ forceRender?: boolean;
43
61
  /** Where to place initial focus */
44
62
  initialFocus?: 'close' | 'content';
63
+ /** Show loading skeleton */
64
+ loading?: boolean;
65
+ /** Custom container for portal (false to disable portal) */
66
+ getContainer?: HTMLElement | (() => HTMLElement) | false;
67
+ /** Nested drawer push behavior */
68
+ push?: boolean | DrawerPushConfig;
69
+ /** Test ID for testing */
70
+ 'data-testid'?: string;
71
+ }
72
+ export interface DrawerRef {
73
+ /** The drawer panel element */
74
+ nativeElement: HTMLDivElement | null;
45
75
  }
46
76
  /**
47
77
  * Drawer - A panel that slides in from the edge of the screen.
48
78
  * Use for forms, details, or task panels.
49
- * For responsive sidebar navigation, use SidebarDrawer instead.
79
+ * For responsive sidebar navigation, use ResponsiveDrawer instead.
50
80
  */
51
- export declare function Drawer({ children, open, onClose, title, placement, size, width, height, closable, mask, maskClosable, keyboard, footer, extra, className, rootClassName, style, zIndex, destroyOnClose, initialFocus, }: DrawerProps): React.ReactPortal | null;
81
+ export declare const Drawer: React.ForwardRefExoticComponent<DrawerProps & React.RefAttributes<DrawerRef>>;
@@ -1,175 +1,246 @@
1
- import { jsxs as c, jsx as o } from "react/jsx-runtime";
2
- import { useRef as l, useId as $, useState as D, useEffect as R, useCallback as S } from "react";
3
- import { createPortal as U } from "react-dom";
4
- function ee({
5
- children: j,
6
- open: r = !1,
7
- onClose: s,
8
- title: a,
9
- placement: i = "right",
10
- size: u = "default",
11
- width: M,
12
- height: q,
13
- closable: y = !0,
14
- mask: I = !0,
15
- maskClosable: K = !0,
16
- keyboard: p = !0,
17
- footer: v,
18
- extra: x,
19
- className: A = "",
20
- rootClassName: B = "",
21
- style: C,
22
- zIndex: F = 1e3,
23
- destroyOnClose: w = !1,
24
- initialFocus: g = "close"
25
- }) {
26
- const d = l(null), f = l(null), m = l(null), k = l(null), E = $(), N = $(), [V, X] = D(!1), [Y, L] = D(r);
27
- R(() => {
28
- X(!0);
29
- }, []);
30
- const z = () => typeof u == "number" ? u : u === "large" ? 736 : 378, H = () => {
31
- const e = i === "left" || i === "right", n = z();
32
- if (e) {
33
- const t = M ?? n;
34
- return { width: typeof t == "number" ? `${t}px` : t };
35
- } else {
36
- const t = q ?? n;
37
- return { height: typeof t == "number" ? `${t}px` : t };
38
- }
39
- }, b = S((e) => {
40
- if (!d.current || e.key !== "Tab" || typeof document > "u") return;
41
- const n = d.current.querySelectorAll(
42
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
43
- ), t = n[0], T = n[n.length - 1];
44
- e.shiftKey && document.activeElement === t ? (e.preventDefault(), T?.focus()) : !e.shiftKey && document.activeElement === T && (e.preventDefault(), t?.focus());
45
- }, []), h = S((e) => {
46
- p && e.key === "Escape" && s && (e.preventDefault(), s());
47
- }, [p, s]);
48
- R(() => {
49
- if (!(typeof document > "u"))
50
- if (r) {
51
- L(!0), k.current = document.activeElement, document.body.style.overflow = "hidden";
52
- const e = setTimeout(() => {
53
- g === "close" && f.current ? f.current.focus() : m.current && m.current.querySelector(
54
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
55
- )?.focus();
56
- }, 0);
57
- return document.addEventListener("keydown", h), document.addEventListener("keydown", b), () => {
58
- clearTimeout(e), document.body.style.overflow = "", document.removeEventListener("keydown", h), document.removeEventListener("keydown", b);
59
- };
1
+ import { jsx as n, jsxs as u } from "react/jsx-runtime";
2
+ import { forwardRef as ve, useRef as h, useId as B, useState as k, useImperativeHandle as we, useEffect as C, useCallback as E, createContext as xe, useContext as ge } from "react";
3
+ import { createPortal as ke } from "react-dom";
4
+ import { Skeleton as d } from "./Skeleton.js";
5
+ const F = xe(null);
6
+ function Ee() {
7
+ return ge(F);
8
+ }
9
+ const Ne = ve(
10
+ ({
11
+ children: H,
12
+ open: r = !1,
13
+ onClose: o,
14
+ afterOpenChange: N,
15
+ title: f,
16
+ placement: i = "right",
17
+ size: b = "default",
18
+ width: V,
19
+ height: z,
20
+ closable: D = !0,
21
+ mask: W = !0,
22
+ maskClosable: G = !0,
23
+ keyboard: $ = !0,
24
+ footer: T,
25
+ extra: L,
26
+ className: J = "",
27
+ rootClassName: Q = "",
28
+ style: U,
29
+ headerStyle: Z,
30
+ bodyStyle: _,
31
+ footerStyle: O,
32
+ rootStyle: ee,
33
+ maskStyle: te,
34
+ zIndex: ne = 1e3,
35
+ destroyOnClose: R = !1,
36
+ forceRender: S = !1,
37
+ initialFocus: j = "close",
38
+ loading: re = !1,
39
+ getContainer: l,
40
+ push: c = { distance: 180 },
41
+ "data-testid": y,
42
+ ...se
43
+ }, ae) => {
44
+ const m = h(null), p = h(null), v = h(null), A = h(null), I = B(), M = B(), [oe, ie] = k(!1), [le, X] = k(r || S), [De, w] = k(!1), Y = Ee();
45
+ we(ae, () => ({
46
+ nativeElement: m.current
47
+ })), C(() => {
48
+ ie(!0);
49
+ }, []);
50
+ const ce = () => typeof b == "number" ? b : b === "large" ? 736 : 378, ue = () => {
51
+ const e = i === "left" || i === "right", t = ce();
52
+ if (e) {
53
+ const s = V ?? t;
54
+ return { width: typeof s == "number" ? `${s}px` : s };
60
55
  } else {
61
- const e = k.current;
62
- if (e && document.body.contains(e) && e.focus(), w) {
63
- const n = setTimeout(() => L(!1), 300);
64
- return () => clearTimeout(n);
56
+ const s = z ?? t;
57
+ return { height: typeof s == "number" ? `${s}px` : s };
58
+ }
59
+ }, de = () => c ? typeof c == "boolean" ? c ? 180 : 0 : c.distance ?? 180 : 0, x = E((e) => {
60
+ if (!m.current || e.key !== "Tab" || typeof document > "u") return;
61
+ const t = m.current.querySelectorAll(
62
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
63
+ ), s = t[0], P = t[t.length - 1];
64
+ e.shiftKey && document.activeElement === s ? (e.preventDefault(), P?.focus()) : !e.shiftKey && document.activeElement === P && (e.preventDefault(), s?.focus());
65
+ }, []), g = E(
66
+ (e) => {
67
+ $ && e.key === "Escape" && o && (e.preventDefault(), o());
68
+ },
69
+ [$, o]
70
+ ), fe = E(() => {
71
+ w(!1), N?.(r), !r && R && X(!1);
72
+ }, [r, N, R]);
73
+ C(() => {
74
+ if (!(typeof document > "u"))
75
+ if (r) {
76
+ X(!0), w(!0), A.current = document.activeElement, document.body.style.overflow = "hidden";
77
+ const e = setTimeout(() => {
78
+ j === "close" && p.current ? p.current.focus() : v.current && v.current.querySelector(
79
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
80
+ )?.focus();
81
+ }, 0);
82
+ return document.addEventListener("keydown", g), document.addEventListener("keydown", x), () => {
83
+ clearTimeout(e), document.body.style.overflow = "", document.removeEventListener("keydown", g), document.removeEventListener("keydown", x);
84
+ };
85
+ } else {
86
+ w(!0);
87
+ const e = A.current;
88
+ e && document.body.contains(e) && e.focus();
89
+ }
90
+ }, [r, g, x, j]);
91
+ const me = (e) => {
92
+ G && o && o(e);
93
+ }, he = {
94
+ top: "inset-x-0 top-0",
95
+ right: "inset-y-0 right-0",
96
+ bottom: "inset-x-0 bottom-0",
97
+ left: "inset-y-0 left-0"
98
+ }, be = (e) => {
99
+ const t = Y && r ? Y.pushDistance : 0;
100
+ if (e) {
101
+ if (t === 0) return "translate(0, 0)";
102
+ switch (i) {
103
+ case "right":
104
+ return `translateX(-${t}px)`;
105
+ case "left":
106
+ return `translateX(${t}px)`;
107
+ case "top":
108
+ return `translateY(${t}px)`;
109
+ case "bottom":
110
+ return `translateY(-${t}px)`;
65
111
  }
66
112
  }
67
- }, [r, h, b, w, g]);
68
- const P = () => {
69
- K && s && s();
70
- }, W = {
71
- top: "inset-x-0 top-0",
72
- right: "inset-y-0 right-0",
73
- bottom: "inset-x-0 bottom-0",
74
- left: "inset-y-0 left-0"
75
- }, G = (e) => {
76
- if (e) return "translate(0, 0)";
77
- switch (i) {
78
- case "top":
79
- return "translateY(-100%)";
80
- case "right":
81
- return "translateX(100%)";
82
- case "bottom":
83
- return "translateY(100%)";
84
- case "left":
85
- return "translateX(-100%)";
86
- }
87
- }, J = H(), Q = /* @__PURE__ */ c(
88
- "div",
89
- {
90
- className: `fixed inset-0 ${r ? "" : "pointer-events-none"} ${B}`,
91
- style: { zIndex: F },
92
- role: "presentation",
93
- "data-state": r ? "open" : "closed",
94
- children: [
95
- I && /* @__PURE__ */ o(
96
- "div",
97
- {
98
- className: `absolute inset-0 bg-black transition-opacity duration-300 ${r ? "opacity-50" : "opacity-0"}`,
99
- onClick: P,
100
- "aria-hidden": "true"
101
- }
102
- ),
103
- /* @__PURE__ */ c(
104
- "div",
105
- {
106
- ref: d,
107
- role: "dialog",
108
- "aria-modal": "true",
109
- "aria-labelledby": a ? E : void 0,
110
- "aria-describedby": N,
111
- className: `fixed flex flex-col bg-base-100 shadow-xl transition-transform duration-300 ease-in-out ${W[i]} ${A}`,
112
- style: {
113
- ...J,
114
- transform: G(r),
115
- ...C
116
- },
117
- children: [
118
- (a || y || x) && /* @__PURE__ */ c("div", { className: "flex items-center justify-between px-6 py-4 border-b border-base-300", children: [
119
- a && /* @__PURE__ */ o("h2", { id: E, className: "text-lg font-semibold", children: a }),
120
- /* @__PURE__ */ c("div", { className: "flex items-center gap-2 ml-auto", children: [
121
- x,
122
- y && /* @__PURE__ */ o(
123
- "button",
124
- {
125
- ref: f,
126
- type: "button",
127
- className: "btn btn-ghost btn-sm btn-square",
128
- onClick: s,
129
- "aria-label": "Close drawer",
130
- children: /* @__PURE__ */ o(
131
- "svg",
132
- {
133
- xmlns: "http://www.w3.org/2000/svg",
134
- className: "h-5 w-5",
135
- fill: "none",
136
- viewBox: "0 0 24 24",
137
- stroke: "currentColor",
138
- "aria-hidden": "true",
139
- children: /* @__PURE__ */ o(
140
- "path",
141
- {
142
- strokeLinecap: "round",
143
- strokeLinejoin: "round",
144
- strokeWidth: 2,
145
- d: "M6 18L18 6M6 6l12 12"
146
- }
147
- )
148
- }
149
- )
150
- }
151
- )
152
- ] })
153
- ] }),
154
- /* @__PURE__ */ o(
155
- "div",
156
- {
157
- ref: m,
158
- id: N,
159
- className: "flex-1 overflow-auto p-6",
160
- children: j
161
- }
162
- ),
163
- v && /* @__PURE__ */ o("div", { className: "px-6 py-4 border-t border-base-300", children: v })
164
- ]
165
- }
166
- )
167
- ]
168
- }
169
- );
170
- return !V || !Y && !r ? null : U(Q, document.body);
171
- }
113
+ switch (i) {
114
+ case "top":
115
+ return "translateY(-100%)";
116
+ case "right":
117
+ return "translateX(100%)";
118
+ case "bottom":
119
+ return "translateY(100%)";
120
+ case "left":
121
+ return "translateX(-100%)";
122
+ }
123
+ }, ye = ue(), pe = () => l === !1 ? null : typeof l == "function" ? l() : l || (typeof document < "u" ? document.body : null), a = (e) => y ? `${y}-${e}` : void 0, q = /* @__PURE__ */ n(F.Provider, { value: { push: c, pushDistance: de() }, children: /* @__PURE__ */ u(
124
+ "div",
125
+ {
126
+ className: `fixed inset-0 ${r ? "" : "pointer-events-none"} ${Q}`,
127
+ style: { zIndex: ne, ...ee },
128
+ role: "presentation",
129
+ "data-state": r ? "open" : "closed",
130
+ "data-testid": y,
131
+ ...se,
132
+ children: [
133
+ W && /* @__PURE__ */ n(
134
+ "div",
135
+ {
136
+ className: `absolute inset-0 bg-black transition-opacity duration-300 ${r ? "opacity-50" : "opacity-0"}`,
137
+ style: te,
138
+ onClick: me,
139
+ "aria-hidden": "true",
140
+ "data-testid": a("mask")
141
+ }
142
+ ),
143
+ /* @__PURE__ */ u(
144
+ "div",
145
+ {
146
+ ref: m,
147
+ role: "dialog",
148
+ "aria-modal": "true",
149
+ "aria-labelledby": f ? I : void 0,
150
+ "aria-describedby": M,
151
+ className: `fixed flex flex-col bg-base-100 shadow-xl transition-transform duration-300 ease-in-out ${he[i]} ${J}`,
152
+ style: {
153
+ ...ye,
154
+ transform: be(r),
155
+ ...U
156
+ },
157
+ onTransitionEnd: fe,
158
+ "data-testid": a("panel"),
159
+ children: [
160
+ (f || D || L) && /* @__PURE__ */ u(
161
+ "div",
162
+ {
163
+ className: "flex items-center justify-between px-6 py-4 border-b border-base-300",
164
+ style: Z,
165
+ "data-testid": a("header"),
166
+ children: [
167
+ f && /* @__PURE__ */ n("h2", { id: I, className: "text-lg font-semibold", children: f }),
168
+ /* @__PURE__ */ u("div", { className: "flex items-center gap-2 ml-auto", children: [
169
+ L,
170
+ D && /* @__PURE__ */ n(
171
+ "button",
172
+ {
173
+ ref: p,
174
+ type: "button",
175
+ className: "btn btn-ghost btn-sm btn-square",
176
+ onClick: o,
177
+ "aria-label": "Close drawer",
178
+ "data-testid": a("close"),
179
+ children: /* @__PURE__ */ n(
180
+ "svg",
181
+ {
182
+ xmlns: "http://www.w3.org/2000/svg",
183
+ className: "h-5 w-5",
184
+ fill: "none",
185
+ viewBox: "0 0 24 24",
186
+ stroke: "currentColor",
187
+ "aria-hidden": "true",
188
+ children: /* @__PURE__ */ n(
189
+ "path",
190
+ {
191
+ strokeLinecap: "round",
192
+ strokeLinejoin: "round",
193
+ strokeWidth: 2,
194
+ d: "M6 18L18 6M6 6l12 12"
195
+ }
196
+ )
197
+ }
198
+ )
199
+ }
200
+ )
201
+ ] })
202
+ ]
203
+ }
204
+ ),
205
+ /* @__PURE__ */ n(
206
+ "div",
207
+ {
208
+ ref: v,
209
+ id: M,
210
+ className: "flex-1 overflow-auto p-6",
211
+ style: _,
212
+ "data-testid": a("body"),
213
+ children: re ? /* @__PURE__ */ u("div", { className: "space-y-4", "data-testid": a("skeleton"), children: [
214
+ /* @__PURE__ */ n(d, { className: "h-4 w-3/4" }),
215
+ /* @__PURE__ */ n(d, { className: "h-4 w-full" }),
216
+ /* @__PURE__ */ n(d, { className: "h-4 w-5/6" }),
217
+ /* @__PURE__ */ n(d, { className: "h-4 w-2/3" }),
218
+ /* @__PURE__ */ n(d, { className: "h-32 w-full" })
219
+ ] }) : H
220
+ }
221
+ ),
222
+ T && /* @__PURE__ */ n(
223
+ "div",
224
+ {
225
+ className: "px-6 py-4 border-t border-base-300",
226
+ style: O,
227
+ "data-testid": a("footer"),
228
+ children: T
229
+ }
230
+ )
231
+ ]
232
+ }
233
+ )
234
+ ]
235
+ }
236
+ ) });
237
+ if (!oe || !le && !r && !S) return null;
238
+ const K = pe();
239
+ return K === null ? q : ke(q, K);
240
+ }
241
+ );
242
+ Ne.displayName = "Drawer";
172
243
  export {
173
- ee as Drawer
244
+ Ne as Drawer
174
245
  };
175
246
  //# sourceMappingURL=Drawer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.js","sources":["../../src/components/Drawer.tsx"],"sourcesContent":["import React, { useEffect, useRef, useId, useCallback, useState } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left'\nexport type DrawerSize = 'default' | 'large' | number\n\nexport interface DrawerProps {\n /** Drawer content */\n children: React.ReactNode\n /** Whether the drawer is visible */\n open?: boolean\n /** Callback when drawer is closed */\n onClose?: () => void\n /** Drawer title */\n title?: React.ReactNode\n /** Direction drawer slides from */\n placement?: DrawerPlacement\n /** Preset size or custom width/height in pixels */\n size?: DrawerSize\n /** Custom width (overrides size for left/right placement) */\n width?: number | string\n /** Custom height (overrides size for top/bottom placement) */\n height?: number | string\n /** Whether to show close button */\n closable?: boolean\n /** Whether to show mask/backdrop */\n mask?: boolean\n /** Whether clicking mask closes drawer */\n maskClosable?: boolean\n /** Whether ESC closes drawer */\n keyboard?: boolean\n /** Footer content */\n footer?: React.ReactNode\n /** Extra content in header (right side) */\n extra?: React.ReactNode\n /** CSS class for drawer panel */\n className?: string\n /** CSS class for drawer wrapper */\n rootClassName?: string\n /** Style for drawer panel */\n style?: React.CSSProperties\n /** z-index of drawer */\n zIndex?: number\n /** Destroy content when closed */\n destroyOnClose?: boolean\n /** Where to place initial focus */\n initialFocus?: 'close' | 'content'\n}\n\n/**\n * Drawer - A panel that slides in from the edge of the screen.\n * Use for forms, details, or task panels.\n * For responsive sidebar navigation, use SidebarDrawer instead.\n */\nexport function Drawer({\n children,\n open = false,\n onClose,\n title,\n placement = 'right',\n size = 'default',\n width,\n height,\n closable = true,\n mask = true,\n maskClosable = true,\n keyboard = true,\n footer,\n extra,\n className = '',\n rootClassName = '',\n style,\n zIndex = 1000,\n destroyOnClose = false,\n initialFocus = 'close',\n}: DrawerProps) {\n const drawerRef = useRef<HTMLDivElement>(null)\n const closeButtonRef = useRef<HTMLButtonElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n const previousActiveElement = useRef<HTMLElement | null>(null)\n const titleId = useId()\n const contentId = useId()\n const [mounted, setMounted] = useState(false)\n const [shouldRender, setShouldRender] = useState(open)\n\n // Handle SSR - only render portal after mounting in browser\n useEffect(() => {\n setMounted(true)\n }, [])\n\n // Calculate dimensions\n const getSizeValue = (): number => {\n if (typeof size === 'number') return size\n return size === 'large' ? 736 : 378\n }\n\n const getDimension = (): { width?: string; height?: string } => {\n const isHorizontal = placement === 'left' || placement === 'right'\n const sizeValue = getSizeValue()\n\n if (isHorizontal) {\n const w = width ?? sizeValue\n return { width: typeof w === 'number' ? `${w}px` : w }\n } else {\n const h = height ?? sizeValue\n return { height: typeof h === 'number' ? `${h}px` : h }\n }\n }\n\n // Focus trap\n const trapFocus = useCallback((e: KeyboardEvent) => {\n if (!drawerRef.current || e.key !== 'Tab' || typeof document === 'undefined') return\n\n const focusableElements = drawerRef.current.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n const firstElement = focusableElements[0]\n const lastElement = focusableElements[focusableElements.length - 1]\n\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault()\n lastElement?.focus()\n } else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault()\n firstElement?.focus()\n }\n }, [])\n\n // Handle ESC key\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (keyboard && e.key === 'Escape' && onClose) {\n e.preventDefault()\n onClose()\n }\n }, [keyboard, onClose])\n\n // Open/close effects\n useEffect(() => {\n if (typeof document === 'undefined') return\n\n if (open) {\n setShouldRender(true)\n previousActiveElement.current = document.activeElement as HTMLElement\n document.body.style.overflow = 'hidden'\n\n // Set initial focus\n const focusTimeout = setTimeout(() => {\n if (initialFocus === 'close' && closeButtonRef.current) {\n closeButtonRef.current.focus()\n } else if (contentRef.current) {\n const firstFocusable = contentRef.current.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n firstFocusable?.focus()\n }\n }, 0)\n\n // Add event listeners\n document.addEventListener('keydown', handleKeyDown)\n document.addEventListener('keydown', trapFocus)\n\n return () => {\n clearTimeout(focusTimeout)\n document.body.style.overflow = ''\n document.removeEventListener('keydown', handleKeyDown)\n document.removeEventListener('keydown', trapFocus)\n }\n } else {\n // Restore focus to previously focused element if it's still in the DOM\n const prevElement = previousActiveElement.current\n if (prevElement && document.body.contains(prevElement)) {\n prevElement.focus()\n }\n\n // Handle destroyOnClose\n if (destroyOnClose) {\n const timeout = setTimeout(() => setShouldRender(false), 300)\n return () => clearTimeout(timeout)\n }\n }\n }, [open, handleKeyDown, trapFocus, destroyOnClose, initialFocus])\n\n const handleMaskClick = () => {\n if (maskClosable && onClose) {\n onClose()\n }\n }\n\n // Position classes\n const placementClasses: Record<DrawerPlacement, string> = {\n top: 'inset-x-0 top-0',\n right: 'inset-y-0 right-0',\n bottom: 'inset-x-0 bottom-0',\n left: 'inset-y-0 left-0',\n }\n\n // Transform for animation\n const getTransform = (isOpen: boolean): string => {\n if (isOpen) return 'translate(0, 0)'\n switch (placement) {\n case 'top': return 'translateY(-100%)'\n case 'right': return 'translateX(100%)'\n case 'bottom': return 'translateY(100%)'\n case 'left': return 'translateX(-100%)'\n }\n }\n\n const dimension = getDimension()\n\n const drawerContent = (\n <div\n className={`fixed inset-0 ${open ? '' : 'pointer-events-none'} ${rootClassName}`}\n style={{ zIndex }}\n role=\"presentation\"\n data-state={open ? 'open' : 'closed'}\n >\n {/* Backdrop/Mask */}\n {mask && (\n <div\n className={`absolute inset-0 bg-black transition-opacity duration-300 ${\n open ? 'opacity-50' : 'opacity-0'\n }`}\n onClick={handleMaskClick}\n aria-hidden=\"true\"\n />\n )}\n\n {/* Drawer Panel */}\n <div\n ref={drawerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={contentId}\n className={`fixed flex flex-col bg-base-100 shadow-xl transition-transform duration-300 ease-in-out ${placementClasses[placement]} ${className}`}\n style={{\n ...dimension,\n transform: getTransform(open),\n ...style,\n }}\n >\n {/* Header */}\n {(title || closable || extra) && (\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-base-300\">\n {title && (\n <h2 id={titleId} className=\"text-lg font-semibold\">\n {title}\n </h2>\n )}\n <div className=\"flex items-center gap-2 ml-auto\">\n {extra}\n {closable && (\n <button\n ref={closeButtonRef}\n type=\"button\"\n className=\"btn btn-ghost btn-sm btn-square\"\n onClick={onClose}\n aria-label=\"Close drawer\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"h-5 w-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Content */}\n <div\n ref={contentRef}\n id={contentId}\n className=\"flex-1 overflow-auto p-6\"\n >\n {children}\n </div>\n\n {/* Footer */}\n {footer && (\n <div className=\"px-6 py-4 border-t border-base-300\">\n {footer}\n </div>\n )}\n </div>\n </div>\n )\n\n // Don't render during SSR or when not needed\n if (!mounted || (!shouldRender && !open)) return null\n\n return createPortal(drawerContent, document.body)\n}\n"],"names":["Drawer","children","open","onClose","title","placement","size","width","height","closable","mask","maskClosable","keyboard","footer","extra","className","rootClassName","style","zIndex","destroyOnClose","initialFocus","drawerRef","useRef","closeButtonRef","contentRef","previousActiveElement","titleId","useId","contentId","mounted","setMounted","useState","shouldRender","setShouldRender","useEffect","getSizeValue","getDimension","isHorizontal","sizeValue","w","h","trapFocus","useCallback","focusableElements","firstElement","lastElement","handleKeyDown","focusTimeout","prevElement","timeout","handleMaskClick","placementClasses","getTransform","isOpen","dimension","drawerContent","jsxs","jsx","createPortal"],"mappings":"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"}
1
+ {"version":3,"file":"Drawer.js","sources":["../../src/components/Drawer.tsx"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n useId,\n useCallback,\n useState,\n forwardRef,\n useImperativeHandle,\n createContext,\n useContext,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { Skeleton } from './Skeleton'\n\nexport type DrawerPlacement = 'top' | 'right' | 'bottom' | 'left'\nexport type DrawerSize = 'default' | 'large' | number\n\nexport interface DrawerPushConfig {\n /** Distance to push parent drawer (default: 180) */\n distance?: number\n}\n\nexport interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Drawer content */\n children: React.ReactNode\n /** Whether the drawer is visible */\n open?: boolean\n /** Callback when drawer is closed */\n onClose?: (e?: React.MouseEvent | React.KeyboardEvent) => void\n /** Callback after open/close animation completes */\n afterOpenChange?: (open: boolean) => void\n /** Drawer title */\n title?: React.ReactNode\n /** Direction drawer slides from */\n placement?: DrawerPlacement\n /** Preset size or custom width/height in pixels */\n size?: DrawerSize\n /** Custom width (overrides size for left/right placement) */\n width?: number | string\n /** Custom height (overrides size for top/bottom placement) */\n height?: number | string\n /** Whether to show close button */\n closable?: boolean\n /** Whether to show mask/backdrop */\n mask?: boolean\n /** Whether clicking mask closes drawer */\n maskClosable?: boolean\n /** Whether ESC closes drawer */\n keyboard?: boolean\n /** Footer content */\n footer?: React.ReactNode\n /** Extra content in header (right side) */\n extra?: React.ReactNode\n /** CSS class for drawer panel */\n className?: string\n /** CSS class for drawer wrapper */\n rootClassName?: string\n /** Style for drawer panel */\n style?: React.CSSProperties\n /** Style for drawer header */\n headerStyle?: React.CSSProperties\n /** Style for drawer body/content area */\n bodyStyle?: React.CSSProperties\n /** Style for drawer footer */\n footerStyle?: React.CSSProperties\n /** Style for drawer wrapper (includes mask) */\n rootStyle?: React.CSSProperties\n /** Style for mask/backdrop */\n maskStyle?: React.CSSProperties\n /** z-index of drawer */\n zIndex?: number\n /** Destroy content when closed */\n destroyOnClose?: boolean\n /** Pre-render drawer content (keep in DOM even when closed) */\n forceRender?: boolean\n /** Where to place initial focus */\n initialFocus?: 'close' | 'content'\n /** Show loading skeleton */\n loading?: boolean\n /** Custom container for portal (false to disable portal) */\n getContainer?: HTMLElement | (() => HTMLElement) | false\n /** Nested drawer push behavior */\n push?: boolean | DrawerPushConfig\n /** Test ID for testing */\n 'data-testid'?: string\n}\n\nexport interface DrawerRef {\n /** The drawer panel element */\n nativeElement: HTMLDivElement | null\n}\n\n// Context for nested drawer push behavior\ninterface DrawerContextValue {\n push: boolean | DrawerPushConfig\n pushDistance: number\n}\n\nconst DrawerContext = createContext<DrawerContextValue | null>(null)\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/**\n * Drawer - A panel that slides in from the edge of the screen.\n * Use for forms, details, or task panels.\n * For responsive sidebar navigation, use ResponsiveDrawer instead.\n */\nexport const Drawer = forwardRef<DrawerRef, DrawerProps>(\n (\n {\n children,\n open = false,\n onClose,\n afterOpenChange,\n title,\n placement = 'right',\n size = 'default',\n width,\n height,\n closable = true,\n mask = true,\n maskClosable = true,\n keyboard = true,\n footer,\n extra,\n className = '',\n rootClassName = '',\n style,\n headerStyle,\n bodyStyle,\n footerStyle,\n rootStyle,\n maskStyle,\n zIndex = 1000,\n destroyOnClose = false,\n forceRender = false,\n initialFocus = 'close',\n loading = false,\n getContainer,\n push = { distance: 180 },\n 'data-testid': testId,\n ...rest\n },\n ref\n ) => {\n const drawerRef = useRef<HTMLDivElement>(null)\n const closeButtonRef = useRef<HTMLButtonElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n const previousActiveElement = useRef<HTMLElement | null>(null)\n const titleId = useId()\n const contentId = useId()\n const [mounted, setMounted] = useState(false)\n const [shouldRender, setShouldRender] = useState(open || forceRender)\n const [isAnimating, setIsAnimating] = useState(false)\n\n // Get parent drawer context for nested push behavior\n const parentDrawer = useDrawerContext()\n\n // Expose ref\n useImperativeHandle(ref, () => ({\n nativeElement: drawerRef.current,\n }))\n\n // Handle SSR - only render portal after mounting in browser\n useEffect(() => {\n setMounted(true)\n }, [])\n\n // Calculate dimensions\n const getSizeValue = (): number => {\n if (typeof size === 'number') return size\n return size === 'large' ? 736 : 378\n }\n\n const getDimension = (): { width?: string; height?: string } => {\n const isHorizontal = placement === 'left' || placement === 'right'\n const sizeValue = getSizeValue()\n\n if (isHorizontal) {\n const w = width ?? sizeValue\n return { width: typeof w === 'number' ? `${w}px` : w }\n } else {\n const h = height ?? sizeValue\n return { height: typeof h === 'number' ? `${h}px` : h }\n }\n }\n\n // Calculate push distance for nested drawers\n const getPushDistance = (): number => {\n if (!push) return 0\n if (typeof push === 'boolean') return push ? 180 : 0\n return push.distance ?? 180\n }\n\n // Focus trap\n const trapFocus = useCallback((e: KeyboardEvent) => {\n if (!drawerRef.current || e.key !== 'Tab' || typeof document === 'undefined') return\n\n const focusableElements = drawerRef.current.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n const firstElement = focusableElements[0]\n const lastElement = focusableElements[focusableElements.length - 1]\n\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault()\n lastElement?.focus()\n } else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault()\n firstElement?.focus()\n }\n }, [])\n\n // Handle ESC key\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (keyboard && e.key === 'Escape' && onClose) {\n e.preventDefault()\n onClose()\n }\n },\n [keyboard, onClose]\n )\n\n // Handle animation end\n const handleTransitionEnd = useCallback(() => {\n setIsAnimating(false)\n afterOpenChange?.(open)\n\n if (!open && destroyOnClose) {\n setShouldRender(false)\n }\n }, [open, afterOpenChange, destroyOnClose])\n\n // Open/close effects\n useEffect(() => {\n if (typeof document === 'undefined') return\n\n if (open) {\n setShouldRender(true)\n setIsAnimating(true)\n previousActiveElement.current = document.activeElement as HTMLElement\n document.body.style.overflow = 'hidden'\n\n // Set initial focus\n const focusTimeout = setTimeout(() => {\n if (initialFocus === 'close' && closeButtonRef.current) {\n closeButtonRef.current.focus()\n } else if (contentRef.current) {\n const firstFocusable = contentRef.current.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n )\n firstFocusable?.focus()\n }\n }, 0)\n\n // Add event listeners\n document.addEventListener('keydown', handleKeyDown)\n document.addEventListener('keydown', trapFocus)\n\n return () => {\n clearTimeout(focusTimeout)\n document.body.style.overflow = ''\n document.removeEventListener('keydown', handleKeyDown)\n document.removeEventListener('keydown', trapFocus)\n }\n } else {\n setIsAnimating(true)\n // Restore focus to previously focused element if it's still in the DOM\n const prevElement = previousActiveElement.current\n if (prevElement && document.body.contains(prevElement)) {\n prevElement.focus()\n }\n }\n }, [open, handleKeyDown, trapFocus, initialFocus])\n\n const handleMaskClick = (e: React.MouseEvent) => {\n if (maskClosable && onClose) {\n onClose(e)\n }\n }\n\n // Position classes\n const placementClasses: Record<DrawerPlacement, string> = {\n top: 'inset-x-0 top-0',\n right: 'inset-y-0 right-0',\n bottom: 'inset-x-0 bottom-0',\n left: 'inset-y-0 left-0',\n }\n\n // Transform for animation\n const getTransform = (isOpen: boolean): string => {\n // Apply push offset from child drawer\n const pushOffset = parentDrawer && open ? parentDrawer.pushDistance : 0\n\n if (isOpen) {\n if (pushOffset === 0) return 'translate(0, 0)'\n switch (placement) {\n case 'right':\n return `translateX(-${pushOffset}px)`\n case 'left':\n return `translateX(${pushOffset}px)`\n case 'top':\n return `translateY(${pushOffset}px)`\n case 'bottom':\n return `translateY(-${pushOffset}px)`\n }\n }\n\n switch (placement) {\n case 'top':\n return 'translateY(-100%)'\n case 'right':\n return 'translateX(100%)'\n case 'bottom':\n return 'translateY(100%)'\n case 'left':\n return 'translateX(-100%)'\n }\n }\n\n const dimension = getDimension()\n\n // Get container element\n const getContainerElement = (): HTMLElement | null => {\n if (getContainer === false) return null\n if (typeof getContainer === 'function') return getContainer()\n if (getContainer) return getContainer\n return typeof document !== 'undefined' ? document.body : null\n }\n\n // Generate test IDs\n const getTestId = (suffix: string) => (testId ? `${testId}-${suffix}` : undefined)\n\n const drawerContent = (\n <DrawerContext.Provider value={{ push, pushDistance: getPushDistance() }}>\n <div\n className={`fixed inset-0 ${open ? '' : 'pointer-events-none'} ${rootClassName}`}\n style={{ zIndex, ...rootStyle }}\n role=\"presentation\"\n data-state={open ? 'open' : 'closed'}\n data-testid={testId}\n {...rest}\n >\n {/* Backdrop/Mask */}\n {mask && (\n <div\n className={`absolute inset-0 bg-black transition-opacity duration-300 ${\n open ? 'opacity-50' : 'opacity-0'\n }`}\n style={maskStyle}\n onClick={handleMaskClick}\n aria-hidden=\"true\"\n data-testid={getTestId('mask')}\n />\n )}\n\n {/* Drawer Panel */}\n <div\n ref={drawerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={contentId}\n className={`fixed flex flex-col bg-base-100 shadow-xl transition-transform duration-300 ease-in-out ${placementClasses[placement]} ${className}`}\n style={{\n ...dimension,\n transform: getTransform(open),\n ...style,\n }}\n onTransitionEnd={handleTransitionEnd}\n data-testid={getTestId('panel')}\n >\n {/* Header */}\n {(title || closable || extra) && (\n <div\n className=\"flex items-center justify-between px-6 py-4 border-b border-base-300\"\n style={headerStyle}\n data-testid={getTestId('header')}\n >\n {title && (\n <h2 id={titleId} className=\"text-lg font-semibold\">\n {title}\n </h2>\n )}\n <div className=\"flex items-center gap-2 ml-auto\">\n {extra}\n {closable && (\n <button\n ref={closeButtonRef}\n type=\"button\"\n className=\"btn btn-ghost btn-sm btn-square\"\n onClick={onClose}\n aria-label=\"Close drawer\"\n data-testid={getTestId('close')}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"h-5 w-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M6 18L18 6M6 6l12 12\"\n />\n </svg>\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Content */}\n <div\n ref={contentRef}\n id={contentId}\n className=\"flex-1 overflow-auto p-6\"\n style={bodyStyle}\n data-testid={getTestId('body')}\n >\n {loading ? (\n <div className=\"space-y-4\" data-testid={getTestId('skeleton')}>\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-5/6\" />\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"h-32 w-full\" />\n </div>\n ) : (\n children\n )}\n </div>\n\n {/* Footer */}\n {footer && (\n <div\n className=\"px-6 py-4 border-t border-base-300\"\n style={footerStyle}\n data-testid={getTestId('footer')}\n >\n {footer}\n </div>\n )}\n </div>\n </div>\n </DrawerContext.Provider>\n )\n\n // Don't render during SSR or when not needed\n if (!mounted) return null\n if (!shouldRender && !open && !forceRender) return null\n\n // Render without portal if getContainer is false\n const container = getContainerElement()\n if (container === null) return drawerContent\n\n return createPortal(drawerContent, container)\n }\n)\n\nDrawer.displayName = 'Drawer'\n"],"names":["DrawerContext","createContext","useDrawerContext","useContext","Drawer","forwardRef","children","open","onClose","afterOpenChange","title","placement","size","width","height","closable","mask","maskClosable","keyboard","footer","extra","className","rootClassName","style","headerStyle","bodyStyle","footerStyle","rootStyle","maskStyle","zIndex","destroyOnClose","forceRender","initialFocus","loading","getContainer","push","testId","rest","ref","drawerRef","useRef","closeButtonRef","contentRef","previousActiveElement","titleId","useId","contentId","mounted","setMounted","useState","shouldRender","setShouldRender","isAnimating","setIsAnimating","parentDrawer","useImperativeHandle","useEffect","getSizeValue","getDimension","isHorizontal","sizeValue","w","h","getPushDistance","trapFocus","useCallback","focusableElements","firstElement","lastElement","handleKeyDown","handleTransitionEnd","focusTimeout","prevElement","handleMaskClick","placementClasses","getTransform","isOpen","pushOffset","dimension","getContainerElement","getTestId","suffix","drawerContent","jsx","jsxs","Skeleton","container","createPortal"],"mappings":"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"}