Spaces:
Sleeping
Sleeping
File size: 1,888 Bytes
f4854a1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import { useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { HiX } from 'react-icons/hi';
import './Modal.css';
/**
* Modal component with overlay, animation, and close behavior
*/
export default function Modal({ isOpen, onClose, title, children, size = 'md', showClose = true }) {
// Prevent body scroll when modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => { document.body.style.overflow = ''; };
}, [isOpen]);
// Close on Escape
useEffect(() => {
const handleEsc = (e) => e.key === 'Escape' && onClose();
if (isOpen) window.addEventListener('keydown', handleEsc);
return () => window.removeEventListener('keydown', handleEsc);
}, [isOpen, onClose]);
return (
<AnimatePresence>
{isOpen && (
<div className="modal-overlay" onClick={onClose}>
<motion.div
className={`modal modal--${size}`}
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
onClick={(e) => e.stopPropagation()}
>
{(title || showClose) && (
<div className="modal__header">
{title && <h3 className="modal__title">{title}</h3>}
{showClose && (
<button className="modal__close" onClick={onClose} aria-label="Close modal">
<HiX size={20} />
</button>
)}
</div>
)}
<div className="modal__body">
{children}
</div>
</motion.div>
</div>
)}
</AnimatePresence>
);
}
|