import React, { FC, useEffect, useState, useRef, useCallback, useMemo, } from 'react'; import { Dialog, Transition } from '@headlessui/react'; import Spin from './Spin'; import Button from './Button'; import Close from '../icons/Close'; import cx from 'classnames'; import { useTranslation } from 'react-i18next'; import ConfirmDialog from './ConfirmDialog'; export interface Props { title?: string | React.ReactNode; open?: boolean; data?: any; onClose?: () => void; width?: number | string; children?: React.ReactNode; footer?: { leftAction?: React.ReactNode; leftActionClassName?: string; onSubmit?: () => void; loading?: boolean; }; extra?: React.ReactNode; className?: string; placement?: 'left' | 'right'; description?: string | JSX.Element | React.ReactNode; loading?: boolean; animated?: boolean; closable?: boolean; widthMd?: string; widthLg?: string; confirmDialogTitle?: string; confirmDialogMessage?: string; showBackdrop?: boolean; preventBackdropClose?: boolean; enterDuration?: string; leaveDuration?: string; titleWithClosable?: { title: string; actions?: Array<{ icon?: React.ReactNode; onClick?: () => void; title?: string; visible?: boolean; disabled?: boolean; loading?: boolean; className?: string; [key: string]: any; }>; showClosable?: boolean; }; } const Drawer: FC = ({ title, open = false, data, onClose = () => { }, children, width = '80%', footer, showBackdrop = true, extra, className, placement = 'right', description, loading = false, animated = true, closable = true, widthMd = '80%', widthLg = '60%', confirmDialogTitle, confirmDialogMessage, preventBackdropClose = false, enterDuration = 'duration-300', leaveDuration = 'duration-200', titleWithClosable, }: Props) => { const [originalData, setOriginalData] = useState(null); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const { t } = useTranslation(); // Set original data when drawer opens and data is available useEffect(() => { if (open && data && !originalData) { setOriginalData(data); } // Reset original data when drawer closes if (!open) { setOriginalData(null); } }, [open, data, originalData]); // Check if data has changed const checkChanges = useCallback(() => { if (!data || Object.keys(data).length === 0) { return onClose(); } // Compare current data with original data if (originalData && JSON.stringify(originalData) !== JSON.stringify(data)) { setConfirmDialogOpen(true); } else { onClose(); } }, [data, originalData, onClose]); // Handle drawer close const handleClose = useCallback(() => { checkChanges(); }, [checkChanges]); // Confirm unsaved changes const handleConfirmUnsavedChanges = useCallback(() => { setConfirmDialogOpen(false); onClose(); }, [onClose]); return ( <> setConfirmDialogOpen(false)} onConfirm={handleConfirmUnsavedChanges} title={confirmDialogTitle || t('confirmDialog.title')} message={confirmDialogMessage || t('confirmDialog.message')} confirmText={t('confirm') || 'Confirm'} cancelText={t('cancel') || 'Cancel'} /> { } : handleClose} className={cx('memori-drawer', className)} > {showBackdrop && (
)}
{titleWithClosable ? (
{titleWithClosable.title}
{titleWithClosable.actions?.filter((action) => action.visible).map((action, index) => { const { icon, onClick, title, disabled, loading, className, ...restProps } = action; return (
) : ( <> {closable && (
)} )}
{!titleWithClosable && title && ( {title} )} {description && ( {description} )}
{children}
{footer && (
{footer.leftAction && (
{footer.leftAction}
)} {footer.onSubmit && (
)} {extra && (
{extra}
)}
)}
); }; Drawer.displayName = 'Drawer'; export default Drawer;