@mshafiqyajid/react-modal 0.1.0 → 0.3.0

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/styled.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var react = require('react');
4
4
  var reactDom = require('react-dom');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var client = require('react-dom/client');
6
7
 
7
8
  // src/styled/ModalStyled.tsx
8
9
  var FOCUSABLE_SELECTORS = [
@@ -64,11 +65,19 @@ function useFocusTrap() {
64
65
  }, []);
65
66
  return { activate, deactivate, handleKeyDown };
66
67
  }
68
+ var _openModals = [];
69
+ var _modalIdCounter = 0;
70
+ var _stackListeners = /* @__PURE__ */ new Set();
71
+ function _notifyStack() {
72
+ _stackListeners.forEach((fn) => fn());
73
+ }
67
74
  var ModalStyled = react.forwardRef(
68
75
  function ModalStyled2({
76
+ open,
69
77
  isOpen,
70
78
  onClose,
71
79
  title,
80
+ description,
72
81
  children,
73
82
  footer,
74
83
  size = "md",
@@ -80,83 +89,201 @@ var ModalStyled = react.forwardRef(
80
89
  overlayColor,
81
90
  padding = "md",
82
91
  scrollable = true,
83
- className
92
+ className,
93
+ initialFocusRef,
94
+ finalFocusRef,
95
+ onAfterOpen,
96
+ onAfterClose,
97
+ preventClose,
98
+ lockBodyScroll = true,
99
+ container,
100
+ transition = "fade",
101
+ swipeToDismiss,
102
+ closeOnSubmit = false
84
103
  }, ref) {
104
+ const swipeEnabled = swipeToDismiss ?? variant === "drawer-bottom";
105
+ const myStackIdRef = react.useRef(-1);
106
+ const [stackDepth, setStackDepth] = react.useState(0);
107
+ const [stackPosition, setStackPosition] = react.useState(0);
108
+ const [swipeY, setSwipeY] = react.useState(0);
85
109
  const titleId = react.useId();
110
+ const descId = react.useId();
111
+ const isActuallyOpen = open ?? isOpen ?? false;
86
112
  const [mounted, setMounted] = react.useState(false);
87
113
  const [rendered, setRendered] = react.useState(false);
88
114
  const [visible, setVisible] = react.useState(false);
89
115
  const panelRef = react.useRef(null);
90
116
  const originalOverflowRef = react.useRef("");
91
117
  const exitTimerRef = react.useRef(null);
118
+ const enterTimerRef = react.useRef(null);
92
119
  const { activate, deactivate, handleKeyDown } = useFocusTrap();
120
+ const onAfterOpenRef = react.useRef(onAfterOpen);
121
+ onAfterOpenRef.current = onAfterOpen;
122
+ const onAfterCloseRef = react.useRef(onAfterClose);
123
+ onAfterCloseRef.current = onAfterClose;
93
124
  react.useEffect(() => {
94
125
  setMounted(true);
95
126
  }, []);
127
+ const requestClose = react.useCallback(
128
+ (reason) => {
129
+ if (preventClose && !preventClose(reason)) return;
130
+ onClose();
131
+ },
132
+ [preventClose, onClose]
133
+ );
96
134
  react.useEffect(() => {
97
135
  if (exitTimerRef.current) {
98
136
  clearTimeout(exitTimerRef.current);
99
137
  exitTimerRef.current = null;
100
138
  }
101
- if (isOpen) {
139
+ if (enterTimerRef.current) {
140
+ clearTimeout(enterTimerRef.current);
141
+ enterTimerRef.current = null;
142
+ }
143
+ if (isActuallyOpen) {
102
144
  setRendered(true);
103
145
  requestAnimationFrame(() => {
104
146
  requestAnimationFrame(() => {
105
147
  setVisible(true);
106
148
  });
107
149
  });
108
- originalOverflowRef.current = document.body.style.overflow;
109
- document.body.style.overflow = "hidden";
150
+ if (lockBodyScroll) {
151
+ originalOverflowRef.current = document.body.style.overflow;
152
+ document.body.style.overflow = "hidden";
153
+ }
154
+ enterTimerRef.current = setTimeout(() => {
155
+ onAfterOpenRef.current?.();
156
+ }, 320);
110
157
  } else {
111
158
  setVisible(false);
112
159
  exitTimerRef.current = setTimeout(() => {
113
160
  setRendered(false);
161
+ onAfterCloseRef.current?.();
114
162
  }, 300);
115
- document.body.style.overflow = originalOverflowRef.current;
163
+ if (lockBodyScroll) {
164
+ document.body.style.overflow = originalOverflowRef.current;
165
+ }
116
166
  }
117
- }, [isOpen]);
167
+ }, [isActuallyOpen, lockBodyScroll]);
118
168
  react.useEffect(() => {
119
169
  return () => {
120
170
  if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
121
- document.body.style.overflow = originalOverflowRef.current;
171
+ if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
172
+ if (lockBodyScroll) {
173
+ document.body.style.overflow = originalOverflowRef.current;
174
+ }
122
175
  };
123
- }, []);
176
+ }, [lockBodyScroll]);
177
+ react.useEffect(() => {
178
+ if (!isActuallyOpen) return;
179
+ const id = ++_modalIdCounter;
180
+ myStackIdRef.current = id;
181
+ _openModals.push({ id });
182
+ _notifyStack();
183
+ const sync = () => {
184
+ const idx = _openModals.findIndex((m) => m.id === id);
185
+ const total = _openModals.length;
186
+ setStackDepth(total);
187
+ setStackPosition(idx === -1 ? 0 : total - 1 - idx);
188
+ };
189
+ sync();
190
+ _stackListeners.add(sync);
191
+ return () => {
192
+ _stackListeners.delete(sync);
193
+ const idx = _openModals.findIndex((m) => m.id === id);
194
+ if (idx !== -1) _openModals.splice(idx, 1);
195
+ _notifyStack();
196
+ };
197
+ }, [isActuallyOpen]);
198
+ react.useEffect(() => {
199
+ if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;
200
+ const panel = panelRef.current;
201
+ const onSubmit = (e) => {
202
+ if (e.defaultPrevented) return;
203
+ setTimeout(() => requestClose("submit"), 0);
204
+ };
205
+ panel.addEventListener("submit", onSubmit);
206
+ return () => panel.removeEventListener("submit", onSubmit);
207
+ }, [closeOnSubmit, isActuallyOpen, requestClose]);
208
+ const swipeStartYRef = react.useRef(null);
209
+ const handleSwipePointerDown = (e) => {
210
+ if (!swipeEnabled || e.pointerType !== "touch") return;
211
+ swipeStartYRef.current = e.clientY;
212
+ };
213
+ const handleSwipePointerMove = (e) => {
214
+ if (swipeStartYRef.current === null) return;
215
+ const dy = Math.max(0, e.clientY - swipeStartYRef.current);
216
+ setSwipeY(dy);
217
+ };
218
+ const handleSwipePointerUp = () => {
219
+ if (swipeStartYRef.current === null) return;
220
+ if (swipeY > 120) requestClose("swipe");
221
+ setSwipeY(0);
222
+ swipeStartYRef.current = null;
223
+ };
224
+ const previouslyFocusedRef = react.useRef(null);
124
225
  react.useEffect(() => {
125
- if (isOpen && panelRef.current) {
126
- activate(panelRef.current);
127
- } else if (!isOpen) {
226
+ if (isActuallyOpen) {
227
+ previouslyFocusedRef.current = document.activeElement;
228
+ if (panelRef.current) {
229
+ activate(panelRef.current);
230
+ requestAnimationFrame(() => {
231
+ if (initialFocusRef?.current) {
232
+ initialFocusRef.current.focus();
233
+ }
234
+ });
235
+ }
236
+ } else {
128
237
  deactivate();
238
+ const target = finalFocusRef?.current ?? previouslyFocusedRef.current;
239
+ if (target && typeof target.focus === "function") {
240
+ target.focus();
241
+ }
129
242
  }
130
- }, [isOpen, activate, deactivate]);
243
+ }, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);
131
244
  const onKeyDown = react.useCallback(
132
245
  (e) => {
133
246
  if (closeOnEsc && e.key === "Escape") {
134
- onClose();
247
+ requestClose("esc");
135
248
  return;
136
249
  }
137
250
  handleKeyDown(e.nativeEvent);
138
251
  },
139
- [closeOnEsc, onClose, handleKeyDown]
252
+ [closeOnEsc, requestClose, handleKeyDown]
140
253
  );
