74 lines
2.7 KiB
JavaScript

import { useEffect } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { X } from "lucide-react";
const WIDTHS = {
sm: "max-w-md",
md: "max-w-xl",
lg: "max-w-3xl",
xl: "max-w-5xl",
};
export default function Modal({ open, onClose, title, subtitle, icon: Icon, size = "md", children, footer }) {
useEffect(() => {
if (!open) return;
function onKey(event) {
if (event.key === "Escape") onClose();
}
window.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
return () => {
window.removeEventListener("keydown", onKey);
document.body.style.overflow = "";
};
}, [open, onClose]);
return (
<AnimatePresence>
{open && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
<motion.div
role="dialog"
aria-modal="true"
aria-label={title}
className={`relative w-full ${WIDTHS[size]} overflow-hidden rounded-2xl border border-line bg-surface shadow-2xl shadow-black/50`}
initial={{ opacity: 0, y: 18, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 12, scale: 0.98 }}
transition={{ type: "spring", stiffness: 320, damping: 28 }}
>
<header className="flex items-start justify-between gap-4 border-b border-line p-5">
<div className="flex items-center gap-3">
{Icon && (
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500/15 to-indigo-500/15 text-accent">
<Icon size={20} />
</span>
)}
<div>
<h2 className="text-base font-bold text-ink">{title}</h2>
{subtitle && <p className="mt-0.5 text-xs text-ink-muted">{subtitle}</p>}
</div>
</div>
<button
aria-label="Close"
className="rounded-lg p-1.5 text-ink-muted transition hover:bg-surface-2 hover:text-ink"
onClick={onClose}
>
<X size={18} />
</button>
</header>
<div className="max-h-[68vh] overflow-y-auto p-5">{children}</div>
{footer && <footer className="flex flex-wrap justify-end gap-3 border-t border-line bg-surface-2/50 p-4">{footer}</footer>}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}