@mshafiqyajid/react-modal 0.0.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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/chunk-BFH4N5GR.js +66 -0
- package/dist/chunk-BFH4N5GR.js.map +1 -0
- package/dist/index.cjs +159 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/styled.cjs +209 -0
- package/dist/styled.cjs.map +1 -0
- package/dist/styled.d.cts +31 -0
- package/dist/styled.d.ts +31 -0
- package/dist/styled.js +148 -0
- package/dist/styled.js.map +1 -0
- package/dist/styles.css +247 -0
- package/package.json +84 -0
package/dist/styled.cjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactDom = require('react-dom');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/styled/ModalStyled.tsx
|
|
8
|
+
var FOCUSABLE_SELECTORS = [
|
|
9
|
+
"a[href]",
|
|
10
|
+
"button:not([disabled])",
|
|
11
|
+
"input:not([disabled])",
|
|
12
|
+
"select:not([disabled])",
|
|
13
|
+
"textarea:not([disabled])",
|
|
14
|
+
"[tabindex]:not([tabindex='-1'])",
|
|
15
|
+
"details > summary"
|
|
16
|
+
].join(", ");
|
|
17
|
+
function getFocusableElements(container) {
|
|
18
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS)).filter(
|
|
19
|
+
(el) => !el.closest("[inert]") && getComputedStyle(el).display !== "none"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function useFocusTrap() {
|
|
23
|
+
const containerRef = react.useRef(null);
|
|
24
|
+
const previousFocusRef = react.useRef(null);
|
|
25
|
+
const activate = react.useCallback((container) => {
|
|
26
|
+
containerRef.current = container;
|
|
27
|
+
previousFocusRef.current = document.activeElement;
|
|
28
|
+
const focusable = getFocusableElements(container);
|
|
29
|
+
const first = focusable[0];
|
|
30
|
+
if (first) {
|
|
31
|
+
first.focus();
|
|
32
|
+
} else {
|
|
33
|
+
container.focus();
|
|
34
|
+
}
|
|
35
|
+
}, []);
|
|
36
|
+
const deactivate = react.useCallback(() => {
|
|
37
|
+
containerRef.current = null;
|
|
38
|
+
const prev = previousFocusRef.current;
|
|
39
|
+
if (prev && typeof prev.focus === "function") {
|
|
40
|
+
prev.focus();
|
|
41
|
+
}
|
|
42
|
+
previousFocusRef.current = null;
|
|
43
|
+
}, []);
|
|
44
|
+
const handleKeyDown = react.useCallback((e) => {
|
|
45
|
+
if (e.key !== "Tab" || !containerRef.current) return;
|
|
46
|
+
const focusable = getFocusableElements(containerRef.current);
|
|
47
|
+
if (focusable.length === 0) {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const first = focusable[0];
|
|
52
|
+
const last = focusable[focusable.length - 1];
|
|
53
|
+
if (e.shiftKey) {
|
|
54
|
+
if (document.activeElement === first) {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
last.focus();
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
if (document.activeElement === last) {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
first.focus();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, []);
|
|
65
|
+
return { activate, deactivate, handleKeyDown };
|
|
66
|
+
}
|
|
67
|
+
var ModalStyled = react.forwardRef(
|
|
68
|
+
function ModalStyled2({
|
|
69
|
+
isOpen,
|
|
70
|
+
onClose,
|
|
71
|
+
title,
|
|
72
|
+
children,
|
|
73
|
+
footer,
|
|
74
|
+
size = "md",
|
|
75
|
+
variant = "dialog",
|
|
76
|
+
closeOnOverlayClick = true,
|
|
77
|
+
closeOnEsc = true,
|
|
78
|
+
showCloseButton = true,
|
|
79
|
+
blur = "md",
|
|
80
|
+
overlayColor,
|
|
81
|
+
padding = "md",
|
|
82
|
+
scrollable = true,
|
|
83
|
+
className
|
|
84
|
+
}, ref) {
|
|
85
|
+
const titleId = react.useId();
|
|
86
|
+
const [mounted, setMounted] = react.useState(false);
|
|
87
|
+
const [rendered, setRendered] = react.useState(false);
|
|
88
|
+
const [visible, setVisible] = react.useState(false);
|
|
89
|
+
const panelRef = react.useRef(null);
|
|
90
|
+
const originalOverflowRef = react.useRef("");
|
|
91
|
+
const exitTimerRef = react.useRef(null);
|
|
92
|
+
const { activate, deactivate, handleKeyDown } = useFocusTrap();
|
|
93
|
+
react.useEffect(() => {
|
|
94
|
+
setMounted(true);
|
|
95
|
+
}, []);
|
|
96
|
+
react.useEffect(() => {
|
|
97
|
+
if (exitTimerRef.current) {
|
|
98
|
+
clearTimeout(exitTimerRef.current);
|
|
99
|
+
exitTimerRef.current = null;
|
|
100
|
+
}
|
|
101
|
+
if (isOpen) {
|
|
102
|
+
setRendered(true);
|
|
103
|
+
requestAnimationFrame(() => {
|
|
104
|
+
requestAnimationFrame(() => {
|
|
105
|
+
setVisible(true);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
109
|
+
document.body.style.overflow = "hidden";
|
|
110
|
+
} else {
|
|
111
|
+
setVisible(false);
|
|
112
|
+
exitTimerRef.current = setTimeout(() => {
|
|
113
|
+
setRendered(false);
|
|
114
|
+
}, 300);
|
|
115
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
116
|
+
}
|
|
117
|
+
}, [isOpen]);
|
|
118
|
+
react.useEffect(() => {
|
|
119
|
+
return () => {
|
|
120
|
+
if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
|
|
121
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
122
|
+
};
|
|
123
|
+
}, []);
|
|
124
|
+
react.useEffect(() => {
|
|
125
|
+
if (isOpen && panelRef.current) {
|
|
126
|
+
activate(panelRef.current);
|
|
127
|
+
} else if (!isOpen) {
|
|
128
|
+
deactivate();
|
|
129
|
+
}
|
|
130
|
+
}, [isOpen, activate, deactivate]);
|
|
131
|
+
const onKeyDown = react.useCallback(
|
|
132
|
+
(e) => {
|
|
133
|
+
if (closeOnEsc && e.key === "Escape") {
|
|
134
|
+
onClose();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
handleKeyDown(e.nativeEvent);
|
|
138
|
+
},
|
|
139
|
+
[closeOnEsc, onClose, handleKeyDown]
|
|
140
|
+
);
|
|
141
|
+
const handleOverlayClick = react.useCallback(
|
|
142
|
+
(e) => {
|
|
143
|
+
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
144
|
+
onClose();
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[closeOnOverlayClick, onClose]
|
|
148
|
+
);
|
|
149
|
+
const setPanelRef = react.useCallback(
|
|
150
|
+
(el) => {
|
|
151
|
+
panelRef.current = el;
|
|
152
|
+
if (typeof ref === "function") ref(el);
|
|
153
|
+
else if (ref) ref.current = el;
|
|
154
|
+
if (el && isOpen) activate(el);
|
|
155
|
+
},
|
|
156
|
+
[ref, isOpen, activate]
|
|
157
|
+
);
|
|
158
|
+
if (!mounted || !rendered) return null;
|
|
159
|
+
const hasHeader = title !== void 0 || showCloseButton;
|
|
160
|
+
return reactDom.createPortal(
|
|
161
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
162
|
+
"div",
|
|
163
|
+
{
|
|
164
|
+
className: [
|
|
165
|
+
"rmod-overlay",
|
|
166
|
+
visible ? "rmod-overlay--visible" : ""
|
|
167
|
+
].filter(Boolean).join(" "),
|
|
168
|
+
"data-variant": variant,
|
|
169
|
+
"data-blur": blur,
|
|
170
|
+
style: overlayColor ? { "--rmod-overlay-bg": overlayColor } : void 0,
|
|
171
|
+
onClick: handleOverlayClick,
|
|
172
|
+
onKeyDown,
|
|
173
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
174
|
+
"div",
|
|
175
|
+
{
|
|
176
|
+
ref: setPanelRef,
|
|
177
|
+
role: "dialog",
|
|
178
|
+
"aria-modal": "true",
|
|
179
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
180
|
+
tabIndex: -1,
|
|
181
|
+
className: [
|
|
182
|
+
"rmod-panel",
|
|
183
|
+
className,
|
|
184
|
+
visible ? "rmod-panel--visible" : ""
|
|
185
|
+
].filter(Boolean).join(" "),
|
|
186
|
+
"data-size": size,
|
|
187
|
+
"data-variant": variant,
|
|
188
|
+
"data-padding": padding,
|
|
189
|
+
"data-scrollable": scrollable ? "true" : void 0,
|
|
190
|
+
children: [
|
|
191
|
+
hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rmod-header", children: [
|
|
192
|
+
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" }) }) })
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-body", children }),
|
|
196
|
+
footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-footer", children: footer })
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
),
|
|
202
|
+
document.body
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
exports.ModalStyled = ModalStyled;
|
|
208
|
+
//# sourceMappingURL=styled.cjs.map
|
|
209
|
+
//# sourceMappingURL=styled.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useFocusTrap.ts","../src/styled/ModalStyled.tsx"],"names":["useRef","useCallback","forwardRef","ModalStyled","useId","useState","useEffect","createPortal","jsx","jsxs"],"mappings":";;;;;;;AAEA,IAAM,mBAAA,GAAsB;AAAA,EAC1B,SAAA;AAAA,EACA,wBAAA;AAAA,EACA,uBAAA;AAAA,EACA,wBAAA;AAAA,EACA,0BAAA;AAAA,EACA,iCAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,IAAI,CAAA;AAEX,SAAS,qBAAqB,SAAA,EAAuC;AACnE,EAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,gBAAA,CAA8B,mBAAmB,CAAC,CAAA,CAAE,MAAA;AAAA,IAC9E,CAAC,EAAA,KAAO,CAAC,EAAA,CAAG,OAAA,CAAQ,SAAS,CAAA,IAAK,gBAAA,CAAiB,EAAE,CAAA,CAAE,OAAA,KAAY;AAAA,GACrE;AACF;AAQO,SAAS,YAAA,GAAmC;AACjD,EAAA,MAAM,YAAA,GAAeA,aAA2B,IAAI,CAAA;AACpD,EAAA,MAAM,gBAAA,GAAmBA,aAA2B,IAAI,CAAA;AAExD,EAAA,MAAM,QAAA,GAAWC,iBAAA,CAAY,CAAC,SAAA,KAA2B;AACvD,IAAA,YAAA,CAAa,OAAA,GAAU,SAAA;AACvB,IAAA,gBAAA,CAAiB,UAAU,QAAA,CAAS,aAAA;AAEpC,IAAA,MAAM,SAAA,GAAY,qBAAqB,SAAS,CAAA;AAChD,IAAA,MAAM,KAAA,GAAQ,UAAU,CAAC,CAAA;AACzB,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,KAAA,CAAM,KAAA,EAAM;AAAA,IACd,CAAA,MAAO;AACL,MAAA,SAAA,CAAU,KAAA,EAAM;AAAA,IAClB;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,kBAAY,MAAM;AACnC,IAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,IAAA,MAAM,OAAO,gBAAA,CAAiB,OAAA;AAC9B,IAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,CAAK,KAAA,KAAU,UAAA,EAAY;AAC5C,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,IACb;AACA,IAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAAA,EAC7B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,iBAAA,CAAY,CAAC,CAAA,KAAqB;AACtD,IAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,KAAA,IAAS,CAAC,aAAa,OAAA,EAAS;AAE9C,IAAA,MAAM,SAAA,GAAY,oBAAA,CAAqB,YAAA,CAAa,OAAO,CAAA;AAC3D,IAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAQ,UAAU,CAAC,CAAA;AACzB,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,SAAA,CAAU,MAAA,GAAS,CAAC,CAAA;AAE3C,IAAA,IAAI,EAAE,QAAA,EAAU;AACd,MAAA,IAAI,QAAA,CAAS,kBAAkB,KAAA,EAAO;AACpC,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb;AAAA,IACF,CAAA,MAAO;AACL,MAAA,IAAI,QAAA,CAAS,kBAAkB,IAAA,EAAM;AACnC,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,KAAA,CAAM,KAAA,EAAM;AAAA,MACd;AAAA,IACF;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,QAAA,EAAU,UAAA,EAAY,aAAA,EAAc;AAC/C;ACrCO,IAAM,WAAA,GAAcC,gBAAA;AAAA,EACzB,SAASC,YAAAA,CACP;AAAA,IACE,MAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA,GAAO,IAAA;AAAA,IACP,OAAA,GAAU,QAAA;AAAA,IACV,mBAAA,GAAsB,IAAA;AAAA,IACtB,UAAA,GAAa,IAAA;AAAA,IACb,eAAA,GAAkB,IAAA;AAAA,IAClB,IAAA,GAAO,IAAA;AAAA,IACP,YAAA;AAAA,IACA,OAAA,GAAU,IAAA;AAAA,IACV,UAAA,GAAa,IAAA;AAAA,IACb;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,UAAUC,WAAA,EAAM;AACtB,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIC,eAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC9C,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,QAAA,GAAWL,aAA8B,IAAI,CAAA;AACnD,IAAA,MAAM,mBAAA,GAAsBA,aAAO,EAAE,CAAA;AACrC,IAAA,MAAM,YAAA,GAAeA,aAA6C,IAAI,CAAA;AACtE,IAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAY,aAAA,KAAkB,YAAA,EAAa;AAE7D,IAAAM,eAAA,CAAU,MAAM;AAAE,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA,IAAG,CAAA,EAAG,EAAE,CAAA;AAEzC,IAAAA,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,aAAa,OAAA,EAAS;AACxB,QAAA,YAAA,CAAa,aAAa,OAAO,CAAA;AACjC,QAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AAAA,MACzB;AAEA,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,WAAA,CAAY,IAAI,CAAA;AAEhB,QAAA,qBAAA,CAAsB,MAAM;AAC1B,UAAA,qBAAA,CAAsB,MAAM;AAC1B,YAAA,UAAA,CAAW,IAAI,CAAA;AAAA,UACjB,CAAC,CAAA;AAAA,QACH,CAAC,CAAA;AACD,QAAA,mBAAA,CAAoB,OAAA,GAAU,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,QAAA;AAClD,QAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,QAAA;AAAA,MACjC,CAAA,MAAO;AAEL,QAAA,UAAA,CAAW,KAAK,CAAA;AAEhB,QAAA,YAAA,CAAa,OAAA,GAAU,WAAW,MAAM;AACtC,UAAA,WAAA,CAAY,KAAK,CAAA;AAAA,QACnB,GAAG,GAAG,CAAA;AACN,QAAA,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,QAAA,GAAW,mBAAA,CAAoB,OAAA;AAAA,MACrD;AAAA,IACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAGX,IAAAA,eAAA,CAAU,MAAM;AACd,MAAA,OAAO,MAAM;AACX,QAAA,IAAI,YAAA,CAAa,OAAA,EAAS,YAAA,CAAa,YAAA,CAAa,OAAO,CAAA;AAC3D,QAAA,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,QAAA,GAAW,mBAAA,CAAoB,OAAA;AAAA,MACrD,CAAA;AAAA,IACF,CAAA,EAAG,EAAE,CAAA;AAEL,IAAAA,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,MAAA,IAAU,SAAS,OAAA,EAAS;AAC9B,QAAA,QAAA,CAAS,SAAS,OAAO,CAAA;AAAA,MAC3B,CAAA,MAAA,IAAW,CAAC,MAAA,EAAQ;AAClB,QAAA,UAAA,EAAW;AAAA,MACb;AAAA,IACF,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,UAAU,CAAC,CAAA;AAEjC,IAAA,MAAM,SAAA,GAAYL,iBAAAA;AAAA,MAChB,CAAC,CAAA,KAA2B;AAC1B,QAAA,IAAI,UAAA,IAAc,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU;AACpC,UAAA,OAAA,EAAQ;AACR,UAAA;AAAA,QACF;AACA,QAAA,aAAA,CAAc,EAAE,WAAW,CAAA;AAAA,MAC7B,CAAA;AAAA,MACA,CAAC,UAAA,EAAY,OAAA,EAAS,aAAa;AAAA,KACrC;AAEA,IAAA,MAAM,kBAAA,GAAqBA,iBAAAA;AAAA,MACzB,CAAC,CAAA,KAAwC;AACvC,QAAA,IAAI,mBAAA,IAAuB,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,aAAA,EAAe;AACvD,UAAA,OAAA,EAAQ;AAAA,QACV;AAAA,MACF,CAAA;AAAA,MACA,CAAC,qBAAqB,OAAO;AAAA,KAC/B;AAEA,IAAA,MAAM,WAAA,GAAcA,iBAAAA;AAAA,MAClB,CAAC,EAAA,KAA8B;AAC7B,QAAA,QAAA,CAAS,OAAA,GAAU,EAAA;AACnB,QAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,GAAA,CAAI,EAAE,CAAA;AAAA,aAAA,IAC5B,GAAA,EAAM,GAAA,CAAsD,OAAA,GAAU,EAAA;AAC/E,QAAA,IAAI,EAAA,IAAM,MAAA,EAAQ,QAAA,CAAS,EAAE,CAAA;AAAA,MAC/B,CAAA;AAAA,MACA,CAAC,GAAA,EAAK,MAAA,EAAQ,QAAQ;AAAA,KACxB;AAEA,IAAA,IAAI,CAAC,OAAA,IAAW,CAAC,QAAA,EAAU,OAAO,IAAA;AAElC,IAAA,MAAM,SAAA,GAAY,UAAU,MAAA,IAAa,eAAA;AAEzC,IAAA,OAAOM,qBAAA;AAAA,sBACLC,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAW;AAAA,YACT,cAAA;AAAA,YACA,UAAU,uBAAA,GAA0B;AAAA,WACtC,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,UAC1B,cAAA,EAAc,OAAA;AAAA,UACd,WAAA,EAAW,IAAA;AAAA,UACX,KAAA,EAAO,YAAA,GAAe,EAAE,mBAAA,EAAqB,cAAa,GAA2B,MAAA;AAAA,UACrF,OAAA,EAAS,kBAAA;AAAA,UACT,SAAA;AAAA,UAEA,QAAA,kBAAAC,eAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,WAAA;AAAA,cACL,IAAA,EAAK,QAAA;AAAA,cACL,YAAA,EAAW,MAAA;AAAA,cACX,iBAAA,EAAiB,QAAQ,OAAA,GAAU,MAAA;AAAA,cACnC,QAAA,EAAU,EAAA;AAAA,cACV,SAAA,EAAW;AAAA,gBACT,YAAA;AAAA,gBACA,SAAA;AAAA,gBACA,UAAU,qBAAA,GAAwB;AAAA,eACpC,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,cAC1B,WAAA,EAAW,IAAA;AAAA,cACX,cAAA,EAAc,OAAA;AAAA,cACd,cAAA,EAAc,OAAA;AAAA,cACd,iBAAA,EAAiB,aAAa,MAAA,GAAS,MAAA;AAAA,cAEtC,QAAA,EAAA;AAAA,gBAAA,SAAA,oBACCA,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EACZ,QAAA,EAAA;AAAA,kBAAA,KAAA,mBACCD,cAAA,CAAC,QAAG,EAAA,EAAI,OAAA,EAAS,WAAU,YAAA,EAAc,QAAA,EAAA,KAAA,EAAM,CAAA,mBAE/CA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA;AAAA,kBAEP,eAAA,oBACCA,cAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,UAAS,SAAA,EAAU,YAAA,EAAa,YAAA,EAAW,OAAA,EAAQ,SAAS,OAAA,EACvE,QAAA,kBAAAA,cAAA,CAAC,KAAA,EAAA,EAAI,aAAA,EAAY,QAAO,KAAA,EAAM,IAAA,EAAK,MAAA,EAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EACtE,yCAAC,MAAA,EAAA,EAAK,CAAA,EAAE,oBAAA,EAAqB,MAAA,EAAO,gBAAe,WAAA,EAAY,MAAA,EAAO,aAAA,EAAc,OAAA,EAAQ,GAC9F,CAAA,EACF;AAAA,iBAAA,EAEJ,CAAA;AAAA,gCAEFA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EAAa,QAAA,EAAS,CAAA;AAAA,gBACpC,MAAA,oBAAUA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAe,QAAA,EAAA,MAAA,EAAO;AAAA;AAAA;AAAA;AAClD;AAAA,OACF;AAAA,MACA,QAAA,CAAS;AAAA,KACX;AAAA,EACF;AACF","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"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type ModalSize = "sm" | "md" | "lg" | "full";
|
|
5
|
+
type ModalVariant = "dialog" | "drawer-left" | "drawer-right" | "drawer-bottom";
|
|
6
|
+
interface ModalStyledProps {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
/** Modal heading — omit to hide the header entirely */
|
|
10
|
+
title?: ReactNode;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
/** Footer content — omit to hide footer */
|
|
13
|
+
footer?: ReactNode;
|
|
14
|
+
size?: ModalSize;
|
|
15
|
+
variant?: ModalVariant;
|
|
16
|
+
closeOnOverlayClick?: boolean;
|
|
17
|
+
closeOnEsc?: boolean;
|
|
18
|
+
showCloseButton?: boolean;
|
|
19
|
+
/** Backdrop blur intensity. Default: "md" */
|
|
20
|
+
blur?: "none" | "sm" | "md" | "lg";
|
|
21
|
+
/** Custom overlay color e.g. "rgba(0,0,0,0.6)" */
|
|
22
|
+
overlayColor?: string;
|
|
23
|
+
/** Padding inside the body. Default: "md" */
|
|
24
|
+
padding?: "none" | "sm" | "md" | "lg";
|
|
25
|
+
/** Max height of scrollable body. Default: auto */
|
|
26
|
+
scrollable?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
|
|
31
|
+
export { type ModalSize, ModalStyled, type ModalStyledProps, type ModalVariant };
|
package/dist/styled.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type ModalSize = "sm" | "md" | "lg" | "full";
|
|
5
|
+
type ModalVariant = "dialog" | "drawer-left" | "drawer-right" | "drawer-bottom";
|
|
6
|
+
interface ModalStyledProps {
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
/** Modal heading — omit to hide the header entirely */
|
|
10
|
+
title?: ReactNode;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
/** Footer content — omit to hide footer */
|
|
13
|
+
footer?: ReactNode;
|
|
14
|
+
size?: ModalSize;
|
|
15
|
+
variant?: ModalVariant;
|
|
16
|
+
closeOnOverlayClick?: boolean;
|
|
17
|
+
closeOnEsc?: boolean;
|
|
18
|
+
showCloseButton?: boolean;
|
|
19
|
+
/** Backdrop blur intensity. Default: "md" */
|
|
20
|
+
blur?: "none" | "sm" | "md" | "lg";
|
|
21
|
+
/** Custom overlay color e.g. "rgba(0,0,0,0.6)" */
|
|
22
|
+
overlayColor?: string;
|
|
23
|
+
/** Padding inside the body. Default: "md" */
|
|
24
|
+
padding?: "none" | "sm" | "md" | "lg";
|
|
25
|
+
/** Max height of scrollable body. Default: auto */
|
|
26
|
+
scrollable?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
|
|
31
|
+
export { type ModalSize, ModalStyled, type ModalStyledProps, type ModalVariant };
|
package/dist/styled.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { useFocusTrap } from './chunk-BFH4N5GR.js';
|
|
2
|
+
import { forwardRef, useId, useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var ModalStyled = forwardRef(
|
|
7
|
+
function ModalStyled2({
|
|
8
|
+
isOpen,
|
|
9
|
+
onClose,
|
|
10
|
+
title,
|
|
11
|
+
children,
|
|
12
|
+
footer,
|
|
13
|
+
size = "md",
|
|
14
|
+
variant = "dialog",
|
|
15
|
+
closeOnOverlayClick = true,
|
|
16
|
+
closeOnEsc = true,
|
|
17
|
+
showCloseButton = true,
|
|
18
|
+
blur = "md",
|
|
19
|
+
overlayColor,
|
|
20
|
+
padding = "md",
|
|
21
|
+
scrollable = true,
|
|
22
|
+
className
|
|
23
|
+
}, ref) {
|
|
24
|
+
const titleId = useId();
|
|
25
|
+
const [mounted, setMounted] = useState(false);
|
|
26
|
+
const [rendered, setRendered] = useState(false);
|
|
27
|
+
const [visible, setVisible] = useState(false);
|
|
28
|
+
const panelRef = useRef(null);
|
|
29
|
+
const originalOverflowRef = useRef("");
|
|
30
|
+
const exitTimerRef = useRef(null);
|
|
31
|
+
const { activate, deactivate, handleKeyDown } = useFocusTrap();
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setMounted(true);
|
|
34
|
+
}, []);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (exitTimerRef.current) {
|
|
37
|
+
clearTimeout(exitTimerRef.current);
|
|
38
|
+
exitTimerRef.current = null;
|
|
39
|
+
}
|
|
40
|
+
if (isOpen) {
|
|
41
|
+
setRendered(true);
|
|
42
|
+
requestAnimationFrame(() => {
|
|
43
|
+
requestAnimationFrame(() => {
|
|
44
|
+
setVisible(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
48
|
+
document.body.style.overflow = "hidden";
|
|
49
|
+
} else {
|
|
50
|
+
setVisible(false);
|
|
51
|
+
exitTimerRef.current = setTimeout(() => {
|
|
52
|
+
setRendered(false);
|
|
53
|
+
}, 300);
|
|
54
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
55
|
+
}
|
|
56
|
+
}, [isOpen]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
return () => {
|
|
59
|
+
if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
|
|
60
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
61
|
+
};
|
|
62
|
+
}, []);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (isOpen && panelRef.current) {
|
|
65
|
+
activate(panelRef.current);
|
|
66
|
+
} else if (!isOpen) {
|
|
67
|
+
deactivate();
|
|
68
|
+
}
|
|
69
|
+
}, [isOpen, activate, deactivate]);
|
|
70
|
+
const onKeyDown = useCallback(
|
|
71
|
+
(e) => {
|
|
72
|
+
if (closeOnEsc && e.key === "Escape") {
|
|
73
|
+
onClose();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
handleKeyDown(e.nativeEvent);
|
|
77
|
+
},
|
|
78
|
+
[closeOnEsc, onClose, handleKeyDown]
|
|
79
|
+
);
|
|
80
|
+
const handleOverlayClick = useCallback(
|
|
81
|
+
(e) => {
|
|
82
|
+
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
83
|
+
onClose();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[closeOnOverlayClick, onClose]
|
|
87
|
+
);
|
|
88
|
+
const setPanelRef = useCallback(
|
|
89
|
+
(el) => {
|
|
90
|
+
panelRef.current = el;
|
|
91
|
+
if (typeof ref === "function") ref(el);
|
|
92
|
+
else if (ref) ref.current = el;
|
|
93
|
+
if (el && isOpen) activate(el);
|
|
94
|
+
},
|
|
95
|
+
[ref, isOpen, activate]
|
|
96
|
+
);
|
|
97
|
+
if (!mounted || !rendered) return null;
|
|
98
|
+
const hasHeader = title !== void 0 || showCloseButton;
|
|
99
|
+
return createPortal(
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
"div",
|
|
102
|
+
{
|
|
103
|
+
className: [
|
|
104
|
+
"rmod-overlay",
|
|
105
|
+
visible ? "rmod-overlay--visible" : ""
|
|
106
|
+
].filter(Boolean).join(" "),
|
|
107
|
+
"data-variant": variant,
|
|
108
|
+
"data-blur": blur,
|
|
109
|
+
style: overlayColor ? { "--rmod-overlay-bg": overlayColor } : void 0,
|
|
110
|
+
onClick: handleOverlayClick,
|
|
111
|
+
onKeyDown,
|
|
112
|
+
children: /* @__PURE__ */ jsxs(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
ref: setPanelRef,
|
|
116
|
+
role: "dialog",
|
|
117
|
+
"aria-modal": "true",
|
|
118
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
119
|
+
tabIndex: -1,
|
|
120
|
+
className: [
|
|
121
|
+
"rmod-panel",
|
|
122
|
+
className,
|
|
123
|
+
visible ? "rmod-panel--visible" : ""
|
|
124
|
+
].filter(Boolean).join(" "),
|
|
125
|
+
"data-size": size,
|
|
126
|
+
"data-variant": variant,
|
|
127
|
+
"data-padding": padding,
|
|
128
|
+
"data-scrollable": scrollable ? "true" : void 0,
|
|
129
|
+
children: [
|
|
130
|
+
hasHeader && /* @__PURE__ */ jsxs("div", { className: "rmod-header", children: [
|
|
131
|
+
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" }) }) })
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "rmod-body", children }),
|
|
135
|
+
footer && /* @__PURE__ */ jsx("div", { className: "rmod-footer", children: footer })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
document.body
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
export { ModalStyled };
|
|
147
|
+
//# sourceMappingURL=styled.js.map
|
|
148
|
+
//# sourceMappingURL=styled.js.map
|
|
@@ -0,0 +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"]}
|