Status Board

Service health grid for surfacing infrastructure state, queue pressure, and incidents.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/status-board.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

3 stories available:

Code

import * as React from "react"; import type { HeadingTag } from "../../lib/types"; import { cn } from "../../lib/utils"; import { Badge } from "../badge/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../card"; export type StatusBoardStatus = | "critical" | "healthy" | "maintenance" | "offline" | "warning"; export type StatusBoardItem = { description?: string; label: string; meta?: string; status: StatusBoardStatus; value?: string; }; export type StatusBoardProps = React.ComponentPropsWithoutRef<"div"> & { /** Heading tag for the title. Defaults to `h2`. */ as?: HeadingTag; columns?: 2 | 3 | 4; description?: string; items: StatusBoardItem[]; title?: string; }; type StatusMeta = { badgeVariant: "default" | "destructive" | "outline" | "secondary"; dotClassName: string; label: string; panelClassName: string; }; const STATUS_META: Record<StatusBoardStatus, StatusMeta> = { critical: { badgeVariant: "destructive", dotClassName: "bg-destructive", label: "Critical", panelClassName: "border-destructive/30 bg-destructive/5", }, healthy: { badgeVariant: "default", dotClassName: "bg-emerald-500", label: "Healthy", panelClassName: "border-emerald-500/25 bg-emerald-500/5", }, maintenance: { badgeVariant: "secondary", dotClassName: "bg-sky-500", label: "Maintenance", panelClassName: "border-sky-500/25 bg-sky-500/5", }, offline: { badgeVariant: "outline", dotClassName: "bg-muted-foreground", label: "Offline", panelClassName: "border-border bg-muted/30", }, warning: { badgeVariant: "secondary", dotClassName: "bg-amber-500", label: "Warning", panelClassName: "border-amber-500/25 bg-amber-500/5", }, }; const STATUS_ORDER: StatusBoardStatus[] = [ "healthy", "warning", "critical", "maintenance", "offline", ]; function getColumnsClassName(columns: 2 | 3 | 4): string { if (columns === 2) { return "md:grid-cols-2"; } if (columns === 4) { return "md:grid-cols-2 xl:grid-cols-4"; } return "md:grid-cols-2 xl:grid-cols-3"; } function getSummary(items: StatusBoardItem[]) { const counts = items.reduce<Record<StatusBoardStatus, number>>( (summary, item) => ({ ...summary, [item.status]: summary[item.status] + 1, }), { critical: 0, healthy: 0, maintenance: 0, offline: 0, warning: 0, }, ); return STATUS_ORDER.reduce<{ count: number; status: StatusBoardStatus }[]>( (summary, status) => { const count = counts[status]; if (count > 0) { summary.push({ count, status }); } return summary; }, [], ); } function StatusBoardSummary({ items }: { items: StatusBoardItem[] }) { return ( <div className="flex flex-wrap gap-2"> {getSummary(items).map(({ count, status }) => { const meta = STATUS_META[status]; return ( <Badge key={status} variant={meta.badgeVariant}> {count} {meta.label} </Badge> ); })} </div> ); } function StatusBoardCard({ item }: { item: StatusBoardItem }) { const meta = STATUS_META[item.status]; return ( <Card className={cn("shadow-sm", meta.panelClassName)}> <CardHeader className="gap-3 space-y-0"> <div className="flex items-start justify-between gap-3"> <div className="space-y-1"> <div className="flex items-center gap-2"> <span aria-hidden="true" className={cn("size-2.5 rounded-full", meta.dotClassName)} /> <CardTitle className="text-base leading-none"> {item.label} </CardTitle> </div> {item.description ? ( <CardDescription>{item.description}</CardDescription> ) : null} </div> <Badge variant={meta.badgeVariant}>{meta.label}</Badge> </div> </CardHeader> <CardContent className="flex items-end justify-between gap-3"> <div> {item.value ? ( <div className="text-2xl font-semibold tracking-tight"> {item.value} </div> ) : ( <div className="text-sm text-muted-foreground"> No metric reported </div> )} </div> {item.meta ? ( <div className="text-xs text-muted-foreground">{item.meta}</div> ) : null} </CardContent> </Card> ); } export const StatusBoard = React.forwardRef<HTMLDivElement, StatusBoardProps>( ( { as: Heading = "h2", className, columns = 3, description, items, title = "Status board", ...props }, ref, ) => { return ( <div className={cn("space-y-4", className)} ref={ref} {...props}> <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"> <div className="space-y-1"> <Heading className="text-lg font-semibold tracking-tight"> {title} </Heading> {description ? ( <p className="text-sm text-muted-foreground">{description}</p> ) : null} </div> <StatusBoardSummary items={items} /> </div> <div className={cn("grid gap-4", getColumnsClassName(columns))}> {items.map((item) => ( <StatusBoardCard item={item} key={item.label} /> ))} </div> </div> ); }, ); StatusBoard.displayName = "StatusBoard";

Dependencies

  • @vllnt/ui@^0.2.1