141
254
  const handleOverlayClick = react.useCallback(
142
255
  (e) => {
143
256
  if (closeOnOverlayClick && e.target === e.currentTarget) {
144
- onClose();
257
+ requestClose("overlay");
145
258
  }
146
259
  },
147
- [closeOnOverlayClick, onClose]
260
+ [closeOnOverlayClick, requestClose]
148
261
  );
149
262
  const setPanelRef = react.useCallback(
150
263
  (el) => {
151
264
  panelRef.current = el;
152
265
  if (typeof ref === "function") ref(el);
153
266
  else if (ref) ref.current = el;
154
- if (el && isOpen) activate(el);
267
+ if (el && isActuallyOpen) activate(el);
155
268
  },
156
- [ref, isOpen, activate]
269
+ [ref, isActuallyOpen, activate]
157
270
  );
158
271
  if (!mounted || !rendered) return null;
159
272
  const hasHeader = title !== void 0 || showCloseButton;
273
+ const portalTarget = container ?? document.body;
274
+ const overlayStyle = {
275
+ ...overlayColor ? { "--rmod-overlay-bg": overlayColor } : {},
276
+ // Higher stack depth → lower modals scale down + fade slightly behind the topmost.
277
+ zIndex: 1e3 + stackDepth * 10
278
+ };
279
+ const panelStyle = {
280
+ // Stack-position effects: only applied when this isn't the topmost modal.
281
+ ...stackPosition > 0 ? {
282
+ transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,
283
+ opacity: Math.max(0.6, 1 - stackPosition * 0.18)
284
+ } : {},
285
+ ...swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: "none" } : {}
286
+ };
160
287
  return reactDom.createPortal(
161
288
  /* @__PURE__ */ jsxRuntime.jsx(
162
289
  "div",
@@ -167,7 +294,10 @@ var ModalStyled = react.forwardRef(
167
294
  ].filter(Boolean).join(" "),
168
295
  "data-variant": variant,
169
296
  "data-blur": blur,
170
- style: overlayColor ? { "--rmod-overlay-bg": overlayColor } : void 0,
297
+ "data-transition": transition,
298
+ "data-stack-position": stackPosition || void 0,
299
+ "data-state": visible ? "open" : "closed",
300
+ style: overlayStyle,
171
301
  onClick: handleOverlayClick,
172
302
  onKeyDown,
173
303
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -177,6 +307,7 @@ var ModalStyled = react.forwardRef(
177
307
  role: "dialog",
178
308
  "aria-modal": "true",
179
309
  "aria-labelledby": title ? titleId : void 0,
310
+ "aria-describedby": description ? descId : void 0,
180
311
  tabIndex: -1,
181
312
  className: [
182
313
  "rmod-panel",
@@ -187,11 +318,29 @@ var ModalStyled = react.forwardRef(
187
318
  "data-variant": variant,
188
319
  "data-padding": padding,
189
320
  "data-scrollable": scrollable ? "true" : void 0,
321
+ "data-transition": transition,
322
+ "data-state": visible ? "open" : "closed",
323
+ "data-swiping": swipeY > 0 ? "true" : void 0,
324
+ style: panelStyle,
325
+ onPointerDown: swipeEnabled ? handleSwipePointerDown : void 0,
326
+ onPointerMove: swipeEnabled ? handleSwipePointerMove : void 0,
327
+ onPointerUp: swipeEnabled ? handleSwipePointerUp : void 0,
328
+ onPointerCancel: swipeEnabled ? handleSwipePointerUp : void 0,
190
329
  children: [
191
330
  hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rmod-header", children: [
192
331
  title ? /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
193
- showCloseButton && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "rmod-close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }) }) })
332
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
333
+ "button",
334
+ {
335
+ type: "button",
336
+ className: "rmod-close",
337
+ "aria-label": "Close",
338
+ onClick: () => requestClose("close-button"),
339
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }) })
340
+ }
341
+ )
194
342
  ] }),
343
+ description !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "rmod-description", children: description }),
195
344
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-body", children }),
196
345
  footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-footer", children: footer })
197
346
  ]
@@ -199,11 +348,74 @@ var ModalStyled = react.forwardRef(
199
348
  )
200
349
  }
201
350
  ),
202
- document.body
351
+ portalTarget
203
352
  );
204
353
  }
205
354
  );
355
+ function confirm(opts = {}) {
356
+ return new Promise((resolve) => {
357
+ if (typeof window === "undefined" || typeof document === "undefined") {
358
+ resolve(false);
359
+ return;
360
+ }
361
+ const host = document.createElement("div");
362
+ document.body.appendChild(host);
363
+ const root = client.createRoot(host);
364
+ const cleanup = () => {
365
+ setTimeout(() => {
366
+ try {
367
+ root.unmount();
368
+ } catch {
369
+ }
370
+ if (host.parentNode) host.parentNode.removeChild(host);
371
+ }, 320);
372
+ };
373
+ function ConfirmShell() {
374
+ const [open, setOpen] = react.useState(true);
375
+ const finish = (value) => {
376
+ setOpen(false);
377
+ cleanup();
378
+ resolve(value);
379
+ };
380
+ return /* @__PURE__ */ jsxRuntime.jsx(
381
+ ModalStyled,
382
+ {
383
+ open,
384
+ onClose: () => finish(false),
385
+ title: opts.title ?? "Are you sure?",
386
+ size: "sm",
387
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
388
+ /* @__PURE__ */ jsxRuntime.jsx(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ className: "rmod-btn rmod-btn--cancel",
393
+ onClick: () => finish(false),
394
+ children: opts.cancelLabel ?? "Cancel"
395
+ }
396
+ ),
397
+ /* @__PURE__ */ jsxRuntime.jsx(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ className: `rmod-btn rmod-btn--confirm${opts.danger ? " rmod-btn--danger" : ""}`,
402
+ onClick: () => finish(true),
403
+ autoFocus: true,
404
+ children: opts.confirmLabel ?? "Confirm"
405
+ }
406
+ )
407
+ ] }),
408
+ children: opts.body ?? null
409
+ }
410
+ );
411
+ }
412
+ root.render(/* @__PURE__ */ jsxRuntime.jsx(ConfirmShell, {}));
413
+ });
414
+ }
415
+ var modal = { confirm };
206
416
 
207
417
  exports.ModalStyled = ModalStyled;
418
+ exports.confirm = confirm;
419
+ exports.modal = modal;
208
420
  //# sourceMappingURL=styled.cjs.map
209
421
  //# sourceMappingURL=styled.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useFocusTrap.ts","../src/styled/ModalStyled.tsx"],"names":["useRef","useCallback","forwardRef","ModalStyled","useId","useState","useEffect","createPortal","jsx","jsxs"],"mappings":"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","file":"styled.cjs","sourcesContent":["import { useCallback, useRef } from \"react\";\n\nconst FOCUSABLE_SELECTORS = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"details > summary\",\n].join(\", \");\n\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\n (el) => !el.closest(\"[inert]\") && getComputedStyle(el).display !== \"none\",\n );\n}\n\nexport interface UseFocusTrapResult {\n activate: (container: HTMLElement) => void;\n deactivate: () => void;\n handleKeyDown: (e: KeyboardEvent) => void;\n}\n\nexport function useFocusTrap(): UseFocusTrapResult {\n const containerRef = useRef<HTMLElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n\n const activate = useCallback((container: HTMLElement) => {\n containerRef.current = container;\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n\n const focusable = getFocusableElements(container);\n const first = focusable[0];\n if (first) {\n first.focus();\n } else {\n container.focus();\n }\n }, []);\n\n const deactivate = useCallback(() => {\n containerRef.current = null;\n const prev = previousFocusRef.current;\n if (prev && typeof prev.focus === \"function\") {\n prev.focus();\n }\n previousFocusRef.current = null;\n }, []);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key !== \"Tab\" || !containerRef.current) return;\n\n const focusable = getFocusableElements(containerRef.current);\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }, []);\n\n return { activate, deactivate, handleKeyDown };\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\n\nexport interface ModalStyledProps {\n isOpen: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n isOpen,\n onClose,\n title,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n },\n ref,\n ) {\n const titleId = useId();\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n useEffect(() => { setMounted(true); }, []);\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n\n if (isOpen) {\n setRendered(true);\n // Double RAF ensures the element is in the DOM before we add the visible class\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n } else {\n // Remove visible class first (triggers CSS exit transition)\n setVisible(false);\n // Then unmount after transition finishes\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n }, 300);\n document.body.style.overflow = originalOverflowRef.current;\n }\n }, [isOpen]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n document.body.style.overflow = originalOverflowRef.current;\n };\n }, []);\n\n useEffect(() => {\n if (isOpen && panelRef.current) {\n activate(panelRef.current);\n } else if (!isOpen) {\n deactivate();\n }\n }, [isOpen, activate, deactivate]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n onClose();\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, onClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n },\n [closeOnOverlayClick, onClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isOpen) activate(el);\n },\n [ref, isOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n style={overlayColor ? { \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties : undefined}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button type=\"button\" className=\"rmod-close\" aria-label=\"Close\" onClick={onClose}>\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n document.body,\n );\n },\n);\n"]}
