/** * @file HtmlViewerModal.tsx * @description An HTML file shown as the page in a modal over the whole * screen — the one viewer the File Explorer and the Changes panel's Preview * open (the Chat's file preview opens its own panel as a modal instead, for * its versions and Code tab). The page itself is `HtmlFrame`: the chat * artifact's sandbox, its relative files read through the caller's guarded * `fetchFile`. Keys and clicks stop at the modal, so a shortcut of the view * underneath (the explorer's Delete) never fires from inside it. * @author Nguyễn Ngọc Trí Vĩ */ import { useRef, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; import { Download, X } from "lucide-react"; import { useDialog } from "../hooks/useDialog"; import { HtmlFrame } from "./HtmlFrame"; import type { FetchFile } from "../lib/htmlInline"; export function HtmlViewerModal({ path, html, fetchFile, onDownload, onClose, testId = "html-viewer", }: { /** The file's path relative to the place `fetchFile` reads from; also its title. */ path: string; /** The page's text; read through `fetchFile` when absent. */ html?: string; fetchFile?: FetchFile | null; onDownload?: () => void; onClose: () => void; testId?: string; }) { const { t } = useTranslation(["lanes"]); const panelRef = useRef(null); useDialog(true, panelRef, onClose); const name = path.slice(path.lastIndexOf("/") + 1); return createPortal(
{ e.stopPropagation(); onClose(); }} onKeyDown={(e) => { // Nothing reaches the view underneath — so Esc, which useDialog's // document listener would never see, closes here. e.stopPropagation(); if (e.key === "Escape") onClose(); }} role="presentation" >
e.stopPropagation()} >
{path} {onDownload && ( )}
, document.body ); } function HeadButton({ label, onClick, children, }: { label: string; onClick: () => void; children: ReactNode; }) { return ( ); }