Skip to content

Commit 27e95d2

Browse files
committed
Add nested modal management
1 parent 6819081 commit 27e95d2

File tree

7 files changed

+251
-32
lines changed

7 files changed

+251
-32
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![bundle size](https://img.shields.io/bundlephobia/minzip/react-easy-modals)](https://bundlephobia.com/result?p=react-easy-modals)
44

5-
A simple, flexible, zero-dependency, type-safe modal manager for React (port of [svelte-modals](https://github.com/mattjennings/svelte-modals)).
5+
A simple, flexible, zero-dependency, type-safe modal manager for React.
66

77
[📚 Documentation](https://react-easy-modals-docs.vercel.app/)
88

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-easy-modals",
33
"version": "1.2.0",
4-
"description": "A simple, flexible, headless, zero-dependency modal stack manager for React (port of svelte-modals).",
4+
"description": "A simple, flexible, headless, zero-dependency modal stack manager for React.",
55
"homepage": "https://react-easy-modals-docs.vercel.app/",
66
"main": "dist/index.js",
77
"types": "dist/index.d.ts",

src/context.tsx

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { createContext, useContext } from "react";
1+
import { createContext, ReactNode, useContext } from "react";
22
import { ModalItemProvider } from "./item-context";
3-
import { ModalManager, ModalProviderProps } from "./types";
3+
import {
4+
InternalModalInstance,
5+
ModalInstance,
6+
ModalManager,
7+
ModalProviderProps,
8+
} from "./types";
49
import { useModalManager } from "./use-modal-manager";
510

611
const ModalContext = createContext<ModalManager | null>(null);
@@ -27,26 +32,46 @@ function Modals({
2732
modal,
2833
}: Omit<ModalProviderProps, "children">) {
2934
const modalManager = useModals();
30-
const { stack, isLoading } = modalManager;
35+
const { stack, isLoading, createOpen } = modalManager;
36+
37+
const renderModal = (internalModal: InternalModalInstance, isNested: boolean = false): ReactNode => {
38+
const open = createOpen(internalModal.id);
39+
40+
const nestedElement = internalModal.nested
41+
? renderModal(internalModal.nested, true)
42+
: null;
43+
44+
const modalInstance: ModalInstance = {
45+
...internalModal,
46+
nested: nestedElement,
47+
open,
48+
isNested,
49+
};
50+
51+
return (
52+
<ModalItemProvider key={modalInstance.id} modal={modalInstance}>
53+
{modal ? (
54+
modal(modalInstance, modalManager)
55+
) : (
56+
<modalInstance.component
57+
{...modalInstance.props}
58+
close={modalInstance.close}
59+
isOpen={modalInstance.isOpen}
60+
isNested={isNested}
61+
id={modalInstance.id}
62+
index={modalInstance.index}
63+
open={modalInstance.open}
64+
nested={nestedElement}
65+
/>
66+
)}
67+
</ModalItemProvider>
68+
);
69+
};
3170

3271
return (
3372
<>
3473
{isLoading && loading && loading(modalManager)}
35-
{stack.map((modalInstance) => (
36-
<ModalItemProvider key={modalInstance.id} modal={modalInstance}>
37-
{modal ? (
38-
modal(modalInstance, modalManager)
39-
) : (
40-
<modalInstance.component
41-
{...modalInstance.props}
42-
close={modalInstance.close}
43-
isOpen={modalInstance.isOpen}
44-
id={modalInstance.id}
45-
index={modalInstance.index}
46-
/>
47-
)}
48-
</ModalItemProvider>
49-
))}
74+
{stack.map((internalModal) => renderModal(internalModal))}
5075
{(isLoading || stack.length > 0) && backdrop && backdrop(modalManager)}
5176
</>
5277
);

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { ModalProvider, useModals } from "./context";
2-
export { useBeforeClose } from "./item-context";
2+
export { useBeforeClose, useModal } from "./item-context";
33
export type { ModalProps } from "./types";
44
export { useModalManager } from "./use-modal-manager";

src/item-context.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ export function useModal() {
1414
}
1515

1616
export function useBeforeClose<R = unknown>(callback: (value?: R) => boolean) {
17-
const modal = useModal();
17+
const modal = useContext(ModalItemContext);
18+
19+
if (!modal) {
20+
throw new Error("useBeforeClose must be called inside a modal component");
21+
}
1822

1923
useEffect(() => {
2024
modal.onBeforeClose(callback as (value?: any) => boolean);

src/types.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,30 @@ export interface ModalProviderProps {
1212
modal?: (modal: ModalInstance, modals: ModalManager) => ReactElement;
1313
}
1414

15+
export type OpenFunction = {
16+
<T extends Record<string, never>, R = unknown>(
17+
component:
18+
| ModalComponent<T, R>
19+
| (() => Promise<{ default: ModalComponent<T, R> }>),
20+
options?: ModalOptions
21+
): Promise<R>;
22+
<T, R = unknown>(
23+
component:
24+
| ModalComponent<T, R>
25+
| (() => Promise<{ default: ModalComponent<T, R> }>),
26+
props: Omit<T, keyof ModalProps<R>>,
27+
options?: ModalOptions
28+
): Promise<R>;
29+
};
30+
1531
export interface ModalProps<ReturnValue = any> {
1632
index: number;
1733
id: string;
1834
isOpen: boolean;
35+
isNested: boolean;
1936
close: (value?: ReturnValue) => void;
37+
open: OpenFunction;
38+
nested?: ReactNode;
2039
}
2140

2241
// Type commun pour les composants modaux
@@ -29,9 +48,12 @@ export interface ModalInstance<T = any, ReturnValue = any> {
2948
id: string;
3049
props?: Omit<T, keyof ModalProps<ReturnValue>>;
3150
isOpen: boolean;
51+
isNested: boolean;
3252
close: (value?: ReturnValue) => void;
3353
index: number;
3454
onBeforeClose: (callback: (value?: ReturnValue) => boolean) => void;
55+
nested: ReactNode;
56+
open: OpenFunction;
3557
}
3658

3759
export interface ModalManager {
@@ -53,9 +75,21 @@ export interface ModalManager {
5375
close: (n?: number) => boolean;
5476
closeById: (id: string) => boolean;
5577
closeAll: () => boolean;
56-
stack: ModalInstance[];
78+
stack: InternalModalInstance[];
5779
action: ModalAction;
5880
isLoading: boolean;
81+
createOpen: (parentId: string) => OpenFunction;
82+
}
83+
84+
export interface InternalModalInstance<T = any, ReturnValue = any> {
85+
component: ModalComponent<T, ReturnValue>;
86+
id: string;
87+
props?: Omit<T, keyof ModalProps<ReturnValue>>;
88+
isOpen: boolean;
89+
close: (value?: ReturnValue) => void;
90+
index: number;
91+
onBeforeClose: (callback: (value?: ReturnValue) => boolean) => void;
92+
nested?: InternalModalInstance;
5993
}
6094

6195
export type ModalAction = "none" | "push" | "replace" | "pop";

0 commit comments

Comments
 (0)