Files
CertScope-Public/app/components/report/report-actions.tsx
T

64 lines
3.7 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
function RefreshIcon() {
return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 1 0 2 5.3" fill="none" stroke="currentColor" strokeWidth="2"/><path d="M20 5v6h-6" fill="none" stroke="currentColor" strokeWidth="2"/></svg>;
}
function ShareIcon() {
return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16V3m0 0L7 8m5-5 5 5M5 13v7h14v-7" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>;
}
type ReportActionsProps = {
loading: boolean;
onReinspect: () => void;
onInspectAnother: () => void;
onShare: () => void;
onExportJson: () => void;
onExportMarkdown: () => void;
};
export function ReportActions({ loading, onReinspect, onInspectAnother, onShare, onExportJson, onExportMarkdown }: ReportActionsProps) {
const [mobileOpen, setMobileOpen] = useState(false);
const [desktopExportOpen, setDesktopExportOpen] = useState(false);
const [mobileExportOpen, setMobileExportOpen] = useState(false);
const desktopRef = useRef<HTMLDivElement | null>(null);
const mobileRef = useRef<HTMLDivElement | null>(null);
const close = () => {
setMobileOpen(false);
setDesktopExportOpen(false);
setMobileExportOpen(false);
};
useEffect(() => {
if (!mobileOpen && !desktopExportOpen && !mobileExportOpen) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") close();
};
const onPointerDown = (event: PointerEvent) => {
if (![desktopRef.current, mobileRef.current].some((element) => element?.contains(event.target as Node))) close();
};
document.addEventListener("keydown", onKeyDown);
document.addEventListener("pointerdown", onPointerDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("pointerdown", onPointerDown);
};
}, [mobileOpen, desktopExportOpen, mobileExportOpen]);
const controls = (variant: "desktop" | "mobile") => {
const exportOpen = variant === "desktop" ? desktopExportOpen : mobileExportOpen;
const setExportOpen = variant === "desktop" ? setDesktopExportOpen : setMobileExportOpen;
const exportId = variant + "-export-options";
return <>
<button type="button" className="action-reinspect" onClick={() => { close(); onReinspect(); }} disabled={loading}><RefreshIcon />{loading ? "Inspecting…" : "Re-inspect"}</button>
<button type="button" className="inspect-another-btn" onClick={() => { close(); onInspectAnother(); }}>Inspect another host</button>
<button type="button" className="share-btn" onClick={() => { close(); onShare(); }}><ShareIcon />Share</button>
<div className="export-control"><button type="button" className="export-btn" aria-expanded={exportOpen} aria-controls={exportId} onClick={() => setExportOpen((open) => !open)}>Export <span aria-hidden="true">⌄</span></button>{exportOpen && <div id={exportId} className="export-popover" aria-label="Export report"><button type="button" onClick={() => { onExportJson(); close(); }}>JSON</button><button type="button" onClick={() => { onExportMarkdown(); close(); }}>Markdown</button></div>}</div>
</>;
};
return <><div ref={desktopRef} className="report-actions">{controls("desktop")}</div><div ref={mobileRef} className="mobile-action-menu"><button type="button" className="actions-toggle" aria-expanded={mobileOpen} aria-controls="mobile-report-actions" onClick={() => setMobileOpen((open) => !open)}>Actions <span aria-hidden="true">⌄</span></button>{mobileOpen && <div id="mobile-report-actions" className="actions-popover" aria-label="Report actions">{controls("mobile")}</div>}</div></>;
}