Spaces:
Paused
Paused
File size: 3,246 Bytes
1c72248 |
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
import React, { Fragment, useEffect } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
showCloseButton?: boolean;
size?: 'sm' | 'md' | 'lg' | 'xl';
closeOnOverlayClick?: boolean;
}
export const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
title,
children,
showCloseButton = true,
size = 'md',
closeOnOverlayClick = true,
}) => {
// Close on ESC key press
useEffect(() => {
const handleEscKey = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscKey);
// Prevent body scrolling when modal is open
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscKey);
document.body.style.overflow = 'auto';
};
}, [isOpen, onClose]);
// Handle overlay click
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget && closeOnOverlayClick) {
onClose();
}
};
if (!isOpen) return null;
// Size mapping
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
};
return (
<Fragment>
{/* Modal backdrop */}
<div
className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto bg-gray-900 bg-opacity-75 backdrop-blur-sm transition-opacity"
onClick={handleOverlayClick}
aria-modal="true"
role="dialog"
aria-labelledby="modal-title"
>
{/* Modal panel */}
<div
className={`relative mx-auto w-full ${sizeClasses[size]} rounded-lg bg-gray-800 border border-gray-700 shadow-xl transition-all`}
onClick={e => e.stopPropagation()}
>
{/* Modal header */}
{(title || showCloseButton) && (
<div className="flex items-center justify-between rounded-t-lg border-b border-gray-700 bg-gray-850 px-6 py-4">
{title && (
<h3 id="modal-title" className="text-xl font-semibold text-gray-100">
{title}
</h3>
)}
{showCloseButton && (
<button
type="button"
className="ml-auto inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500"
onClick={onClose}
aria-label="Close modal"
>
<svg
className="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
)}
</div>
)}
{/* Modal content */}
<div className="px-6 py-4">{children}</div>
</div>
</div>
</Fragment>
);
};
|