52 / 122 · Structured output
Activity graph
A half-year of runs as a calendar of cells, dense where the work was.
Agent runs1,743 in 6 months
TueThuSat
lessmore
Installation
npx shadcn@latest add "@assistant-ui/elements-activity-graph"
Usage
import { ActivityGraph } from "@/components/elements/activity-graph";
<ActivityGraph
data={data}
start={start}
end={end}
title="Agent runs"
total="1,204 in 6 months"
/>Props
data*DataPoint[]Date and count pairs from the heat-graph package. Levels are classified for you; gaps are filled as empty cells.
start*string | DateFirst day of the window. Pass it explicitly: left unset, heat-graph draws a full year ending today, which will not match your data.
end*string | DateLast day of the window. Cells are fixed-size, so a long window scrolls horizontally rather than shrinking.
title*stringWhat is being counted.
total*stringPre-formatted summary shown opposite the title.
classNamestringExtra classes merged onto the root.
Source
activity-graph.tsx"use client";
import * as HeatGraph from "heat-graph";
import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";
const LEVEL_TINT = [
"bg-foreground/[0.06]",
"bg-blue-500/25 dark:bg-blue-400/25",
"bg-blue-500/45 dark:bg-blue-400/45",
"bg-blue-500/70 dark:bg-blue-400/70",
"bg-blue-500 dark:bg-blue-400",
] as const;
export function ActivityGraph({
data,
start,
end,
title,
total,
className,
}: {
data: readonly HeatGraph.DataPoint[];
start: string | Date;
end: string | Date;
title: string;
total: string;
className?: string;
}) {
return (
<div
className={cn(
paper,
"flex w-full max-w-sm flex-col gap-3 rounded-2xl p-4",
className,
)}
>
<div className="flex items-baseline justify-between">
<span className="text-[13.5px] font-medium">{title}</span>
<span className={cn(mono, "text-foreground/35 tabular-nums")}>
{total}
</span>
</div>
<HeatGraph.Root
data={[...data]}
start={start}
end={end}
weekStart="monday"
className="flex flex-col gap-2"
>
<div className="flex gap-2 overflow-x-auto">
<div className="flex shrink-0 flex-col gap-[3px]">
<HeatGraph.DayLabels>
{({ label }) => (
<span
className={cn(
mono,
"text-foreground/25 flex h-[9px] items-center leading-none",
)}
>
{label.row % 2 === 1
? HeatGraph.DAY_SHORT[label.dayOfWeek]
: ""}
</span>
)}
</HeatGraph.DayLabels>
</div>
<HeatGraph.Grid className="gap-[3px]">
{({ cell }) => (
<HeatGraph.Cell
className={cn(
"size-[9px] rounded-[2px]",
LEVEL_TINT[cell.level] ?? LEVEL_TINT[0],
)}
/>
)}
</HeatGraph.Grid>
</div>
<div className="flex items-center gap-1.5 self-end">
<span className={cn(mono, "text-foreground/25")}>less</span>
{LEVEL_TINT.map((tint, level) => (
<span
key={level}
aria-hidden
className={cn("size-[9px] rounded-[2px]", tint)}
/>
))}
<span className={cn(mono, "text-foreground/25")}>more</span>
</div>
</HeatGraph.Root>
</div>
);
}