60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
import { ReactNode, useEffect } from "react";
|
|
|
|
export function Modal({
|
|
open,
|
|
onClose,
|
|
onSubmit,
|
|
title,
|
|
children,
|
|
footer,
|
|
wide,
|
|
}: {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onSubmit?: () => void;
|
|
title: string;
|
|
children: ReactNode;
|
|
footer?: ReactNode;
|
|
wide?: boolean;
|
|
}) {
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") onClose();
|
|
if (e.key === "Enter" && (e.ctrlKey || e.metaKey) && onSubmit) onSubmit();
|
|
};
|
|
window.addEventListener("keydown", onKey);
|
|
document.body.style.overflow = "hidden";
|
|
return () => {
|
|
window.removeEventListener("keydown", onKey);
|
|
document.body.style.overflow = "";
|
|
};
|
|
}, [open, onClose, onSubmit]);
|
|
|
|
if (!open) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
|
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
|
<div
|
|
className={`animate-fade-in relative z-10 flex max-h-[92dvh] w-full flex-col ${wide ? "max-w-3xl" : "max-w-lg"} rounded-t-2xl border border-border-strong bg-surface shadow-2xl sm:max-h-[90vh] sm:rounded-2xl`}
|
|
>
|
|
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
|
<h2 className="text-lg font-semibold text-text">{title}</h2>
|
|
<button
|
|
onClick={onClose}
|
|
className="rounded-lg p-1 text-muted hover:bg-surface-raised hover:text-text"
|
|
aria-label="Close"
|
|
>
|
|
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<path d="M6 6l12 12M18 6L6 18" strokeLinecap="round" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-4 sm:px-5">{children}</div>
|
|
{footer && <div className="flex flex-wrap justify-end gap-2 border-t border-border px-4 py-3 sm:px-5 sm:py-4">{footer}</div>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|