Files
2025-04-27 09:58:38 +02:00

127 lines
3.7 KiB
TypeScript

// src/components/ui/Modal.tsx
import React, { ReactNode, useEffect } from "react";
import { FaTimes } from "react-icons/fa";
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: ReactNode;
size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl"; // Optional size control
}
const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
title,
children,
size = "2xl", // Default size
}) => {
// Close modal on Escape key press
useEffect(() => {
const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};
if (isOpen) {
document.addEventListener("keydown", handleEscape);
}
// Cleanup listener on component unmount or when modal closes
return () => {
document.removeEventListener("keydown", handleEscape);
};
}, [isOpen, onClose]);
// Prevent scrolling on the body when the modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "auto";
}
// Cleanup overflow style on component unmount
return () => {
document.body.style.overflow = "auto";
};
}, [isOpen]);
if (!isOpen) return null;
// Map size prop to Tailwind max-width classes
const sizeClasses = {
sm: "max-w-sm",
md: "max-w-md",
lg: "max-w-lg",
xl: "max-w-xl",
"2xl": "max-w-2xl",
"3xl": "max-w-3xl",
"4xl": "max-w-4xl",
"5xl": "max-w-5xl",
};
return (
// Backdrop
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 backdrop-blur-sm p-4 transition-opacity duration-300 ease-in-out"
onClick={onClose} // Close when clicking the backdrop
role="dialog"
aria-modal="true"
aria-labelledby={title ? "modal-title" : undefined}
>
{/* Modal Panel */}
<div
className={`relative w-full ${sizeClasses[size]} bg-white dark:bg-gray-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 ease-in-out transform scale-95 opacity-0 animate-modal-appear`}
onClick={(e) => e.stopPropagation()} // Prevent clicks inside the modal from closing it
>
{/* Modal Header (Optional) */}
{title && (
<div className="flex items-center justify-between p-4 sm:p-5 border-b border-gray-200 dark:border-gray-700">
{title && (
<h2
id="modal-title"
className="text-lg font-semibold text-gray-900 dark:text-white font-poppins"
>
{title}
</h2>
)}
<button
onClick={onClose}
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
aria-label="Close modal"
>
<FaTimes className="w-5 h-5" />
</button>
</div>
)}
{/* Modal Body */}
<div className="p-5 sm:p-6 space-y-4 max-h-[80vh] overflow-y-auto">
{" "}
{/* Added max-height and scroll */}
{children}
</div>
</div>
{/* Add animation keyframes to your global CSS (e.g., src/app/globals.css) */}
<style jsx global>{`
@keyframes modal-appear {
0% {
opacity: 0;
transform: scale(0.95);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.animate-modal-appear {
animation: modal-appear 0.3s ease-out forwards;
}
`}</style>
</div>
);
};
export default Modal;