custome-modal 1.0.10 → 1.0.11

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Custom Modal
2
2
 
3
- Lightweight modal component for React. Focused on rendering any React children, with simple API and optional imperative control.
3
+ Lightweight modal component for React. Make you own modal component with your favorite css framework or style (TailwindCSS) or without any framework.
4
4
 
5
5
  ## Install
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "custome-modal",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Custom modal component for react",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -12,12 +12,12 @@
12
12
  "default": "./src/index.js"
13
13
  },
14
14
  "./react": {
15
- "types": "./src/react-modal.d.ts",
16
- "default": "./src/react-modal.js"
15
+ "types": "./src/react/modal.d.ts",
16
+ "default": "./src/react/modal.js"
17
17
  }
18
18
  },
19
19
  "publishConfig": {
20
- "@firstzxd:registry": "https://npm.pkg.github.com"
20
+ "registry": "https://registry.npmjs.org"
21
21
  },
22
22
  "repository": {
23
23
  "url": "https://github.com/First1zaza/CustomeModal.git"
@@ -1,244 +1,228 @@
1
- const template = document.createElement("template");
2
-
3
- template.innerHTML = `
4
- <style>
5
- :host {
6
- --cm-overlay-bg: rgba(0, 0, 0, 0.55);
7
- --cm-bg: #ffffff;
8
- --cm-text: #1a1a1a;
9
- --cm-radius: 16px;
10
- --cm-width: 720px;
11
- --cm-max-width: 92vw;
12
- --cm-max-height: 85vh;
13
- --cm-padding: 12px;
14
- --cm-shadow: 0 30px 80px rgba(0, 0, 0, 0.22);
15
- --cm-z-index: 1000;
16
- --cm-anim-duration: 180ms;
17
- --cm-align-items: center;
18
- --cm-justify-content: center;
19
-
20
- display: none;
21
- position: fixed;
22
- inset: 0;
23
- z-index: var(--cm-z-index);
24
- font: inherit;
25
- color: var(--cm-text);
26
- }
27
-
28
- :host([open]) {
29
- display: block;
30
- }
31
-
32
- .overlay {
33
- position: absolute;
34
- inset: 0;
35
- display: flex;
36
- align-items: var(--cm-align-items);
37
- justify-content: var(--cm-justify-content);
38
- padding: var(--cm-padding);
39
- background: var(--cm-overlay-bg);
40
- opacity: 0;
41
- transition: opacity var(--cm-anim-duration) ease;
42
- }
43
-
44
- :host([open]) .overlay {
45
- opacity: 1;
46
- }
47
-
48
- .dialog {
49
- position: relative;
50
- width: var(--cm-width);
51
- max-width: var(--cm-max-width);
52
- max-height: var(--cm-max-height);
53
- overflow: auto;
54
- background: var(--cm-bg);
55
- border-radius: var(--cm-radius);
56
- box-shadow: var(--cm-shadow);
57
- transform: translateY(10px) scale(0.98);
58
- opacity: 0;
59
- transition: transform var(--cm-anim-duration) ease, opacity var(--cm-anim-duration) ease;
60
- outline: none;
61
- }
62
-
63
- :host([open]) .dialog {
64
- transform: translateY(0) scale(1);
65
- opacity: 1;
66
- }
67
- </style>
68
-
69
- <div class="overlay" part="overlay">
70
- <div class="dialog" part="dialog" role="dialog" aria-modal="true" tabindex="-1">
71
- <slot></slot>
72
- </div>
73
- </div>
74
- `;
75
-
76
- class CustomModal extends HTMLElement {
77
- static get observedAttributes() {
78
- return ["open", "width", "align", "justify", "dismiss-on-backdrop"];
79
- }
80
-
81
- constructor() {
82
- super();
83
- this.attachShadow({ mode: "open" });
84
- this.shadowRoot.appendChild(template.content.cloneNode(true));
85
-
86
- this._overlayEl = this.shadowRoot.querySelector(".overlay");
87
- this._dialogEl = this.shadowRoot.querySelector(".dialog");
88
-
89
- this._handleOverlayClick = this._handleOverlayClick.bind(this);
90
- }
91
-
92
- connectedCallback() {
93
- this._upgradeProperty("open");
94
- this._upgradeProperty("width");
95
- this._upgradeProperty("align");
96
- this._upgradeProperty("justify");
97
- this._upgradeProperty("dismissOnBackdrop");
98
-
99
- this._overlayEl.addEventListener("click", this._handleOverlayClick);
100
- this._syncLayout();
101
- }
102
-
103
- disconnectedCallback() {
104
- this._overlayEl.removeEventListener("click", this._handleOverlayClick);
105
- }
106
-
107
- attributeChangedCallback(name, oldValue, newValue) {
108
- if (oldValue === newValue) {
109
- return;
110
- }
111
-
112
- if (name === "open") {
113
- if (this.open) {
114
- this._onOpen();
115
- } else {
116
- this._onClose();
117
- }
118
- return;
119
- }
120
-
121
- if (name === "width" || name === "align" || name === "justify") {
122
- this._syncLayout();
123
- }
124
- }
125
-
126
- get open() {
127
- return this.hasAttribute("open");
128
- }
129
-
130
- set open(value) {
131
- if (value) {
132
- this.setAttribute("open", "");
133
- } else {
134
- this.removeAttribute("open");
135
- }
136
- }
137
-
138
- openModal() {
139
- this.open = true;
140
- }
141
-
142
- closeModal() {
143
- this.open = false;
144
- }
145
-
146
- get dismissOnBackdrop() {
147
- return !this._isFalseAttr("dismiss-on-backdrop");
148
- }
149
-
150
- set dismissOnBackdrop(value) {
151
- if (value === false) {
152
- this.setAttribute("dismiss-on-backdrop", "false");
153
- } else {
154
- this.removeAttribute("dismiss-on-backdrop");
155
- }
156
- }
157
-
158
- _upgradeProperty(prop) {
159
- if (Object.prototype.hasOwnProperty.call(this, prop)) {
160
- const value = this[prop];
161
- delete this[prop];
162
- this[prop] = value;
163
- }
164
- }
165
-
166
- _isFalseAttr(name) {
167
- const attr = this.getAttribute(name);
168
- return attr !== null && attr.toLowerCase() === "false";
169
- }
170
-
171
- _handleOverlayClick(event) {
172
- if (event.target !== this._overlayEl) {
173
- return;
174
- }
175
- if (this.dismissOnBackdrop) {
176
- this.closeModal();
177
- }
178
- }
179
-
180
- _onOpen() {
181
- this._lastActive = document.activeElement;
182
- this.dispatchEvent(new CustomEvent("modal-opened", { bubbles: true }));
183
-
184
- window.requestAnimationFrame(() => {
185
- this._dialogEl.focus();
186
- });
187
- }
188
-
189
- _onClose() {
190
- this.dispatchEvent(new CustomEvent("modal-closed", { bubbles: true }));
191
-
192
- if (this._lastActive && typeof this._lastActive.focus === "function") {
193
- this._lastActive.focus();
194
- }
195
- }
196
-
197
- _syncLayout() {
198
- const align = (this.getAttribute("align") || "center").toLowerCase();
199
- const justify = (this.getAttribute("justify") || "center").toLowerCase();
200
- const width = (this.getAttribute("width") || "lg").toLowerCase();
201
-
202
- const alignMap = {
203
- left: "flex-start",
204
- center: "center",
205
- right: "flex-end"
206
- };
207
-
208
- const justifyMap = {
209
- top: "flex-start",
210
- center: "center",
211
- bottom: "flex-end"
212
- };
213
-
214
- const widthMap = {
215
- sm: "300px",
216
- md: "500px",
217
- lg: "720px",
218
- xl: "960px",
219
- "2xl": "1140px",
220
- "3xl": "1280px",
221
- "4xl": "1440px",
222
- "5xl": "1600px"
223
- };
224
-
225
- const widthValue = widthMap[width] || width;
226
- this.style.setProperty("--cm-align-items", alignMap[align] || "center");
227
- this.style.setProperty("--cm-justify-content", justifyMap[justify] || "center");
228
- this.style.setProperty("--cm-width", this._formatWidth(widthValue));
229
- }
230
-
231
- _formatWidth(value) {
232
- if (!value) {
233
- return "720px";
234
- }
235
- if (/^\d+$/.test(value)) {
236
- return `${value}px`;
237
- }
238
- return value;
239
- }
240
- }
241
-
242
- customElements.define("custom-modal", CustomModal);
243
-
244
- export { CustomModal };
1
+ const template = document.createElement("template");
2
+
3
+ template.innerHTML = `
4
+ <style>
5
+ :host {
6
+ --cm-overlay-bg: rgba(0, 0, 0, 0.5);
7
+ --cm-width: 720px;
8
+ --cm-max-width: 92vw;
9
+ --cm-max-height: 90vh;
10
+ --cm-align-items: center;
11
+ --cm-justify-content: center;
12
+ --cm-z-index: 1000;
13
+
14
+ display: none;
15
+ position: fixed;
16
+ inset: 0;
17
+ z-index: var(--cm-z-index);
18
+ font: inherit;
19
+ }
20
+
21
+ :host([open]) {
22
+ display: flex;
23
+ align-items: var(--cm-align-items);
24
+ justify-content: var(--cm-justify-content);
25
+ padding: 12px;
26
+ }
27
+
28
+ .overlay {
29
+ position: absolute;
30
+ inset: 0;
31
+ background: var(--cm-overlay-bg);
32
+ }
33
+
34
+ .dialog {
35
+ position: relative;
36
+ z-index: 1;
37
+ width: var(--cm-width);
38
+ max-width: var(--cm-max-width);
39
+ max-height: var(--cm-max-height);
40
+ overflow: auto;
41
+ outline: none;
42
+ }
43
+ </style>
44
+
45
+ <div class="overlay" part="overlay"></div>
46
+ <div class="dialog" part="dialog" role="dialog" aria-modal="true" tabindex="-1">
47
+ <slot></slot>
48
+ </div>
49
+ `;
50
+
51
+ class CustomModal extends HTMLElement {
52
+ static get observedAttributes() {
53
+ return ["open", "width", "align", "justify", "dismiss-on-backdrop"];
54
+ }
55
+
56
+ constructor() {
57
+ super();
58
+ this.attachShadow({ mode: "open" });
59
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
60
+
61
+ this._overlayEl = this.shadowRoot.querySelector(".overlay");
62
+ this._dialogEl = this.shadowRoot.querySelector(".dialog");
63
+
64
+ this._handleOverlayClick = this._handleOverlayClick.bind(this);
65
+ this._handleKeyDown = this._handleKeyDown.bind(this);
66
+ }
67
+
68
+ connectedCallback() {
69
+ this._upgradeProperty("open");
70
+ this._upgradeProperty("width");
71
+ this._upgradeProperty("align");
72
+ this._upgradeProperty("justify");
73
+ this._upgradeProperty("dismissOnBackdrop");
74
+
75
+ this._overlayEl.addEventListener("click", this._handleOverlayClick);
76
+ this._syncLayout();
77
+ }
78
+
79
+ disconnectedCallback() {
80
+ this._overlayEl.removeEventListener("click", this._handleOverlayClick);
81
+ if (this._hasScrollLock) {
82
+ this._releaseScrollLock();
83
+ }
84
+ }
85
+
86
+ attributeChangedCallback(name, oldValue, newValue) {
87
+ if (oldValue === newValue) return;
88
+
89
+ if (name === "open") {
90
+ if (this.hasAttribute("open")) {
91
+ this._onOpen();
92
+ } else {
93
+ this._onClose();
94
+ }
95
+ return;
96
+ }
97
+
98
+ if (name === "width" || name === "align" || name === "justify") {
99
+ this._syncLayout();
100
+ }
101
+ }
102
+
103
+ get open() {
104
+ return this.hasAttribute("open");
105
+ }
106
+
107
+ set open(value) {
108
+ if (value) {
109
+ this.setAttribute("open", "");
110
+ } else {
111
+ this.removeAttribute("open");
112
+ }
113
+ }
114
+
115
+ openModal() {
116
+ this.open = true;
117
+ }
118
+
119
+ closeModal() {
120
+ this.open = false;
121
+ }
122
+
123
+ get dismissOnBackdrop() {
124
+ return !this._isFalseAttr("dismiss-on-backdrop");
125
+ }
126
+
127
+ set dismissOnBackdrop(value) {
128
+ if (value === false) {
129
+ this.setAttribute("dismiss-on-backdrop", "false");
130
+ } else {
131
+ this.removeAttribute("dismiss-on-backdrop");
132
+ }
133
+ }
134
+
135
+ _upgradeProperty(prop) {
136
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
137
+ const value = this[prop];
138
+ delete this[prop];
139
+ this[prop] = value;
140
+ }
141
+ }
142
+
143
+ _isFalseAttr(name) {
144
+ const attr = this.getAttribute(name);
145
+ return attr !== null && attr.toLowerCase() === "false";
146
+ }
147
+
148
+ _handleOverlayClick() {
149
+ if (this.dismissOnBackdrop) {
150
+ this.closeModal();
151
+ }
152
+ }
153
+
154
+ _handleKeyDown(event) {
155
+ if (event.key === "Escape" && this.open) {
156
+ this.closeModal();
157
+ }
158
+ }
159
+
160
+ _acquireScrollLock() {
161
+ if (window._cmScrollLocks === undefined) {
162
+ window._cmScrollLocks = 0;
163
+ }
164
+ if (window._cmScrollLocks === 0) {
165
+ window._cmPrevOverflow = document.body.style.overflow;
166
+ document.body.style.overflow = "hidden";
167
+ }
168
+ window._cmScrollLocks++;
169
+ this._hasScrollLock = true;
170
+ }
171
+
172
+ _releaseScrollLock() {
173
+ if (!this._hasScrollLock) return;
174
+
175
+ window._cmScrollLocks--;
176
+ this._hasScrollLock = false;
177
+
178
+ if (window._cmScrollLocks === 0 && window._cmPrevOverflow !== undefined) {
179
+ document.body.style.overflow = window._cmPrevOverflow;
180
+ }
181
+ }
182
+
183
+ _onOpen() {
184
+ this._lastActive = document.activeElement;
185
+ document.addEventListener("keydown", this._handleKeyDown);
186
+ this._acquireScrollLock();
187
+
188
+ this.dispatchEvent(new CustomEvent("modal-opened", { bubbles: true }));
189
+
190
+ window.requestAnimationFrame(() => {
191
+ this._dialogEl.focus();
192
+ });
193
+ }
194
+
195
+ _onClose() {
196
+ document.removeEventListener("keydown", this._handleKeyDown);
197
+ this._releaseScrollLock();
198
+
199
+ this.dispatchEvent(new CustomEvent("modal-closed", { bubbles: true }));
200
+
201
+ if (this._lastActive && typeof this._lastActive.focus === "function") {
202
+ this._lastActive.focus();
203
+ }
204
+ }
205
+
206
+ _syncLayout() {
207
+ const align = (this.getAttribute("align") || "center").toLowerCase();
208
+ const justify = (this.getAttribute("justify") || "center").toLowerCase();
209
+ const width = (this.getAttribute("width") || "lg").toLowerCase();
210
+
211
+ const alignMap = { left: "flex-start", center: "center", right: "flex-end" };
212
+ const justifyMap = { top: "flex-start", center: "center", bottom: "flex-end" };
213
+ const widthMap = {
214
+ sm: "300px", md: "500px", lg: "720px", xl: "960px",
215
+ "2xl": "1140px", "3xl": "1280px", "4xl": "1440px", "5xl": "1600px"
216
+ };
217
+
218
+ const widthValue = widthMap[width] || width || "720px";
219
+
220
+ this.style.setProperty("--cm-align-items", alignMap[align] || "center");
221
+ this.style.setProperty("--cm-justify-content", justifyMap[justify] || "center");
222
+ this.style.setProperty("--cm-width", /^\d+$/.test(widthValue) ? `${widthValue}px` : widthValue);
223
+ }
224
+ }
225
+
226
+ customElements.define("custom-modal", CustomModal);
227
+
228
+ export { CustomModal };
@@ -7,19 +7,19 @@ type Justify = "top" | "center" | "bottom";
7
7
  type Width = "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | number | string;
