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 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
+ });
@@ -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 };
@@ -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
+ }