import { type ReactNode, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; // One dialog's worth of wiring: a trigger, a panel, and Escape. export function Modal({ label, trigger, title, actions, wide, children, }: { label: ReactNode; trigger: string; title: string; actions?: ReactNode; wide?: boolean; children: ReactNode; }) { const dialog = useRef(null); const [open, setOpen] = useState(false); // Set here because React's types don't carry it yet. A click handler on the // backdrop would be a way to close that a keyboard can't reach. useEffect(() => { dialog.current?.setAttribute("closedby", "any"); }, []); return ( <> {/* Out of the tree that triggered it: a dialog is flow content, and a trigger inside a sentence would end the paragraph holding it. */} {createPortal( setOpen(false)} >

{title}

{/* Built on opening, so a modal per search result costs three elements rather than a list nobody has asked to see. */} {open && children}

{actions}

, document.body, )} ); }