import { useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; export function Modal({ isOpen, onClose, children, maxW, h }: { isOpen: boolean; onClose: () => void; children: React.ReactNode; maxW?: number; h?: number; }) { const modalRef = useRef(null); const [shouldRender, setShouldRender] = useState(isOpen); const [isClosing, setIsClosing] = useState(false); // Show/hide logic useEffect(() => { if (isOpen) { setShouldRender(true); setIsClosing(false); } else if (shouldRender) { setIsClosing(true); const timeout = setTimeout(() => { setShouldRender(false); }, 100); // Match fade-out duration return () => clearTimeout(timeout); } }, [isOpen, shouldRender]); // Handle keyboard events useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Close on Escape key if (e.key === 'Escape') { onClose() // Trap tab navigation to the modal } else if (e.key === 'Tab') { if (modalRef.current) { const focusableEls = modalRef.current.querySelectorAll( 'button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])' ); const firstEl = focusableEls[0]; const lastEl = focusableEls[focusableEls.length - 1]; const activeEl = document.activeElement if (e.shiftKey && activeEl === firstEl) { e.preventDefault(); lastEl.focus(); } else if (!e.shiftKey && activeEl === lastEl) { e.preventDefault(); firstEl.focus(); } else if (!Array.from(focusableEls).find(node => node.isEqualNode(activeEl))) { e.preventDefault(); firstEl.focus(); } } }; }; if (isOpen) document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); // Close on outside click useEffect(() => { const handleClick = (e: MouseEvent) => { if ( modalRef.current && !modalRef.current.contains(e.target as Node) ) { onClose(); } }; if (isOpen) document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [isOpen, onClose]); if (!shouldRender) return null; return ReactDOM.createPortal(
{children}
, document.body ); }