Chore: Introduce Modal Region (#25962)

pull/25964/head
Douglas Fabris 4 years ago committed by GitHub
parent 8547ffff6a
commit 3ead8ebd3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      apps/meteor/client/providers/ModalProvider.tsx
  2. 22
      apps/meteor/client/stories/contexts/ModalContextMock.tsx
  3. 23
      apps/meteor/client/views/modal/ModalRegion.tsx
  4. 2
      apps/meteor/client/views/root/AppRoot.tsx
  5. 54
      packages/ui-contexts/src/ModalContext.ts
  6. 16
      packages/ui-contexts/src/hooks/useCurrentModal.ts
  7. 13
      packages/ui-contexts/src/hooks/useModal.ts
  8. 6
      packages/ui-contexts/src/hooks/useSetModal.ts
  9. 1
      packages/ui-contexts/src/index.ts

@ -1,9 +1,7 @@
import { ModalContext } from '@rocket.chat/ui-contexts';
import React, { useState, useMemo, memo, ReactNode, useCallback, ReactElement } from 'react';
import React, { useState, useMemo, memo, ReactNode, ReactElement } from 'react';
import { modal } from '../../app/ui-utils/client/lib/modal';
import ModalBackdrop from '../components/modal/ModalBackdrop';
import ModalPortal from '../components/modal/ModalPortal';
import { useImperativeModal } from '../views/hooks/useImperativeModal';
type ModalProviderProps = {
@ -14,27 +12,18 @@ const ModalProvider = ({ children }: ModalProviderProps): ReactElement => {
const [currentModal, setCurrentModal] = useState<ReactNode>(null);
const contextValue = useMemo(
() =>
Object.assign(modal, {
() => ({
modal: Object.assign(modal, {
setModal: setCurrentModal,
}),
[],
currentModal,
}),
[currentModal],
);
useImperativeModal(setCurrentModal);
const handleDismiss = useCallback(() => setCurrentModal(null), [setCurrentModal]);
return (
<ModalContext.Provider value={contextValue}>
{children}
{currentModal && (
<ModalPortal>
<ModalBackdrop onDismiss={handleDismiss}>{currentModal}</ModalBackdrop>
</ModalPortal>
)}
</ModalContext.Provider>
);
return <ModalContext.Provider value={contextValue} children={children} />;
};
export default memo<typeof ModalProvider>(ModalProvider);

@ -9,16 +9,22 @@ type ModalContextMockProps = {
};
const ModalContextMock = ({ children }: ModalContextMockProps): ReactElement => {
const parent = useContext(ModalContext);
const context = useContext(ModalContext);
const value = useMemo(
(): ContextType<typeof ModalContext> => ({
...parent,
setModal: (modal): void => {
logAction('setModal', modal);
},
}),
[parent],
(): ContextType<typeof ModalContext> =>
context?.modal
? {
modal: {
...context.modal,
setModal: (modal): void => {
logAction('setModal', modal);
},
},
currentModal: context.currentModal,
}
: undefined,
[context],
);
return <ModalContext.Provider children={children} value={value} />;

@ -0,0 +1,23 @@
import { useModal, useCurrentModal } from '@rocket.chat/ui-contexts';
import React, { useCallback, ReactElement } from 'react';
import ModalBackdrop from '../../components/modal/ModalBackdrop';
import ModalPortal from '../../components/modal/ModalPortal';
const ModalRegion = (): ReactElement | null => {
const currentModal = useCurrentModal();
const { setModal } = useModal();
const handleDismiss = useCallback(() => setModal(null), [setModal]);
if (!currentModal) {
return null;
}
return (
<ModalPortal>
<ModalBackdrop onDismiss={handleDismiss}>{currentModal}</ModalBackdrop>
</ModalPortal>
);
};
export default ModalRegion;

@ -10,6 +10,7 @@ const MeteorProvider = lazy(() => import('../../providers/MeteorProvider'));
const BannerRegion = lazy(() => import('../banners/BannerRegion'));
const AppLayout = lazy(() => import('./AppLayout'));
const PortalsWrapper = lazy(() => import('./PortalsWrapper'));
const ModalRegion = lazy(() => import('../modal/ModalRegion'));
const AppRoot: FC = () => (
<Suspense fallback={<PageLoading />}>
@ -20,6 +21,7 @@ const AppRoot: FC = () => (
<BannerRegion />
<AppLayout />
<PortalsWrapper />
<ModalRegion />
</OmnichannelRoomIconProvider>
</QueryClientProvider>
</MeteorProvider>

@ -35,39 +35,25 @@ type ModalInstance = ModalConfiguration & {
};
export type ModalContextValue = {
open(
config?: ModalConfiguration,
fn?: (instance: ModalInstance, value: unknown) => void,
onCancel?: (instance: ModalInstance) => void,
): void;
push(
config?: ModalConfiguration,
fn?: (instance: ModalInstance, value: unknown) => void,
onCancel?: (instance: ModalInstance) => void,
): ModalInstance;
cancel(): void;
close(): void;
confirm(value: unknown): void;
showInputError(text: string): void;
onKeyDown(event: KeyboardEvent): void;
setModal(modal: ReactNode): void;
modal: {
open(
config?: ModalConfiguration,
fn?: (instance: ModalInstance, value: unknown) => void,
onCancel?: (instance: ModalInstance) => void,
): void;
push(
config?: ModalConfiguration,
fn?: (instance: ModalInstance, value: unknown) => void,
onCancel?: (instance: ModalInstance) => void,
): ModalInstance;
cancel(): void;
close(): void;
confirm(value: unknown): void;
showInputError(text: string): void;
onKeyDown(event: KeyboardEvent): void;
setModal(modal: ReactNode): void;
};
currentModal: ReactNode;
};
export const ModalContext = createContext<ModalContextValue>({
open: () => undefined,
push: () => ({
render: (): void => undefined,
hide: (): void => undefined,
destroy: (): void => undefined,
close: (): void => undefined,
confirm: (): void => undefined,
cancel: (): void => undefined,
showInputError: (): void => undefined,
}),
cancel: () => undefined,
close: () => undefined,
confirm: () => undefined,
showInputError: () => undefined,
onKeyDown: () => undefined,
setModal: () => undefined,
});
export const ModalContext = createContext<ModalContextValue | undefined>(undefined);

@ -0,0 +1,16 @@
import { useContext } from 'react';
import { ModalContext, ModalContextValue } from '../ModalContext';
/**
* Similar to useModal this hook return the current modal from the context value
*/
export const useCurrentModal = (): ModalContextValue['currentModal'] => {
const context = useContext(ModalContext);
if (!context) {
throw new Error('useCurrentModal must be used inside Modal Context');
}
return context.currentModal;
};

@ -2,4 +2,15 @@ import { useContext } from 'react';
import { ModalContext, ModalContextValue } from '../ModalContext';
export const useModal = (): ModalContextValue => useContext(ModalContext);
/**
* Consider using useCurrentModal to get the current modal
*/
export const useModal = (): ModalContextValue['modal'] => {
const context = useContext(ModalContext);
if (!context) {
throw new Error('useModal must be used inside Modal Context');
}
return context.modal;
};

@ -1,5 +1,5 @@
import { ReactNode, useContext } from 'react';
import type { ReactNode } from 'react';
import { ModalContext } from '../ModalContext';
import { useModal } from './useModal';
export const useSetModal = (): ((modal?: ReactNode) => void) => useContext(ModalContext).setModal;
export const useSetModal = (): ((modal?: ReactNode) => void) => useModal().setModal;

@ -21,6 +21,7 @@ export { useAttachmentAutoLoadEmbedMedia } from './hooks/useAttachmentAutoLoadEm
export { useAttachmentDimensions } from './hooks/useAttachmentDimensions';
export { useAttachmentIsCollapsedByDefault } from './hooks/useAttachmentIsCollapsedByDefault';
export { useConnectionStatus } from './hooks/useConnectionStatus';
export { useCurrentModal } from './hooks/useCurrentModal';
export { useCurrentRoute } from './hooks/useCurrentRoute';
export { useCustomSound } from './hooks/useCustomSound';
export { useEndpoint } from './hooks/useEndpoint';

Loading…
Cancel
Save