import type { ReactNode } from "react";
import { cn } from "./cn";
export interface MetricProps {
value: ReactNode;
unit?: ReactNode;
label: ReactNode;
delta?: number | null;
deltaLabel?: string;
note?: ReactNode;
size?: "sm" | "md" | "lg";
className?: string;
/** render as skeleton */
loading?: boolean;
href?: string;
}
/** Big figure in mono/tabular, tight tracking; unit in small caps beside; label under. */
export function Metric({ value, unit, label, delta, deltaLabel, note, size = "md", className, loading }: MetricProps) {
const vs = size === "lg" ? "text-[30px] sm:text-[36px]" : size === "sm" ? "text-[18px]" : "text-[24px] sm:text-[28px]";
return (
{loading ? : {value}}
{unit && !loading && {unit}}
{delta !== undefined && delta !== null && !loading && (
0 ? "text-ok" : delta < 0 ? "text-danger" : "text-ink-3")} title={deltaLabel}>
{delta > 0 ? "+" : ""}
{delta}
)}
{label}
{note &&
{note}
}
);
}
/**
* Metric strip. Under 640 px: a 2-column grid so every figure is visible without scrolling (an odd
* last cell spans both columns); ≥ 640 px: one row of `cols` columns. Hairline separators, no cards.
*/
export function MetricRow({ children, className, cols }: { children: ReactNode; className?: string; cols?: number }) {
return (
*]:min-w-0",
// phones: vertical hairline between the two columns, horizontal hairline between rows, odd last cell full width
"max-sm:[&>*:nth-child(even)]:border-l max-sm:[&>*:nth-child(n+3)]:border-t max-sm:[&>*:nth-child(odd)]:pl-0 max-sm:[&>*:last-child:nth-child(odd)]:col-span-2 max-sm:[&>*]:border-hair",
// ≥ sm: one row, separators between cells
"sm:[grid-template-columns:var(--metric-cols)] sm:[&>*+*]:border-l sm:[&>*]:border-hair",
className,
)}
style={{ ["--metric-cols" as string]: `repeat(${cols ?? 4}, minmax(0, 1fr))` }}
>
{children}
);
}
/** Wrapper for one metric cell inside MetricRow. */
export function MetricCell({ children, className }: { children: ReactNode; className?: string }) {
return {children}
;
}