mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
desktop-new: delete the proving-ground dashboard
It was meant to stress the design system by putting many decisions next to each other. It did surface one real finding - the neutral hover step is tuned for hovering a filled component, not a bare row, so the same token is too weak for a quiet button and too strong for a bare row - but the page was ~85% bespoke CSS routed around the shared components, so most of what it showed was its own scaffolding rather than the system. The app itself is the better proving ground. Removing this rather than carrying a mockup whose defects have to be triaged separately from the product's. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { ProvingGroundPage } from "@/features/proving-ground/ui/ProvingGroundPage";
|
||||
|
||||
export const Route = createFileRoute("/proving-ground")({
|
||||
component: ProvingGroundPage,
|
||||
});
|
||||
@@ -1,187 +0,0 @@
|
||||
/**
|
||||
* Fixture data for the proving ground.
|
||||
*
|
||||
* A fictional product — a fleet-monitoring dashboard — deliberately unrelated to
|
||||
* Buzz. The point is to exercise the design system at the density and variety a
|
||||
* real page has, without any of it becoming a claim about what Buzz looks like.
|
||||
* Nothing here is a product decision; it is load for the tokens to carry.
|
||||
*/
|
||||
|
||||
export type Health = "healthy" | "degraded" | "failing" | "paused";
|
||||
|
||||
export type Region = {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
health: Health;
|
||||
/** Requests per second, for the sparkline and the headline figure. */
|
||||
throughput: number;
|
||||
/** Percentage, one decimal. */
|
||||
successRate: number;
|
||||
latencyMs: number;
|
||||
/** Twelve readings, oldest first. */
|
||||
history: number[];
|
||||
};
|
||||
|
||||
export type Incident = {
|
||||
id: string;
|
||||
title: string;
|
||||
region: string;
|
||||
severity: "critical" | "warning" | "info";
|
||||
openedAt: string;
|
||||
assignee?: { name: string; initial: string };
|
||||
acknowledged: boolean;
|
||||
};
|
||||
|
||||
export type Deploy = {
|
||||
id: string;
|
||||
service: string;
|
||||
version: string;
|
||||
status: "live" | "rolling" | "held" | "rolled-back";
|
||||
author: string;
|
||||
at: string;
|
||||
};
|
||||
|
||||
export const REGIONS: Region[] = [
|
||||
{
|
||||
id: "use1",
|
||||
name: "US East",
|
||||
code: "use1",
|
||||
health: "healthy",
|
||||
throughput: 18420,
|
||||
successRate: 99.98,
|
||||
latencyMs: 42,
|
||||
history: [12, 14, 13, 16, 18, 17, 19, 21, 20, 22, 24, 23],
|
||||
},
|
||||
{
|
||||
id: "usw2",
|
||||
name: "US West",
|
||||
code: "usw2",
|
||||
health: "healthy",
|
||||
throughput: 11260,
|
||||
successRate: 99.94,
|
||||
latencyMs: 51,
|
||||
history: [9, 10, 11, 10, 12, 13, 12, 14, 13, 15, 14, 16],
|
||||
},
|
||||
{
|
||||
id: "euw1",
|
||||
name: "EU West",
|
||||
code: "euw1",
|
||||
health: "degraded",
|
||||
throughput: 8940,
|
||||
successRate: 97.21,
|
||||
latencyMs: 189,
|
||||
history: [14, 13, 15, 14, 12, 11, 9, 8, 10, 9, 7, 8],
|
||||
},
|
||||
{
|
||||
id: "apse1",
|
||||
name: "AP Southeast",
|
||||
code: "apse1",
|
||||
health: "failing",
|
||||
throughput: 1180,
|
||||
successRate: 71.44,
|
||||
latencyMs: 842,
|
||||
history: [11, 12, 10, 11, 9, 7, 5, 3, 2, 2, 1, 1],
|
||||
},
|
||||
{
|
||||
id: "sae1",
|
||||
name: "SA East",
|
||||
code: "sae1",
|
||||
health: "paused",
|
||||
throughput: 0,
|
||||
successRate: 0,
|
||||
latencyMs: 0,
|
||||
history: [6, 6, 5, 6, 4, 3, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
];
|
||||
|
||||
export const INCIDENTS: Incident[] = [
|
||||
{
|
||||
id: "INC-4821",
|
||||
title: "Elevated 5xx from the ingest gateway",
|
||||
region: "apse1",
|
||||
severity: "critical",
|
||||
openedAt: "14 min ago",
|
||||
assignee: { name: "Priya Raman", initial: "P" },
|
||||
acknowledged: true,
|
||||
},
|
||||
{
|
||||
id: "INC-4820",
|
||||
title: "Replica lag above threshold on the primary cluster",
|
||||
region: "euw1",
|
||||
severity: "warning",
|
||||
openedAt: "38 min ago",
|
||||
assignee: { name: "Tomas Lindqvist", initial: "T" },
|
||||
acknowledged: true,
|
||||
},
|
||||
{
|
||||
id: "INC-4819",
|
||||
title: "Certificate expires in 6 days",
|
||||
region: "usw2",
|
||||
severity: "info",
|
||||
openedAt: "2 hr ago",
|
||||
acknowledged: false,
|
||||
},
|
||||
{
|
||||
id: "INC-4817",
|
||||
title: "Queue depth climbing without a matching request increase",
|
||||
region: "euw1",
|
||||
severity: "warning",
|
||||
openedAt: "5 hr ago",
|
||||
assignee: { name: "Dani Okonkwo", initial: "D" },
|
||||
acknowledged: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const DEPLOYS: Deploy[] = [
|
||||
{
|
||||
id: "d-9931",
|
||||
service: "ingest-gateway",
|
||||
version: "2026.9.4",
|
||||
status: "rolling",
|
||||
author: "Priya Raman",
|
||||
at: "6 min ago",
|
||||
},
|
||||
{
|
||||
id: "d-9930",
|
||||
service: "index-writer",
|
||||
version: "2026.9.3",
|
||||
status: "live",
|
||||
author: "Marco Bellini",
|
||||
at: "1 hr ago",
|
||||
},
|
||||
{
|
||||
id: "d-9929",
|
||||
service: "query-planner",
|
||||
version: "2026.9.3",
|
||||
status: "held",
|
||||
author: "Dani Okonkwo",
|
||||
at: "3 hr ago",
|
||||
},
|
||||
{
|
||||
id: "d-9927",
|
||||
service: "ingest-gateway",
|
||||
version: "2026.9.2",
|
||||
status: "rolled-back",
|
||||
author: "Tomas Lindqvist",
|
||||
at: "yesterday",
|
||||
},
|
||||
];
|
||||
|
||||
/** Maps a health state to the status family that carries its meaning. */
|
||||
export const HEALTH_TONE: Record<
|
||||
Health,
|
||||
"success" | "warning" | "danger" | "neutral"
|
||||
> = {
|
||||
healthy: "success",
|
||||
degraded: "warning",
|
||||
failing: "danger",
|
||||
paused: "neutral",
|
||||
};
|
||||
|
||||
export const HEALTH_LABEL: Record<Health, string> = {
|
||||
healthy: "Healthy",
|
||||
degraded: "Degraded",
|
||||
failing: "Failing",
|
||||
paused: "Paused",
|
||||
};
|
||||
@@ -1,401 +0,0 @@
|
||||
import {
|
||||
IconActivity,
|
||||
IconAlertTriangle,
|
||||
IconChartDots,
|
||||
IconChevronRight,
|
||||
IconCloud,
|
||||
IconMoon,
|
||||
IconRocket,
|
||||
IconSun,
|
||||
} from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useColorScheme } from "@/shared/theme/useColorScheme";
|
||||
import { Avatar } from "@/shared/ui/Avatar";
|
||||
import { Button } from "@/shared/ui/Button";
|
||||
import { IconButton } from "@/shared/ui/IconButton";
|
||||
import { SearchField } from "@/shared/ui/SearchField";
|
||||
import { SegmentedNavigation } from "@/shared/ui/SegmentedNavigation";
|
||||
|
||||
import { DEPLOYS, HEALTH_TONE, INCIDENTS, REGIONS } from "../data";
|
||||
import {
|
||||
HealthPill,
|
||||
KeyValue,
|
||||
Metric,
|
||||
Panel,
|
||||
Row,
|
||||
Sparkline,
|
||||
StatusPill,
|
||||
} from "./parts";
|
||||
|
||||
/**
|
||||
* The proving ground: a fictional fleet-monitoring dashboard.
|
||||
*
|
||||
* Deliberately not Buzz. Its job is to load the design system the way a real
|
||||
* product does — many decisions adjacent to each other, at density, in both
|
||||
* modes — and to be the surface a generated theme is judged on. A specimen page
|
||||
* shows one component at a time on a backdrop the designer chose; this shows
|
||||
* forty at once, which is where a missing role or a wrong neutral step appears.
|
||||
*
|
||||
* Built only from role tokens. If something here needs a value the system does
|
||||
* not have, that is the finding.
|
||||
*/
|
||||
|
||||
const RANGES = [
|
||||
{ value: "1h", label: "1 hour" },
|
||||
{ value: "24h", label: "24 hours" },
|
||||
{ value: "7d", label: "7 days" },
|
||||
] as const;
|
||||
|
||||
const SEVERITY_TONE = {
|
||||
critical: "danger",
|
||||
warning: "warning",
|
||||
info: "info",
|
||||
} as const;
|
||||
|
||||
const DEPLOY_TONE = {
|
||||
live: "success",
|
||||
rolling: "accent",
|
||||
held: "warning",
|
||||
"rolled-back": "danger",
|
||||
} as const;
|
||||
|
||||
const DEPLOY_LABEL = {
|
||||
live: "Live",
|
||||
rolling: "Rolling out",
|
||||
held: "Held",
|
||||
"rolled-back": "Rolled back",
|
||||
} as const;
|
||||
|
||||
export function ProvingGroundPage() {
|
||||
const { scheme, toggle } = useColorScheme();
|
||||
const [range, setRange] = useState<string>("24h");
|
||||
const [section, setSection] = useState("overview");
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const openIncidents = INCIDENTS.length;
|
||||
const failing = REGIONS.filter((r) => r.health === "failing").length;
|
||||
|
||||
return (
|
||||
<div className="dash-shell">
|
||||
<nav className="dash-nav" aria-label="Sections">
|
||||
<div className="dash-nav-group">
|
||||
<span className="dash-nav-label text-body-sm">Monitor</span>
|
||||
<NavItem
|
||||
icon={<IconActivity size={16} />}
|
||||
label="Overview"
|
||||
id="overview"
|
||||
current={section}
|
||||
onSelect={setSection}
|
||||
/>
|
||||
<NavItem
|
||||
icon={<IconAlertTriangle size={16} />}
|
||||
label="Incidents"
|
||||
id="incidents"
|
||||
count={openIncidents}
|
||||
current={section}
|
||||
onSelect={setSection}
|
||||
/>
|
||||
<NavItem
|
||||
icon={<IconCloud size={16} />}
|
||||
label="Regions"
|
||||
id="regions"
|
||||
count={REGIONS.length}
|
||||
current={section}
|
||||
onSelect={setSection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="dash-nav-group">
|
||||
<span className="dash-nav-label text-body-sm">Ship</span>
|
||||
<NavItem
|
||||
icon={<IconRocket size={16} />}
|
||||
label="Deploys"
|
||||
id="deploys"
|
||||
current={section}
|
||||
onSelect={setSection}
|
||||
/>
|
||||
<NavItem
|
||||
icon={<IconChartDots size={16} />}
|
||||
label="Reports"
|
||||
id="reports"
|
||||
current={section}
|
||||
onSelect={setSection}
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="dash-main">
|
||||
<header className="dash-topbar">
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<h1 className="text-title text-primary">Fleet</h1>
|
||||
<p className="text-body text-secondary">
|
||||
Five regions, {openIncidents} open incidents
|
||||
{failing > 0 ? `, ${failing} failing` : ""}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<SearchField
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
placeholder="Search regions and services"
|
||||
/>
|
||||
<SegmentedNavigation
|
||||
label="Time range"
|
||||
items={RANGES}
|
||||
value={range}
|
||||
onValueChange={setRange}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={
|
||||
scheme === "dark" ? "Use light mode" : "Use dark mode"
|
||||
}
|
||||
onClick={toggle}
|
||||
icon={
|
||||
scheme === "dark" ? (
|
||||
<IconSun size={16} />
|
||||
) : (
|
||||
<IconMoon size={16} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button variant="primary">New check</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="dash-metric-grid">
|
||||
<Metric
|
||||
label="Requests"
|
||||
value="39.8"
|
||||
unit="k/s"
|
||||
change="+4.2% vs yesterday"
|
||||
direction="up"
|
||||
/>
|
||||
<Metric
|
||||
label="Success rate"
|
||||
value="97.4"
|
||||
unit="%"
|
||||
change="−1.9 points"
|
||||
direction="down"
|
||||
/>
|
||||
<Metric
|
||||
label="p95 latency"
|
||||
value="188"
|
||||
unit="ms"
|
||||
change="+61 ms"
|
||||
direction="down"
|
||||
/>
|
||||
<Metric
|
||||
label="Open incidents"
|
||||
value="4"
|
||||
change="2 acknowledged"
|
||||
direction="flat"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="dash-columns">
|
||||
<div className="flex min-w-0 flex-col gap-6">
|
||||
<Panel
|
||||
title="Regions"
|
||||
description="Throughput over the selected window."
|
||||
>
|
||||
<div className="dash-region-grid">
|
||||
{REGIONS.map((region) => (
|
||||
<article key={region.id} className="dash-region">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="text-body text-primary">
|
||||
{region.name}
|
||||
</span>
|
||||
<span className="text-mono-sm text-tertiary">
|
||||
{region.code}
|
||||
</span>
|
||||
</div>
|
||||
<HealthPill health={region.health} />
|
||||
</div>
|
||||
<Sparkline
|
||||
values={region.history}
|
||||
tone={HEALTH_TONE[region.health]}
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<KeyValue
|
||||
label="Requests"
|
||||
value={
|
||||
region.throughput === 0
|
||||
? "—"
|
||||
: `${(region.throughput / 1000).toFixed(1)}k/s`
|
||||
}
|
||||
/>
|
||||
<KeyValue
|
||||
label="Success"
|
||||
value={
|
||||
region.successRate === 0
|
||||
? "—"
|
||||
: `${region.successRate.toFixed(2)}%`
|
||||
}
|
||||
/>
|
||||
<KeyValue
|
||||
label="p95"
|
||||
value={
|
||||
region.latencyMs === 0
|
||||
? "—"
|
||||
: `${region.latencyMs} ms`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel
|
||||
title="Open incidents"
|
||||
description="Newest first."
|
||||
padded={false}
|
||||
action={
|
||||
<Button variant="quiet" size="compact">
|
||||
View all
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{INCIDENTS.map((incident) => (
|
||||
<Row key={incident.id} interactive>
|
||||
<StatusPill tone={SEVERITY_TONE[incident.severity]}>
|
||||
{incident.severity}
|
||||
</StatusPill>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<span className="truncate text-body text-primary">
|
||||
{incident.title}
|
||||
</span>
|
||||
<span className="text-body-sm text-tertiary">
|
||||
<span className="text-mono-sm">{incident.id}</span> ·{" "}
|
||||
{incident.region} · {incident.openedAt}
|
||||
</span>
|
||||
</div>
|
||||
{incident.assignee ? (
|
||||
<Avatar
|
||||
size="small"
|
||||
alt={`Assigned to ${incident.assignee.name}`}
|
||||
fallback={incident.assignee.initial}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-body-sm text-disabled">
|
||||
Unassigned
|
||||
</span>
|
||||
)}
|
||||
<IconChevronRight
|
||||
size={16}
|
||||
className="text-tertiary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Row>
|
||||
))}
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-col gap-6">
|
||||
<Panel title="Recent deploys" padded={false}>
|
||||
{DEPLOYS.map((deploy) => (
|
||||
<Row key={deploy.id}>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<span className="truncate text-body text-primary">
|
||||
{deploy.service}
|
||||
</span>
|
||||
<span className="text-body-sm text-tertiary">
|
||||
<span className="text-mono-sm">{deploy.version}</span> ·{" "}
|
||||
{deploy.author} · {deploy.at}
|
||||
</span>
|
||||
</div>
|
||||
<StatusPill tone={DEPLOY_TONE[deploy.status]}>
|
||||
{DEPLOY_LABEL[deploy.status]}
|
||||
</StatusPill>
|
||||
</Row>
|
||||
))}
|
||||
</Panel>
|
||||
|
||||
<Panel
|
||||
title="Rollout"
|
||||
description="ingest-gateway 2026.9.4 is going out region by region."
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-baseline justify-between">
|
||||
<span className="text-body-sm text-secondary">
|
||||
Progress
|
||||
</span>
|
||||
<span className="text-body-sm text-primary tabular-nums">
|
||||
2 of 5 regions
|
||||
</span>
|
||||
</div>
|
||||
{/* A determinate bar: inset track, accent fill. */}
|
||||
<div
|
||||
className="h-2 overflow-hidden rounded-pill bg-inset"
|
||||
role="progressbar"
|
||||
aria-valuenow={40}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-label="Rollout progress"
|
||||
>
|
||||
<div className="h-full w-2/5 rounded-pill bg-accent" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<KeyValue label="Started" value="6 min ago" />
|
||||
<KeyValue label="Error budget" value="98.2% remaining" />
|
||||
<KeyValue label="Next region" value="euw1" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="primary" size="compact">
|
||||
Continue rollout
|
||||
</Button>
|
||||
<Button variant="quiet" size="compact">
|
||||
Hold
|
||||
</Button>
|
||||
<Button variant="ghost" size="compact" disabled>
|
||||
Roll back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NavItem({
|
||||
icon,
|
||||
label,
|
||||
id,
|
||||
count,
|
||||
current,
|
||||
onSelect,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
id: string;
|
||||
count?: number;
|
||||
current: string;
|
||||
onSelect: (id: string) => void;
|
||||
}) {
|
||||
const isCurrent = current === id;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="dash-nav-item text-body"
|
||||
aria-current={isCurrent ? "page" : undefined}
|
||||
onClick={() => onSelect(id)}
|
||||
>
|
||||
<span aria-hidden="true" className="flex shrink-0">
|
||||
{icon}
|
||||
</span>
|
||||
{label}
|
||||
{count !== undefined ? (
|
||||
<span className="dash-nav-count text-body-sm">{count}</span>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { type Health, HEALTH_LABEL, HEALTH_TONE } from "../data";
|
||||
|
||||
/**
|
||||
* Pieces the proving-ground dashboard is assembled from.
|
||||
*
|
||||
* These are page-local on purpose. The shared layer earns a component when the
|
||||
* product repeats one; this page exists to find out *which* things repeat, so
|
||||
* putting its parts in `shared/ui` first would be guessing. Anything that turns
|
||||
* out to be load-bearing graduates afterwards.
|
||||
*/
|
||||
|
||||
type Tone = "success" | "warning" | "danger" | "neutral" | "accent" | "info";
|
||||
|
||||
/**
|
||||
* A status pill. Tint fill, coloured text — the tint arrangement from
|
||||
* DESIGN.md, where a colour means something rather than carrying an action.
|
||||
*/
|
||||
export function StatusPill({
|
||||
tone,
|
||||
children,
|
||||
}: {
|
||||
tone: Tone;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<span className="status-pill" data-tone={tone}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function HealthPill({ health }: { health: Health }) {
|
||||
return (
|
||||
<StatusPill tone={HEALTH_TONE[health]}>{HEALTH_LABEL[health]}</StatusPill>
|
||||
);
|
||||
}
|
||||
|
||||
/** A page region. Panel surface, generous inset, no card-per-row inside. */
|
||||
export function Panel({
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
children,
|
||||
padded = true,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
action?: ReactNode;
|
||||
children: ReactNode;
|
||||
/** False when the content is edge-to-edge rows that supply their own inset. */
|
||||
padded?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<section className="dash-panel">
|
||||
<header className="dash-panel-header">
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<h2 className="text-heading text-primary">{title}</h2>
|
||||
{description ? (
|
||||
<p className="text-body-sm text-secondary">{description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{action}
|
||||
</header>
|
||||
<div data-padded={padded || undefined} className="dash-panel-body">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A headline figure with its label and change.
|
||||
*
|
||||
* Hierarchy comes from size, not colour: the number is `text-display`, the label
|
||||
* is small and secondary. Only the change direction earns a colour, because a
|
||||
* direction is a signal.
|
||||
*/
|
||||
export function Metric({
|
||||
label,
|
||||
value,
|
||||
unit,
|
||||
change,
|
||||
direction,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
unit?: string;
|
||||
change?: string;
|
||||
direction?: "up" | "down" | "flat";
|
||||
}) {
|
||||
return (
|
||||
<div className="dash-metric">
|
||||
<span className="text-body-sm text-secondary">{label}</span>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className="text-display text-primary tabular-nums">{value}</span>
|
||||
{unit ? (
|
||||
<span className="text-body-sm text-tertiary">{unit}</span>
|
||||
) : null}
|
||||
</div>
|
||||
{change ? (
|
||||
<span
|
||||
className="text-body-sm tabular-nums"
|
||||
data-direction={direction}
|
||||
// A rise is not automatically good, so direction is coloured by what
|
||||
// it means on this metric rather than by which way it points.
|
||||
style={{
|
||||
color:
|
||||
direction === "up"
|
||||
? "var(--text-success)"
|
||||
: direction === "down"
|
||||
? "var(--text-danger)"
|
||||
: "var(--text-tertiary)",
|
||||
}}
|
||||
>
|
||||
{change}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A bar sparkline, drawn with divs rather than SVG.
|
||||
*
|
||||
* Deliberate: it inherits `currentColor` and the rem-based type scale, so it
|
||||
* tracks the theme and the font-size preference without a redraw. Decorative —
|
||||
* the figure beside it carries the information, so this is hidden from
|
||||
* assistive technology rather than described.
|
||||
*/
|
||||
export function Sparkline({
|
||||
values,
|
||||
tone = "accent",
|
||||
}: {
|
||||
values: number[];
|
||||
tone?: Tone;
|
||||
}) {
|
||||
const peak = Math.max(...values, 1);
|
||||
// A reading's position in the window is part of its identity — the oldest
|
||||
// sample is a different thing from the newest — so the key carries both.
|
||||
const bars = values.map((value, index) => ({
|
||||
id: `${index}:${value}`,
|
||||
height: `${Math.max((value / peak) * 100, 4)}%`,
|
||||
}));
|
||||
return (
|
||||
<div className="dash-sparkline" data-tone={tone} aria-hidden="true">
|
||||
{bars.map((bar) => (
|
||||
<span
|
||||
key={bar.id}
|
||||
className="dash-sparkline-bar"
|
||||
style={{ height: bar.height }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** A dense table row. Dividers, edge to edge — never a card per row. */
|
||||
export function Row({
|
||||
children,
|
||||
interactive = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
interactive?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="dash-row" data-interactive={interactive || undefined}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function KeyValue({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-baseline justify-between gap-4">
|
||||
<span className="text-body-sm text-secondary">{label}</span>
|
||||
<span className="text-body-sm text-primary tabular-nums">{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
@import "./typography.css";
|
||||
@import "./components.css";
|
||||
@import "./chips.css";
|
||||
@import "./proving-ground.css";
|
||||
@import "./design-components.css";
|
||||
@import "./product.css";
|
||||
@import "./agents.css";
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
@layer components {
|
||||
/**
|
||||
* Proving-ground dashboard.
|
||||
*
|
||||
* A fictional product built only from role tokens, to find out whether the
|
||||
* system holds up on a real page rather than on a specimen page. Specimen
|
||||
* pages are honest but sympathetic — they show one component at a time, on a
|
||||
* generous backdrop, at a size the designer chose. A dashboard puts forty
|
||||
* decisions next to each other and is where a missing role shows up.
|
||||
*/
|
||||
|
||||
.dash-shell {
|
||||
display: grid;
|
||||
min-height: 100vh;
|
||||
grid-template-columns: var(--size-navigator) minmax(0, 1fr);
|
||||
background: var(--bg-app);
|
||||
}
|
||||
|
||||
/* Narrow: the navigator becomes a top strip rather than a drawer. There is no
|
||||
drawer machinery here yet, and a horizontal strip is honest about that. */
|
||||
@media (max-width: 60rem) {
|
||||
.dash-shell {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.dash-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
padding: var(--space-panel-inset);
|
||||
border-right: 1px solid var(--border-tertiary);
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.dash-nav {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
overflow-x: auto;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--border-tertiary);
|
||||
padding-block: var(--space-3);
|
||||
}
|
||||
}
|
||||
|
||||
.dash-nav-group {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.dash-nav-group {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
/* The section labels are useful in a column and noise in a strip. */
|
||||
.dash-nav-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dash-nav-label {
|
||||
padding-inline: var(--space-2);
|
||||
padding-block: var(--space-1);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.dash-nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-row-gap);
|
||||
border: 0;
|
||||
border-radius: var(--radius-row);
|
||||
padding: var(--space-2) var(--space-2);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font: inherit;
|
||||
text-align: start;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color var(--duration-state) var(--easing-state),
|
||||
color var(--duration-state) var(--easing-state);
|
||||
}
|
||||
|
||||
.dash-nav-item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Selected is a tint plus coloured text — the tint arrangement. Weight is
|
||||
never used to say "selected"; that is reserved for structure. */
|
||||
.dash-nav-item[aria-current="page"] {
|
||||
background: var(--bg-accent-tint);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.dash-nav-item:focus-visible {
|
||||
outline: 2px solid var(--ring-focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.dash-nav-count {
|
||||
margin-inline-start: auto;
|
||||
color: var(--text-tertiary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.dash-nav-count {
|
||||
margin-inline-start: var(--space-1);
|
||||
}
|
||||
}
|
||||
|
||||
.dash-main {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
padding: var(--space-panel-inset);
|
||||
}
|
||||
|
||||
.dash-topbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.dash-metric-grid {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||||
}
|
||||
|
||||
.dash-metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dash-columns {
|
||||
display: grid;
|
||||
gap: var(--space-6);
|
||||
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 72rem) {
|
||||
.dash-columns {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.dash-panel {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-tertiary);
|
||||
border-radius: var(--radius-panel);
|
||||
background: var(--bg-panel);
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
.dash-panel-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-5) var(--space-panel-inset);
|
||||
}
|
||||
|
||||
.dash-panel-body[data-padded] {
|
||||
padding: 0 var(--space-panel-inset) var(--space-panel-inset);
|
||||
}
|
||||
|
||||
/* Dense rows, edge to edge, separated by a line rather than by cards. */
|
||||
.dash-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-3) var(--space-panel-inset);
|
||||
border-top: 1px solid var(--border-tertiary);
|
||||
}
|
||||
|
||||
.dash-row[data-interactive] {
|
||||
cursor: pointer;
|
||||
transition: background-color var(--duration-state) var(--easing-state);
|
||||
}
|
||||
|
||||
.dash-row[data-interactive]:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 0.125rem var(--space-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-pill[data-tone="success"] {
|
||||
background: var(--bg-success-tint);
|
||||
color: var(--text-success);
|
||||
}
|
||||
|
||||
.status-pill[data-tone="warning"] {
|
||||
background: var(--bg-warning-tint);
|
||||
color: var(--text-warning);
|
||||
}
|
||||
|
||||
.status-pill[data-tone="danger"] {
|
||||
background: var(--bg-danger-tint);
|
||||
color: var(--text-danger);
|
||||
}
|
||||
|
||||
.status-pill[data-tone="info"] {
|
||||
background: var(--bg-info-tint);
|
||||
color: var(--text-info);
|
||||
}
|
||||
|
||||
.status-pill[data-tone="accent"] {
|
||||
background: var(--bg-accent-tint);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
/* Paused is not a status colour: nothing is wrong, so a colour would teach
|
||||
people to distrust the coloured ones. */
|
||||
.status-pill[data-tone="neutral"] {
|
||||
background: var(--bg-inset);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.dash-sparkline {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
height: 2rem;
|
||||
min-width: 4rem;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.dash-sparkline-bar {
|
||||
flex: 1 1 0;
|
||||
min-width: 2px;
|
||||
border-radius: 1px;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.dash-sparkline[data-tone="success"] {
|
||||
color: var(--bg-success);
|
||||
}
|
||||
|
||||
.dash-sparkline[data-tone="warning"] {
|
||||
color: var(--bg-warning);
|
||||
}
|
||||
|
||||
.dash-sparkline[data-tone="danger"] {
|
||||
color: var(--bg-danger);
|
||||
}
|
||||
|
||||
.dash-sparkline[data-tone="accent"] {
|
||||
color: var(--bg-accent);
|
||||
}
|
||||
|
||||
.dash-sparkline[data-tone="neutral"] {
|
||||
color: var(--border-secondary);
|
||||
}
|
||||
|
||||
.dash-region-grid {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
|
||||
}
|
||||
|
||||
/* A card carries no default fill. It sits on the panel and is grouped by a
|
||||
hairline, so fill is free to mean one thing only: you are pointing at this.
|
||||
Add `data-interactive` when a card is actually clickable. */
|
||||
.dash-region {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
border: 1px solid var(--border-tertiary);
|
||||
border-radius: var(--radius-control);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.dash-region[data-interactive] {
|
||||
cursor: pointer;
|
||||
transition: background-color var(--duration-state) var(--easing-state);
|
||||
}
|
||||
|
||||
.dash-region[data-interactive]:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dash-nav-item,
|
||||
.dash-row[data-interactive],
|
||||
.dash-region[data-interactive] {
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user