import React, { useEffect, useRef, useCallback, useState, useMemo } from 'react'; import { AlertTriangle, Trash2, X, CheckCircle, Info } from 'lucide-react'; export type ConfirmVariant = 'danger' | 'warning' | 'info' | 'success'; interface ConfirmModalProps { isOpen: boolean; onClose: () => void; onConfirm: () => void; title: string; message: string; confirmText?: string; cancelText?: string; variant?: ConfirmVariant; isLoading?: boolean; requireTypedConfirmation?: string; icon?: React.ReactNode; } const variantConfig = { danger: { icon: Trash2, iconBg: 'bg-red-500/10', iconColor: 'text-red-500', buttonClass: 'bg-red-500 hover:bg-red-600', borderColor: 'border-red-500/20', }, warning: { icon: AlertTriangle, iconBg: 'bg-amber-500/10', iconColor: 'text-amber-500', buttonClass: 'bg-amber-500 hover:bg-amber-600', borderColor: 'border-amber-500/20', }, info: { icon: Info, iconBg: 'bg-blue-500/10', iconColor: 'text-blue-500', buttonClass: 'bg-blue-500 hover:bg-blue-600', borderColor: 'border-blue-500/20', }, success: { icon: CheckCircle, iconBg: 'bg-emerald-500/10', iconColor: 'text-emerald-500', buttonClass: 'bg-emerald-500 hover:bg-emerald-600', borderColor: 'border-emerald-500/20', }, }; // Memoized Modal Content to prevent re-renders const ConfirmModalContent = React.memo<{ title: string; message: string; confirmText: string; cancelText: string; variant: ConfirmVariant; isLoading: boolean; requireTypedConfirmation?: string; icon?: React.ReactNode; onClose: () => void; onConfirm: () => void; }>(({ title, message, confirmText, cancelText, variant, isLoading, requireTypedConfirmation, icon, onClose, onConfirm, }) => { const [typedValue, setTypedValue] = useState(''); const inputRef = useRef(null); const config = variantConfig[variant]; const IconComponent = config.icon; const canConfirm = requireTypedConfirmation ? typedValue.toUpperCase() === requireTypedConfirmation.toUpperCase() : true; // Focus input on mount useEffect(() => { if (requireTypedConfirmation && inputRef.current) { inputRef.current.focus(); } }, [requireTypedConfirmation]); const handleConfirmClick = () => { if (canConfirm && !isLoading) { onConfirm(); } }; const handleInputChange = (e: React.ChangeEvent) => { setTypedValue(e.target.value); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && canConfirm && !isLoading) { onConfirm(); } }; return (
{/* Header */}
{icon ? (
{icon}
) : ( )}

{title}

{message}

{/* Typed Confirmation Input */} {requireTypedConfirmation && (
)}
{/* Actions */}
); }); ConfirmModalContent.displayName = 'ConfirmModalContent'; export const ConfirmModal: React.FC = ({ isOpen, onClose, onConfirm, title, message, confirmText = 'Confirm', cancelText = 'Cancel', variant = 'danger', isLoading = false, requireTypedConfirmation, icon, }) => { const [isVisible, setIsVisible] = useState(false); const [isAnimating, setIsAnimating] = useState(false); // Handle open/close with CSS transitions useEffect(() => { if (isOpen) { setIsVisible(true); // Small delay to trigger animation requestAnimationFrame(() => { setIsAnimating(true); }); document.body.style.overflow = 'hidden'; } else { setIsAnimating(false); const timer = setTimeout(() => { setIsVisible(false); }, 200); document.body.style.overflow = ''; return () => clearTimeout(timer); } return () => { document.body.style.overflow = ''; }; }, [isOpen]); // Handle escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && !isLoading && isOpen) { onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isOpen, isLoading, onClose]); const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget && !isLoading) { onClose(); } }; if (!isVisible) return null; return (
{/* Backdrop */}
{/* Modal */}
e.stopPropagation()} >
); }; // Stable hook for confirm modal interface ModalConfig { title: string; message: string; confirmText?: string; cancelText?: string; variant?: ConfirmVariant; requireTypedConfirmation?: string; icon?: React.ReactNode; } export const useConfirmModal = () => { const [isOpen, setIsOpen] = useState(false); const [config, setConfig] = useState({ title: '', message: '' }); const resolverRef = useRef<((value: boolean) => void) | null>(null); const confirm = useCallback((newConfig: ModalConfig): Promise => { return new Promise((resolve) => { resolverRef.current = resolve; setConfig(newConfig); setIsOpen(true); }); }, []); const handleClose = useCallback(() => { setIsOpen(false); // Delay resolver call to allow animation setTimeout(() => { if (resolverRef.current) { resolverRef.current(false); resolverRef.current = null; } }, 50); }, []); const handleConfirm = useCallback(() => { setIsOpen(false); // Delay resolver call to allow animation setTimeout(() => { if (resolverRef.current) { resolverRef.current(true); resolverRef.current = null; } }, 50); }, []); // Stable modal component const ConfirmModalComponent = useMemo(() => { const Modal = () => ( ); return Modal; }, [isOpen, config, handleClose, handleConfirm]); return { confirm, ConfirmModal: ConfirmModalComponent }; }; export default ConfirmModal;