mirror of
https://github.com/OwethuManagedServices/oms-website-nextjs.git
synced 2025-12-17 19:08:09 +00:00
127 lines
3.7 KiB
TypeScript
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;
|