import React, { useEffect } from 'react';
/**
* A reusable Modal component.
*
* @param {object} props - The component props.
* @param {boolean} props.isOpen - Determines if the modal is currently open.
* @param {function} props.onClose - Function to call when the modal should be closed.
* @param {string} props.title - The title to display at the top of the modal.
* @param {React.ReactNode} props.children - The content to be displayed within the modal body.
* @param {'sm' | 'md' | 'lg' | 'xl'} [props.size='md'] - Controls the maximum width of the modal.
* @returns {JSX.Element | null} The Modal component or null if not open.
*/
const Modal = ({ isOpen, onClose, title, children, size = 'md' }) => {
/**
* Handles side effects when the modal's open state changes.
* - Adds/removes event listener for ESC key to close the modal.
* - Prevents/restores body scrolling when the modal is open/closed.
*/
useEffect(() => {
const handleEsc = (event) => {
if (event.keyCode === 27) onClose();
};
if (isOpen) {
document.addEventListener('keydown', handleEsc);
// Prevent body scroll
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEsc);
document.body.style.overflow = 'unset';
};
}, [isOpen, onClose]);
if (!isOpen) return null;
/** Defines Tailwind CSS classes for different modal sizes. */
const sizeClasses = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Background overlay with blur/dim effect */}
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
onClick={onClose}
></div>
{/* Modal box */}
<div
className={`z-50 bg-white rounded-xl shadow-2xl p-6 w-full ${sizeClasses[size]} relative max-h-[90vh] overflow-y-auto`}
onClick={(e) => e.stopPropagation()} // Prevent closing when clicking inside modal
>
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-gray-800">{title}</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full w-8 h-8 flex items-center justify-center text-2xl font-bold transition-colors"
aria-label="Close modal"
>
×
</button>
</div>
<div>{children}</div>
</div>
</div>
);
};
export default Modal;
Source