1
+ {"version":3,"sources":["../src/useFocusTrap.ts","../src/styled/ModalStyled.tsx","../src/styled/confirm.tsx"],"names":["useRef","useCallback","forwardRef","ModalStyled","useState","useId","useEffect","createPortal","jsx","jsxs","createRoot"],"mappings":"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","file":"styled.cjs","sourcesContent":["import { useCallback, useRef } from \"react\";\n\nconst FOCUSABLE_SELECTORS = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"details > summary\",\n].join(\", \");\n\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\n (el) => !el.closest(\"[inert]\") && getComputedStyle(el).display !== \"none\",\n );\n}\n\nexport interface UseFocusTrapResult {\n activate: (container: HTMLElement) => void;\n deactivate: () => void;\n handleKeyDown: (e: KeyboardEvent) => void;\n}\n\nexport function useFocusTrap(): UseFocusTrapResult {\n const containerRef = useRef<HTMLElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n\n const activate = useCallback((container: HTMLElement) => {\n containerRef.current = container;\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n\n const focusable = getFocusableElements(container);\n const first = focusable[0];\n if (first) {\n first.focus();\n } else {\n container.focus();\n }\n }, []);\n\n const deactivate = useCallback(() => {\n containerRef.current = null;\n const prev = previousFocusRef.current;\n if (prev && typeof prev.focus === \"function\") {\n prev.focus();\n }\n previousFocusRef.current = null;\n }, []);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key !== \"Tab\" || !containerRef.current) return;\n\n const focusable = getFocusableElements(containerRef.current);\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }, []);\n\n return { activate, deactivate, handleKeyDown };\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\nexport type CloseReason = \"esc\" | \"overlay\" | \"close-button\" | \"programmatic\" | \"swipe\" | \"submit\";\nexport type ModalTransition = \"fade\" | \"zoom\" | \"slide-up\" | \"slide-down\";\n\n// ---- Stacking registry (depth-aware z-index + scale-behind effect) -------\nconst _openModals: { id: number }[] = [];\nlet _modalIdCounter = 0;\nconst _stackListeners = new Set<() => void>();\nfunction _notifyStack() { _stackListeners.forEach((fn) => fn()); }\n\nexport interface ModalStyledProps {\n /** Open state. `open` is the canonical name; `isOpen` continues to work. */\n open?: boolean;\n isOpen?: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n /** Optional accessible description. Renders below the title and links via aria-describedby. */\n description?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n /** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */\n initialFocusRef?: RefObject<HTMLElement | null>;\n /** Element to focus when the modal closes. Defaults to whatever was focused before opening. */\n finalFocusRef?: RefObject<HTMLElement | null>;\n /** Fires after the open transition completes. */\n onAfterOpen?: () => void;\n /** Fires after the close transition completes (and unmount). */\n onAfterClose?: () => void;\n /** Return false to veto a close attempt. Receives the trigger reason. */\n preventClose?: (reason: CloseReason) => boolean;\n /** Disable body scroll lock while open. Default: true (locked). */\n lockBodyScroll?: boolean;\n /** Override the portal container. Default: document.body. */\n container?: HTMLElement | null;\n /** Transition variant for the panel (only `dialog`; drawers always slide). Default: \"fade\" */\n transition?: ModalTransition;\n /** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */\n swipeToDismiss?: boolean;\n /** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */\n closeOnSubmit?: boolean;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n open,\n isOpen,\n onClose,\n title,\n description,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n initialFocusRef,\n finalFocusRef,\n onAfterOpen,\n onAfterClose,\n preventClose,\n lockBodyScroll = true,\n container,\n transition = \"fade\",\n swipeToDismiss,\n closeOnSubmit = false,\n },\n ref,\n ) {\n const swipeEnabled = swipeToDismiss ?? variant === \"drawer-bottom\";\n const myStackIdRef = useRef<number>(-1);\n const [stackDepth, setStackDepth] = useState(0);\n const [stackPosition, setStackPosition] = useState(0); // 0 = top\n const [swipeY, setSwipeY] = useState(0);\n const titleId = useId();\n const descId = useId();\n const isActuallyOpen = open ?? isOpen ?? false;\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n const onAfterOpenRef = useRef(onAfterOpen);\n onAfterOpenRef.current = onAfterOpen;\n const onAfterCloseRef = useRef(onAfterClose);\n onAfterCloseRef.current = onAfterClose;\n\n useEffect(() => { setMounted(true); }, []);\n\n const requestClose = useCallback(\n (reason: CloseReason) => {\n if (preventClose && !preventClose(reason)) return;\n onClose();\n },\n [preventClose, onClose],\n );\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n if (enterTimerRef.current) {\n clearTimeout(enterTimerRef.current);\n enterTimerRef.current = null;\n }\n\n if (isActuallyOpen) {\n setRendered(true);\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n if (lockBodyScroll) {\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n // Fire onAfterOpen once the enter transition has had time to settle.\n enterTimerRef.current = setTimeout(() => {\n onAfterOpenRef.current?.();\n }, 320);\n } else {\n setVisible(false);\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n onAfterCloseRef.current?.();\n }, 300);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n }\n }, [isActuallyOpen, lockBodyScroll]);\n\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n if (enterTimerRef.current) clearTimeout(enterTimerRef.current);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n };\n }, [lockBodyScroll]);\n\n // ---- Stacking: register / deregister this modal ---------------------\n useEffect(() => {\n if (!isActuallyOpen) return;\n const id = ++_modalIdCounter;\n myStackIdRef.current = id;\n _openModals.push({ id });\n _notifyStack();\n const sync = () => {\n const idx = _openModals.findIndex((m) => m.id === id);\n const total = _openModals.length;\n setStackDepth(total);\n setStackPosition(idx === -1 ? 0 : total - 1 - idx);\n };\n sync();\n _stackListeners.add(sync);\n return () => {\n _stackListeners.delete(sync);\n const idx = _openModals.findIndex((m) => m.id === id);\n if (idx !== -1) _openModals.splice(idx, 1);\n _notifyStack();\n };\n }, [isActuallyOpen]);\n\n // ---- closeOnSubmit: auto-close on a contained <form> submit ---------\n useEffect(() => {\n if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;\n const panel = panelRef.current;\n const onSubmit = (e: Event) => {\n // Defer the close so the consumer's onSubmit handler runs first\n // and can call e.preventDefault() if it wants to abort the close.\n if (e.defaultPrevented) return;\n setTimeout(() => requestClose(\"submit\"), 0);\n };\n panel.addEventListener(\"submit\", onSubmit);\n return () => panel.removeEventListener(\"submit\", onSubmit);\n }, [closeOnSubmit, isActuallyOpen, requestClose]);\n\n // ---- Swipe-to-dismiss (touch only) -----------------------------------\n const swipeStartYRef = useRef<number | null>(null);\n const handleSwipePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!swipeEnabled || e.pointerType !== \"touch\") return;\n // Only initiate from the panel header / the panel itself, not buttons.\n swipeStartYRef.current = e.clientY;\n };\n const handleSwipePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (swipeStartYRef.current === null) return;\n const dy = Math.max(0, e.clientY - swipeStartYRef.current);\n setSwipeY(dy);\n };\n const handleSwipePointerUp = () => {\n if (swipeStartYRef.current === null) return;\n if (swipeY > 120) requestClose(\"swipe\");\n setSwipeY(0);\n swipeStartYRef.current = null;\n };\n\n // Focus management\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n useEffect(() => {\n if (isActuallyOpen) {\n previouslyFocusedRef.current = document.activeElement as HTMLElement | null;\n if (panelRef.current) {\n activate(panelRef.current);\n // Honor initialFocusRef after the panel is mounted+visible\n requestAnimationFrame(() => {\n if (initialFocusRef?.current) {\n initialFocusRef.current.focus();\n }\n });\n }\n } else {\n deactivate();\n // Honor finalFocusRef on close, otherwise restore previous focus\n const target = finalFocusRef?.current ?? previouslyFocusedRef.current;\n if (target && typeof target.focus === \"function\") {\n target.focus();\n }\n }\n }, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n requestClose(\"esc\");\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, requestClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n requestClose(\"overlay\");\n }\n },\n [closeOnOverlayClick, requestClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isActuallyOpen) activate(el);\n },\n [ref, isActuallyOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n const portalTarget = container ?? document.body;\n\n const overlayStyle: React.CSSProperties = {\n ...(overlayColor ? ({ \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties) : {}),\n // Higher stack depth → lower modals scale down + fade slightly behind the topmost.\n zIndex: 1000 + stackDepth * 10,\n };\n\n const panelStyle: React.CSSProperties = {\n // Stack-position effects: only applied when this isn't the topmost modal.\n ...(stackPosition > 0\n ? {\n transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,\n opacity: Math.max(0.6, 1 - stackPosition * 0.18),\n }\n : {}),\n ...(swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: \"none\" } : {}),\n };\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n data-transition={transition}\n data-stack-position={stackPosition || undefined}\n data-state={visible ? \"open\" : \"closed\"}\n style={overlayStyle}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={description ? descId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n data-transition={transition}\n data-state={visible ? \"open\" : \"closed\"}\n data-swiping={swipeY > 0 ? \"true\" : undefined}\n style={panelStyle}\n onPointerDown={swipeEnabled ? handleSwipePointerDown : undefined}\n onPointerMove={swipeEnabled ? handleSwipePointerMove : undefined}\n onPointerUp={swipeEnabled ? handleSwipePointerUp : undefined}\n onPointerCancel={swipeEnabled ? handleSwipePointerUp : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button\n type=\"button\"\n className=\"rmod-close\"\n aria-label=\"Close\"\n onClick={() => requestClose(\"close-button\")}\n >\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n {description !== undefined && (\n <p id={descId} className=\"rmod-description\">{description}</p>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n portalTarget,\n );\n },\n);\n","import { useState, type ReactNode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { ModalStyled } from \"./ModalStyled\";\n\nexport interface ConfirmOptions {\n title?: ReactNode;\n body?: ReactNode;\n confirmLabel?: ReactNode;\n cancelLabel?: ReactNode;\n /** Style the confirm button as the danger CTA (red). */\n danger?: boolean;\n}\n\n/**\n * Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled\n * or dismissed via overlay/Esc/close button.\n *\n * Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`\n * and unmounts on close — no provider required.\n */\nexport function confirm(opts: ConfirmOptions = {}): Promise<boolean> {\n return new Promise((resolve) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n resolve(false);\n return;\n }\n const host = document.createElement(\"div\");\n document.body.appendChild(host);\n const root = createRoot(host);\n\n const cleanup = () => {\n // Defer unmount so the close transition can run.\n setTimeout(() => {\n try { root.unmount(); } catch { /* ignore */ }\n if (host.parentNode) host.parentNode.removeChild(host);\n }, 320);\n };\n\n function ConfirmShell() {\n const [open, setOpen] = useState(true);\n const finish = (value: boolean) => {\n setOpen(false);\n cleanup();\n resolve(value);\n };\n return (\n <ModalStyled\n open={open}\n onClose={() => finish(false)}\n title={opts.title ?? \"Are you sure?\"}\n size=\"sm\"\n footer={\n <div style={{ display: \"flex\", gap: 8, justifyContent: \"flex-end\" }}>\n <button\n type=\"button\"\n className=\"rmod-btn rmod-btn--cancel\"\n onClick={() => finish(false)}\n >\n {opts.cancelLabel ?? \"Cancel\"}\n </button>\n <button\n type=\"button\"\n className={`rmod-btn rmod-btn--confirm${opts.danger ? \" rmod-btn--danger\" : \"\"}`}\n onClick={() => finish(true)}\n autoFocus\n >\n {opts.confirmLabel ?? \"Confirm\"}\n </button>\n </div>\n }\n >\n {opts.body ?? null}\n </ModalStyled>\n );\n }\n\n root.render(<ConfirmShell />);\n });\n}\n\n/** Module-style export so consumers can do `modal.confirm(...)`. */\nexport const modal = { confirm };\n"]}
package/dist/styled.d.cts CHANGED
@@ -1,13 +1,19 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
3
 
