@mshafiqyajid/react-modal 0.0.0 → 0.2.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 +85 -20
- package/dist/styled.cjs.map +1 -1
- package/dist/styled.d.cts +21 -2
- package/dist/styled.d.ts +21 -2
- package/dist/styled.js +85 -20
- package/dist/styled.js.map +1 -1
- package/dist/styles.css +14 -0
- package/package.json +1 -1
package/dist/styled.cjs
CHANGED
|
@@ -66,9 +66,11 @@ function useFocusTrap() {
|
|
|
66
66
|
}
|
|
67
67
|
var ModalStyled = react.forwardRef(
|
|
68
68
|
function ModalStyled2({
|
|
69
|
+
open,
|
|
69
70
|
isOpen,
|
|
70
71
|
onClose,
|
|
71
72
|
title,
|
|
73
|
+
description,
|
|
72
74
|
children,
|
|
73
75
|
footer,
|
|
74
76
|
size = "md",
|
|
@@ -80,83 +82,133 @@ var ModalStyled = react.forwardRef(
|
|
|
80
82
|
overlayColor,
|
|
81
83
|
padding = "md",
|
|
82
84
|
scrollable = true,
|
|
83
|
-
className
|
|
85
|
+
className,
|
|
86
|
+
initialFocusRef,
|
|
87
|
+
finalFocusRef,
|
|
88
|
+
onAfterOpen,
|
|
89
|
+
onAfterClose,
|
|
90
|
+
preventClose,
|
|
91
|
+
lockBodyScroll = true,
|
|
92
|
+
container
|
|
84
93
|
}, ref) {
|
|
85
94
|
const titleId = react.useId();
|
|
95
|
+
const descId = react.useId();
|
|
96
|
+
const isActuallyOpen = open ?? isOpen ?? false;
|
|
86
97
|
const [mounted, setMounted] = react.useState(false);
|
|
87
98
|
const [rendered, setRendered] = react.useState(false);
|
|
88
99
|
const [visible, setVisible] = react.useState(false);
|
|
89
100
|
const panelRef = react.useRef(null);
|
|
90
101
|
const originalOverflowRef = react.useRef("");
|
|
91
102
|
const exitTimerRef = react.useRef(null);
|
|
103
|
+
const enterTimerRef = react.useRef(null);
|
|
92
104
|
const { activate, deactivate, handleKeyDown } = useFocusTrap();
|
|
105
|
+
const onAfterOpenRef = react.useRef(onAfterOpen);
|
|
106
|
+
onAfterOpenRef.current = onAfterOpen;
|
|
107
|
+
const onAfterCloseRef = react.useRef(onAfterClose);
|
|
108
|
+
onAfterCloseRef.current = onAfterClose;
|
|
93
109
|
react.useEffect(() => {
|
|
94
110
|
setMounted(true);
|
|
95
111
|
}, []);
|
|
112
|
+
const requestClose = react.useCallback(
|
|
113
|
+
(reason) => {
|
|
114
|
+
if (preventClose && !preventClose(reason)) return;
|
|
115
|
+
onClose();
|
|
116
|
+
},
|
|
117
|
+
[preventClose, onClose]
|
|
118
|
+
);
|
|
96
119
|
react.useEffect(() => {
|
|
97
120
|
if (exitTimerRef.current) {
|
|
98
121
|
clearTimeout(exitTimerRef.current);
|
|
99
122
|
exitTimerRef.current = null;
|
|
100
123
|
}
|
|
101
|
-
if (
|
|
124
|
+
if (enterTimerRef.current) {
|
|
125
|
+
clearTimeout(enterTimerRef.current);
|
|
126
|
+
enterTimerRef.current = null;
|
|
127
|
+
}
|
|
128
|
+
if (isActuallyOpen) {
|
|
102
129
|
setRendered(true);
|
|
103
130
|
requestAnimationFrame(() => {
|
|
104
131
|
requestAnimationFrame(() => {
|
|
105
132
|
setVisible(true);
|
|
106
133
|
});
|
|
107
134
|
});
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
if (lockBodyScroll) {
|
|
136
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
137
|
+
document.body.style.overflow = "hidden";
|
|
138
|
+
}
|
|
139
|
+
enterTimerRef.current = setTimeout(() => {
|
|
140
|
+
onAfterOpenRef.current?.();
|
|
141
|
+
}, 320);
|
|
110
142
|
} else {
|
|
111
143
|
setVisible(false);
|
|
112
144
|
exitTimerRef.current = setTimeout(() => {
|
|
113
145
|
setRendered(false);
|
|
146
|
+
onAfterCloseRef.current?.();
|
|
114
147
|
}, 300);
|
|
115
|
-
|
|
148
|
+
if (lockBodyScroll) {
|
|
149
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
150
|
+
}
|
|
116
151
|
}
|
|
117
|
-
}, [
|
|
152
|
+
}, [isActuallyOpen, lockBodyScroll]);
|
|
118
153
|
react.useEffect(() => {
|
|
119
154
|
return () => {
|
|
120
155
|
if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
|
|
121
|
-
|
|
156
|
+
if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
|
|
157
|
+
if (lockBodyScroll) {
|
|
158
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
159
|
+
}
|
|
122
160
|
};
|
|
123
|
-
}, []);
|
|
161
|
+
}, [lockBodyScroll]);
|
|
162
|
+
const previouslyFocusedRef = react.useRef(null);
|
|
124
163
|
react.useEffect(() => {
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
164
|
+
if (isActuallyOpen) {
|
|
165
|
+
previouslyFocusedRef.current = document.activeElement;
|
|
166
|
+
if (panelRef.current) {
|
|
167
|
+
activate(panelRef.current);
|
|
168
|
+
requestAnimationFrame(() => {
|
|
169
|
+
if (initialFocusRef?.current) {
|
|
170
|
+
initialFocusRef.current.focus();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
128
175
|
deactivate();
|
|
176
|
+
const target = finalFocusRef?.current ?? previouslyFocusedRef.current;
|
|
177
|
+
if (target && typeof target.focus === "function") {
|
|
178
|
+
target.focus();
|
|
179
|
+
}
|
|
129
180
|
}
|
|
130
|
-
}, [
|
|
181
|
+
}, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);
|
|
131
182
|
const onKeyDown = react.useCallback(
|
|
132
183
|
(e) => {
|
|
133
184
|
if (closeOnEsc && e.key === "Escape") {
|
|
134
|
-
|
|
185
|
+
requestClose("esc");
|
|
135
186
|
return;
|
|
136
187
|
}
|
|
137
188
|
handleKeyDown(e.nativeEvent);
|
|
138
189
|
},
|
|
139
|
-
[closeOnEsc,
|
|
190
|
+
[closeOnEsc, requestClose, handleKeyDown]
|
|
140
191
|
);
|
|
141
192
|
const handleOverlayClick = react.useCallback(
|
|
142
193
|
(e) => {
|
|
143
194
|
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
144
|
-
|
|
195
|
+
requestClose("overlay");
|
|
145
196
|
}
|
|
146
197
|
},
|
|
147
|
-
[closeOnOverlayClick,
|
|
198
|
+
[closeOnOverlayClick, requestClose]
|
|
148
199
|
);
|
|
149
200
|
const setPanelRef = react.useCallback(
|
|
150
201
|
(el) => {
|
|
151
202
|
panelRef.current = el;
|
|
152
203
|
if (typeof ref === "function") ref(el);
|
|
153
204
|
else if (ref) ref.current = el;
|
|
154
|
-
if (el &&
|
|
205
|
+
if (el && isActuallyOpen) activate(el);
|
|
155
206
|
},
|
|
156
|
-
[ref,
|
|
207
|
+
[ref, isActuallyOpen, activate]
|
|
157
208
|
);
|
|
158
209
|
if (!mounted || !rendered) return null;
|
|
159
210
|
const hasHeader = title !== void 0 || showCloseButton;
|
|
211
|
+
const portalTarget = container ?? document.body;
|
|
160
212
|
return reactDom.createPortal(
|
|
161
213
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
162
214
|
"div",
|
|
@@ -167,6 +219,7 @@ var ModalStyled = react.forwardRef(
|
|
|
167
219
|
].filter(Boolean).join(" "),
|
|
168
220
|
"data-variant": variant,
|
|
169
221
|
"data-blur": blur,
|
|
222
|
+
"data-state": visible ? "open" : "closed",
|
|
170
223
|
style: overlayColor ? { "--rmod-overlay-bg": overlayColor } : void 0,
|
|
171
224
|
onClick: handleOverlayClick,
|
|
172
225
|
onKeyDown,
|
|
@@ -177,6 +230,7 @@ var ModalStyled = react.forwardRef(
|
|
|
177
230
|
role: "dialog",
|
|
178
231
|
"aria-modal": "true",
|
|
179
232
|
"aria-labelledby": title ? titleId : void 0,
|
|
233
|
+
"aria-describedby": description ? descId : void 0,
|
|
180
234
|
tabIndex: -1,
|
|
181
235
|
className: [
|
|
182
236
|
"rmod-panel",
|
|
@@ -187,11 +241,22 @@ var ModalStyled = react.forwardRef(
|
|
|
187
241
|
"data-variant": variant,
|
|
188
242
|
"data-padding": padding,
|
|
189
243
|
"data-scrollable": scrollable ? "true" : void 0,
|
|
244
|
+
"data-state": visible ? "open" : "closed",
|
|
190
245
|
children: [
|
|
191
246
|
hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rmod-header", children: [
|
|
192
247
|
title ? /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
|
|
193
|
-
showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
248
|
+
showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
249
|
+
"button",
|
|
250
|
+
{
|
|
251
|
+
type: "button",
|
|
252
|
+
className: "rmod-close",
|
|
253
|
+
"aria-label": "Close",
|
|
254
|
+
onClick: () => requestClose("close-button"),
|
|
255
|
+
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" }) })
|
|
256
|
+
}
|
|
257
|
+
)
|
|
194
258
|
] }),
|
|
259
|
+
description !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "rmod-description", children: description }),
|
|
195
260
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-body", children }),
|
|
196
261
|
footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-footer", children: footer })
|
|
197
262
|
]
|
|
@@ -199,7 +264,7 @@ var ModalStyled = react.forwardRef(
|
|
|
199
264
|
)
|
|
200
265
|
}
|
|
201
266
|
),
|
|
202
|
-
|
|
267
|
+
portalTarget
|
|
203
268
|
);
|
|
204
269
|
}
|
|
205
270
|
);
|
package/dist/styled.cjs.map
CHANGED
|
@@ -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"],"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 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\";\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}\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 },\n ref,\n ) {\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 // 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 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-state={visible ? \"open\" : \"closed\"}\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 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-state={visible ? \"open\" : \"closed\"}\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"]}
|
package/dist/styled.d.cts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
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";
|
|
6
7
|
interface ModalStyledProps {
|
|
7
|
-
isOpen
|
|
8
|
+
/** Open state. `open` is the canonical name; `isOpen` continues to work. */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
isOpen?: boolean;
|
|
8
11
|
onClose: () => void;
|
|
9
12
|
/** Modal heading — omit to hide the header entirely */
|
|
10
13
|
title?: ReactNode;
|
|
14
|
+
/** Optional accessible description. Renders below the title and links via aria-describedby. */
|
|
15
|
+
description?: ReactNode;
|
|
11
16
|
children: ReactNode;
|
|
12
17
|
/** Footer content — omit to hide footer */
|
|
13
18
|
footer?: ReactNode;
|
|
@@ -25,6 +30,20 @@ interface ModalStyledProps {
|
|
|
25
30
|
/** Max height of scrollable body. Default: auto */
|
|
26
31
|
scrollable?: boolean;
|
|
27
32
|
className?: string;
|
|
33
|
+
/** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */
|
|
34
|
+
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
35
|
+
/** Element to focus when the modal closes. Defaults to whatever was focused before opening. */
|
|
36
|
+
finalFocusRef?: RefObject<HTMLElement | null>;
|
|
37
|
+
/** Fires after the open transition completes. */
|
|
38
|
+
onAfterOpen?: () => void;
|
|
39
|
+
/** Fires after the close transition completes (and unmount). */
|
|
40
|
+
onAfterClose?: () => void;
|
|
41
|
+
/** Return false to veto a close attempt. Receives the trigger reason. */
|
|
42
|
+
preventClose?: (reason: CloseReason) => boolean;
|
|
43
|
+
/** Disable body scroll lock while open. Default: true (locked). */
|
|
44
|
+
lockBodyScroll?: boolean;
|
|
45
|
+
/** Override the portal container. Default: document.body. */
|
|
46
|
+
container?: HTMLElement | null;
|
|
28
47
|
}
|
|
29
48
|
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
30
49
|
|
package/dist/styled.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
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";
|
|
6
7
|
interface ModalStyledProps {
|
|
7
|
-
isOpen
|
|
8
|
+
/** Open state. `open` is the canonical name; `isOpen` continues to work. */
|
|
9
|
+
open?: boolean;
|
|
10
|
+
isOpen?: boolean;
|
|
8
11
|
onClose: () => void;
|
|
9
12
|
/** Modal heading — omit to hide the header entirely */
|
|
10
13
|
title?: ReactNode;
|
|
14
|
+
/** Optional accessible description. Renders below the title and links via aria-describedby. */
|
|
15
|
+
description?: ReactNode;
|
|
11
16
|
children: ReactNode;
|
|
12
17
|
/** Footer content — omit to hide footer */
|
|
13
18
|
footer?: ReactNode;
|
|
@@ -25,6 +30,20 @@ interface ModalStyledProps {
|
|
|
25
30
|
/** Max height of scrollable body. Default: auto */
|
|
26
31
|
scrollable?: boolean;
|
|
27
32
|
className?: string;
|
|
33
|
+
/** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */
|
|
34
|
+
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
35
|
+
/** Element to focus when the modal closes. Defaults to whatever was focused before opening. */
|
|
36
|
+
finalFocusRef?: RefObject<HTMLElement | null>;
|
|
37
|
+
/** Fires after the open transition completes. */
|
|
38
|
+
onAfterOpen?: () => void;
|
|
39
|
+
/** Fires after the close transition completes (and unmount). */
|
|
40
|
+
onAfterClose?: () => void;
|
|
41
|
+
/** Return false to veto a close attempt. Receives the trigger reason. */
|
|
42
|
+
preventClose?: (reason: CloseReason) => boolean;
|
|
43
|
+
/** Disable body scroll lock while open. Default: true (locked). */
|
|
44
|
+
lockBodyScroll?: boolean;
|
|
45
|
+
/** Override the portal container. Default: document.body. */
|
|
46
|
+
container?: HTMLElement | null;
|
|
28
47
|
}
|
|
29
48
|
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
30
49
|
|
package/dist/styled.js
CHANGED
|
@@ -5,9 +5,11 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
5
5
|
|
|
6
6
|
var ModalStyled = forwardRef(
|
|
7
7
|
function ModalStyled2({
|
|
8
|
+
open,
|
|
8
9
|
isOpen,
|
|
9
10
|
onClose,
|
|
10
11
|
title,
|
|
12
|
+
description,
|
|
11
13
|
children,
|
|
12
14
|
footer,
|
|
13
15
|
size = "md",
|
|
@@ -19,83 +21,133 @@ var ModalStyled = forwardRef(
|
|
|
19
21
|
overlayColor,
|
|
20
22
|
padding = "md",
|
|
21
23
|
scrollable = true,
|
|
22
|
-
className
|
|
24
|
+
className,
|
|
25
|
+
initialFocusRef,
|
|
26
|
+
finalFocusRef,
|
|
27
|
+
onAfterOpen,
|
|
28
|
+
onAfterClose,
|
|
29
|
+
preventClose,
|
|
30
|
+
lockBodyScroll = true,
|
|
31
|
+
container
|
|
23
32
|
}, ref) {
|
|
24
33
|
const titleId = useId();
|
|
34
|
+
const descId = useId();
|
|
35
|
+
const isActuallyOpen = open ?? isOpen ?? false;
|
|
25
36
|
const [mounted, setMounted] = useState(false);
|
|
26
37
|
const [rendered, setRendered] = useState(false);
|
|
27
38
|
const [visible, setVisible] = useState(false);
|
|
28
39
|
const panelRef = useRef(null);
|
|
29
40
|
const originalOverflowRef = useRef("");
|
|
30
41
|
const exitTimerRef = useRef(null);
|
|
42
|
+
const enterTimerRef = useRef(null);
|
|
31
43
|
const { activate, deactivate, handleKeyDown } = useFocusTrap();
|
|
44
|
+
const onAfterOpenRef = useRef(onAfterOpen);
|
|
45
|
+
onAfterOpenRef.current = onAfterOpen;
|
|
46
|
+
const onAfterCloseRef = useRef(onAfterClose);
|
|
47
|
+
onAfterCloseRef.current = onAfterClose;
|
|
32
48
|
useEffect(() => {
|
|
33
49
|
setMounted(true);
|
|
34
50
|
}, []);
|
|
51
|
+
const requestClose = useCallback(
|
|
52
|
+
(reason) => {
|
|
53
|
+
if (preventClose && !preventClose(reason)) return;
|
|
54
|
+
onClose();
|
|
55
|
+
},
|
|
56
|
+
[preventClose, onClose]
|
|
57
|
+
);
|
|
35
58
|
useEffect(() => {
|
|
36
59
|
if (exitTimerRef.current) {
|
|
37
60
|
clearTimeout(exitTimerRef.current);
|
|
38
61
|
exitTimerRef.current = null;
|
|
39
62
|
}
|
|
40
|
-
if (
|
|
63
|
+
if (enterTimerRef.current) {
|
|
64
|
+
clearTimeout(enterTimerRef.current);
|
|
65
|
+
enterTimerRef.current = null;
|
|
66
|
+
}
|
|
67
|
+
if (isActuallyOpen) {
|
|
41
68
|
setRendered(true);
|
|
42
69
|
requestAnimationFrame(() => {
|
|
43
70
|
requestAnimationFrame(() => {
|
|
44
71
|
setVisible(true);
|
|
45
72
|
});
|
|
46
73
|
});
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
if (lockBodyScroll) {
|
|
75
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
76
|
+
document.body.style.overflow = "hidden";
|
|
77
|
+
}
|
|
78
|
+
enterTimerRef.current = setTimeout(() => {
|
|
79
|
+
onAfterOpenRef.current?.();
|
|
80
|
+
}, 320);
|
|
49
81
|
} else {
|
|
50
82
|
setVisible(false);
|
|
51
83
|
exitTimerRef.current = setTimeout(() => {
|
|
52
84
|
setRendered(false);
|
|
85
|
+
onAfterCloseRef.current?.();
|
|
53
86
|
}, 300);
|
|
54
|
-
|
|
87
|
+
if (lockBodyScroll) {
|
|
88
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
89
|
+
}
|
|
55
90
|
}
|
|
56
|
-
}, [
|
|
91
|
+
}, [isActuallyOpen, lockBodyScroll]);
|
|
57
92
|
useEffect(() => {
|
|
58
93
|
return () => {
|
|
59
94
|
if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
|
|
60
|
-
|
|
95
|
+
if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
|
|
96
|
+
if (lockBodyScroll) {
|
|
97
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
98
|
+
}
|
|
61
99
|
};
|
|
62
|
-
}, []);
|
|
100
|
+
}, [lockBodyScroll]);
|
|
101
|
+
const previouslyFocusedRef = useRef(null);
|
|
63
102
|
useEffect(() => {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
if (isActuallyOpen) {
|
|
104
|
+
previouslyFocusedRef.current = document.activeElement;
|
|
105
|
+
if (panelRef.current) {
|
|
106
|
+
activate(panelRef.current);
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
if (initialFocusRef?.current) {
|
|
109
|
+
initialFocusRef.current.focus();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
67
114
|
deactivate();
|
|
115
|
+
const target = finalFocusRef?.current ?? previouslyFocusedRef.current;
|
|
116
|
+
if (target && typeof target.focus === "function") {
|
|
117
|
+
target.focus();
|
|
118
|
+
}
|
|
68
119
|
}
|
|
69
|
-
}, [
|
|
120
|
+
}, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);
|
|
70
121
|
const onKeyDown = useCallback(
|
|
71
122
|
(e) => {
|
|
72
123
|
if (closeOnEsc && e.key === "Escape") {
|
|
73
|
-
|
|
124
|
+
requestClose("esc");
|
|
74
125
|
return;
|
|
75
126
|
}
|
|
76
127
|
handleKeyDown(e.nativeEvent);
|
|
77
128
|
},
|
|
78
|
-
[closeOnEsc,
|
|
129
|
+
[closeOnEsc, requestClose, handleKeyDown]
|
|
79
130
|
);
|
|
80
131
|
const handleOverlayClick = useCallback(
|
|
81
132
|
(e) => {
|
|
82
133
|
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
83
|
-
|
|
134
|
+
requestClose("overlay");
|
|
84
135
|
}
|
|
85
136
|
},
|
|
86
|
-
[closeOnOverlayClick,
|
|
137
|
+
[closeOnOverlayClick, requestClose]
|
|
87
138
|
);
|
|
88
139
|
const setPanelRef = useCallback(
|
|
89
140
|
(el) => {
|
|
90
141
|
panelRef.current = el;
|
|
91
142
|
if (typeof ref === "function") ref(el);
|
|
92
143
|
else if (ref) ref.current = el;
|
|
93
|
-
if (el &&
|
|
144
|
+
if (el && isActuallyOpen) activate(el);
|
|
94
145
|
},
|
|
95
|
-
[ref,
|
|
146
|
+
[ref, isActuallyOpen, activate]
|
|
96
147
|
);
|
|
97
148
|
if (!mounted || !rendered) return null;
|
|
98
149
|
const hasHeader = title !== void 0 || showCloseButton;
|
|
150
|
+
const portalTarget = container ?? document.body;
|
|
99
151
|
return createPortal(
|
|
100
152
|
/* @__PURE__ */ jsx(
|
|
101
153
|
"div",
|
|
@@ -106,6 +158,7 @@ var ModalStyled = forwardRef(
|
|
|
106
158
|
].filter(Boolean).join(" "),
|
|
107
159
|
"data-variant": variant,
|
|
108
160
|
"data-blur": blur,
|
|
161
|
+
"data-state": visible ? "open" : "closed",
|
|
109
162
|
style: overlayColor ? { "--rmod-overlay-bg": overlayColor } : void 0,
|
|
110
163
|
onClick: handleOverlayClick,
|
|
111
164
|
onKeyDown,
|
|
@@ -116,6 +169,7 @@ var ModalStyled = forwardRef(
|
|
|
116
169
|
role: "dialog",
|
|
117
170
|
"aria-modal": "true",
|
|
118
171
|
"aria-labelledby": title ? titleId : void 0,
|
|
172
|
+
"aria-describedby": description ? descId : void 0,
|
|
119
173
|
tabIndex: -1,
|
|
120
174
|
className: [
|
|
121
175
|
"rmod-panel",
|
|
@@ -126,11 +180,22 @@ var ModalStyled = forwardRef(
|
|
|
126
180
|
"data-variant": variant,
|
|
127
181
|
"data-padding": padding,
|
|
128
182
|
"data-scrollable": scrollable ? "true" : void 0,
|
|
183
|
+
"data-state": visible ? "open" : "closed",
|
|
129
184
|
children: [
|
|
130
185
|
hasHeader && /* @__PURE__ */ jsxs("div", { className: "rmod-header", children: [
|
|
131
186
|
title ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsx("span", {}),
|
|
132
|
-
showCloseButton && /* @__PURE__ */ jsx(
|
|
187
|
+
showCloseButton && /* @__PURE__ */ jsx(
|
|
188
|
+
"button",
|
|
189
|
+
{
|
|
190
|
+
type: "button",
|
|
191
|
+
className: "rmod-close",
|
|
192
|
+
"aria-label": "Close",
|
|
193
|
+
onClick: () => requestClose("close-button"),
|
|
194
|
+
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" }) })
|
|
195
|
+
}
|
|
196
|
+
)
|
|
133
197
|
] }),
|
|
198
|
+
description !== void 0 && /* @__PURE__ */ jsx("p", { id: descId, className: "rmod-description", children: description }),
|
|
134
199
|
/* @__PURE__ */ jsx("div", { className: "rmod-body", children }),
|
|
135
200
|
footer && /* @__PURE__ */ jsx("div", { className: "rmod-footer", children: footer })
|
|
136
201
|
]
|
|
@@ -138,7 +203,7 @@ var ModalStyled = forwardRef(
|
|
|
138
203
|
)
|
|
139
204
|
}
|
|
140
205
|
),
|
|
141
|
-
|
|
206
|
+
portalTarget
|
|
142
207
|
);
|
|
143
208
|
}
|
|
144
209
|
);
|
package/dist/styled.js.map
CHANGED
|
@@ -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"],"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 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\";\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}\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 },\n ref,\n ) {\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 // 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 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-state={visible ? \"open\" : \"closed\"}\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 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-state={visible ? \"open\" : \"closed\"}\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"]}
|
package/dist/styles.css
CHANGED
|
@@ -245,3 +245,17 @@
|
|
|
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; }
|
package/package.json
CHANGED