react-flow-modal 0.1.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 +100 -0
- package/dist/index.cjs +96 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +67 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
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
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ModalHost: () => ModalHost,
|
|
24
|
+
ModalProvider: () => ModalProvider,
|
|
25
|
+
useModal: () => useModal
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/ModalProvider.tsx
|
|
30
|
+
var import_react2 = require("react");
|
|
31
|
+
|
|
32
|
+
// src/modalContext.ts
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
var ModalContext = (0, import_react.createContext)({
|
|
35
|
+
stack: [],
|
|
36
|
+
setStack: () => []
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// src/ModalProvider.tsx
|
|
40
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
41
|
+
var ModalProvider = ({ children }) => {
|
|
42
|
+
const [stack, setStack] = (0, import_react2.useState)([]);
|
|
43
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContext.Provider, { value: { stack, setStack }, children });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/ModalHost.tsx
|
|
47
|
+
var import_react4 = require("react");
|
|
48
|
+
|
|
49
|
+
// src/useModalContext.ts
|
|
50
|
+
var import_react3 = require("react");
|
|
51
|
+
var useModalContext = () => {
|
|
52
|
+
const context = (0, import_react3.useContext)(ModalContext);
|
|
53
|
+
if (!context) {
|
|
54
|
+
throw new Error("useModalContext must be used within a ModalProvider");
|
|
55
|
+
}
|
|
56
|
+
return context;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/ModalHost.tsx
|
|
60
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
61
|
+
var ModalHost = () => {
|
|
62
|
+
const { stack } = useModalContext();
|
|
63
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Fragment, { children: stack.map((item) => item) });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/useModal.tsx
|
|
67
|
+
var import_react5 = require("react");
|
|
68
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
69
|
+
var useModal = () => {
|
|
70
|
+
const { setStack } = useModalContext();
|
|
71
|
+
const pop = () => {
|
|
72
|
+
setStack((prev) => prev.slice(0, -1));
|
|
73
|
+
};
|
|
74
|
+
const push = (element) => {
|
|
75
|
+
setStack((prev) => [...prev, element]);
|
|
76
|
+
};
|
|
77
|
+
const open = (key, render) => {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const element = render((value) => {
|
|
80
|
+
resolve(value);
|
|
81
|
+
pop();
|
|
82
|
+
}, (reason) => {
|
|
83
|
+
reject(reason);
|
|
84
|
+
pop();
|
|
85
|
+
});
|
|
86
|
+
push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Fragment, { children: element }, key));
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
return { open };
|
|
90
|
+
};
|
|
91
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
+
0 && (module.exports = {
|
|
93
|
+
ModalHost,
|
|
94
|
+
ModalProvider,
|
|
95
|
+
useModal
|
|
96
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React, { FC, PropsWithChildren } from 'react';
|
|
2
|
+
|
|
3
|
+
declare const ModalProvider: FC<PropsWithChildren>;
|
|
4
|
+
|
|
5
|
+
declare const ModalHost: FC;
|
|
6
|
+
|
|
7
|
+
declare const useModal: () => {
|
|
8
|
+
open: <T>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) => React.ReactNode) => Promise<T>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { ModalHost, ModalProvider, useModal };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React, { FC, PropsWithChildren } from 'react';
|
|
2
|
+
|
|
3
|
+
declare const ModalProvider: FC<PropsWithChildren>;
|
|
4
|
+
|
|
5
|
+
declare const ModalHost: FC;
|
|
6
|
+
|
|
7
|
+
declare const useModal: () => {
|
|
8
|
+
open: <T>(key: string, render: (resolve: (value: T) => void, reject: (reason?: unknown) => void) => React.ReactNode) => Promise<T>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { ModalHost, ModalProvider, useModal };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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
|
|
12
|
+
import { jsx } from "react/jsx-runtime";
|
|
13
|
+
var ModalProvider = ({ children }) => {
|
|
14
|
+
const [stack, setStack] = useState([]);
|
|
15
|
+
return /* @__PURE__ */ jsx(ModalContext.Provider, { value: { stack, setStack }, children });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/ModalHost.tsx
|
|
19
|
+
import { Fragment } from "react";
|
|
20
|
+
|
|
21
|
+
// src/useModalContext.ts
|
|
22
|
+
import { useContext } from "react";
|
|
23
|
+
var useModalContext = () => {
|
|
24
|
+
const context = useContext(ModalContext);
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error("useModalContext must be used within a ModalProvider");
|
|
27
|
+
}
|
|
28
|
+
return context;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/ModalHost.tsx
|
|
32
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
33
|
+
var ModalHost = () => {
|
|
34
|
+
const { stack } = useModalContext();
|
|
35
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: stack.map((item) => item) });
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/useModal.tsx
|
|
39
|
+
import { Fragment as Fragment2 } from "react";
|
|
40
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
41
|
+
var useModal = () => {
|
|
42
|
+
const { setStack } = useModalContext();
|
|
43
|
+
const pop = () => {
|
|
44
|
+
setStack((prev) => prev.slice(0, -1));
|
|
45
|
+
};
|
|
46
|
+
const push = (element) => {
|
|
47
|
+
setStack((prev) => [...prev, element]);
|
|
48
|
+
};
|
|
49
|
+
const open = (key, render) => {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const element = render((value) => {
|
|
52
|
+
resolve(value);
|
|
53
|
+
pop();
|
|
54
|
+
}, (reason) => {
|
|
55
|
+
reject(reason);
|
|
56
|
+
pop();
|
|
57
|
+
});
|
|
58
|
+
push(/* @__PURE__ */ jsx3(Fragment2, { children: element }, key));
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
return { open };
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
ModalHost,
|
|
65
|
+
ModalProvider,
|
|
66
|
+
useModal
|
|
67
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-flow-modal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Promise-based modal flows for React",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/dohyeon-kr/react-flow-modal.git"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"react",
|
|
11
|
+
"modal",
|
|
12
|
+
"async",
|
|
13
|
+
"promise",
|
|
14
|
+
"flow"
|
|
15
|
+
],
|
|
16
|
+
"author": "Dohyeon Ju",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"prepublishOnly": "pnpm build"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18",
|
|
35
|
+
"react-dom": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "^19.2.9",
|
|
39
|
+
"@types/react-dom": "^19.2.3",
|
|
40
|
+
"react": "^19.2.3",
|
|
41
|
+
"react-dom": "^19.2.3",
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"packageManager": "pnpm@10.27.0"
|
|
46
|
+
}
|