8
8
 
9
9
  export type ModalProps = {
10
- children?: ReactNode;
11
- dismissOnBackdrop?: boolean;
12
- align?: Align;
13
- justify?: Justify;
14
- width?: Width;
10
+ children?: ReactNode;
11
+ dismissOnBackdrop?: boolean;
12
+ align?: Align;
13
+ justify?: Justify;
14
+ width?: Width;
15
15
  };
16
16
 
17
17
  export function Modal(props: ModalProps): JSX.Element | null;
18
18
 
19
19
  export function useModal(options?: Partial<ModalProps>): {
20
- open: boolean;
21
- setOpen: (value: boolean) => void;
22
- openModal: () => void;
23
- closeModal: () => void;
24
- Modal: (props: ModalProps) => JSX.Element | null;
20
+ open: boolean;
21
+ setOpen: (value: boolean) => void;
22
+ openModal: () => void;
23
+ closeModal: () => void;
24
+ Modal: (props: ModalProps) => JSX.Element | null;
25
25
  };
@@ -0,0 +1,102 @@
1
+ import { useEffect, useRef, useState, createElement } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ const ALIGN_MAP = { left: "flex-start", center: "center", right: "flex-end" };
5
+ const JUSTIFY_MAP = { top: "flex-start", center: "center", bottom: "flex-end" };
6
+ const WIDTH_MAP = {
7
+ sm: "300px", md: "500px", lg: "720px", xl: "960px",
8
+ "2xl": "1140px", "3xl": "1280px", "4xl": "1440px", "5xl": "1600px"
9
+ };
10
+
11
+ let scrollLocks = 0;
12
+ let prevOverflow = "";
13
+
14
+ export function Modal({
15
+ children,
16
+ dismissOnBackdrop = true,
17
+ align = "center",
18
+ justify = "center",
19
+ width = "lg",
20
+ _open = false,
21
+ _onClose
22
+ }) {
23
+ useEffect(() => {
24
+ if (!_open) return;
25
+
26
+ if (scrollLocks === 0) {
27
+ prevOverflow = document.body.style.overflow;
28
+ document.body.style.overflow = "hidden";
29
+ }
30
+ scrollLocks++;
31
+
32
+ const onEsc = (e) => e.key === "Escape" && _onClose?.();
33
+ document.addEventListener("keydown", onEsc);
34
+
35
+ return () => {
36
+ scrollLocks--;
37
+ if (scrollLocks === 0 && prevOverflow !== undefined) {
38
+ document.body.style.overflow = prevOverflow;
39
+ }
40
+ document.removeEventListener("keydown", onEsc);
41
+ };
42
+ }, [_open, _onClose]);
43
+
44
+ if (!_open) return null;
45
+
46
+ const alignItems = ALIGN_MAP[align] || "center";
47
+ const justifyContent = JUSTIFY_MAP[justify] || "center";
48
+ const widthValue = typeof width === "number" ? `${width}px` : (WIDTH_MAP[width] || width || "720px");
49
+
50
+ return createPortal(
51
+ createElement("div", {
52
+ style: {
53
+ position: "fixed", inset: 0, zIndex: 1000, display: "flex",
54
+ alignItems, justifyContent, padding: "12px"
55
+ }
56
+ },
57
+ createElement("div", {
58
+ style: { position: "absolute", inset: 0, background: "rgba(0, 0, 0, 0.5)" },
59
+ onClick: () => dismissOnBackdrop && _onClose?.()
60
+ }),
61
+ createElement("div", {
62
+ role: "dialog", "aria-modal": "true",
63
+ style: {
64
+ position: "relative",
65
+ width: widthValue, maxWidth: "92vw", maxHeight: "90vh", overflow: "auto"
66
+ },
67
+ onClick: (e) => e.stopPropagation()
68
+ }, children)
69
+ ),
70
+ document.body
71
+ );
72
+ }
73
+
74
+ export function useModal(options = {}) {
75
+ const [open, setOpen] = useState(false);
76
+ const close = () => setOpen(false);
77
+
78
+ const stateRef = useRef({ open, options });
79
+ stateRef.current = { open, options };
80
+
81
+ const modalSlotRef = useRef(null);
82
+ if (!modalSlotRef.current) {
83
+ modalSlotRef.current = function ModalSlot(props) {
84
+ const current = stateRef.current;
85
+ return createElement(Modal, {
86
+ _open: current.open,
87
+ _onClose: close,
88
+ ...current.options,
89
+ ...props
90
+ });
91
+ };
92
+ modalSlotRef.current.displayName = "ModalSlot";
93
+ }
94
+
95
+ return {
96
+ open,
97
+ setOpen,
98
+ openModal: () => setOpen(true),
99
+ closeModal: close,
100
+ Modal: modalSlotRef.current
101
+ };
102
+ }
@@ -1,216 +0,0 @@
1
- import React, { useEffect, useMemo, useRef, useState } from "react";
2
- import { createPortal } from "react-dom";
3
-
4
- const ALIGN_MAP = {
5
- left: "flex-start",
6
- center: "center",
7
- right: "flex-end"
8
- };
9
-
10
- const JUSTIFY_MAP = {
11
- top: "flex-start",
12
- center: "center",
13
- bottom: "flex-end"
14
- };
15
-
16
- const WIDTH_MAP = {
17
- sm: "300px",
18
- md: "500px",
19
- lg: "720px",
20
- xl: "960px",
21
- "2xl": "1140px",
22
- "3xl": "1280px",
23
- "4xl": "1440px",
24
- "5xl": "1600px"
25
- };
26
-
27
- let scrollLockCount = 0;
28
-
29
- function enableScrollLock() {
30
- if (scrollLockCount === 0) {
31
- const prevOverflow = document.body.style.overflow;
32
- document.body.dataset.scrollLockPrev = prevOverflow || "auto";
33
- document.body.style.overflow = "hidden";
34
- }
35
- scrollLockCount += 1;
36
- }
37
-
38
- function disableScrollLock() {
39
- scrollLockCount = Math.max(0, scrollLockCount - 1);
40
- if (scrollLockCount === 0) {
41
- const prevOverflow = document.body.dataset.scrollLockPrev || "auto";
42
- document.body.style.overflow = prevOverflow;
43
- delete document.body.dataset.scrollLockPrev;
44
- }
45
- }
46
-
47
- function Portal({ children, selector = "body" }) {
48
- const container = useMemo(() => document.createElement("div"), []);
49
-
50
- useEffect(() => {
51
- let host = document.body;
52
- if (typeof selector === "string" && selector.length > 0) {
53
- try {
54
- host = document.querySelector(selector) ?? document.body;
55
- } catch {
56
- host = document.body;
57
- }
58
- }
59
-
60
- host.appendChild(container);
61
-
62
- return () => {
63
- if (host.contains(container)) {
64
- host.removeChild(container);
65
- }
66
- };
67
- }, [selector, container]);
68
-
69
- return createPortal(children, container);
70
- }
71
-
72
- export function Modal({
73
- children,
74
- dismissOnBackdrop = true,
75
- align = "center",
76
- justify = "center",
77
- width = "lg",
78
- _open = false,
79
- _onClose,
80
- _lockScroll = true,
81
- _closeOnEsc = true
82
- } = {}) {
83
- const dialogRef = useRef(null);
84
-
85
- useEffect(() => {
86
- if (!_open || !_lockScroll) {
87
- return undefined;
88
- }
89
- enableScrollLock();
90
- return () => disableScrollLock();
91
- }, [_open, _lockScroll]);
92
-
93
- useEffect(() => {
94
- if (!_open || !_closeOnEsc) {
95
- return undefined;
96
- }
97
-
98
- function onKey(event) {
99
- if (event.key === "Escape") {
100
- _onClose?.();
101
- }
102
- }
103
-
104
- document.addEventListener("keydown", onKey);
105
- return () => document.removeEventListener("keydown", onKey);
106
- }, [_open, _closeOnEsc, _onClose]);
107
-
108
-
109
- if (!_open) {
110
- return null;
111
- }
112
-
113
- const alignItems = ALIGN_MAP[align] || "center";
114
- const justifyContent = JUSTIFY_MAP[justify] || "center";
115
- const widthValue = typeof width === "number" ? `${width}px` : (WIDTH_MAP[width] || width);
116
-
117
- const safeContainerStyle = {
118
- position: "fixed",
119
- inset: 0,
120
- zIndex: 1000,
121
- display: "flex",
122
- alignItems,
123
- justifyContent,
124
- padding: "12px"
125
- };
126
-
127
- const safeOverlayStyle = {
128
- position: "absolute",
129
- inset: 0,
130
- background: "rgba(0, 0, 0, 0.5)"
131
- };
132
-
133
- const safeDialogStyle = {
134
- position: "relative",
135
- zIndex: 1,
136
- background: "#ffffff",
137
- borderRadius: "12px",
138
- boxShadow: "0 20px 60px rgba(0, 0, 0, 0.2)",
139
- width: widthValue || "720px",
140
- maxWidth: "92vw",
141
- maxHeight: "85vh",
142
- overflow: "auto"
143
- };
144
-
145
- return React.createElement(
146
- Portal,
147
- { selector: "body" },
148
- React.createElement(
149
- "div",
150
- {
151
- className: "",
152
- style: safeContainerStyle
153
- },
154
- React.createElement("div", {
155
- className: "",
156
- style: safeOverlayStyle,
157
- onClick: () => dismissOnBackdrop && _onClose?.()
158
- }),
159
- React.createElement(
160
- "div",
161
- {
162
- ref: dialogRef,
163
- role: "dialog",
164
- "aria-modal": "true",
165
- className: "",
166
- style: safeDialogStyle,
167
- onClick: (event) => event.stopPropagation()
168
- },
169
- children
170
- )
171
- )
172
- );
173
- }
174
-
175
- export function useModal(options = {}) {
176
- const [open, setOpen] = useState(false);
177
-
178
- const openModal = () => setOpen(true);
179
- const closeModal = () => setOpen(false);
180
-
181
- const stateRef = useRef({
182
- open,
183
- closeModal,
184
- options
185
- });
186
-
187
- stateRef.current = {
188
- open,
189
- closeModal,
190
- options
191
- };
192
-
193
- const modalSlotRef = useRef(null);
194
- if (!modalSlotRef.current) {
195
- modalSlotRef.current = function ModalSlot(props) {
196
- const current = stateRef.current;
197
- return React.createElement(Modal, {
198
- _open: current.open,
199
- _onClose: current.closeModal,
200
- _lockScroll: true,
201
- _closeOnEsc: true,
202
- ...current.options,
203
- ...props
204
- });
205
- };
206
- modalSlotRef.current.displayName = "ModalSlot";
207
- }
208
-
209
- return {
210
- open,
211
- setOpen,
212
- openModal,
213
- closeModal,
214
- Modal: modalSlotRef.current
215
- };
216
- }