/
NestedDialogContext.tsx
89 lines (79 loc) · 2.32 KB
/
NestedDialogContext.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/* eslint-disable react/prop-types */
import React, {
createContext,
ReactElement,
ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from "react";
interface NestedDialogContext {
stack: string[];
add: (dialogId: string) => void;
remove: (dialogId: string) => void;
}
const noop = (): void => {
// do nothing
};
const context = createContext<NestedDialogContext>({
stack: [],
add: noop,
remove: noop,
});
/* istanbul ignore next */
if (process.env.NODE_ENV !== "production") {
context.displayName = "NestedDialogContext";
}
const { Provider } = context;
export interface NestedDialogContextProviderProps {
children: ReactNode;
}
/**
* This component is used to help with handling nested dialogs by:
* - preventing all dialogs to be closed when the escape key is pressed
* - hiding the overlays for dialogs that are not the top-most focus
*
* This should be added to the root of your app if you would like to enable this
* feature.
*/
export function NestedDialogContextProvider({
children,
}: NestedDialogContextProviderProps): ReactElement {
const [stack, setStack] = useState<string[]>([]);
const add = useCallback((dialogId: string) => {
setStack((prevStack) => {
/* istanbul ignore next */
if (
process.env.NODE_ENV !== "production" &&
prevStack.includes(dialogId)
) {
/* eslint-disable no-console */
console.warn(
"Tried to add a duplicate dialog id to the `NestedDialogContext`."
);
console.warn(
`This means that you have two dialogs with the same id: \`${dialogId}\`.`
);
console.warn(
"This should be fixed before moving to production since this will break accessibility and is technically invalid."
);
}
return prevStack.concat(dialogId);
});
}, []);
const remove = useCallback((dialogId: string) => {
setStack((prevStack) => prevStack.filter((id) => id !== dialogId));
}, []);
const value = useMemo(() => ({ stack, add, remove }), [add, remove, stack]);
return <Provider value={value}>{children}</Provider>;
}
/**
* Gets the current nested dialog context. This shouldn't really be used
* externally and is a private context hook.
*
* @internal
*/
export function useNestedDialogContext(): NestedDialogContext {
return useContext(context);
}