4
4
  type ModalSize = "sm" | "md" | "lg" | "full";
5
5
  type ModalVariant = "dialog" | "drawer-left" | "drawer-right" | "drawer-bottom";
6
+ type CloseReason = "esc" | "overlay" | "close-button" | "programmatic" | "swipe" | "submit";
7
+ type ModalTransition = "fade" | "zoom" | "slide-up" | "slide-down";
6
8
  interface ModalStyledProps {
7
- isOpen: boolean;
9
+ /** Open state. `open` is the canonical name; `isOpen` continues to work. */
10
+ open?: boolean;
11
+ isOpen?: boolean;
8
12
  onClose: () => void;
9
13
  /** Modal heading — omit to hide the header entirely */
10
14
  title?: ReactNode;
15
+ /** Optional accessible description. Renders below the title and links via aria-describedby. */
16
+ description?: ReactNode;
11
17
  children: ReactNode;
12
18
  /** Footer content — omit to hide footer */
13
19
  footer?: ReactNode;
@@ -25,7 +31,48 @@ interface ModalStyledProps {
25
31
  /** Max height of scrollable body. Default: auto */
26
32
  scrollable?: boolean;
27
33
  className?: string;
34
+ /** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */
35
+ initialFocusRef?: RefObject<HTMLElement | null>;
36
+ /** Element to focus when the modal closes. Defaults to whatever was focused before opening. */
37
+ finalFocusRef?: RefObject<HTMLElement | null>;
38
+ /** Fires after the open transition completes. */
39
+ onAfterOpen?: () => void;
40
+ /** Fires after the close transition completes (and unmount). */
41
+ onAfterClose?: () => void;
42
+ /** Return false to veto a close attempt. Receives the trigger reason. */
43
+ preventClose?: (reason: CloseReason) => boolean;
44
+ /** Disable body scroll lock while open. Default: true (locked). */
45
+ lockBodyScroll?: boolean;
46
+ /** Override the portal container. Default: document.body. */
47
+ container?: HTMLElement | null;
48
+ /** Transition variant for the panel (only `dialog`; drawers always slide). Default: "fade" */
49
+ transition?: ModalTransition;
50
+ /** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */
51
+ swipeToDismiss?: boolean;
52
+ /** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */
53
+ closeOnSubmit?: boolean;
28
54
  }
29
55
  declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
30
56
 
31
- export { type ModalSize, ModalStyled, type ModalStyledProps, type ModalVariant };
57
+ interface ConfirmOptions {
58
+ title?: ReactNode;
59
+ body?: ReactNode;
60
+ confirmLabel?: ReactNode;
61
+ cancelLabel?: ReactNode;
62
+ /** Style the confirm button as the danger CTA (red). */
63
+ danger?: boolean;
64
+ }
65
+ /**
66
+ * Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled
67
+ * or dismissed via overlay/Esc/close button.
68
+ *
69
+ * Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`
70
+ * and unmounts on close — no provider required.
71
+ */
72
+ declare function confirm(opts?: ConfirmOptions): Promise<boolean>;
73
+ /** Module-style export so consumers can do `modal.confirm(...)`. */
74
+ declare const modal: {
75
+ confirm: typeof confirm;
76
+ };
77
+
78
+ export { type ConfirmOptions, type ModalSize, ModalStyled, type ModalStyledProps, type ModalTransition, type ModalVariant, confirm, modal };
package/dist/styled.d.ts CHANGED
@@ -1,13 +1,19 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
3
 
4
4
  type ModalSize = "sm" | "md" | "lg" | "full";
5
5
  type ModalVariant = "dialog" | "drawer-left" | "drawer-right" | "drawer-bottom";
6
+ type CloseReason = "esc" | "overlay" | "close-button" | "programmatic" | "swipe" | "submit";
7
+ type ModalTransition = "fade" | "zoom" | "slide-up" | "slide-down";
6
8
  interface ModalStyledProps {
7
- isOpen: boolean;
9
+ /** Open state. `open` is the canonical name; `isOpen` continues to work. */
10
+ open?: boolean;
11
+ isOpen?: boolean;
8
12
  onClose: () => void;
9
13
  /** Modal heading — omit to hide the header entirely */
10
14
  title?: ReactNode;
15
+ /** Optional accessible description. Renders below the title and links via aria-describedby. */
16
+ description?: ReactNode;
11
17
  children: ReactNode;
12
18
  /** Footer content — omit to hide footer */
13
19
  footer?: ReactNode;
@@ -25,7 +31,48 @@ interface ModalStyledProps {
25
31
  /** Max height of scrollable body. Default: auto */
26
32
  scrollable?: boolean;
27
33
  className?: string;
34
+ /** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */
35
+ initialFocusRef?: RefObject<HTMLElement | null>;
36
+ /** Element to focus when the modal closes. Defaults to whatever was focused before opening. */
37
+ finalFocusRef?: RefObject<HTMLElement | null>;
38
+ /** Fires after the open transition completes. */
39
+ onAfterOpen?: () => void;
40
+ /** Fires after the close transition completes (and unmount). */
41
+ onAfterClose?: () => void;
42
+ /** Return false to veto a close attempt. Receives the trigger reason. */
43
+ preventClose?: (reason: CloseReason) => boolean;
44
+ /** Disable body scroll lock while open. Default: true (locked). */
45
+ lockBodyScroll?: boolean;
46
+ /** Override the portal container. Default: document.body. */
47
+ container?: HTMLElement | null;
48
+ /** Transition variant for the panel (only `dialog`; drawers always slide). Default: "fade" */
49
+ transition?: ModalTransition;
50
+ /** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */
51
+ swipeToDismiss?: boolean;
52
+ /** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */
53
+ closeOnSubmit?: boolean;
28
54
  }
29
55
  declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
30
56
 
31
- export { type ModalSize, ModalStyled, type ModalStyledProps, type ModalVariant };
57
+ interface ConfirmOptions {
58
+ title?: ReactNode;
59
+ body?: ReactNode;
60
+ confirmLabel?: ReactNode;
61
+ cancelLabel?: ReactNode;
62
+ /** Style the confirm button as the danger CTA (red). */
63
+ danger?: boolean;
64
+ }
65
+ /**
66
+ * Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled
67
+ * or dismissed via overlay/Esc/close button.
68
+ *
69
+ * Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`
70
+ * and unmounts on close — no provider required.
71
+ */
72
+ declare function confirm(opts?: ConfirmOptions): Promise<boolean>;
73
+ /** Module-style export so consumers can do `modal.confirm(...)`. */
74
+ declare const modal: {
75
+ confirm: typeof confirm;
76
+ };
77
+
78
+ export { type ConfirmOptions, type ModalSize, ModalStyled, type ModalStyledProps, type ModalTransition, type ModalVariant, confirm, modal };
package/dist/styled.js CHANGED
@@ -1,13 +1,22 @@
1
1
  import { useFocusTrap } from './chunk-BFH4N5GR.js';
2
- import { forwardRef, useId, useState, useRef, useEffect, useCallback } from 'react';
2
+ import { forwardRef, useRef, useState, useId, useEffect, useCallback } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { createRoot } from 'react-dom/client';
5
6
 
7
+ var _openModals = [];
8
+ var _modalIdCounter = 0;
9
+ var _stackListeners = /* @__PURE__ */ new Set();
10
+ function _notifyStack() {
11
+ _stackListeners.forEach((fn) => fn());
12
+ }
6
13
  var ModalStyled = forwardRef(
7
14
  function ModalStyled2({
15
+ open,
8
16
  isOpen,
9
17
  onClose,
10
18
  title,
19
+ description,
11
20
  children,
12
21
  footer,
13
22
  size = "md",
@@ -19,83 +28,201 @@ var ModalStyled = forwardRef(
19
28
  overlayColor,
20
29
  padding = "md",
21
30
  scrollable = true,
22
- className
31
+ className,
32
+ initialFocusRef,
33
+ finalFocusRef,
34
+ onAfterOpen,
35
+ onAfterClose,
36
+ preventClose,
37
+ lockBodyScroll = true,
38
+ container,
39
+ transition = "fade",
40
+ swipeToDismiss,
41
+ closeOnSubmit = false
23
42
  }, ref) {
43
+ const swipeEnabled = swipeToDismiss ?? variant === "drawer-bottom";
44
+ const myStackIdRef = useRef(-1);
45
+ const [stackDepth, setStackDepth] = useState(0);
46
+ const [stackPosition, setStackPosition] = useState(0);
47
+ const [swipeY, setSwipeY] = useState(0);
24
48
  const titleId = useId();
49
+ const descId = useId();
50
+ const isActuallyOpen = open ?? isOpen ?? false;
25
51
  const [mounted, setMounted] = useState(false);
26
52
  const [rendered, setRendered] = useState(false);
27
53
  const [visible, setVisible] = useState(false);
28
54
  const panelRef = useRef(null);
29
55
  const originalOverflowRef = useRef("");
30
56
  const exitTimerRef = useRef(null);
57
+ const enterTimerRef = useRef(null);
31
58
  const { activate, deactivate, handleKeyDown } = useFocusTrap();
59
+ const onAfterOpenRef = useRef(onAfterOpen);
60
+ onAfterOpenRef.current = onAfterOpen;
61
+ const onAfterCloseRef = useRef(onAfterClose);
62
+ onAfterCloseRef.current = onAfterClose;
32
63
  useEffect(() => {
33
64
  setMounted(true);
34
65
  }, []);
66
+ const requestClose = useCallback(
67
+ (reason) => {
68
+ if (preventClose && !preventClose(reason)) return;
69
+ onClose();
70
+ },
71
+ [preventClose, onClose]
72
+ );
35
73
  useEffect(() => {
36
74
  if (exitTimerRef.current) {
37
75
  clearTimeout(exitTimerRef.current);
38
76
  exitTimerRef.current = null;
39
77
  }
40
- if (isOpen) {
78
+ if (enterTimerRef.current) {
79
+ clearTimeout(enterTimerRef.current);
80
+ enterTimerRef.current = null;
81
+ }
82
+ if (isActuallyOpen) {
41
83
  setRendered(true);
42
84
  requestAnimationFrame(() => {
43
85
  requestAnimationFrame(() => {
44
86
  setVisible(true);
45
87
  });
46
88
  });
47
- originalOverflowRef.current = document.body.style.overflow;
48
- document.body.style.overflow = "hidden";
89
+ if (lockBodyScroll) {
90
+ originalOverflowRef.current = document.body.style.overflow;
91
+ document.body.style.overflow = "hidden";
92
+ }
93
+ enterTimerRef.current = setTimeout(() => {
94
+ onAfterOpenRef.current?.();
95
+ }, 320);
49
96
  } else {
50
97
  setVisible(false);
51
98
  exitTimerRef.current = setTimeout(() => {
52
99
  setRendered(false);
100
+ onAfterCloseRef.current?.();
53
101
  }, 300);
54
- document.body.style.overflow = originalOverflowRef.current;
102
+ if (lockBodyScroll) {
103
+ document.body.style.overflow = originalOverflowRef.current;
104
+ }
55
105
  }
56
- }, [isOpen]);
106
+ }, [isActuallyOpen, lockBodyScroll]);
57
107
  useEffect(() => {
58
108
  return () => {
59
109
  if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
60
- document.body.style.overflow = originalOverflowRef.current;
110
+ if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
111
+ if (lockBodyScroll) {
112
+ document.body.style.overflow = originalOverflowRef.current;
113
+ }
61
114
  };
62
- }, []);
115
+ }, [lockBodyScroll]);
116
+ useEffect(() => {
117
+ if (!isActuallyOpen) return;
118
+ const id = ++_modalIdCounter;
119
+ myStackIdRef.current = id;
120
+ _openModals.push({ id });
121
+ _notifyStack();
122
+ const sync = () => {
123
+ const idx = _openModals.findIndex((m) => m.id === id);
124
+ const total = _openModals.length;
125
+ setStackDepth(total);
126
+ setStackPosition(idx === -1 ? 0 : total - 1 - idx);
127
+ };
128
+ sync();
129
+ _stackListeners.add(sync);
130
+ return () => {
131
+ _stackListeners.delete(sync);
132
+ const idx = _openModals.findIndex((m) => m.id === id);
133
+ if (idx !== -1) _openModals.splice(idx, 1);
134
+ _notifyStack();
135
+ };
136
+ }, [isActuallyOpen]);
63
137
  useEffect(() => {
64
- if (isOpen && panelRef.current) {
65
- activate(panelRef.current);
66
- } else if (!isOpen) {
138
+ if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;
139
+ const panel = panelRef.current;
140
+ const onSubmit = (e) => {
141
+ if (e.defaultPrevented) return;
142
+ setTimeout(() => requestClose("submit"), 0);
143
+ };
144
+ panel.addEventListener("submit", onSubmit);
145
+ return () => panel.removeEventListener("submit", onSubmit);
146
+ }, [closeOnSubmit, isActuallyOpen, requestClose]);
147
+ const swipeStartYRef = useRef(null);
148
+ const handleSwipePointerDown = (e) => {
149
+ if (!swipeEnabled || e.pointerType !== "touch") return;
150
+ swipeStartYRef.current = e.clientY;
151
+ };
152
+ const handleSwipePointerMove = (e) => {
153
+ if (swipeStartYRef.current === null) return;
154
+ const dy = Math.max(0, e.clientY - swipeStartYRef.current);
155
+ setSwipeY(dy);
156
+ };
157
+ const handleSwipePointerUp = () => {
158
+ if (swipeStartYRef.current === null) return;
159
+ if (swipeY > 120) requestClose("swipe");
160
+ setSwipeY(0);
161
+ swipeStartYRef.current = null;
162
+ };
163
+ const previouslyFocusedRef = useRef(null);
164
+ useEffect(() => {
165
+ if (isActuallyOpen) {
166
+ previouslyFocusedRef.current = document.activeElement;
167
+ if (panelRef.current) {
168
+ activate(panelRef.current);
169
+ requestAnimationFrame(() => {
170
+ if (initialFocusRef?.current) {
171
+ initialFocusRef.current.focus();
172
+ }
173
+ });
174
+ }
175
+ } else {
67
176
  deactivate();
177
+ const target = finalFocusRef?.current ?? previouslyFocusedRef.current;
178
+ if (target && typeof target.focus === "function") {
179
+ target.focus();
180
+ }
68
181
  }
69
- }, [isOpen, activate, deactivate]);
182
+ }, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);
70
183
  const onKeyDown = useCallback(
71
184
  (e) => {
72
185
  if (closeOnEsc && e.key === "Escape") {
73
- onClose();
186
+ requestClose("esc");
74
187
  return;
75
188
  }
76
189
  handleKeyDown(e.nativeEvent);
77
190
  },
78
- [closeOnEsc, onClose, handleKeyDown]
191
+ [closeOnEsc, requestClose, handleKeyDown]
79
192
  );
80
193
  const handleOverlayClick = useCallback(
81
194
  (e) => {
82
195
  if (closeOnOverlayClick && e.target === e.currentTarget) {
83
- onClose();
196
+ requestClose("overlay");
84
197
  }
85
198
  },
86
- [closeOnOverlayClick, onClose]
199
+ [closeOnOverlayClick, requestClose]
87
200
  );
88
201
  const setPanelRef = useCallback(
89
202
  (el) => {
90
203
  panelRef.current = el;
91
204
  if (typeof ref === "function") ref(el);
92
205
  else if (ref) ref.current = el;
93
- if (el && isOpen) activate(el);
206
+ if (el && isActuallyOpen) activate(el);
94
207
  },
95
- [ref, isOpen, activate]
208
+ [ref, isActuallyOpen, activate]
96
209
  );
97
210
  if (!mounted || !rendered) return null;
98
211
  const hasHeader = title !== void 0 || showCloseButton;
212
+ const portalTarget = container ?? document.body;
213
+ const overlayStyle = {
214
+ ...overlayColor ? { "--rmod-overlay-bg": overlayColor } : {},
215
+ // Higher stack depth → lower modals scale down + fade slightly behind the topmost.
216
+ zIndex: 1e3 + stackDepth * 10
217
+ };
218
+ const panelStyle = {
219
+ // Stack-position effects: only applied when this isn't the topmost modal.
220
+ ...stackPosition > 0 ? {
221
+ transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,
222
+ opacity: Math.max(0.6, 1 - stackPosition * 0.18)
223
+ } : {},
224
+ ...swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: "none" } : {}
225
+ };
99
226
  return createPortal(
100
227
  /* @__PURE__ */ jsx(
101
228
  "div",
@@ -106,7 +233,10 @@ var ModalStyled = forwardRef(
106
233
  ].filter(Boolean).join(" "),
107
234
  "data-variant": variant,
108
235
  "data-blur": blur,
109
- style: overlayColor ? { "--rmod-overlay-bg": overlayColor } : void 0,
236
+ "data-transition": transition,
237
+ "data-stack-position": stackPosition || void 0,
238
+ "data-state": visible ? "open" : "closed",
239
+ style: overlayStyle,
110
240
  onClick: handleOverlayClick,
111
241
  onKeyDown,
112
242
  children: /* @__PURE__ */ jsxs(
@@ -116,6 +246,7 @@ var ModalStyled = forwardRef(
116
246
  role: "dialog",
117
247
  "aria-modal": "true",
118
248
  "aria-labelledby": title ? titleId : void 0,
249
+ "aria-describedby": description ? descId : void 0,
119
250
  tabIndex: -1,
120
251
  className: [
121
252
  "rmod-panel",
@@ -126,11 +257,29 @@ var ModalStyled = forwardRef(
126
257
  "data-variant": variant,
127
258
  "data-padding": padding,
128
259
  "data-scrollable": scrollable ? "true" : void 0,
260
+ "data-transition": transition,
261
+ "data-state": visible ? "open" : "closed",
262
+ "data-swiping": swipeY > 0 ? "true" : void 0,
263
+ style: panelStyle,
264
+ onPointerDown: swipeEnabled ? handleSwipePointerDown : void 0,
265
+ onPointerMove: swipeEnabled ? handleSwipePointerMove : void 0,
266
+ onPointerUp: swipeEnabled ? handleSwipePointerUp : void 0,
267
+ onPointerCancel: swipeEnabled ? handleSwipePointerUp : void 0,
129
268
  children: [
130
269
  hasHeader && /* @__PURE__ */ jsxs("div", { className: "rmod-header", children: [
131
270
  title ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsx("span", {}),
132
- showCloseButton && /* @__PURE__ */ jsx("button", { type: "button", className: "rmod-close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }) }) })
271
+ showCloseButton && /* @__PURE__ */ jsx(
272
+ "button",
273
+ {
274
+ type: "button",
275
+ className: "rmod-close",
276
+ "aria-label": "Close",
277
+ onClick: () => requestClose("close-button"),
278
+ children: /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }) })
279
+ }
280
+ )
133
281
  ] }),
282
+ description !== void 0 && /* @__PURE__ */ jsx("p", { id: descId, className: "rmod-description", children: description }),
134
283
  /* @__PURE__ */ jsx("div", { className: "rmod-body", children }),
135
284
  footer && /* @__PURE__ */ jsx("div", { className: "rmod-footer", children: footer })
136
285
  ]
@@ -138,11 +287,72 @@ var ModalStyled = forwardRef(
138
287
  )
139
288
  }
140
289
  ),
141
- document.body
290
+ portalTarget
142
291
  );
143
292
  }
