Update dialog logic to support defining buttons/icon from anywhere
This commit is contained in:
parent
48af9bced1
commit
7c4028f8f1
9 changed files with 182 additions and 121 deletions
|
@ -1,101 +1,91 @@
|
|||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { Dialog as HDialog } from '@headlessui/react';
|
||||
import { Button } from '@/components/elements/button/index';
|
||||
import { XIcon } from '@heroicons/react/solid';
|
||||
import DialogIcon from '@/components/elements/dialog/DialogIcon';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import classNames from 'classnames';
|
||||
import ConfirmationDialog from '@/components/elements/dialog/ConfirmationDialog';
|
||||
import DialogContext from './context';
|
||||
import DialogFooter from '@/components/elements/dialog/DialogFooter';
|
||||
import styles from './style.module.css';
|
||||
|
||||
export interface DialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export interface FullDialogProps extends DialogProps {
|
||||
hideCloseIcon?: boolean;
|
||||
title?: string;
|
||||
description?: string | undefined;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const DialogButtons = ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
||||
|
||||
const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }: DialogProps) => {
|
||||
const items = React.Children.toArray(children || []);
|
||||
const [buttons, icon, content] = [
|
||||
// @ts-expect-error not sure how to get this correct
|
||||
items.find((child) => child.type === DialogButtons),
|
||||
// @ts-expect-error not sure how to get this correct
|
||||
items.find((child) => child.type === DialogIcon),
|
||||
// @ts-expect-error not sure how to get this correct
|
||||
items.filter((child) => ![DialogIcon, DialogButtons].includes(child.type)),
|
||||
];
|
||||
const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }: FullDialogProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const icon = useRef<HTMLDivElement>(null);
|
||||
const buttons = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<HDialog
|
||||
static
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={'fixed inset-0 bg-gray-900/50 z-40'} />
|
||||
<div className={'fixed inset-0 overflow-y-auto z-50'}>
|
||||
<div className={'flex min-h-full items-center justify-center p-4 text-center'}>
|
||||
<HDialog.Panel
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, scale: 0.85 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ type: 'spring', damping: 15, stiffness: 300, duration: 0.15 }}
|
||||
className={classNames([
|
||||
'relative bg-gray-600 rounded max-w-xl w-full mx-auto shadow-lg text-left',
|
||||
'ring-4 ring-gray-800 ring-opacity-80',
|
||||
])}
|
||||
>
|
||||
<div className={'flex p-6 overflow-y-auto'}>
|
||||
{icon && <div className={'mr-4'}>{icon}</div>}
|
||||
<div className={'flex-1 max-h-[70vh]'}>
|
||||
{title && (
|
||||
<HDialog.Title
|
||||
className={'font-header text-xl font-medium mb-2 text-gray-50 pr-4'}
|
||||
<DialogContext.Provider value={{ icon, buttons }}>
|
||||
<HDialog
|
||||
static
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<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}
|
||||
initial={{ opacity: 0, scale: 0.85 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ type: 'spring', damping: 15, stiffness: 300, duration: 0.15 }}
|
||||
className={styles.panel}
|
||||
>
|
||||
<div className={'flex p-6 overflow-y-auto'}>
|
||||
<div ref={ref} className={'flex-1 max-h-[70vh]'}>
|
||||
<div className={'flex items-center'}>
|
||||
<div ref={icon} />
|
||||
<div>
|
||||
{title && (
|
||||
<HDialog.Title className={styles.title}>{title}</HDialog.Title>
|
||||
)}
|
||||
{description && (
|
||||
<HDialog.Description>{description}</HDialog.Description>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<div ref={buttons} />
|
||||
{/* 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'}
|
||||
>
|
||||
{title}
|
||||
</HDialog.Title>
|
||||
)}
|
||||
{description && <HDialog.Description>{description}</HDialog.Description>}
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
{buttons && (
|
||||
<div
|
||||
className={
|
||||
'px-6 py-3 bg-gray-700 flex items-center justify-end space-x-3 rounded-b'
|
||||
}
|
||||
>
|
||||
{buttons}
|
||||
</div>
|
||||
)}
|
||||
{/* 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={'hover:rotate-90'}
|
||||
>
|
||||
<XIcon className={'w-5 h-5'} />
|
||||
</Button.Text>
|
||||
</div>
|
||||
)}
|
||||
</HDialog.Panel>
|
||||
<XIcon className={styles.close_icon} />
|
||||
</Button.Text>
|
||||
</div>
|
||||
)}
|
||||
</HDialog.Panel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HDialog>
|
||||
</HDialog>
|
||||
</DialogContext.Provider>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
@ -103,7 +93,7 @@ const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }:
|
|||
|
||||
const _Dialog = Object.assign(Dialog, {
|
||||
Confirm: ConfirmationDialog,
|
||||
Buttons: DialogButtons,
|
||||
Footer: DialogFooter,
|
||||
Icon: DialogIcon,
|
||||
});
|
||||
|
||||
|
|
Reference in a new issue