48 lines
1.4 KiB
TypeScript
48 lines
1.4 KiB
TypeScript
|
|
import { useEffect } from 'react';
|
||
|
|
import { createPortal } from 'react-dom';
|
||
|
|
import { useModal } from './useModal';
|
||
|
|
|
||
|
|
export const ModalRoot = () => {
|
||
|
|
const { isOpen, content, closeModal } = useModal();
|
||
|
|
|
||
|
|
// Handle Escape Key
|
||
|
|
useEffect(() => {
|
||
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||
|
|
if (e.key === 'Escape' && isOpen) closeModal();
|
||
|
|
};
|
||
|
|
if (isOpen) window.addEventListener('keydown', handleKeyDown);
|
||
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||
|
|
}, [isOpen, closeModal]);
|
||
|
|
|
||
|
|
// Prevent body scroll when modal is open
|
||
|
|
useEffect(() => {
|
||
|
|
if (isOpen) {
|
||
|
|
document.body.style.overflow = 'hidden';
|
||
|
|
} else {
|
||
|
|
document.body.style.overflow = 'unset';
|
||
|
|
}
|
||
|
|
return () => { document.body.style.overflow = 'unset'; };
|
||
|
|
}, [isOpen]);
|
||
|
|
|
||
|
|
if (!isOpen || !content) return null;
|
||
|
|
|
||
|
|
return createPortal(
|
||
|
|
<div
|
||
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
|
||
|
|
onClick={closeModal} // Click outside to close
|
||
|
|
role="dialog"
|
||
|
|
aria-modal="true"
|
||
|
|
aria-labelledby="modal-title"
|
||
|
|
>
|
||
|
|
{/* Stop propagation so clicking modal content doesn't close it */}
|
||
|
|
<div
|
||
|
|
className="bg-white rounded-lg shadow-xl w-full max-w-md overflow-hidden flex flex-col transform transition-all"
|
||
|
|
onClick={(e) => e.stopPropagation()}
|
||
|
|
>
|
||
|
|
{content({ onClose: closeModal })}
|
||
|
|
</div>
|
||
|
|
</div>,
|
||
|
|
document.body
|
||
|
|
);
|
||
|
|
};
|