144
293
  );
294
+ function confirm(opts = {}) {
295
+ return new Promise((resolve) => {
296
+ if (typeof window === "undefined" || typeof document === "undefined") {
297
+ resolve(false);
298
+ return;
299
+ }
300
+ const host = document.createElement("div");
301
+ document.body.appendChild(host);
302
+ const root = createRoot(host);
303
+ const cleanup = () => {
304
+ setTimeout(() => {
305
+ try {
306
+ root.unmount();
307
+ } catch {
308
+ }
309
+ if (host.parentNode) host.parentNode.removeChild(host);
310
+ }, 320);
311
+ };
312
+ function ConfirmShell() {
313
+ const [open, setOpen] = useState(true);
314
+ const finish = (value) => {
315
+ setOpen(false);
316
+ cleanup();
317
+ resolve(value);
318
+ };
319
+ return /* @__PURE__ */ jsx(
320
+ ModalStyled,
321
+ {
322
+ open,
323
+ onClose: () => finish(false),
324
+ title: opts.title ?? "Are you sure?",
325
+ size: "sm",
326
+ footer: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
327
+ /* @__PURE__ */ jsx(
328
+ "button",
329
+ {
330
+ type: "button",
331
+ className: "rmod-btn rmod-btn--cancel",
332
+ onClick: () => finish(false),
333
+ children: opts.cancelLabel ?? "Cancel"
334
+ }
335
+ ),
336
+ /* @__PURE__ */ jsx(
337
+ "button",
338
+ {
339
+ type: "button",
340
+ className: `rmod-btn rmod-btn--confirm${opts.danger ? " rmod-btn--danger" : ""}`,
341
+ onClick: () => finish(true),
342
+ autoFocus: true,
343
+ children: opts.confirmLabel ?? "Confirm"
344
+ }
345
+ )
346
+ ] }),
347
+ children: opts.body ?? null
348
+ }
349
+ );
350
+ }
351
+ root.render(/* @__PURE__ */ jsx(ConfirmShell, {}));
352
+ });
353
+ }
354
+ var modal = { confirm };
145
355
 
