import { useEffect, useRef, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; import { X } from "lucide-react"; import { useBackToClose } from "../lib/history"; import { pushLayer, removeLayer, isTopmost } from "../lib/layerStack"; import { useFullscreenElement } from "../lib/fullscreen"; // Small centered modal shell: backdrop + ESC + scroll-lock close. Each open modal is a layer in the // shared `layerStack` so ESC only closes the topmost one — e.g. an error dialog over the tag editor // dismisses just the error and returns to the editor underneath, and a player below defers to it // (R8 S3). Portals into `document.fullscreenElement ?? document.body`: a node under
is painted // beneath whatever holds the fullscreen lock, so a Modal opened from a player in true fullscreen (the // download dialog) would otherwise open invisibly. export default function Modal({ title, onClose, children, maxWidth = "max-w-lg", }: { title: ReactNode; onClose: () => void; children: ReactNode; maxWidth?: string; }) { const { t } = useTranslation(); // Browser/mouse Back closes the topmost modal instead of navigating away. useBackToClose(onClose); const fsRoot = useFullscreenElement(); // Backdrop-click close must only fire when the press STARTED on the backdrop too. Otherwise a // drag that begins inside the dialog (e.g. selecting text in an input) and is released out on // the backdrop wrongly dismisses — the click event bubbles to the common ancestor (the backdrop). const pressedOnBackdrop = useRef(false); // Keep a stable handler across renders so the stack id is assigned once per mount. const onCloseRef = useRef(onClose); onCloseRef.current = onClose; useEffect(() => { const id = pushLayer(); const onKey = (e: KeyboardEvent) => { if (e.key === "Escape" && isTopmost(id)) { e.stopPropagation(); onCloseRef.current(); } }; window.addEventListener("keydown", onKey); const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); removeLayer(id); document.body.style.overflow = prev; }; }, []); return createPortal(