the-tip-top-frontend/components/ui/Modal.tsx
2025-11-17 23:38:02 +01:00

108 lines
2.6 KiB
TypeScript

'use client';
import React, { useEffect, useRef } from 'react';
import { cn } from '@/utils/helpers';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
showCloseButton?: boolean;
}
export const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
title,
children,
size = 'md',
showCloseButton = true,
}) => {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = 'unset';
};
}, [isOpen, onClose]);
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
if (!isOpen) return null;
const sizes = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
};
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50"
onClick={handleBackdropClick}
role="dialog"
aria-modal="true"
aria-labelledby={title ? 'modal-title' : undefined}
>
<div
ref={modalRef}
className={cn(
'relative w-full bg-white rounded-lg shadow-xl animate-fade-in',
sizes[size]
)}
>
{(title || showCloseButton) && (
<div className="flex items-center justify-between p-4 border-b">
{title && (
<h2 id="modal-title" className="text-xl font-semibold text-gray-900">
{title}
</h2>
)}
{showCloseButton && (
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition-colors"
aria-label="Fermer"
>
<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="p-6">{children}</div>
</div>
</div>
);
};