146
- export { ModalStyled };
356
+ export { ModalStyled, confirm, modal };
147
357
  //# sourceMappingURL=styled.js.map
148
358
  //# sourceMappingURL=styled.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/styled/ModalStyled.tsx"],"names":["ModalStyled"],"mappings":"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","file":"styled.js","sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\n\nexport interface ModalStyledProps {\n isOpen: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n isOpen,\n onClose,\n title,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n },\n ref,\n ) {\n const titleId = useId();\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n useEffect(() => { setMounted(true); }, []);\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n\n if (isOpen) {\n setRendered(true);\n // Double RAF ensures the element is in the DOM before we add the visible class\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n } else {\n // Remove visible class first (triggers CSS exit transition)\n setVisible(false);\n // Then unmount after transition finishes\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n }, 300);\n document.body.style.overflow = originalOverflowRef.current;\n }\n }, [isOpen]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n document.body.style.overflow = originalOverflowRef.current;\n };\n }, []);\n\n useEffect(() => {\n if (isOpen && panelRef.current) {\n activate(panelRef.current);\n } else if (!isOpen) {\n deactivate();\n }\n }, [isOpen, activate, deactivate]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n onClose();\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, onClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n },\n [closeOnOverlayClick, onClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isOpen) activate(el);\n },\n [ref, isOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n style={overlayColor ? { \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties : undefined}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button type=\"button\" className=\"rmod-close\" aria-label=\"Close\" onClick={onClose}>\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n document.body,\n );\n },\n);\n"]}
