import Link from 'next/link';
import type { ReactNode } from 'react';
import { cn } from '@/lib/cn';
import { fmtInt } from '@/lib/format';
export type BarRow = { key: string; label: ReactNode; value: number; href?: string; color?: string; hint?: ReactNode };
/** Horizontal bars (server-renderable): label · bar · value. Sorted by the caller. */
export function Bars({ rows, className, format = fmtInt, max: maxOverride, dense = false }: { rows: BarRow[]; className?: string; format?: (v: number) => string; max?: number; dense?: boolean }) {
const max = maxOverride ?? Math.max(1, ...rows.map((r) => Math.abs(r.value)));
if (!rows.length) return null;
return (
{rows.map((r) => {
const pct = Math.min(100, (Math.abs(r.value) / max) * 100);
const inner = (
<>
{r.label}
{format(r.value)}
{r.hint !== undefined && {r.hint}}
>
);
const cls = cn('grid grid-cols-[minmax(0,1fr)_minmax(4rem,2fr)_auto] items-center gap-x-3', dense ? 'py-0.5' : 'py-1');
return (
-
{r.href ? (
{inner}
) : (
{inner}
)}
);
})}
);
}
/** Tiny vertical column strip (e.g. events per day) — SVG, server-renderable. */
export function Columns({ values, width = 120, height = 28, className, color = 'var(--accent)', ariaLabel }: { values: number[]; width?: number; height?: number; className?: string; color?: string; ariaLabel?: string }) {
const max = Math.max(1, ...values);
const n = values.length || 1;
const gap = 1;
const bw = Math.max(1, (width - gap * (n - 1)) / n);
return (
);
}