feat: Updated plans and catalogue pages
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
/*
|
||||
* A native <dialog>, not a div with a fixed overlay.
|
||||
*
|
||||
* showModal() gives focus trapping, inert background, Escape and the top layer
|
||||
* for free — all four are things a hand-rolled overlay gets wrong, and the third
|
||||
* is the one staff will actually reach for. The only wiring needed is keeping
|
||||
* React state and the element's open state in step, and routing every close —
|
||||
* Escape, backdrop, button — through one onClose.
|
||||
*/
|
||||
export function Modal({
|
||||
open,
|
||||
onClose,
|
||||
title,
|
||||
meta,
|
||||
footer,
|
||||
children,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
meta?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
if (open && !el.open) el.showModal();
|
||||
if (!open && el.open) el.close();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<dialog
|
||||
ref={ref}
|
||||
onCancel={(e) => {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
}}
|
||||
/* Clicking the backdrop hits the dialog element itself, never a
|
||||
* child — so this closes on backdrop and not on content. */
|
||||
onClick={(e) => {
|
||||
if (e.target === ref.current) onClose();
|
||||
}}
|
||||
className="w-[min(44rem,94vw)] rounded border border-rule bg-panel p-0 text-ink shadow-lg backdrop:bg-[rgba(4,12,24,0.55)]"
|
||||
>
|
||||
<header className="flex flex-wrap items-center gap-3 border-b border-rule-soft bg-panel-2 px-4 py-3">
|
||||
<h2 className="text-[1.02rem] font-bold tracking-[-0.01em]">{title}</h2>
|
||||
{meta && <span className="font-mono text-[0.68rem] uppercase tracking-[0.12em] text-ink-3">{meta}</span>}
|
||||
<button type="button" onClick={onClose} className="ml-auto rounded border border-rule px-2 py-1 font-mono text-[0.68rem] uppercase tracking-[0.1em] text-ink-2 hover:border-ink-3" aria-label="Close">
|
||||
Esc
|
||||
</button>
|
||||
</header>
|
||||
<div className="grid max-h-[68vh] gap-4 overflow-y-auto p-4">{children}</div>
|
||||
{footer && <footer className="flex flex-wrap items-center gap-3 border-t border-rule-soft bg-panel-2 px-4 py-3">{footer}</footer>}
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user