Make modals programatically controllable via a HOC
This allows entire components to be unmounted when the modal is hidden without affecting the fade in/out of the modal itself. This also makes it easier to programatically dismiss a modal without having to copy the visibility all over the place, and makes working with props much simpler in those modal components
This commit is contained in:
parent
d41b86f0ea
commit
c28cba92e2
14 changed files with 192 additions and 70 deletions
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import asModal from '@/hoc/asModal';
|
||||
import ModalContext from '@/context/ModalContext';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
|
@ -9,26 +10,31 @@ type Props = {
|
|||
children: string;
|
||||
onConfirmed: () => void;
|
||||
showSpinnerOverlay?: boolean;
|
||||
} & RequiredModalProps;
|
||||
};
|
||||
|
||||
const ConfirmationModal = ({ title, appear, children, visible, buttonText, onConfirmed, showSpinnerOverlay, onDismissed }: Props) => (
|
||||
<Modal
|
||||
appear={appear || true}
|
||||
visible={visible}
|
||||
showSpinnerOverlay={showSpinnerOverlay}
|
||||
onDismissed={() => onDismissed()}
|
||||
>
|
||||
<h2 css={tw`text-2xl mb-6`}>{title}</h2>
|
||||
<p css={tw`text-sm`}>{children}</p>
|
||||
<div css={tw`flex items-center justify-end mt-8`}>
|
||||
<Button isSecondary onClick={() => onDismissed()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color={'red'} css={tw`ml-4`} onClick={() => onConfirmed()}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
const ConfirmationModal = ({ title, children, buttonText, onConfirmed, showSpinnerOverlay }: Props) => {
|
||||
const { dismiss, toggleSpinner } = useContext(ModalContext);
|
||||
|
||||
export default ConfirmationModal;
|
||||
useEffect(() => {
|
||||
toggleSpinner(showSpinnerOverlay);
|
||||
}, [ showSpinnerOverlay ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 css={tw`text-2xl mb-6`}>{title}</h2>
|
||||
<p css={tw`text-sm`}>{children}</p>
|
||||
<div css={tw`flex items-center justify-end mt-8`}>
|
||||
<Button isSecondary onClick={() => dismiss()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color={'red'} css={tw`ml-4`} onClick={() => onConfirmed()}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmationModal.displayName = 'ConfirmationModal';
|
||||
|
||||
export default asModal()(ConfirmationModal);
|
||||
|
|
|
@ -8,14 +8,14 @@ interface Props extends Omit<CSSTransitionProps, 'timeout' | 'classNames'> {
|
|||
}
|
||||
|
||||
const Container = styled.div<{ timeout: number }>`
|
||||
.fade-enter, .fade-exit {
|
||||
.fade-enter, .fade-exit, .fade-appear {
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.fade-enter {
|
||||
.fade-enter, .fade-appear {
|
||||
${tw`opacity-0`};
|
||||
|
||||
&.fade-enter-active {
|
||||
&.fade-enter-active, &.fade-appear-active {
|
||||
${tw`opacity-100 transition-opacity ease-in`};
|
||||
transition-duration: ${props => props.timeout}ms;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export interface RequiredModalProps {
|
|||
top?: boolean;
|
||||
}
|
||||
|
||||
interface Props extends RequiredModalProps {
|
||||
export interface ModalProps extends RequiredModalProps {
|
||||
dismissable?: boolean;
|
||||
closeOnEscape?: boolean;
|
||||
closeOnBackground?: boolean;
|
||||
|
@ -40,7 +40,7 @@ const ModalContainer = styled.div<{ alignTop?: boolean }>`
|
|||
}
|
||||
`;
|
||||
|
||||
const Modal: React.FC<Props> = ({ visible, appear, dismissable, showSpinnerOverlay, top = true, closeOnBackground = true, closeOnEscape = true, onDismissed, children }) => {
|
||||
const Modal: React.FC<ModalProps> = ({ visible, appear, dismissable, showSpinnerOverlay, top = true, closeOnBackground = true, closeOnEscape = true, onDismissed, children }) => {
|
||||
const [ render, setRender ] = useState(visible);
|
||||
|
||||
const isDismissable = useMemo(() => {
|
||||
|
@ -62,7 +62,13 @@ const Modal: React.FC<Props> = ({ visible, appear, dismissable, showSpinnerOverl
|
|||
}, [ render ]);
|
||||
|
||||
return (
|
||||
<Fade timeout={150} appear={appear} in={render} unmountOnExit onExited={onDismissed}>
|
||||
<Fade
|
||||
in={render}
|
||||
timeout={150}
|
||||
appear={appear || true}
|
||||
unmountOnExit
|
||||
onExited={() => onDismissed()}
|
||||
>
|
||||
<ModalMask
|
||||
onClick={e => {
|
||||
if (isDismissable && closeOnBackground) {
|
||||
|
|
Reference in a new issue