1
+ {"version":3,"sources":["../src/styled/ModalStyled.tsx","../src/styled/confirm.tsx"],"names":["ModalStyled","useState","jsx","jsxs"],"mappings":"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","file":"styled.js","sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\nexport type CloseReason = \"esc\" | \"overlay\" | \"close-button\" | \"programmatic\" | \"swipe\" | \"submit\";\nexport type ModalTransition = \"fade\" | \"zoom\" | \"slide-up\" | \"slide-down\";\n\n// ---- Stacking registry (depth-aware z-index + scale-behind effect) -------\nconst _openModals: { id: number }[] = [];\nlet _modalIdCounter = 0;\nconst _stackListeners = new Set<() => void>();\nfunction _notifyStack() { _stackListeners.forEach((fn) => fn()); }\n\nexport interface ModalStyledProps {\n /** Open state. `open` is the canonical name; `isOpen` continues to work. */\n open?: boolean;\n isOpen?: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n /** Optional accessible description. Renders below the title and links via aria-describedby. */\n description?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n /** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */\n initialFocusRef?: RefObject<HTMLElement | null>;\n /** Element to focus when the modal closes. Defaults to whatever was focused before opening. */\n finalFocusRef?: RefObject<HTMLElement | null>;\n /** Fires after the open transition completes. */\n onAfterOpen?: () => void;\n /** Fires after the close transition completes (and unmount). */\n onAfterClose?: () => void;\n /** Return false to veto a close attempt. Receives the trigger reason. */\n preventClose?: (reason: CloseReason) => boolean;\n /** Disable body scroll lock while open. Default: true (locked). */\n lockBodyScroll?: boolean;\n /** Override the portal container. Default: document.body. */\n container?: HTMLElement | null;\n /** Transition variant for the panel (only `dialog`; drawers always slide). Default: \"fade\" */\n transition?: ModalTransition;\n /** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */\n swipeToDismiss?: boolean;\n /** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */\n closeOnSubmit?: boolean;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n open,\n isOpen,\n onClose,\n title,\n description,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n initialFocusRef,\n finalFocusRef,\n onAfterOpen,\n onAfterClose,\n preventClose,\n lockBodyScroll = true,\n container,\n transition = \"fade\",\n swipeToDismiss,\n closeOnSubmit = false,\n },\n ref,\n ) {\n const swipeEnabled = swipeToDismiss ?? variant === \"drawer-bottom\";\n const myStackIdRef = useRef<number>(-1);\n const [stackDepth, setStackDepth] = useState(0);\n const [stackPosition, setStackPosition] = useState(0); // 0 = top\n const [swipeY, setSwipeY] = useState(0);\n const titleId = useId();\n const descId = useId();\n const isActuallyOpen = open ?? isOpen ?? false;\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n const onAfterOpenRef = useRef(onAfterOpen);\n onAfterOpenRef.current = onAfterOpen;\n const onAfterCloseRef = useRef(onAfterClose);\n onAfterCloseRef.current = onAfterClose;\n\n useEffect(() => { setMounted(true); }, []);\n\n const requestClose = useCallback(\n (reason: CloseReason) => {\n if (preventClose && !preventClose(reason)) return;\n onClose();\n },\n [preventClose, onClose],\n );\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n if (enterTimerRef.current) {\n clearTimeout(enterTimerRef.current);\n enterTimerRef.current = null;\n }\n\n if (isActuallyOpen) {\n setRendered(true);\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n if (lockBodyScroll) {\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n // Fire onAfterOpen once the enter transition has had time to settle.\n enterTimerRef.current = setTimeout(() => {\n onAfterOpenRef.current?.();\n }, 320);\n } else {\n setVisible(false);\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n onAfterCloseRef.current?.();\n }, 300);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n }\n }, [isActuallyOpen, lockBodyScroll]);\n\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n if (enterTimerRef.current) clearTimeout(enterTimerRef.current);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n };\n }, [lockBodyScroll]);\n\n // ---- Stacking: register / deregister this modal ---------------------\n useEffect(() => {\n if (!isActuallyOpen) return;\n const id = ++_modalIdCounter;\n myStackIdRef.current = id;\n _openModals.push({ id });\n _notifyStack();\n const sync = () => {\n const idx = _openModals.findIndex((m) => m.id === id);\n const total = _openModals.length;\n setStackDepth(total);\n setStackPosition(idx === -1 ? 0 : total - 1 - idx);\n };\n sync();\n _stackListeners.add(sync);\n return () => {\n _stackListeners.delete(sync);\n const idx = _openModals.findIndex((m) => m.id === id);\n if (idx !== -1) _openModals.splice(idx, 1);\n _notifyStack();\n };\n }, [isActuallyOpen]);\n\n // ---- closeOnSubmit: auto-close on a contained <form> submit ---------\n useEffect(() => {\n if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;\n const panel = panelRef.current;\n const onSubmit = (e: Event) => {\n // Defer the close so the consumer's onSubmit handler runs first\n // and can call e.preventDefault() if it wants to abort the close.\n if (e.defaultPrevented) return;\n setTimeout(() => requestClose(\"submit\"), 0);\n };\n panel.addEventListener(\"submit\", onSubmit);\n return () => panel.removeEventListener(\"submit\", onSubmit);\n }, [closeOnSubmit, isActuallyOpen, requestClose]);\n\n // ---- Swipe-to-dismiss (touch only) -----------------------------------\n const swipeStartYRef = useRef<number | null>(null);\n const handleSwipePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!swipeEnabled || e.pointerType !== \"touch\") return;\n // Only initiate from the panel header / the panel itself, not buttons.\n swipeStartYRef.current = e.clientY;\n };\n const handleSwipePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (swipeStartYRef.current === null) return;\n const dy = Math.max(0, e.clientY - swipeStartYRef.current);\n setSwipeY(dy);\n };\n const handleSwipePointerUp = () => {\n if (swipeStartYRef.current === null) return;\n if (swipeY > 120) requestClose(\"swipe\");\n setSwipeY(0);\n swipeStartYRef.current = null;\n };\n\n // Focus management\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n useEffect(() => {\n if (isActuallyOpen) {\n previouslyFocusedRef.current = document.activeElement as HTMLElement | null;\n if (panelRef.current) {\n activate(panelRef.current);\n // Honor initialFocusRef after the panel is mounted+visible\n requestAnimationFrame(() => {\n if (initialFocusRef?.current) {\n initialFocusRef.current.focus();\n }\n });\n }\n } else {\n deactivate();\n // Honor finalFocusRef on close, otherwise restore previous focus\n const target = finalFocusRef?.current ?? previouslyFocusedRef.current;\n if (target && typeof target.focus === \"function\") {\n target.focus();\n }\n }\n }, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n requestClose(\"esc\");\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, requestClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n requestClose(\"overlay\");\n }\n },\n [closeOnOverlayClick, requestClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isActuallyOpen) activate(el);\n },\n [ref, isActuallyOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n const portalTarget = container ?? document.body;\n\n const overlayStyle: React.CSSProperties = {\n ...(overlayColor ? ({ \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties) : {}),\n // Higher stack depth → lower modals scale down + fade slightly behind the topmost.\n zIndex: 1000 + stackDepth * 10,\n };\n\n const panelStyle: React.CSSProperties = {\n // Stack-position effects: only applied when this isn't the topmost modal.\n ...(stackPosition > 0\n ? {\n transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,\n opacity: Math.max(0.6, 1 - stackPosition * 0.18),\n }\n : {}),\n ...(swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: \"none\" } : {}),\n };\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n data-transition={transition}\n data-stack-position={stackPosition || undefined}\n data-state={visible ? \"open\" : \"closed\"}\n style={overlayStyle}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={description ? descId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n data-transition={transition}\n data-state={visible ? \"open\" : \"closed\"}\n data-swiping={swipeY > 0 ? \"true\" : undefined}\n style={panelStyle}\n onPointerDown={swipeEnabled ? handleSwipePointerDown : undefined}\n onPointerMove={swipeEnabled ? handleSwipePointerMove : undefined}\n onPointerUp={swipeEnabled ? handleSwipePointerUp : undefined}\n onPointerCancel={swipeEnabled ? handleSwipePointerUp : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button\n type=\"button\"\n className=\"rmod-close\"\n aria-label=\"Close\"\n onClick={() => requestClose(\"close-button\")}\n >\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n {description !== undefined && (\n <p id={descId} className=\"rmod-description\">{description}</p>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n portalTarget,\n );\n },\n);\n","import { useState, type ReactNode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { ModalStyled } from \"./ModalStyled\";\n\nexport interface ConfirmOptions {\n title?: ReactNode;\n body?: ReactNode;\n confirmLabel?: ReactNode;\n cancelLabel?: ReactNode;\n /** Style the confirm button as the danger CTA (red). */\n danger?: boolean;\n}\n\n/**\n * Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled\n * or dismissed via overlay/Esc/close button.\n *\n * Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`\n * and unmounts on close — no provider required.\n */\nexport function confirm(opts: ConfirmOptions = {}): Promise<boolean> {\n return new Promise((resolve) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n resolve(false);\n return;\n }\n const host = document.createElement(\"div\");\n document.body.appendChild(host);\n const root = createRoot(host);\n\n const cleanup = () => {\n // Defer unmount so the close transition can run.\n setTimeout(() => {\n try { root.unmount(); } catch { /* ignore */ }\n if (host.parentNode) host.parentNode.removeChild(host);\n }, 320);\n };\n\n function ConfirmShell() {\n const [open, setOpen] = useState(true);\n const finish = (value: boolean) => {\n setOpen(false);\n cleanup();\n resolve(value);\n };\n return (\n <ModalStyled\n open={open}\n onClose={() => finish(false)}\n title={opts.title ?? \"Are you sure?\"}\n size=\"sm\"\n footer={\n <div style={{ display: \"flex\", gap: 8, justifyContent: \"flex-end\" }}>\n <button\n type=\"button\"\n className=\"rmod-btn rmod-btn--cancel\"\n onClick={() => finish(false)}\n >\n {opts.cancelLabel ?? \"Cancel\"}\n </button>\n <button\n type=\"button\"\n className={`rmod-btn rmod-btn--confirm${opts.danger ? \" rmod-btn--danger\" : \"\"}`}\n onClick={() => finish(true)}\n autoFocus\n >\n {opts.confirmLabel ?? \"Confirm\"}\n </button>\n </div>\n }\n >\n {opts.body ?? null}\n </ModalStyled>\n );\n }\n\n root.render(<ConfirmShell />);\n });\n}\n\n/** Module-style export so consumers can do `modal.confirm(...)`. */\nexport const modal = { confirm };\n"]}
package/dist/styles.css CHANGED
@@ -245,3 +245,118 @@
245
245
  @media (prefers-reduced-motion: reduce) {
246
246
  .rmod-overlay, .rmod-panel { transition-duration: 0ms !important; }
247
247
  }
