react-flow-modal 0.1.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/package.json +1 -4
- package/src/ModalHost.tsx +12 -0
- package/src/ModalProvider.tsx +12 -0
- package/src/index.ts +3 -0
- package/src/modalContext.ts +9 -0
- package/src/useModal.tsx +37 -0
- package/src/useModalContext.ts +12 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +8 -0
- package/README.md +0 -100
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-flow-modal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Promise-based modal flows for React",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,9 +23,6 @@
|
|
|
23
23
|
"require": "./dist/index.cjs"
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
-
"files": [
|
|
27
|
-
"dist"
|
|
28
|
-
],
|
|
29
26
|
"scripts": {
|
|
30
27
|
"build": "tsup",
|
|
31
28
|
"prepublishOnly": "pnpm build"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { FC, Fragment } from 'react';
|
|
2
|
+
import { useModalContext } from './useModalContext';
|
|
3
|
+
|
|
4
|
+
export const ModalHost: FC = () => {
|
|
5
|
+
const { stack } = useModalContext();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<Fragment>
|
|
9
|
+
{stack.map((item) => (item))}
|
|
10
|
+
</Fragment>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { FC, PropsWithChildren, useState } from "react";
|
|
2
|
+
import { ModalContext } from "./modalContext";
|
|
3
|
+
|
|
4
|
+
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
|
|
5
|
+
const [stack, setStack] = useState<React.ReactNode[]>([]);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<ModalContext.Provider value={{ stack, setStack }}>
|
|
9
|
+
{children}
|
|
10
|
+
</ModalContext.Provider>
|
|
11
|
+
);
|
|
12
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
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/useModal.tsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { Fragment } from "react";
|
|
2
|
+
import { useModalContext } from "./useModalContext";
|
|
3
|
+
|
|
4
|
+
export const useModal = () => {
|
|
5
|
+
const { setStack } = useModalContext();
|
|
6
|
+
|
|
7
|
+
const pop = () => {
|
|
8
|
+
setStack((prev) => prev.slice(0, -1));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const push = (element: React.ReactNode) => {
|
|
12
|
+
setStack((prev) => [...prev, element]);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 모달을 열고 닫는 함수
|
|
17
|
+
* @caution 모달을 열고 닫는 함수는 비동기 함수이므로, 모달을 열고 닫는 함수를 호출하면, 반드시 resolve 또는 reject 함수를 호출해야 합니다.
|
|
18
|
+
* 그렇지 않으면 pending 상태가 되어 무한 대기 상태가 됩니다.
|
|
19
|
+
* @param key - 모달의 고유 키
|
|
20
|
+
* @param render - 모달을 렌더링하는 함수
|
|
21
|
+
* @returns - 모달 컴포넌트가 반환하는 값을 반환하는 Promise
|
|
22
|
+
*/
|
|
23
|
+
const open = <T,>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) => React.ReactNode) => {
|
|
24
|
+
return new Promise<T>((resolve, reject) => {
|
|
25
|
+
const element = render((value) => {
|
|
26
|
+
resolve(value);
|
|
27
|
+
pop();
|
|
28
|
+
}, (reason) => {
|
|
29
|
+
reject(reason);
|
|
30
|
+
pop();
|
|
31
|
+
});
|
|
32
|
+
push(<Fragment key={key}>{element}</Fragment>);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return { open };
|
|
37
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"emitDeclarationOnly": false,
|
|
12
|
+
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
|
|
19
|
+
"outDir": "dist",
|
|
20
|
+
"rootDir": "src"
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"]
|
|
23
|
+
}
|
package/tsup.config.ts
ADDED
package/README.md
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# react-flow-modal
|
|
2
|
-
|
|
3
|
-
Promise-based modal flows for React.
|
|
4
|
-
|
|
5
|
-
`react-flow-modal` lets you treat modals as async flows using
|
|
6
|
-
`Promise` and `async/await`, without coupling your UI to state-driven logic.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Installation
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
pnpm add react-flow-modal
|
|
14
|
-
# or
|
|
15
|
-
npm install react-flow-modal
|
|
16
|
-
# or
|
|
17
|
-
yarn add react-flow-modal
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Basic Usage
|
|
21
|
-
|
|
22
|
-
```JSX
|
|
23
|
-
import { ModalProvider, ModalHost, useModal } from "react-flow-modal";
|
|
24
|
-
|
|
25
|
-
function App() {
|
|
26
|
-
const modal = useModal();
|
|
27
|
-
|
|
28
|
-
const onClick = async () => {
|
|
29
|
-
const result = await modal.open("confirm",
|
|
30
|
-
(resolve, reject) => (
|
|
31
|
-
<ConfirmModal
|
|
32
|
-
onConfirm={() => resolve(true)}
|
|
33
|
-
onCancel={() => resolve(false)}
|
|
34
|
-
/>
|
|
35
|
-
));
|
|
36
|
-
|
|
37
|
-
// Resolving or rejecting the promise will also remove the modal from the stack
|
|
38
|
-
|
|
39
|
-
console.log(result);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return <button onClick={onClick}>Open modal</button>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default function Root() {
|
|
46
|
-
return (
|
|
47
|
-
<ModalProvider>
|
|
48
|
-
<App />
|
|
49
|
-
<ModalHost />
|
|
50
|
-
</ModalProvider>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## API
|
|
56
|
-
|
|
57
|
-
### open
|
|
58
|
-
```TS
|
|
59
|
-
open<T>(
|
|
60
|
-
key: string,
|
|
61
|
-
render: (
|
|
62
|
-
resolve: (value: T) => void,
|
|
63
|
-
reject: (reason?: unknown) => void
|
|
64
|
-
) => React.ReactNode
|
|
65
|
-
): Promise<T>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Important
|
|
69
|
-
|
|
70
|
-
> ⚠️ Make sure to always resolve or reject the promise.
|
|
71
|
-
> Leaving it pending will block the async flow.
|
|
72
|
-
|
|
73
|
-
## Why react-flow-modal?
|
|
74
|
-
|
|
75
|
-
Most modal libraries are state-driven:
|
|
76
|
-
```JSX
|
|
77
|
-
setOpen(true);
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
This makes modal control implicit and tightly coupled to rendering.
|
|
81
|
-
|
|
82
|
-
react-flow-modal treats modals as explicit async control points:
|
|
83
|
-
|
|
84
|
-
```JSX
|
|
85
|
-
const result = await open(...);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
This keeps control flow readable, composable, and testable.
|
|
89
|
-
|
|
90
|
-
## Features
|
|
91
|
-
|
|
92
|
-
- Headless API (no styles, no UI constraints)
|
|
93
|
-
- Promise-based modal control
|
|
94
|
-
- Stack-based modal rendering
|
|
95
|
-
- Fully controlled by user events
|
|
96
|
-
- Works naturally with async / await
|
|
97
|
-
|
|
98
|
-
## License
|
|
99
|
-
|
|
100
|
-
MIT
|