import { useEffect, useRef } from "react"; import clsx from "clsx"; import { Loader2, X } from "lucide-react"; export const cx = clsx; // ---------- formatting ---------- const nf = new Intl.NumberFormat("en-CA"); export const fmt = (n) => (n == null || Number.isNaN(n) ? "—" : nf.format(n)); export const fmtCompact = (n) => n == null ? "—" : n >= 10000 ? new Intl.NumberFormat("en-CA", { notation: "compact", maximumFractionDigits: 1 }).format(n) : nf.format(n); export const fmtDate = (iso, opts = { month: "short", day: "numeric" }) => (iso ? new Date(iso).toLocaleDateString("en-CA", opts) : "—"); export const fmtDateTime = (iso) => iso ? new Date(iso).toLocaleString("en-CA", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : "—"; export function timeAgo(iso) { if (!iso) return "never"; const s = (Date.now() - new Date(iso)) / 1000; if (s < 60) return "just now"; if (s < 3600) return `${Math.floor(s / 60)} min ago`; if (s < 86400) return `${Math.floor(s / 3600)} h ago`; const d = Math.floor(s / 86400); return d === 1 ? "yesterday" : `${d} days ago`; } export const plural = (n, word) => `${fmt(n)} ${word}${n === 1 ? "" : "s"}`; // ---------- primitives ---------- export function Button({ variant = "secondary", size = "md", loading, icon: Icon, className, children, ...props }) { return ( ); } export function Panel({ title, action, children, className, bodyClass, subtitle }) { return (
{(title || action) && (

{title}

{subtitle &&

{subtitle}

}
{action}
)}
{children}
); } const TONES = { neutral: "bg-canvas text-ink-2 border-line", brand: "bg-brand-soft text-brand-deep border-[#cfe0f1]", ok: "bg-ok-soft text-ok border-[#cfe6d7]", warn: "bg-warn-soft text-warn border-[#f1dcb8]", bad: "bg-bad-soft text-bad border-[#f2cdc8]", }; export function Badge({ tone = "neutral", children, className, dot }) { return ( {dot && } {children} ); } /** Headline number with label and optional comparison/note. */ export function Stat({ label, value, note, tone, loading }) { return (
{label}
{loading ? (
) : (
{value}
)} {note &&
{note}
}
); } export function StatRow({ children, className }) { return
{children}
; } export const StatCell = ({ children }) =>
{children}
; export function Spinner({ label }) { return (
{label || "Loading…"}
); } export function Empty({ icon: Icon, title, children, action }) { return (
{Icon && }
{title}
{children &&

{children}

} {action &&
{action}
}
); } export function ErrorNote({ error, className }) { if (!error) return null; return
{String(error.message || error)}
; } export function Tabs({ tabs, value, onChange }) { return (
{tabs.map((t) => ( ))}
); } /** Modal dialog on top of the native element (focus trap + Esc for free). */ export function Dialog({ open, onClose, title, children, footer, wide }) { const ref = useRef(null); useEffect(() => { const d = ref.current; if (!d) return; if (open && !d.open) d.showModal(); if (!open && d.open) d.close(); }, [open]); return ( e.target === ref.current && onClose()} className={cx("m-auto rounded-xl p-0 backdrop:bg-[#1f2024]/40 shadow-[0_20px_60px_-15px_rgba(15,23,42,0.35)] border border-line", wide ? "w-[min(880px,94vw)]" : "w-[min(520px,94vw)]")} > {open && (

{title}

{children}
{footer &&
{footer}
}
)}
); } export function Field({ label, hint, children }) { return ( ); } /** Tiny bar sparkline (no axes) for tables. */ export function Spark({ values, width = 96, height = 26 }) { if (!values?.length) return —; const max = Math.max(...values, 1); const w = width / values.length; return ( ); } export const STATUS_TONE = { queued: "neutral", running: "brand", succeeded: "ok", failed: "bad", cancelled: "neutral", interrupted: "warn" };