react-modal-orchestrator 1.0.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.
Files changed (41) hide show
  1. package/.idea/ModalLib.iml +9 -0
  2. package/.idea/copilot.data.migration.agent.xml +6 -0
  3. package/.idea/copilot.data.migration.ask.xml +6 -0
  4. package/.idea/copilot.data.migration.ask2agent.xml +6 -0
  5. package/.idea/copilot.data.migration.edit.xml +6 -0
  6. package/.idea/misc.xml +15 -0
  7. package/.idea/modules.xml +8 -0
  8. package/README.md +134 -0
  9. package/dist/Modal/ModalHandler.d.ts +2 -0
  10. package/dist/Modal/ModalHandler.d.ts.map +1 -0
  11. package/dist/Modal/ModalHandler.js +14 -0
  12. package/dist/Modal/ModalHandler.js.map +1 -0
  13. package/dist/Modal/ModalOrchestrator.d.ts +7 -0
  14. package/dist/Modal/ModalOrchestrator.d.ts.map +1 -0
  15. package/dist/Modal/ModalOrchestrator.js +13 -0
  16. package/dist/Modal/ModalOrchestrator.js.map +1 -0
  17. package/dist/contexts/ModalContext.d.ts +12 -0
  18. package/dist/contexts/ModalContext.d.ts.map +1 -0
  19. package/dist/contexts/ModalContext.js +7 -0
  20. package/dist/contexts/ModalContext.js.map +1 -0
  21. package/dist/hooks/useModal.d.ts +19 -0
  22. package/dist/hooks/useModal.d.ts.map +1 -0
  23. package/dist/hooks/useModal.js +45 -0
  24. package/dist/hooks/useModal.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/types.d.ts +7 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +2 -0
  32. package/dist/types.js.map +1 -0
  33. package/package.json +32 -0
  34. package/src/MODAL_README.md +97 -0
  35. package/src/Modal/ModalHandler.tsx +17 -0
  36. package/src/Modal/ModalOrchestrator.tsx +33 -0
  37. package/src/contexts/ModalContext.ts +14 -0
  38. package/src/hooks/useModal.tsx +65 -0
  39. package/src/index.ts +3 -0
  40. package/src/types.ts +7 -0
  41. package/tsconfig.json +17 -0
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="JAVA_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="inheritedJdk" />
7
+ <orderEntry type="sourceFolder" forTests="false" />
8
+ </component>
9
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AskMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Ask2AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="EditMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
package/.idea/misc.xml ADDED
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="MaterialThemeProjectNewConfig">
4
+ <option name="metadata">
5
+ <MTProjectMetadataState>
6
+ <option name="migrated" value="true" />
7
+ <option name="pristineConfig" value="false" />
8
+ <option name="userId" value="1f7785cf:191c2f026d3:-7ffe" />
9
+ </MTProjectMetadataState>
10
+ </option>
11
+ </component>
12
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
13
+ <output url="file://$PROJECT_DIR$/out" />
14
+ </component>
15
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/ModalLib.iml" filepath="$PROJECT_DIR$/.idea/ModalLib.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # ModalLib
2
+
3
+ A centralized and flexible modal management system for React applications, now with lazy loading!
4
+
5
+ `ModalLib` provides a clean, decoupled architecture for managing modals. Instead of controlling modal state with `useState` in every component, you can register all your modals in one place and trigger them from anywhere in your application. By using `React.lazy`, modal components are only loaded when they are first opened, reducing your application's initial bundle size.
6
+
7
+ ## Core Concepts
8
+
9
+ - **ModalOrchestrator**: A provider component that wraps your application and powers the entire modal system.
10
+ - **ModalHandler**: A component that listens for active modals and renders them. It now includes `React.Suspense` to handle lazy-loaded components.
11
+ - **Modal Dictionary**: A simple JavaScript object where you map a unique name to each dynamically imported modal component.
12
+ - **`useContext` for Control**: Use React's `useContext` hook to access the `openModal` and `closeModal` functions from anywhere in your component tree.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install modallib
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ First, you need to wrap your application with the `ModalOrchestrator` and place the `ModalHandler` within it.
23
+
24
+ ### 1. Create a Lazy-Loaded Modal Dictionary
25
+
26
+ To take advantage of code-splitting, define your modal dictionary using `React.lazy`. This tells React to load the component code only when it's first rendered.
27
+
28
+ Create a file (e.g., `modals.ts`) to define and register your modals.
29
+
30
+ ```javascript
31
+ // components/MyFirstModal.tsx
32
+ import React, { useContext } from 'react';
33
+ import { ModalContext } from 'modallib';
34
+
35
+ // Note: The component must be a default export for React.lazy to work
36
+ export const MyFirstModal = ({ title }) => {
37
+ const { closeModal } = useContext(ModalContext);
38
+ return (
39
+ <div>
40
+ <h2>{title}</h2>
41
+ <p>This is my first modal!</p>
42
+ <button onClick={closeModal}>Close</button>
43
+ </div>
44
+ );
45
+ };
46
+ export default MyFirstModal;
47
+
48
+
49
+ // modals.ts
50
+ import React from 'react';
51
+
52
+ export const modalDictionary = {
53
+ MY_FIRST_MODAL: React.lazy(() => import('./components/MyFirstModal')),
54
+ // ... register other lazy-loaded modals here
55
+ };
56
+ ```
57
+
58
+ ### 2. Wrap Your App
59
+
60
+ In your main application file (e.g., `App.tsx`), import the `ModalOrchestrator`, `ModalHandler`, and your modal dictionary.
61
+
62
+ ```javascript
63
+ // App.tsx
64
+ import { ModalOrchestrator, ModalHandler } from 'modallib';
65
+ import { modalDictionary } from './modals';
66
+
67
+ function App() {
68
+ return (
69
+ <ModalOrchestrator modals={modalDictionary}>
70
+ {/* Your application components */}
71
+ <h1>My App</h1>
72
+ <TriggerButton />
73
+
74
+ {/*
75
+ Place the handler at the top level.
76
+ You can customize the loading fallback in your own Suspense boundary
77
+ if you don't like the default "Loading..." text.
78
+ */}
79
+ <ModalHandler />
80
+ </ModalOrchestrator>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ### Opening a Modal
88
+
89
+ To open a modal, import `ModalContext`, get the `openModal` function from the context, and call it with the registered name of the modal you want to open.
90
+
91
+ ```javascript
92
+ // components/TriggerButton.tsx
93
+ import React, { useContext } from 'react';
94
+ import { ModalContext } from 'modallib';
95
+
96
+ const TriggerButton = () => {
97
+ const { openModal } = useContext(ModalContext);
98
+
99
+ const handleClick = () => {
100
+ // Open the modal and pass props to it
101
+ openModal('MY_FIRST_MODAL', { title: 'Hello World!' });
102
+ };
103
+
104
+ return <button onClick={handleClick}>Open Modal</button>;
105
+ };
106
+ ```
107
+
108
+ ### Closing a Modal
109
+
110
+ The `ModalHandler` automatically provides a `closeModal` function to your modal component via context. Use the `useContext` hook to access it.
111
+
112
+ ```javascript
113
+ // components/MyFirstModal.tsx
114
+ import React, { useContext } from 'react';
115
+ import { ModalContext } from 'modallib';
116
+
117
+ // Ensure the component is a default export
118
+ const MyFirstModal = ({ title }) => {
119
+ const { closeModal } = useContext(ModalContext); // Get closeModal from context
120
+
121
+ return (
122
+ <div>
123
+ <h2>{title}</h2>
124
+ <button onClick={closeModal}>Close Me</button>
125
+ </div>
126
+ );
127
+ };
128
+
129
+ export default MyFirstModal;
130
+ ```
131
+
132
+ ## License
133
+
134
+ This project is licensed under the ISC License.
@@ -0,0 +1,2 @@
1
+ declare const ModalHandler: () => import("react/jsx-runtime").JSX.Element | null;
2
+ export default ModalHandler;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalHandler.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalHandler.tsx"],"names":[],"mappings":"AAGA,QAAA,MAAM,YAAY,sDAYjB,CAAC;AACF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext, Suspense } from "react";
3
+ import { ModalDispatchContext, ModalStateContext } from "../contexts/ModalContext";
4
+ const ModalHandler = () => {
5
+ const { modal, ModalComponent } = useContext(ModalStateContext);
6
+ const { closeModal } = useContext(ModalDispatchContext);
7
+ if (!modal || !ModalComponent)
8
+ return null;
9
+ if (modal)
10
+ document.body.style.removeProperty("overflow");
11
+ return (_jsx(Suspense, { fallback: _jsx("div", { children: "Loading..." }), children: _jsx(ModalComponent, Object.assign({ onClose: closeModal }, modal.props)) }));
12
+ };
13
+ export default ModalHandler;
14
+ //# sourceMappingURL=ModalHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalHandler.js","sourceRoot":"","sources":["../../src/Modal/ModalHandler.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAC,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEjF,MAAM,YAAY,GAAG,GAAG,EAAE;IACxB,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAChE,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC,oBAAoB,CAAC,CAAC;IAExD,IAAI,CAAC,KAAK,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,KAAK;QAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAE1D,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,uCAAqB,YACvC,KAAC,cAAc,kBAAC,OAAO,EAAE,UAAU,IAAM,KAAK,CAAC,KAAK,EAAI,GAC/C,CACZ,CAAC;AACJ,CAAC,CAAC;AACF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from "react";
2
+ import { type GenericObjectType } from "../types";
3
+ declare const ModalOrchestrator: ({ modals, children, }: {
4
+ modals: GenericObjectType;
5
+ children: ReactNode;
6
+ }) => import("react/jsx-runtime").JSX.Element;
7
+ export default ModalOrchestrator;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalOrchestrator.d.ts","sourceRoot":"","sources":["../../src/Modal/ModalOrchestrator.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAEhD,OAAO,EAAC,KAAK,iBAAiB,EAAC,MAAM,UAAU,CAAC;AAGhD,QAAA,MAAM,iBAAiB,GAAI,uBAGxB;IACD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB,4CAmBA,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { useModal } from "../hooks/useModal";
4
+ import {} from "../types";
5
+ import { ModalDispatchContext, ModalStateContext } from "../contexts/ModalContext";
6
+ const ModalOrchestrator = ({ modals, children, }) => {
7
+ const { modal, ModalComponent, openModal, closeModal } = useModal(modals);
8
+ const state = useMemo(() => ({ modal, ModalComponent }), [modal, ModalComponent]);
9
+ const dispatch = useMemo(() => ({ openModal, closeModal }), [openModal, closeModal]);
10
+ return (_jsx(ModalStateContext.Provider, { value: state, children: _jsx(ModalDispatchContext.Provider, { value: dispatch, children: children }) }));
11
+ };
12
+ export default ModalOrchestrator;
13
+ //# sourceMappingURL=ModalOrchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalOrchestrator.js","sourceRoot":"","sources":["../../src/Modal/ModalOrchestrator.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAkB,OAAO,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAC,QAAQ,EAAC,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAwB,MAAM,UAAU,CAAC;AAChD,OAAO,EAAC,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElF,MAAM,iBAAiB,GAAG,CAAC,EACzB,MAAM,EACN,QAAQ,GAIT,EAAE,EAAE;IACH,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE1E,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,EACjC,CAAC,KAAK,EAAE,cAAc,CAAC,CACxB,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,EACjC,CAAC,SAAS,EAAE,UAAU,CAAC,CACxB,CAAC;IAEF,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACtC,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAC3C,QAAQ,GACqB,GACL,CAC9B,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const ModalStateContext: import("react").Context<Pick<{
2
+ ModalComponent: import("react").FC<any> | null;
3
+ modal: import("../types").GenericModal | null;
4
+ openModal: (name: string, props?: object) => void;
5
+ closeModal: () => void;
6
+ }, "modal" | "ModalComponent">>;
7
+ export declare const ModalDispatchContext: import("react").Context<Pick<{
8
+ ModalComponent: import("react").FC<any> | null;
9
+ modal: import("../types").GenericModal | null;
10
+ openModal: (name: string, props?: object) => void;
11
+ closeModal: () => void;
12
+ }, "closeModal" | "openModal">>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ModalContext.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,iBAAiB;;;mCAQ2D,CAAC;;+BANlF,CAAC;AAGT,eAAO,MAAM,oBAAoB;;;mCAGwD,CAAC;;+BADlF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { createContext } from "react";
2
+ import { useModal } from "../hooks/useModal";
3
+ // This context will hold the stateful values that cause re-renders.
4
+ export const ModalStateContext = createContext(null);
5
+ // This context will hold the stable dispatch functions.
6
+ export const ModalDispatchContext = createContext(null);
7
+ //# sourceMappingURL=ModalContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalContext.js","sourceRoot":"","sources":["../../src/contexts/ModalContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI7C,oEAAoE;AACpE,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAE5C,IAAK,CAAC,CAAC;AAET,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAE/C,IAAK,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type FC } from "react";
2
+ import { type GenericModal, type GenericObjectType } from "../types";
3
+ /**
4
+ * A custom hook to manage modals in a React application.
5
+ * It allows opening and closing modals, and keeps track of the modal stack.
6
+ *
7
+ * @param {GenericObjectType} modals - An object containing modal components.
8
+ * @returns {Object} - An object containing the current modal component, openModal and closeModal functions.
9
+ */
10
+ type UseModalReturnType = {
11
+ ModalComponent: FC<any> | null;
12
+ modal: GenericModal | null;
13
+ openModal: (name: string, props?: object) => void;
14
+ closeModal: () => void;
15
+ };
16
+ export declare const smallGreyFont = "color: grey; font-size: 85%";
17
+ export declare const keyFont = "color: orange;font-weight: bold;";
18
+ export declare const useModal: (modals: GenericObjectType) => UseModalReturnType;
19
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useModal.d.ts","sourceRoot":"","sources":["../../src/hooks/useModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAoB,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAErE;;;;;;GAMG;AAEH,KAAK,kBAAkB,GAAG;IACxB,cAAc,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AACF,eAAO,MAAM,aAAa,gCAAgC,CAAC;AAC3D,eAAO,MAAM,OAAO,qCAAqC,CAAC;AAE1D,eAAO,MAAM,QAAQ,GAAI,QAAQ,iBAAiB,KAAG,kBA4CpD,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { useRef, useState } from "react";
2
+ import {} from "../types";
3
+ export const smallGreyFont = "color: grey; font-size: 85%";
4
+ export const keyFont = "color: orange;font-weight: bold;";
5
+ export const useModal = (modals) => {
6
+ const modalStack = useRef([]); // For purpose of keeping track of the modal stack, especially when there are multiple modals open
7
+ const [modal, setModal] = useState(null);
8
+ const ModalLookup = Object.assign({}, modals);
9
+ const modalName = modal ? modal.name : "";
10
+ const ModalComponent = ModalLookup[modalName];
11
+ const openModal = (name, props = {}) => {
12
+ // Check if the modal is already open
13
+ const existingModal = modalStack.current.find((modal) => modal.name === name);
14
+ if (existingModal) {
15
+ // If the modal is already open, just update its props
16
+ // additionally we don't need try/catch here, because we already know the modal name exists
17
+ existingModal.props = Object.assign(Object.assign({}, existingModal.props), props);
18
+ setModal(existingModal);
19
+ return;
20
+ }
21
+ try {
22
+ if (name in ModalLookup) {
23
+ modalStack.current.push({ name, props });
24
+ setModal({ name, props });
25
+ }
26
+ else {
27
+ console.log("%cModalManager %copening:", keyFont, smallGreyFont, name);
28
+ console.log("%cModalStack", smallGreyFont, modalStack.current);
29
+ throw new Error(`Modal with name "${name}" does not exist in ModalLookup.`);
30
+ }
31
+ }
32
+ catch (error) {
33
+ console.log("Failed to open modal:", error);
34
+ console.error("Failed to open modal:", error);
35
+ }
36
+ };
37
+ const closeModal = () => {
38
+ var _a;
39
+ console.log("closeModal");
40
+ modalStack.current.pop();
41
+ setModal((_a = modalStack.current[modalStack.current.length - 1]) !== null && _a !== void 0 ? _a : null);
42
+ };
43
+ return { ModalComponent, modal, openModal, closeModal };
44
+ };
45
+ //# sourceMappingURL=useModal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useModal.js","sourceRoot":"","sources":["../../src/hooks/useModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAA6C,MAAM,UAAU,CAAC;AAgBrE,MAAM,CAAC,MAAM,aAAa,GAAG,6BAA6B,CAAC;AAC3D,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAE1D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAyB,EAAsB,EAAE;IACxE,MAAM,UAAU,GAAG,MAAM,CAAiB,EAAE,CAAC,CAAC,CAAC,kGAAkG;IACjJ,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAC;IAC9D,MAAM,WAAW,qBAA2B,MAAM,CAAE,CAAC;IACrD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAE9C,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAK,GAAG,EAAE,EAAE,EAAE;QAC7C,qCAAqC;QACrC,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAC3C,CAAC,KAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAC7C,CAAC;QACF,IAAI,aAAa,EAAE,CAAC;YAClB,sDAAsD;YACtD,2FAA2F;YAC3F,aAAa,CAAC,KAAK,mCAAQ,aAAa,CAAC,KAAK,GAAK,KAAK,CAAE,CAAC;YAC3D,QAAQ,CAAC,aAAa,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAI,IAAI,IAAI,WAAW,EAAE,CAAC;gBACxB,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;gBACvE,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;gBACjE,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,kCAAkC,CAC3D,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;YAC5C,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,EAAE;;QACtB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACzB,QAAQ,CAAC,MAAA,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,mCAAI,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAC1D,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { default as ModalOrchestrator } from './Modal/ModalOrchestrator';
2
+ export { default as ModalHandler } from './Modal/ModalHandler';
3
+ export { ModalDispatchContext, ModalStateContext } from './contexts/ModalContext';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default as ModalOrchestrator } from './Modal/ModalOrchestrator';
2
+ export { default as ModalHandler } from './Modal/ModalHandler';
3
+ export { ModalDispatchContext, ModalStateContext } from './contexts/ModalContext';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export type GenericObjectType = {
2
+ [key: string]: any;
3
+ };
4
+ export type GenericModal = {
5
+ name: string;
6
+ props?: Record<string, any>;
7
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,YAAY,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "react-modal-orchestrator",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "prepublishOnly": "npm run build",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "keywords": [
12
+ "react",
13
+ "modal",
14
+ "suspense"
15
+ ],
16
+ "author": "Rudi Wever: rwever@ford.com",
17
+ "license": "ISC",
18
+ "description": "A centralized and flexible modal management system for React applications.",
19
+ "dependencies": {
20
+ "react": "^18.3.1",
21
+ "react-dom": "^18.3.1"
22
+ },
23
+ "devDependencies": {
24
+ "@types/react": "^18.3.12",
25
+ "@types/react-dom": "^18.3.1",
26
+ "typescript": "^5.9.3"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=16.8.0",
30
+ "react-dom": ">=16.8.0"
31
+ }
32
+ }
@@ -0,0 +1,97 @@
1
+ # What to do when creating a modal.
2
+
3
+ # 1. Create a new file named `MyModalComponent.tsx` in the component folder.
4
+
5
+ code template:
6
+
7
+ (do not pass: isOpen or setIsOpen args)
8
+
9
+ - set the `ModalWindow` isOpen to true
10
+ - use `closeModal` from `ModalContext` to close the modal
11
+
12
+ ```javascript
13
+ const ModalComponent = () => {
14
+ const { closeModal } = useContext(ModalContext);
15
+
16
+ return (
17
+ <AEMPage pagePath={pagePath} aemHost={getAEMHost()}>
18
+ <ModalWindow variant={"large"} isOpen={true} onClose={closeModal}>
19
+ <ModalHeader onClose={closeModal}>
20
+ <div className={styles.modalHeader}>
21
+ MODAL HEADER
22
+ </div>
23
+ <button onClick={()=> closeModal()}>CLOSE</button>
24
+ </ModalHeader>
25
+ <ModalBody>
26
+ <div>
27
+ MODAL BODY
28
+ </div>
29
+ </ModalBody>
30
+ <ModalFooter placement="right">
31
+ <div>
32
+ MODAL FOOTER
33
+ </div>
34
+ </ModalFooter>
35
+ </ModalWindow>
36
+ </AEMPage>
37
+ );
38
+ };
39
+ export default ModalComponent;
40
+ ```
41
+
42
+ # 2. Add the new modal to the constant `modals` in `ModalDictionary.tsx`.
43
+ # 3. Add a definition to the RenderingDictionary.
44
+
45
+ When adding the new modal to `bootstrap` for local rendering
46
+ - the aemComponent and/or localComponent should be the ModalHandler
47
+ - the modal will be prefixed with an alias to the group, to specify which modal it is
48
+
49
+ # 4. How to trigger your modal?
50
+
51
+ - use the `openModal` method from `ModalContext` to trigger the modal; `openModal` takes the name of the modal as defined in the
52
+ modalDictionary, optionally you can also pass in props to the modal when you are opening it.
53
+
54
+ ```javascript
55
+ const { openModal } = useContext(ModalContext);
56
+
57
+ const handleClick = () => openModal("PST_MODAL", {
58
+ data: "myDatal"
59
+ });
60
+ ```
61
+
62
+ ## What if my modal needs data?
63
+ It is not recommended to access state and/or functionality using the`useContext` hook from within your modal component (this includes accessing context from any custom hooks employed by the modal component). It is very likely that the provider supplying the context will not be available when the modal is rendered, leading to errors. Instead, you should use
64
+ optional props of the `openModal` method, allowing your modal to receive data when it is rendered. The props will be accessible in the modal component through the `props` argument. The only `useContext` hook you should use in your modal component is the `ModalContext` to access the `closeModal` method.
65
+
66
+ When passing optional props to the modal, you can access them in the modal component like this:
67
+
68
+ ```javascript
69
+ const ModalComponent = ({data}: {data:string}) => {
70
+ console.log(data); // "myData" <<==== ACCESSING THE DATA PASSED TO THE MODAL
71
+ const { closeModal } = useContext(ModalContext); // <<==== ONLY useContext hook useage. ACCESSING THE CLOSE MODAL FUNCTION
72
+ const { data } = modalProps;
73
+
74
+ return (
75
+ <AEMPage pagePath={pagePath} aemHost={getAEMHost()}>
76
+ <ModalWindow variant={"large"} isOpen={true} onClose={closeModal}>
77
+ <ModalHeader onClose={closeModal}>
78
+ <div className={styles.modalHeader}>
79
+ MODAL HEADER
80
+ </div>
81
+ </ModalHeader>
82
+ <ModalBody>
83
+ <div>
84
+ MODAL BODY with data: {data}
85
+ </div>
86
+ </ModalBody>
87
+ <ModalFooter placement="right">
88
+ <div>
89
+ MODAL FOOTER
90
+ </div>
91
+ </ModalFooter>
92
+ </ModalWindow>
93
+ </AEMPage>
94
+ );
95
+ };
96
+ export default ModalComponent;
97
+ ```
@@ -0,0 +1,17 @@
1
+ import { useContext, Suspense } from "react";
2
+ import {ModalDispatchContext, ModalStateContext} from "../contexts/ModalContext";
3
+
4
+ const ModalHandler = () => {
5
+ const { modal, ModalComponent } = useContext(ModalStateContext);
6
+ const { closeModal } = useContext(ModalDispatchContext);
7
+
8
+ if (!modal || !ModalComponent) return null;
9
+ if (modal) document.body.style.removeProperty("overflow");
10
+
11
+ return (
12
+ <Suspense fallback={<div>Loading...</div>}>
13
+ <ModalComponent onClose={closeModal} {...modal.props} />
14
+ </Suspense>
15
+ );
16
+ };
17
+ export default ModalHandler;
@@ -0,0 +1,33 @@
1
+ import { type ReactNode, useMemo } from "react";
2
+ import {useModal} from "../hooks/useModal";
3
+ import {type GenericObjectType} from "../types";
4
+ import {ModalDispatchContext, ModalStateContext } from "../contexts/ModalContext";
5
+
6
+ const ModalOrchestrator = ({
7
+ modals,
8
+ children,
9
+ }: {
10
+ modals: GenericObjectType;
11
+ children: ReactNode;
12
+ }) => {
13
+ const { modal, ModalComponent, openModal, closeModal } = useModal(modals);
14
+
15
+ const state = useMemo(
16
+ () => ({ modal, ModalComponent }),
17
+ [modal, ModalComponent],
18
+ );
19
+ const dispatch = useMemo(
20
+ () => ({ openModal, closeModal }),
21
+ [openModal, closeModal],
22
+ );
23
+
24
+ return (
25
+ <ModalStateContext.Provider value={state}>
26
+ <ModalDispatchContext.Provider value={dispatch}>
27
+ {children}
28
+ </ModalDispatchContext.Provider>
29
+ </ModalStateContext.Provider>
30
+ );
31
+ };
32
+
33
+ export default ModalOrchestrator;
@@ -0,0 +1,14 @@
1
+ import { createContext } from "react";
2
+ import { useModal } from "../hooks/useModal";
3
+
4
+ type ModalContextType = ReturnType<typeof useModal>;
5
+
6
+ // This context will hold the stateful values that cause re-renders.
7
+ export const ModalStateContext = createContext<
8
+ Pick<ModalContextType, "modal" | "ModalComponent">
9
+ >(null!);
10
+
11
+ // This context will hold the stable dispatch functions.
12
+ export const ModalDispatchContext = createContext<
13
+ Pick<ModalContextType, "openModal" | "closeModal">
14
+ >(null!);
@@ -0,0 +1,65 @@
1
+ import { type FC, useRef, useState } from "react";
2
+ import { type GenericModal, type GenericObjectType } from "../types";
3
+
4
+ /**
5
+ * A custom hook to manage modals in a React application.
6
+ * It allows opening and closing modals, and keeps track of the modal stack.
7
+ *
8
+ * @param {GenericObjectType} modals - An object containing modal components.
9
+ * @returns {Object} - An object containing the current modal component, openModal and closeModal functions.
10
+ */
11
+
12
+ type UseModalReturnType = {
13
+ ModalComponent: FC<any> | null;
14
+ modal: GenericModal | null;
15
+ openModal: (name: string, props?: object) => void;
16
+ closeModal: () => void;
17
+ };
18
+ export const smallGreyFont = "color: grey; font-size: 85%";
19
+ export const keyFont = "color: orange;font-weight: bold;";
20
+
21
+ export const useModal = (modals: GenericObjectType): UseModalReturnType => {
22
+ const modalStack = useRef<GenericModal[]>([]); // For purpose of keeping track of the modal stack, especially when there are multiple modals open
23
+ const [modal, setModal] = useState<GenericModal | null>(null);
24
+ const ModalLookup: GenericObjectType = { ...modals };
25
+ const modalName = modal ? modal.name : "";
26
+ const ModalComponent = ModalLookup[modalName];
27
+
28
+ const openModal = (name: string, props = {}) => {
29
+ // Check if the modal is already open
30
+ const existingModal = modalStack.current.find(
31
+ (modal: GenericModal) => modal.name === name,
32
+ );
33
+ if (existingModal) {
34
+ // If the modal is already open, just update its props
35
+ // additionally we don't need try/catch here, because we already know the modal name exists
36
+ existingModal.props = { ...existingModal.props, ...props };
37
+ setModal(existingModal);
38
+ return;
39
+ }
40
+
41
+ try {
42
+ if (name in ModalLookup) {
43
+ modalStack.current.push({ name, props });
44
+ setModal({ name, props });
45
+ } else {
46
+ console.log("%cModalManager %copening:", keyFont, smallGreyFont, name);
47
+ console.log("%cModalStack", smallGreyFont, modalStack.current);
48
+ throw new Error(
49
+ `Modal with name "${name}" does not exist in ModalLookup.`,
50
+ );
51
+ }
52
+ } catch (error) {
53
+ console.log("Failed to open modal:", error);
54
+ console.error("Failed to open modal:", error);
55
+ }
56
+ };
57
+
58
+ const closeModal = () => {
59
+ console.log("closeModal");
60
+ modalStack.current.pop();
61
+ setModal(modalStack.current[modalStack.current.length - 1] ?? null);
62
+ };
63
+
64
+ return { ModalComponent, modal, openModal, closeModal };
65
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { default as ModalOrchestrator } from './Modal/ModalOrchestrator';
2
+ export { default as ModalHandler } from './Modal/ModalHandler';
3
+ export { ModalDispatchContext, ModalStateContext } from './contexts/ModalContext';
package/src/types.ts ADDED
@@ -0,0 +1,7 @@
1
+ export type GenericObjectType = {
2
+ [key: string]: any;
3
+ };
4
+ export type GenericModal = {
5
+ name: string;
6
+ props?: Record<string, any>;
7
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "emitDeclarationOnly": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "isolatedModules": true,
8
+ "jsx": "react-jsx",
9
+ "lib": ["DOM", "ESNext"],
10
+ "module": "CommonJS",
11
+ "moduleResolution": "node",
12
+ "noFallthroughCasesInSwitch": true,
13
+ "outDir": "./dist",
14
+ "strict": true,
15
+ "target": "ESNext"
16
+ }
17
+ }