/** * What the four progress islands share besides the data: the not-ready body * (a skeleton while /api/stats loads, one muted line if it fails) and the * hover tooltip for the two charts drawn as plain SVG (the heatmap calendar * and the topic bubbles — Recharts has neither layout). The tooltip wears the * exact classes of shadcn's ChartTooltipContent, so all four cards' tooltips * look like one component. */ import { type PointerEvent, type ReactNode, useRef, useState } from "react"; import { cn } from "../../lib/utils"; import type { Load } from "../../lib/stats"; import { Skeleton } from "../ui/skeleton"; export function StatsPending({ load, className }: { load: Exclude; className: string }) { if (load.phase === "loading") return ; return (

Stats unavailable — they are served live from the SRS Worker, and it did not respond.

); } interface Tip { x: number; y: number; content: ReactNode; } /** * A pointer-following tooltip for SVG marks. Put `ref` on a `relative` * wrapper, call `show` from a mark's pointer events, render `tip` inside the * wrapper. Positions are relative to the wrapper, so page scroll never skews it. */ export function useHoverTip() { const ref = useRef(null); const [tip, setTip] = useState(null); const show = (event: PointerEvent, content: ReactNode) => { const box = ref.current?.getBoundingClientRect(); if (box) setTip({ x: event.clientX - box.left, y: event.clientY - box.top, content }); }; const hide = () => setTip(null); const node = tip && (
{tip.content}
); return { ref, show, hide, node }; } /** One tooltip row, the shape ChartTooltipContent draws: colour dot, label, mono value. */ export function TipRow({ color, label, value }: { color: string; label: ReactNode; value: ReactNode }) { return (
{label} {value}
); }