-
Notifications
You must be signed in to change notification settings - Fork 83
Expand file tree
/
Copy pathuseModalManager.ts
More file actions
177 lines (158 loc) · 6.34 KB
/
Copy pathuseModalManager.ts
File metadata and controls
177 lines (158 loc) · 6.34 KB
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import { useEffect } from 'react';
import { useEventListener, useMap } from 'usehooks-ts';
import { useDevice } from '@deriv-com/ui';
import useQueryString from './useQueryString';
type TUseModalManagerConfig = {
shouldReinitializeModals?: boolean;
};
type TShowModalOptions = {
shouldClearPreviousModals?: boolean;
shouldStackModals?: boolean;
};
type THideModalOptions = {
shouldHideAllModals?: boolean;
shouldHidePreviousModals?: boolean;
};
const MODAL_QUERY_SEPARATOR = ',';
/**
* Hook to manage states for showing/hiding multiple modals
* Use this hook when you are managing more than 1 modal to show/hide
*
* @example
* ```
* const {isModalOpenFor, showModal} = useModalManager()
*
* return (
* <>
* <ModalA isOpen={isModalOpenFor('ModalA')} />
* <ModalB isOpen={isModalOpenFor('ModalB')} />
* <button onClick={() => showModal('ModalA')}>...</button>
* </>
* )
* ```
*/
export default function useModalManager(config?: TUseModalManagerConfig) {
const { deleteQueryString, queryString, setQueryString } = useQueryString();
const { isDesktop } = useDevice();
const [isModalOpenScopes, actions] = useMap();
const syncModalParams = () => {
if (!queryString.modal) actions.setAll([]);
if (config?.shouldReinitializeModals !== undefined && config.shouldReinitializeModals === false) {
deleteQueryString('modal');
} else {
// sync modal query string in the URL with the initial modal open scopes
const modalHash = queryString.modal;
if (modalHash) {
const modalKeys = modalHash.split(MODAL_QUERY_SEPARATOR);
const currentModal = modalKeys.slice(-1)[0];
actions.setAll([]);
modalKeys.forEach(modalKey => {
actions.set(modalKey, !isDesktop);
});
actions.set(currentModal, true);
location.reload();
}
}
};
useEffect(() => {
// only sync the modal open states with the URL params when initial mount...
syncModalParams();
}, []);
useEffect(() => {
if (!queryString?.modal) actions.reset();
}, [queryString?.modal]);
// ...or when the user clicks the back button
useEventListener('popstate', () => {
syncModalParams();
});
const hideModal = (options?: THideModalOptions) => {
const modalHash = queryString.modal;
if (modalHash) {
let modalIds = modalHash.split(MODAL_QUERY_SEPARATOR);
if (options?.shouldHideAllModals) {
isModalOpenScopes.forEach((_, key) => {
actions.set(key, false);
deleteQueryString('modal');
});
} else if (options?.shouldHidePreviousModals) {
if (modalIds.length > 1) {
const firstModalId = modalIds.shift();
modalIds.forEach(modalId => {
actions.set(modalId, false); // Hide each modal except the first
});
modalIds = [firstModalId ?? '']; // Reset modalIds to only contain the first modal ID
setQueryString({
modal: firstModalId,
});
} else if (modalIds.length === 1) {
setQueryString({
modal: modalIds[0],
});
} else {
deleteQueryString('modal');
}
} else {
const currentModalId = modalIds.pop();
const previousModalId = modalIds.slice(-1)[0];
if (previousModalId) {
actions.set(currentModalId, false);
actions.set(previousModalId, true);
} else {
actions.set(currentModalId, false);
}
if (modalIds.length === 0) {
deleteQueryString('modal');
} else {
setQueryString({
modal: modalIds.join(MODAL_QUERY_SEPARATOR),
});
}
}
}
};
/**
* Keep the previous modal ids in the URL query strings separated by ','
* This way, when there is a new modal to be shown, we can track the previous modals from the query string based on the last 2 segments
*
* Example:
* - ModalA is shown, URL becomes /...?modal=ModalA (current modal is ModalA, there is no previous modal)
* - ModalB is shown next, URL becomes /...?modal=ModalA,ModalB (current modal is ModalB, previous modal is ModalA)
* - ModalC is shown next, URL becomes /...?modal=ModalA,ModalB,ModalC (current modal is ModalC, previous modal is ModalB)
* - ModalC is closed, URL becomes becomes /...?modal=modalA,ModalB (current modal is ModalB, previous modal is ModalA)
*/
const showModal = (modalId: string, options?: TShowModalOptions) => {
const modalHash = queryString.modal;
if (modalHash) {
const modalIds = modalHash.split(MODAL_QUERY_SEPARATOR);
const currentModalId = modalIds.slice(-1)[0];
if (currentModalId === modalId) return;
// If shouldStackModals is false, clear the modal stack
if (options?.shouldStackModals === false) {
actions.set(currentModalId, false);
} else {
// set the previous modal open state to false if shouldStackModals is false, otherwise set it to true (default true for mobile)
// set the new modal open state to true
actions.set(currentModalId, options?.shouldStackModals || !isDesktop);
}
actions.set(modalId, true);
// push the state of the new modal to the hash
modalIds.push(modalId);
setQueryString({
modal: options?.shouldClearPreviousModals ? modalId : modalIds.join(MODAL_QUERY_SEPARATOR),
});
} else {
actions.set(modalId, true);
setQueryString({
modal: modalId,
});
}
};
const isModalOpenFor = (modalKey: string) => {
return isModalOpenScopes.get(modalKey) || false;
};
return {
hideModal,
isModalOpenFor,
showModal,
};
}