248
+
249
+ /* ============================================================================
250
+ * 0.2.0 additions: description below title
251
+ * ========================================================================== */
252
+ .rmod-description {
253
+ margin: 0 0 0.6rem;
254
+ padding: 0 1rem;
255
+ color: var(--rmod-fg-muted);
256
+ font-size: 0.875rem;
257
+ line-height: 1.55;
258
+ }
259
+ .rmod-panel[data-padding="none"] .rmod-description { padding: 0; }
260
+ .rmod-panel[data-padding="sm"] .rmod-description { padding: 0 0.75rem; }
261
+ .rmod-panel[data-padding="lg"] .rmod-description { padding: 0 1.5rem; }
262
+
263
+ /* ============================================================================
264
+ * 1.x additions — transition variants · stacked behind · swipe · confirm()
265
+ * ============================================================================ */
266
+
267
+ /* Transition variants on the dialog panel.
268
+ * Drawers (drawer-left/right/bottom) keep their existing slide-from-edge
269
+ * transition; these only apply to data-variant="dialog". */
270
+ .rmod-panel[data-variant="dialog"][data-transition="zoom"] {
271
+ transform: scale(0.94);
272
+ opacity: 0;
273
+ transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
274
+ }
275
+ .rmod-panel[data-variant="dialog"][data-transition="zoom"].rmod-panel--visible {
276
+ transform: scale(1);
277
+ opacity: 1;
278
+ }
279
+
280
+ .rmod-panel[data-variant="dialog"][data-transition="slide-up"] {
281
+ transform: translateY(20px);
282
+ opacity: 0;
283
+ transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
284
+ }
285
+ .rmod-panel[data-variant="dialog"][data-transition="slide-up"].rmod-panel--visible {
286
+ transform: translateY(0);
287
+ opacity: 1;
288
+ }
289
+
290
+ .rmod-panel[data-variant="dialog"][data-transition="slide-down"] {
291
+ transform: translateY(-20px);
292
+ opacity: 0;
293
+ transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
294
+ }
295
+ .rmod-panel[data-variant="dialog"][data-transition="slide-down"].rmod-panel--visible {
296
+ transform: translateY(0);
297
+ opacity: 1;
298
+ }
299
+
300
+ /* Stacked-behind effect — applied via inline style from JS but we add a
301
+ * smooth transition here so depth changes animate. */
302
+ .rmod-overlay[data-stack-position] .rmod-panel:not([data-swiping="true"]) {
303
+ transition:
304
+ transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
305
+ opacity 220ms ease;
306
+ }
307
+
308
+ /* Swipe-to-dismiss visual */
309
+ .rmod-panel[data-swiping="true"] {
310
+ transition: none;
311
+ cursor: grabbing;
312
+ }
313
+
314
+ /* Confirm() utility's footer buttons — minimal, inherit consumer themes
315
+ * via CSS variables. */
316
+ .rmod-btn {
317
+ appearance: none;
318
+ font: inherit;
319
+ padding: 0.5rem 1rem;
320
+ border-radius: 6px;
321
+ cursor: pointer;
322
+ font-weight: 500;
323
+ font-size: 0.875rem;
324
+ transition: background 120ms ease, border-color 120ms ease;
325
+ }
326
+ .rmod-btn--cancel {
327
+ background: transparent;
328
+ border: 1px solid var(--rmod-border, rgba(0, 0, 0, 0.12));
329
+ color: var(--rmod-fg, #18181b);
330
+ }
331
+ .rmod-btn--cancel:hover {
332
+ background: var(--rmod-surface-hover, rgba(0, 0, 0, 0.04));
333
+ }
334
+ .rmod-btn--confirm {
335
+ background: var(--rmod-confirm-bg, #6366f1);
336
+ border: 1px solid var(--rmod-confirm-bg, #6366f1);
337
+ color: white;
338
+ }
339
+ .rmod-btn--confirm:hover {
340
+ background: var(--rmod-confirm-bg-hover, #5558e6);
341
+ }
342
+ .rmod-btn--danger {
343
+ background: var(--rmod-danger-bg, #dc2626);
344
+ border-color: var(--rmod-danger-bg, #dc2626);
345
+ }
346
+ .rmod-btn--danger:hover {
347
+ background: var(--rmod-danger-bg-hover, #c01f1f);
348
+ }
349
+ [data-theme="dark"] .rmod-btn--cancel {
350
+ border-color: var(--rmod-border, rgba(255, 255, 255, 0.16));
351
+ color: var(--rmod-fg, #f4f4f5);
352
+ }
353
+ [data-theme="dark"] .rmod-btn--cancel:hover {
354
+ background: var(--rmod-surface-hover, rgba(255, 255, 255, 0.06));
355
+ }
356
+
357
+ @media (prefers-reduced-motion: reduce) {
358
+ .rmod-panel[data-variant="dialog"][data-transition] {
359
+ transition: opacity 100ms linear;
360
+ transform: none !important;
361
+ }
362
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mshafiqyajid/react-modal",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Headless modal hook and styled component for React. Accessible, focus-trapped, scroll-locked, animated, SSR-safe, fully typed.",
5
5
  "keywords": [
6
6
  "react",