import { useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { Button } from '@components/Button';
import { classNames } from '@utils/classNames';
const SIZES = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
full: 'max-w-full mx-4',
};
export function Modal({
isOpen,
onClose,
title,
children,
size = 'md',
showCloseButton = true,
closeOnOverlayClick = true,
closeOnEscape = true,
footer,
}) {
const overlayRef = useRef(null);
const previousActiveElement = useRef(null);
const handleEscape = useCallback(
(event) => {
if (closeOnEscape && event.key === 'Escape') {
onClose();
}
},
[closeOnEscape, onClose]
);
const handleOverlayClick = (event) => {
if (closeOnOverlayClick && event.target === overlayRef.current) {
onClose();
}
};
useEffect(() => {
if (isOpen) {
previousActiveElement.current = document.activeElement;
document.body.style.overflow = 'hidden';
document.addEventListener('keydown', handleEscape);
return () => {
document.body.style.overflow = '';
document.removeEventListener('keydown', handleEscape);
previousActiveElement.current?.focus();
};
}
}, [isOpen, handleEscape]);
if (!isOpen) {
return null;
}
const modalContent = (
<div
ref={overlayRef}
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
onClick={handleOverlayClick}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<div
className={classNames(
'relative bg-white rounded-lg shadow-xl',
'transform transition-all duration-300',
'w-full',
SIZES[size]
)}
>
{}
<div className="flex items-center justify-between px-6 py-4 border-b">
<h2 id="modal-title" className="text-lg font-semibold text-gray-900">
{title}
</h2>
{showCloseButton && (
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition-colors"
aria-label="Close modal"
>
<svg className="w-6 h-6" 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>
{}
<div className="px-6 py-4">{children}</div>
{}
{footer && (
<div className="flex justify-end gap-3 px-6 py-4 border-t bg-gray-50">
{footer}
</div>
)}
</div>
</div>
);
return createPortal(modalContent, document.body);
}
export function ConfirmModal({
isOpen,
onClose,
onConfirm,
title,
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
variant = 'danger',
loading = false,
}) {
const handleConfirm = async () => {
await onConfirm();
onClose();
};
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={title}
size="sm"
footer={
<>
<Button variant="ghost" onClick={onClose} disabled={loading}>
{cancelText}
</Button>
<Button variant={variant} onClick={handleConfirm} loading={loading}>
{confirmText}
</Button>
</>
}
>
<p className="text-gray-600">{message}</p>
</Modal>
);
}
export default Modal;