react-flow-modal 0.4.1 → 0.6.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/README.md +36 -49
- package/dist/index.cjs +39 -45
- package/dist/index.d.cts +15 -11
- package/dist/index.d.ts +15 -11
- package/dist/index.js +39 -44
- package/package.json +5 -2
- package/src/ModalHost.tsx +9 -9
- package/src/ModalProvider.tsx +46 -6
- package/src/index.ts +1 -2
- package/src/useModal.tsx +10 -11
- package/src/modalContext.ts +0 -9
- package/src/useModalContext.ts +0 -20
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ yarn add react-flow-modal
|
|
|
22
22
|
## Basic Usage
|
|
23
23
|
|
|
24
24
|
```tsx
|
|
25
|
-
import {
|
|
25
|
+
import { StrictMode } from 'react';
|
|
26
|
+
import { createRoot } from 'react-dom/client';
|
|
27
|
+
import { ModalProvider, useModal, ModalHost } from "react-flow-modal";
|
|
26
28
|
|
|
27
29
|
function ConfirmModal({
|
|
28
30
|
onConfirm,
|
|
@@ -66,6 +68,7 @@ function App() {
|
|
|
66
68
|
const modal = useModal();
|
|
67
69
|
|
|
68
70
|
const onClick = async () => {
|
|
71
|
+
// render modal and await resolve
|
|
69
72
|
const result = await modal.open("confirm", (resolve) => (
|
|
70
73
|
<ConfirmModal
|
|
71
74
|
onConfirm={() => resolve(true)}
|
|
@@ -73,24 +76,22 @@ function App() {
|
|
|
73
76
|
/>
|
|
74
77
|
));
|
|
75
78
|
|
|
79
|
+
// flow resumed
|
|
76
80
|
console.log("Result:", result);
|
|
77
81
|
};
|
|
78
82
|
|
|
79
83
|
return <button onClick={onClick}>Open Confirm Modal</button>;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default function Root() {
|
|
87
|
-
return (
|
|
86
|
+
createRoot(document.getElementById('root')!).render(
|
|
87
|
+
<StrictMode>
|
|
88
88
|
<ModalProvider>
|
|
89
89
|
<App />
|
|
90
|
-
<
|
|
90
|
+
<ModalHost />
|
|
91
91
|
</ModalProvider>
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
</StrictMode>,
|
|
93
|
+
)
|
|
94
|
+
|
|
94
95
|
```
|
|
95
96
|
|
|
96
97
|
---
|
|
@@ -101,8 +102,10 @@ To support exit animations, modals must be rendered inside the same
|
|
|
101
102
|
React tree as `AnimatePresence`.
|
|
102
103
|
|
|
103
104
|
```tsx
|
|
104
|
-
import {
|
|
105
|
-
import {
|
|
105
|
+
import { StrictMode } from 'react';
|
|
106
|
+
import { createRoot } from 'react-dom/client';
|
|
107
|
+
import { ModalHost, ModalProvider } from 'react-flow-modal';
|
|
108
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
106
109
|
|
|
107
110
|
function ConfirmModal({
|
|
108
111
|
onConfirm,
|
|
@@ -113,7 +116,7 @@ function ConfirmModal({
|
|
|
113
116
|
}) {
|
|
114
117
|
return (
|
|
115
118
|
<motion.div
|
|
116
|
-
key="modal"
|
|
119
|
+
key="confirm-modal-container"
|
|
117
120
|
initial={{ opacity: 0 }}
|
|
118
121
|
animate={{ opacity: 1 }}
|
|
119
122
|
exit={{ opacity: 0 }}
|
|
@@ -127,7 +130,6 @@ function ConfirmModal({
|
|
|
127
130
|
}}
|
|
128
131
|
>
|
|
129
132
|
<motion.div
|
|
130
|
-
key="modal-content"
|
|
131
133
|
initial={{ scale: 0.9, opacity: 0 }}
|
|
132
134
|
animate={{ scale: 1, opacity: 1 }}
|
|
133
135
|
exit={{ scale: 0.95, opacity: 0 }}
|
|
@@ -137,6 +139,7 @@ function ConfirmModal({
|
|
|
137
139
|
padding: 24,
|
|
138
140
|
borderRadius: 8,
|
|
139
141
|
minWidth: 300,
|
|
142
|
+
color: "black",
|
|
140
143
|
}}
|
|
141
144
|
>
|
|
142
145
|
<h3>Are you sure?</h3>
|
|
@@ -151,39 +154,24 @@ function ConfirmModal({
|
|
|
151
154
|
);
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
const modal = useModal();
|
|
156
|
-
|
|
157
|
-
const onClick = async () => {
|
|
158
|
-
const result = await modal.open("confirm", (resolve) => (
|
|
159
|
-
<ConfirmModal
|
|
160
|
-
onConfirm={() => resolve(true)}
|
|
161
|
-
onCancel={() => resolve(false)}
|
|
162
|
-
/>
|
|
163
|
-
));
|
|
164
|
-
|
|
165
|
-
console.log("Result:", result);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
return <button onClick={onClick}>Open Confirm Modal</button>;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function ModalRenderer() {
|
|
172
|
-
return (
|
|
173
|
-
<AnimatePresence>
|
|
174
|
-
{renderModals()}
|
|
175
|
-
</AnimatePresence>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
157
|
+
...
|
|
178
158
|
|
|
179
|
-
|
|
180
|
-
|
|
159
|
+
createRoot(document.getElementById('root')!).render(
|
|
160
|
+
<StrictMode>
|
|
181
161
|
<ModalProvider>
|
|
182
162
|
<App />
|
|
183
|
-
<
|
|
163
|
+
<ModalHost>
|
|
164
|
+
{(modals) => (
|
|
165
|
+
<AnimatePresence>
|
|
166
|
+
{modals}
|
|
167
|
+
</AnimatePresence>
|
|
168
|
+
)}
|
|
169
|
+
</ModalHost>
|
|
184
170
|
</ModalProvider>
|
|
185
|
-
|
|
186
|
-
|
|
171
|
+
</StrictMode>,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
187
175
|
```
|
|
188
176
|
|
|
189
177
|
---
|
|
@@ -210,11 +198,13 @@ Returns an object that controls the modal flow.
|
|
|
210
198
|
}
|
|
211
199
|
```
|
|
212
200
|
|
|
213
|
-
###
|
|
201
|
+
### ModalHost
|
|
214
202
|
```ts
|
|
215
|
-
|
|
203
|
+
const ModalHost: FC<{
|
|
204
|
+
children?: (modals: ReactElement[]) => React.ReactNode;
|
|
205
|
+
}>;
|
|
216
206
|
```
|
|
217
|
-
Renders the entire modal stack. This
|
|
207
|
+
Renders the entire modal stack. This component should be rendered **once** in your React tree.
|
|
218
208
|
|
|
219
209
|
---
|
|
220
210
|
|
|
@@ -223,9 +213,6 @@ Renders the entire modal stack. This function should be rendered **once** in you
|
|
|
223
213
|
> ⚠️ Always resolve or reject the promise.
|
|
224
214
|
> Leaving it pending will block the async flow.
|
|
225
215
|
|
|
226
|
-
> ⚠️ `renderModals()` should be rendered once.
|
|
227
|
-
> Rendering it multiple times may result in duplicated modals.
|
|
228
|
-
|
|
229
216
|
---
|
|
230
217
|
|
|
231
218
|
## Why react-flow-modal?
|
package/dist/index.cjs
CHANGED
|
@@ -22,79 +22,73 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ModalHost: () => ModalHost,
|
|
24
24
|
ModalProvider: () => ModalProvider,
|
|
25
|
-
useModal: () => useModal
|
|
26
|
-
useModalHost: () => useModalHost
|
|
25
|
+
useModal: () => useModal
|
|
27
26
|
});
|
|
28
27
|
module.exports = __toCommonJS(index_exports);
|
|
29
28
|
|
|
30
29
|
// src/ModalProvider.tsx
|
|
31
|
-
var import_react2 = require("react");
|
|
32
|
-
|
|
33
|
-
// src/modalContext.ts
|
|
34
30
|
var import_react = require("react");
|
|
35
|
-
var
|
|
36
|
-
stack: [],
|
|
37
|
-
setStack: () => []
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// src/ModalProvider.tsx
|
|
31
|
+
var import_zustand = require("zustand");
|
|
41
32
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
42
|
-
var
|
|
43
|
-
|
|
44
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContext.Provider, { value: { stack, setStack }, children });
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// src/useModalContext.ts
|
|
48
|
-
var import_react3 = require("react");
|
|
49
|
-
var useModalContext = () => {
|
|
50
|
-
const context = (0, import_react3.useContext)(ModalContext);
|
|
51
|
-
if (!context) {
|
|
52
|
-
throw new Error("useModalContext must be used within a ModalProvider");
|
|
53
|
-
}
|
|
54
|
-
return context;
|
|
55
|
-
};
|
|
56
|
-
var useModalHost = () => {
|
|
57
|
-
const { stack } = useModalContext();
|
|
33
|
+
var createModalStore = (initialState) => (0, import_zustand.createStore)((set) => {
|
|
34
|
+
var _a;
|
|
58
35
|
return {
|
|
59
|
-
|
|
36
|
+
stack: (_a = initialState == null ? void 0 : initialState.stack) != null ? _a : /* @__PURE__ */ new Map(),
|
|
37
|
+
appendStack: (key, element) => set((state) => ({ stack: new Map(state.stack).set(key, element) })),
|
|
38
|
+
removeStack: (key) => set((state) => {
|
|
39
|
+
const newStack = new Map(state.stack);
|
|
40
|
+
newStack.delete(key);
|
|
41
|
+
return { stack: newStack };
|
|
42
|
+
})
|
|
60
43
|
};
|
|
44
|
+
});
|
|
45
|
+
var ModalStoreContext = (0, import_react.createContext)(null);
|
|
46
|
+
var ModalProvider = ({ children, initialState }) => {
|
|
47
|
+
const [store] = (0, import_react.useState)(createModalStore(initialState));
|
|
48
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalStoreContext.Provider, { value: store, children });
|
|
49
|
+
};
|
|
50
|
+
var useModalStore = (selector) => {
|
|
51
|
+
const store = (0, import_react.useContext)(ModalStoreContext);
|
|
52
|
+
if (!store) throw new Error("DashboardStoreProvider missing");
|
|
53
|
+
return (0, import_zustand.useStore)(store, selector);
|
|
61
54
|
};
|
|
62
55
|
|
|
63
56
|
// src/useModal.tsx
|
|
57
|
+
var import_react2 = require("react");
|
|
58
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
64
59
|
var useModal = () => {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
60
|
+
const appendStack = useModalStore(
|
|
61
|
+
(state) => state.appendStack
|
|
62
|
+
);
|
|
63
|
+
const removeStack = useModalStore(
|
|
64
|
+
(state) => state.removeStack
|
|
65
|
+
);
|
|
72
66
|
const open = (key, render) => {
|
|
73
67
|
return new Promise((resolve, reject) => {
|
|
74
68
|
const element = render((value) => {
|
|
75
69
|
resolve(value);
|
|
76
|
-
|
|
70
|
+
removeStack(key);
|
|
77
71
|
}, (reason) => {
|
|
78
72
|
reject(reason);
|
|
79
|
-
|
|
73
|
+
removeStack(key);
|
|
80
74
|
});
|
|
81
|
-
|
|
75
|
+
appendStack(key, /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Fragment, { children: element }, `modal-${key}`));
|
|
82
76
|
});
|
|
83
77
|
};
|
|
84
78
|
return { open };
|
|
85
79
|
};
|
|
86
80
|
|
|
87
81
|
// src/ModalHost.tsx
|
|
88
|
-
var
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
var ModalHost = ({ children }) => {
|
|
83
|
+
const stack = useModalStore(
|
|
84
|
+
(state) => state.stack
|
|
85
|
+
);
|
|
86
|
+
if (!children) return Array.from(stack.values());
|
|
87
|
+
return children(Array.from(stack.values()));
|
|
93
88
|
};
|
|
94
89
|
// Annotate the CommonJS export names for ESM import in node:
|
|
95
90
|
0 && (module.exports = {
|
|
96
91
|
ModalHost,
|
|
97
92
|
ModalProvider,
|
|
98
|
-
useModal
|
|
99
|
-
useModalHost
|
|
93
|
+
useModal
|
|
100
94
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import react__default, { FC, PropsWithChildren } from 'react';
|
|
1
|
+
import React$1, { PropsWithChildren, ReactElement, FC } from 'react';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
type ModalState = {
|
|
4
|
+
stack: Map<string, ReactElement>;
|
|
5
|
+
appendStack: (key: string, element: ReactElement) => void;
|
|
6
|
+
removeStack: (key: string) => void;
|
|
7
|
+
};
|
|
8
|
+
type ProviderProps = {
|
|
9
|
+
initialState?: Partial<ModalState>;
|
|
10
|
+
};
|
|
11
|
+
declare const ModalProvider: React$1.FC<PropsWithChildren<ProviderProps>>;
|
|
5
12
|
|
|
6
13
|
declare const useModal: () => {
|
|
7
|
-
open: <T>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) =>
|
|
14
|
+
open: <T>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) => React$1.ReactNode) => Promise<T>;
|
|
8
15
|
};
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
declare const useModalHost: () => {
|
|
14
|
-
render: () => react.ReactNode[];
|
|
15
|
-
};
|
|
17
|
+
declare const ModalHost: FC<{
|
|
18
|
+
children?: (modals: ReactElement[]) => React.ReactNode;
|
|
19
|
+
}>;
|
|
16
20
|
|
|
17
|
-
export { ModalHost, ModalProvider, useModal
|
|
21
|
+
export { ModalHost, ModalProvider, useModal };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import react__default, { FC, PropsWithChildren } from 'react';
|
|
1
|
+
import React$1, { PropsWithChildren, ReactElement, FC } from 'react';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
type ModalState = {
|
|
4
|
+
stack: Map<string, ReactElement>;
|
|
5
|
+
appendStack: (key: string, element: ReactElement) => void;
|
|
6
|
+
removeStack: (key: string) => void;
|
|
7
|
+
};
|
|
8
|
+
type ProviderProps = {
|
|
9
|
+
initialState?: Partial<ModalState>;
|
|
10
|
+
};
|
|
11
|
+
declare const ModalProvider: React$1.FC<PropsWithChildren<ProviderProps>>;
|
|
5
12
|
|
|
6
13
|
declare const useModal: () => {
|
|
7
|
-
open: <T>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) =>
|
|
14
|
+
open: <T>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) => React$1.ReactNode) => Promise<T>;
|
|
8
15
|
};
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
declare const useModalHost: () => {
|
|
14
|
-
render: () => react.ReactNode[];
|
|
15
|
-
};
|
|
17
|
+
declare const ModalHost: FC<{
|
|
18
|
+
children?: (modals: ReactElement[]) => React.ReactNode;
|
|
19
|
+
}>;
|
|
16
20
|
|
|
17
|
-
export { ModalHost, ModalProvider, useModal
|
|
21
|
+
export { ModalHost, ModalProvider, useModal };
|
package/dist/index.js
CHANGED
|
@@ -1,70 +1,65 @@
|
|
|
1
1
|
// src/ModalProvider.tsx
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
|
|
4
|
-
// src/modalContext.ts
|
|
5
|
-
import { createContext } from "react";
|
|
6
|
-
var ModalContext = createContext({
|
|
7
|
-
stack: [],
|
|
8
|
-
setStack: () => []
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
// src/ModalProvider.tsx
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
3
|
+
import { createStore, useStore } from "zustand";
|
|
12
4
|
import { jsx } from "react/jsx-runtime";
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
return /* @__PURE__ */ jsx(ModalContext.Provider, { value: { stack, setStack }, children });
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// src/useModalContext.ts
|
|
19
|
-
import { useContext } from "react";
|
|
20
|
-
var useModalContext = () => {
|
|
21
|
-
const context = useContext(ModalContext);
|
|
22
|
-
if (!context) {
|
|
23
|
-
throw new Error("useModalContext must be used within a ModalProvider");
|
|
24
|
-
}
|
|
25
|
-
return context;
|
|
26
|
-
};
|
|
27
|
-
var useModalHost = () => {
|
|
28
|
-
const { stack } = useModalContext();
|
|
5
|
+
var createModalStore = (initialState) => createStore((set) => {
|
|
6
|
+
var _a;
|
|
29
7
|
return {
|
|
30
|
-
|
|
8
|
+
stack: (_a = initialState == null ? void 0 : initialState.stack) != null ? _a : /* @__PURE__ */ new Map(),
|
|
9
|
+
appendStack: (key, element) => set((state) => ({ stack: new Map(state.stack).set(key, element) })),
|
|
10
|
+
removeStack: (key) => set((state) => {
|
|
11
|
+
const newStack = new Map(state.stack);
|
|
12
|
+
newStack.delete(key);
|
|
13
|
+
return { stack: newStack };
|
|
14
|
+
})
|
|
31
15
|
};
|
|
16
|
+
});
|
|
17
|
+
var ModalStoreContext = createContext(null);
|
|
18
|
+
var ModalProvider = ({ children, initialState }) => {
|
|
19
|
+
const [store] = useState(createModalStore(initialState));
|
|
20
|
+
return /* @__PURE__ */ jsx(ModalStoreContext.Provider, { value: store, children });
|
|
21
|
+
};
|
|
22
|
+
var useModalStore = (selector) => {
|
|
23
|
+
const store = useContext(ModalStoreContext);
|
|
24
|
+
if (!store) throw new Error("DashboardStoreProvider missing");
|
|
25
|
+
return useStore(store, selector);
|
|
32
26
|
};
|
|
33
27
|
|
|
34
28
|
// src/useModal.tsx
|
|
29
|
+
import { Fragment } from "react";
|
|
30
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
35
31
|
var useModal = () => {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
32
|
+
const appendStack = useModalStore(
|
|
33
|
+
(state) => state.appendStack
|
|
34
|
+
);
|
|
35
|
+
const removeStack = useModalStore(
|
|
36
|
+
(state) => state.removeStack
|
|
37
|
+
);
|
|
43
38
|
const open = (key, render) => {
|
|
44
39
|
return new Promise((resolve, reject) => {
|
|
45
40
|
const element = render((value) => {
|
|
46
41
|
resolve(value);
|
|
47
|
-
|
|
42
|
+
removeStack(key);
|
|
48
43
|
}, (reason) => {
|
|
49
44
|
reject(reason);
|
|
50
|
-
|
|
45
|
+
removeStack(key);
|
|
51
46
|
});
|
|
52
|
-
|
|
47
|
+
appendStack(key, /* @__PURE__ */ jsx2(Fragment, { children: element }, `modal-${key}`));
|
|
53
48
|
});
|
|
54
49
|
};
|
|
55
50
|
return { open };
|
|
56
51
|
};
|
|
57
52
|
|
|
58
53
|
// src/ModalHost.tsx
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
var ModalHost = ({ children }) => {
|
|
55
|
+
const stack = useModalStore(
|
|
56
|
+
(state) => state.stack
|
|
57
|
+
);
|
|
58
|
+
if (!children) return Array.from(stack.values());
|
|
59
|
+
return children(Array.from(stack.values()));
|
|
64
60
|
};
|
|
65
61
|
export {
|
|
66
62
|
ModalHost,
|
|
67
63
|
ModalProvider,
|
|
68
|
-
useModal
|
|
69
|
-
useModalHost
|
|
64
|
+
useModal
|
|
70
65
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-flow-modal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Promise-based modal flows for React",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,5 +39,8 @@
|
|
|
39
39
|
"tsup": "^8.5.1",
|
|
40
40
|
"typescript": "^5.9.3"
|
|
41
41
|
},
|
|
42
|
-
"packageManager": "pnpm@10.27.0"
|
|
42
|
+
"packageManager": "pnpm@10.27.0",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"zustand": "^5.0.11"
|
|
45
|
+
}
|
|
43
46
|
}
|
package/src/ModalHost.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { FC,
|
|
2
|
-
import {
|
|
1
|
+
import { FC, isValidElement, ReactElement } from 'react';
|
|
2
|
+
import { useModalStore } from './ModalProvider';
|
|
3
3
|
|
|
4
|
-
/** @deprecated use renderModals instead */
|
|
5
|
-
export const ModalHost: FC = () => {
|
|
6
|
-
const { render } = useModalHost();
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
</Fragment>
|
|
5
|
+
export const ModalHost: FC<{ children?: (modals: ReactElement[]) => React.ReactNode }> = ({ children }) => {
|
|
6
|
+
const stack = useModalStore(
|
|
7
|
+
(state) => state.stack
|
|
12
8
|
);
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if (!children) return Array.from(stack.values());
|
|
12
|
+
return children(Array.from(stack.values()));
|
|
13
13
|
};
|
package/src/ModalProvider.tsx
CHANGED
|
@@ -1,12 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
import { ModalContext } from "./modalContext";
|
|
1
|
+
'use client';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
|
|
4
|
+
import React, { createContext, PropsWithChildren, ReactElement, useContext, useState } from 'react';
|
|
5
|
+
import { createStore, ExtractState, StoreApi, useStore } from 'zustand';
|
|
6
|
+
|
|
7
|
+
type ModalState = {
|
|
8
|
+
stack: Map<string, ReactElement>;
|
|
9
|
+
appendStack: (key: string, element: ReactElement) => void;
|
|
10
|
+
removeStack: (key: string) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const createModalStore = (initialState?: Partial<ModalState>) => createStore<ModalState>((set) => {
|
|
16
|
+
return {
|
|
17
|
+
stack: initialState?.stack ?? new Map<string, ReactElement>(),
|
|
18
|
+
|
|
19
|
+
appendStack: (key: string, element: ReactElement) => set((state) => ({ stack: new Map(state.stack).set(key, element) })),
|
|
20
|
+
|
|
21
|
+
removeStack: (key: string) => set((state) => {
|
|
22
|
+
const newStack = new Map(state.stack);
|
|
23
|
+
newStack.delete(key);
|
|
24
|
+
return { stack: newStack };
|
|
25
|
+
}),
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const ModalStoreContext = createContext<StoreApi<ModalState> | null>(null);
|
|
30
|
+
|
|
31
|
+
type ProviderProps = {
|
|
32
|
+
initialState?: Partial<ModalState>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export const ModalProvider: React.FC<PropsWithChildren<ProviderProps>> = ({ children, initialState }) => {
|
|
37
|
+
// 여러번 호출되어도 store가 하나만 생성되도록 함
|
|
38
|
+
const [store] = useState(createModalStore(initialState));
|
|
6
39
|
|
|
7
40
|
return (
|
|
8
|
-
<
|
|
41
|
+
<ModalStoreContext.Provider value={store}>
|
|
9
42
|
{children}
|
|
10
|
-
</
|
|
43
|
+
</ModalStoreContext.Provider>
|
|
11
44
|
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export const useModalStore = <T,>(selector: (state: ExtractState<StoreApi<ModalState>>) => T) => {
|
|
49
|
+
const store = useContext(ModalStoreContext);
|
|
50
|
+
if (!store) throw new Error('DashboardStoreProvider missing');
|
|
51
|
+
return useStore(store, selector);
|
|
12
52
|
};
|
package/src/index.ts
CHANGED
package/src/useModal.tsx
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import React, { Fragment } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useModalStore } from "./ModalProvider";
|
|
3
3
|
|
|
4
4
|
export const useModal = () => {
|
|
5
|
-
const
|
|
5
|
+
const appendStack = useModalStore(
|
|
6
|
+
(state) => state.appendStack
|
|
7
|
+
);
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const removeStack = useModalStore(
|
|
10
|
+
(state) => state.removeStack
|
|
11
|
+
);
|
|
10
12
|
|
|
11
|
-
const push = (element: React.ReactNode) => {
|
|
12
|
-
setStack((prev) => [...prev, element]);
|
|
13
|
-
};
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* 모달을 열고 닫는 함수
|
|
@@ -24,12 +23,12 @@ export const useModal = () => {
|
|
|
24
23
|
return new Promise<T>((resolve, reject) => {
|
|
25
24
|
const element = render((value) => {
|
|
26
25
|
resolve(value);
|
|
27
|
-
|
|
26
|
+
removeStack(key);
|
|
28
27
|
}, (reason) => {
|
|
29
28
|
reject(reason);
|
|
30
|
-
|
|
29
|
+
removeStack(key);
|
|
31
30
|
});
|
|
32
|
-
|
|
31
|
+
appendStack(key, <Fragment key={`modal-${key}`}>{element}</Fragment>);
|
|
33
32
|
});
|
|
34
33
|
};
|
|
35
34
|
|
package/src/modalContext.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { createContext, Dispatch, SetStateAction } from "react";
|
|
2
|
-
|
|
3
|
-
export const ModalContext = createContext<{
|
|
4
|
-
stack: React.ReactNode[];
|
|
5
|
-
setStack: Dispatch<SetStateAction<React.ReactNode[]>>
|
|
6
|
-
}>({
|
|
7
|
-
stack: [],
|
|
8
|
-
setStack: () => [] as React.ReactNode[],
|
|
9
|
-
});
|
package/src/useModalContext.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useContext } from "react";
|
|
2
|
-
import { ModalContext } from "./modalContext";
|
|
3
|
-
|
|
4
|
-
export const useModalContext = () => {
|
|
5
|
-
const context = useContext(ModalContext);
|
|
6
|
-
|
|
7
|
-
if (!context) {
|
|
8
|
-
throw new Error('useModalContext must be used within a ModalProvider');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return context;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const useModalHost = () => {
|
|
15
|
-
const { stack } = useModalContext();
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
render: () => stack.map((item) => (item)),
|
|
19
|
-
};
|
|
20
|
-
};
|