Skip to content

Commit 3ead8eb

Browse files
authored
Chore: Introduce Modal Region (#25962)
1 parent 8547fff commit 3ead8eb

9 files changed

Lines changed: 98 additions & 64 deletions

File tree

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { ModalContext } from '@rocket.chat/ui-contexts';
2-
import React, { useState, useMemo, memo, ReactNode, useCallback, ReactElement } from 'react';
2+
import React, { useState, useMemo, memo, ReactNode, ReactElement } from 'react';
33

44
import { modal } from '../../app/ui-utils/client/lib/modal';
5-
import ModalBackdrop from '../components/modal/ModalBackdrop';
6-
import ModalPortal from '../components/modal/ModalPortal';
75
import { useImperativeModal } from '../views/hooks/useImperativeModal';
86

97
type ModalProviderProps = {
@@ -14,27 +12,18 @@ const ModalProvider = ({ children }: ModalProviderProps): ReactElement => {
1412
const [currentModal, setCurrentModal] = useState<ReactNode>(null);
1513

1614
const contextValue = useMemo(
17-
() =>
18-
Object.assign(modal, {
15+
() => ({
16+
modal: Object.assign(modal, {
1917
setModal: setCurrentModal,
2018
}),
21-
[],
19+
currentModal,
20+
}),
21+
[currentModal],
2222
);
2323

2424
useImperativeModal(setCurrentModal);
2525

26-
const handleDismiss = useCallback(() => setCurrentModal(null), [setCurrentModal]);
27-
28-
return (
29-
<ModalContext.Provider value={contextValue}>
30-
{children}
31-
{currentModal && (
32-
<ModalPortal>
33-
<ModalBackdrop onDismiss={handleDismiss}>{currentModal}</ModalBackdrop>
34-
</ModalPortal>
35-
)}
36-
</ModalContext.Provider>
37-
);
26+
return <ModalContext.Provider value={contextValue} children={children} />;
3827
};
3928

4029
export default memo<typeof ModalProvider>(ModalProvider);

apps/meteor/client/stories/contexts/ModalContextMock.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@ type ModalContextMockProps = {
99
};
1010

1111
const ModalContextMock = ({ children }: ModalContextMockProps): ReactElement => {
12-
const parent = useContext(ModalContext);
12+
const context = useContext(ModalContext);
1313

1414
const value = useMemo(
15-
(): ContextType<typeof ModalContext> => ({
16-
...parent,
17-
setModal: (modal): void => {
18-
logAction('setModal', modal);
19-
},
20-
}),
21-
[parent],
15+
(): ContextType<typeof ModalContext> =>
16+
context?.modal
17+
? {
18+
modal: {
19+
...context.modal,
20+
setModal: (modal): void => {
21+
logAction('setModal', modal);
22+
},
23+
},
24+
currentModal: context.currentModal,
25+
}
26+
: undefined,
27+
[context],
2228
);
2329

2430
return <ModalContext.Provider children={children} value={value} />;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useModal, useCurrentModal } from '@rocket.chat/ui-contexts';
2+
import React, { useCallback, ReactElement } from 'react';
3+
4+
import ModalBackdrop from '../../components/modal/ModalBackdrop';
5+
import ModalPortal from '../../components/modal/ModalPortal';
6+
7+
const ModalRegion = (): ReactElement | null => {
8+
const currentModal = useCurrentModal();
9+
const { setModal } = useModal();
10+
const handleDismiss = useCallback(() => setModal(null), [setModal]);
11+
12+
if (!currentModal) {
13+
return null;
14+
}
15+
16+
return (
17+
<ModalPortal>
18+
<ModalBackdrop onDismiss={handleDismiss}>{currentModal}</ModalBackdrop>
19+
</ModalPortal>
20+
);
21+
};
22+
23+
export default ModalRegion;

apps/meteor/client/views/root/AppRoot.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const MeteorProvider = lazy(() => import('../../providers/MeteorProvider'));
1010
const BannerRegion = lazy(() => import('../banners/BannerRegion'));
1111
const AppLayout = lazy(() => import('./AppLayout'));
1212
const PortalsWrapper = lazy(() => import('./PortalsWrapper'));
13+
const ModalRegion = lazy(() => import('../modal/ModalRegion'));
1314

1415
const AppRoot: FC = () => (
1516
<Suspense fallback={<PageLoading />}>
@@ -20,6 +21,7 @@ const AppRoot: FC = () => (
2021
<BannerRegion />
2122
<AppLayout />
2223
<PortalsWrapper />
24+
<ModalRegion />
2325
</OmnichannelRoomIconProvider>
2426
</QueryClientProvider>
2527
</MeteorProvider>

packages/ui-contexts/src/ModalContext.ts

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,39 +35,25 @@ type ModalInstance = ModalConfiguration & {
3535
};
3636

3737
export type ModalContextValue = {
38-
open(
39-
config?: ModalConfiguration,
40-
fn?: (instance: ModalInstance, value: unknown) => void,
41-
onCancel?: (instance: ModalInstance) => void,
42-
): void;
43-
push(
44-
config?: ModalConfiguration,
45-
fn?: (instance: ModalInstance, value: unknown) => void,
46-
onCancel?: (instance: ModalInstance) => void,
47-
): ModalInstance;
48-
cancel(): void;
49-
close(): void;
50-
confirm(value: unknown): void;
51-
showInputError(text: string): void;
52-
onKeyDown(event: KeyboardEvent): void;
53-
setModal(modal: ReactNode): void;
38+
modal: {
39+
open(
40+
config?: ModalConfiguration,
41+
fn?: (instance: ModalInstance, value: unknown) => void,
42+
onCancel?: (instance: ModalInstance) => void,
43+
): void;
44+
push(
45+
config?: ModalConfiguration,
46+
fn?: (instance: ModalInstance, value: unknown) => void,
47+
onCancel?: (instance: ModalInstance) => void,
48+
): ModalInstance;
49+
cancel(): void;
50+
close(): void;
51+
confirm(value: unknown): void;
52+
showInputError(text: string): void;
53+
onKeyDown(event: KeyboardEvent): void;
54+
setModal(modal: ReactNode): void;
55+
};
56+
currentModal: ReactNode;
5457
};
5558

56-
export const ModalContext = createContext<ModalContextValue>({
57-
open: () => undefined,
58-
push: () => ({
59-
render: (): void => undefined,
60-
hide: (): void => undefined,
61-
destroy: (): void => undefined,
62-
close: (): void => undefined,
63-
confirm: (): void => undefined,
64-
cancel: (): void => undefined,
65-
showInputError: (): void => undefined,
66-
}),
67-
cancel: () => undefined,
68-
close: () => undefined,
69-
confirm: () => undefined,
70-
showInputError: () => undefined,
71-
onKeyDown: () => undefined,
72-
setModal: () => undefined,
73-
});
59+
export const ModalContext = createContext<ModalContextValue | undefined>(undefined);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useContext } from 'react';
2+
3+
import { ModalContext, ModalContextValue } from '../ModalContext';
4+
5+
/**
6+
* Similar to useModal this hook return the current modal from the context value
7+
*/
8+
export const useCurrentModal = (): ModalContextValue['currentModal'] => {
9+
const context = useContext(ModalContext);
10+
11+
if (!context) {
12+
throw new Error('useCurrentModal must be used inside Modal Context');
13+
}
14+
15+
return context.currentModal;
16+
};

packages/ui-contexts/src/hooks/useModal.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,15 @@ import { useContext } from 'react';
22

33
import { ModalContext, ModalContextValue } from '../ModalContext';
44

5-
export const useModal = (): ModalContextValue => useContext(ModalContext);
5+
/**
6+
* Consider using useCurrentModal to get the current modal
7+
*/
8+
export const useModal = (): ModalContextValue['modal'] => {
9+
const context = useContext(ModalContext);
10+
11+
if (!context) {
12+
throw new Error('useModal must be used inside Modal Context');
13+
}
14+
15+
return context.modal;
16+
};
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { ReactNode, useContext } from 'react';
1+
import type { ReactNode } from 'react';
22

3-
import { ModalContext } from '../ModalContext';
3+
import { useModal } from './useModal';
44

5-
export const useSetModal = (): ((modal?: ReactNode) => void) => useContext(ModalContext).setModal;
5+
export const useSetModal = (): ((modal?: ReactNode) => void) => useModal().setModal;

packages/ui-contexts/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export { useAttachmentAutoLoadEmbedMedia } from './hooks/useAttachmentAutoLoadEm
2121
export { useAttachmentDimensions } from './hooks/useAttachmentDimensions';
2222
export { useAttachmentIsCollapsedByDefault } from './hooks/useAttachmentIsCollapsedByDefault';
2323
export { useConnectionStatus } from './hooks/useConnectionStatus';
24+
export { useCurrentModal } from './hooks/useCurrentModal';
2425
export { useCurrentRoute } from './hooks/useCurrentRoute';
2526
export { useCustomSound } from './hooks/useCustomSound';
2627
export { useEndpoint } from './hooks/useEndpoint';

0 commit comments

Comments
 (0)