81 lines
1.9 KiB
TypeScript
81 lines
1.9 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { X } from 'lucide-react';
|
|
|
|
interface ModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
children: React.ReactNode;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
showCloseButton?: boolean;
|
|
}
|
|
|
|
export default function Modal({
|
|
isOpen,
|
|
onClose,
|
|
children,
|
|
size = 'md',
|
|
showCloseButton = true
|
|
}: ModalProps) {
|
|
// Close modal on ESC key
|
|
useEffect(() => {
|
|
const handleEscape = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape' && isOpen) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleEscape);
|
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
}, [isOpen, onClose]);
|
|
|
|
// 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) return null;
|
|
|
|
const sizeClasses = {
|
|
sm: 'max-w-md',
|
|
md: 'max-w-lg',
|
|
lg: 'max-w-2xl',
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Backdrop */}
|
|
<div
|
|
className="fixed inset-0 bg-black bg-opacity-50 z-[110] transition-opacity duration-300 animate-fade-in"
|
|
onClick={onClose}
|
|
/>
|
|
|
|
{/* Modal */}
|
|
<div className="fixed inset-0 z-[120] flex items-center justify-center p-4 pointer-events-none">
|
|
<div
|
|
className={`${sizeClasses[size]} w-full bg-gray-800 rounded-lg shadow-2xl pointer-events-auto animate-scale-in`}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{showCloseButton && (
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors z-10"
|
|
>
|
|
<X size={24} />
|
|
</button>
|
|
)}
|
|
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|