54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
interface ModalProps {
|
||
open: boolean;
|
||
title: string;
|
||
onClose: () => void;
|
||
children: React.ReactNode;
|
||
/** Footer slot – typically action buttons */
|
||
footer?: React.ReactNode;
|
||
size?: "sm" | "md" | "lg";
|
||
}
|
||
|
||
const MODAL_SIZES = { sm: "max-w-sm", md: "max-w-lg", lg: "max-w-2xl" };
|
||
|
||
export function Modal({ open, title, onClose, children, footer, size = "md" }: ModalProps) {
|
||
if (!open) return null;
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
{/* Backdrop */}
|
||
<div
|
||
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
|
||
onClick={onClose}
|
||
aria-hidden="true"
|
||
/>
|
||
{/* Panel */}
|
||
<div
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="modal-title"
|
||
className={`relative w-full ${MODAL_SIZES[size]} rounded-2xl bg-white shadow-2xl flex flex-col max-h-[90vh]`}
|
||
>
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between border-b border-slate-100 px-6 py-4">
|
||
<h2 id="modal-title" className="text-base font-semibold text-slate-800">
|
||
{title}
|
||
</h2>
|
||
<button
|
||
onClick={onClose}
|
||
aria-label="Close"
|
||
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
{/* Body */}
|
||
<div className="flex-1 overflow-y-auto px-6 py-5">{children}</div>
|
||
{/* Footer */}
|
||
{footer && (
|
||
<div className="flex justify-end gap-2 border-t border-slate-100 px-6 py-4">
|
||
{footer}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
} |