2022-07-02 23:27:22 +01:00
|
|
|
import React, { useEffect, useState } from 'react';
|
2022-06-12 20:07:52 +01:00
|
|
|
import { Dialog as HDialog } from '@headlessui/react';
|
2022-06-05 19:56:42 +01:00
|
|
|
import { Button } from '@/components/elements/button/index';
|
|
|
|
import { XIcon } from '@heroicons/react/solid';
|
2022-07-02 23:27:22 +01:00
|
|
|
import DialogIcon, { IconPosition } from '@/components/elements/dialog/DialogIcon';
|
|
|
|
import { AnimatePresence, motion, useAnimation } from 'framer-motion';
|
2022-06-20 16:17:33 +01:00
|
|
|
import ConfirmationDialog from '@/components/elements/dialog/ConfirmationDialog';
|
2022-07-02 22:24:12 +01:00
|
|
|
import DialogContext from './context';
|
|
|
|
import DialogFooter from '@/components/elements/dialog/DialogFooter';
|
|
|
|
import styles from './style.module.css';
|
2022-06-05 19:56:42 +01:00
|
|
|
|
2022-06-20 16:17:33 +01:00
|
|
|
export interface DialogProps {
|
2022-06-12 20:07:52 +01:00
|
|
|
open: boolean;
|
|
|
|
onClose: () => void;
|
2022-07-02 22:24:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface FullDialogProps extends DialogProps {
|
2022-06-12 20:07:52 +01:00
|
|
|
hideCloseIcon?: boolean;
|
2022-07-02 23:27:22 +01:00
|
|
|
preventExternalClose?: boolean;
|
2022-06-05 19:56:42 +01:00
|
|
|
title?: string;
|
2022-06-20 16:17:33 +01:00
|
|
|
description?: string | undefined;
|
2022-06-05 19:56:42 +01:00
|
|
|
children?: React.ReactNode;
|
|
|
|
}
|
|
|
|
|
2022-07-02 23:27:22 +01:00
|
|
|
const spring = { type: 'spring', damping: 15, stiffness: 300, duration: 0.15 };
|
|
|
|
const variants = {
|
|
|
|
open: { opacity: 1, scale: 1, transition: spring },
|
|
|
|
closed: { opacity: 0, scale: 0.85, transition: spring },
|
|
|
|
bounce: {
|
|
|
|
scale: 0.95,
|
|
|
|
transition: { type: 'linear', duration: 0.075 },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const Dialog = ({
|
|
|
|
open,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
onClose,
|
|
|
|
hideCloseIcon,
|
|
|
|
preventExternalClose,
|
|
|
|
children,
|
|
|
|
}: FullDialogProps) => {
|
|
|
|
const controls = useAnimation();
|
|
|
|
|
|
|
|
const [icon, setIcon] = useState<React.ReactNode>();
|
|
|
|
const [footer, setFooter] = useState<React.ReactNode>();
|
|
|
|
const [iconPosition, setIconPosition] = useState<IconPosition>('title');
|
|
|
|
|
|
|
|
const onDialogClose = (): void => {
|
|
|
|
if (!preventExternalClose) {
|
|
|
|
return onClose();
|
|
|
|
}
|
|
|
|
|
|
|
|
controls
|
|
|
|
.start('bounce')
|
|
|
|
.then(() => controls.start('open'))
|
|
|
|
.catch(console.error);
|
|
|
|
};
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
controls.start(open ? 'open' : 'closed').catch(console.error);
|
|
|
|
}, [open]);
|
2022-06-05 19:56:42 +01:00
|
|
|
|
|
|
|
return (
|
2022-06-12 20:07:52 +01:00
|
|
|
<AnimatePresence>
|
|
|
|
{open && (
|
2022-07-02 23:27:22 +01:00
|
|
|
<DialogContext.Provider value={{ setIcon, setFooter, setIconPosition }}>
|
2022-07-02 22:24:12 +01:00
|
|
|
<HDialog
|
|
|
|
static
|
|
|
|
as={motion.div}
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
transition={{ duration: 0.15 }}
|
|
|
|
open={open}
|
2022-07-02 23:27:22 +01:00
|
|
|
onClose={onDialogClose}
|
2022-07-02 22:24:12 +01:00
|
|
|
>
|
|
|
|
<div className={'fixed inset-0 bg-gray-900/50 z-40'} />
|
|
|
|
<div className={'fixed inset-0 overflow-y-auto z-50'}>
|
|
|
|
<div className={styles.container}>
|
|
|
|
<HDialog.Panel
|
|
|
|
as={motion.div}
|
2022-07-02 23:27:22 +01:00
|
|
|
animate={controls}
|
|
|
|
variants={variants}
|
2022-07-02 22:24:12 +01:00
|
|
|
className={styles.panel}
|
|
|
|
>
|
|
|
|
<div className={'flex p-6 overflow-y-auto'}>
|
2022-07-02 23:27:22 +01:00
|
|
|
{iconPosition === 'container' && icon}
|
|
|
|
<div className={'flex-1 max-h-[70vh]'}>
|
2022-07-02 22:24:12 +01:00
|
|
|
<div className={'flex items-center'}>
|
2022-07-02 23:27:22 +01:00
|
|
|
{iconPosition !== 'container' && icon}
|
2022-07-02 22:24:12 +01:00
|
|
|
<div>
|
|
|
|
{title && (
|
|
|
|
<HDialog.Title className={styles.title}>{title}</HDialog.Title>
|
|
|
|
)}
|
|
|
|
{description && (
|
|
|
|
<HDialog.Description>{description}</HDialog.Description>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{children}
|
|
|
|
</div>
|
2022-06-12 20:07:52 +01:00
|
|
|
</div>
|
2022-07-02 23:27:22 +01:00
|
|
|
{footer}
|
2022-07-02 22:24:12 +01:00
|
|
|
{/* Keep this below the other buttons so that it isn't the default focus if they're present. */}
|
|
|
|
{!hideCloseIcon && (
|
|
|
|
<div className={'absolute right-0 top-0 m-4'}>
|
|
|
|
<Button.Text
|
|
|
|
size={Button.Sizes.Small}
|
|
|
|
shape={Button.Shapes.IconSquare}
|
|
|
|
onClick={onClose}
|
|
|
|
className={'group'}
|
|
|
|
>
|
|
|
|
<XIcon className={styles.close_icon} />
|
|
|
|
</Button.Text>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</HDialog.Panel>
|
|
|
|
</div>
|
2022-06-05 19:56:42 +01:00
|
|
|
</div>
|
2022-07-02 22:24:12 +01:00
|
|
|
</HDialog>
|
|
|
|
</DialogContext.Provider>
|
2022-06-12 20:07:52 +01:00
|
|
|
)}
|
|
|
|
</AnimatePresence>
|
2022-06-05 19:56:42 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-06-20 16:17:33 +01:00
|
|
|
const _Dialog = Object.assign(Dialog, {
|
|
|
|
Confirm: ConfirmationDialog,
|
2022-07-02 22:24:12 +01:00
|
|
|
Footer: DialogFooter,
|
2022-06-20 16:17:33 +01:00
|
|
|
Icon: DialogIcon,
|
|
|
|
});
|
2022-06-05 19:56:42 +01:00
|
|
|
|
|
|
|
export default _Dialog;
|