fix(design-system): remove secondary page descriptions

Signed-off-by: Arjun Mahanti <arjun@squareup.com>
This commit is contained in:
Arjun Mahanti
2026-09-09 12:35:07 -07:00
parent cb51ef90a3
commit 80b61e05ba
18 changed files with 60 additions and 326 deletions

View File

@@ -1,4 +1,3 @@
import { Link } from "@tanstack/react-router";
import { Fragment } from "react";
import {
@@ -9,7 +8,7 @@ import {
} from "@/features/design-system/useResolvedToken";
import { RAMPS, ROLE_GROUPS } from "@/shared/tokens/registry";
import { Note, PageHeader, Section } from "./primitives";
import { PageHeader, Section } from "./primitives";
/**
* Every token in one table: the name you type, the base token it resolves
@@ -77,33 +76,13 @@ export function ColorTablePage() {
return (
<>
<PageHeader
title="Token table"
intro="Every colour token in one list: the name you type, the base token it resolves through, and the value it actually paints. Values are read from the live cascade rather than written down, so this table cannot drift from the system — and it re-resolves when you switch modes."
/>
<PageHeader title="Token table" />
<Note>
Roles are the only layer a screen may use. Ramp steps are listed
underneath so you can see what a role resolves through, but a component
referencing one directly is a bug. The reasoning behind each name lives
on the{" "}
<Link to="/design/color" className="text-accent underline">
colour page
</Link>
.
</Note>
<Section
title="Roles"
description="Grouped as they are in the system. The base column is what the role points at; the value column is where that chain ends."
>
<Section title="Roles">
<TokenTable rows={roleRows} resolved={resolved} showGroups />
</Section>
<Section
title="Ramp steps"
description="Layer 1. These hold the literal values every role resolves to, which is why the base column is empty for them."
>
<Section title="Ramp steps">
<TokenTable rows={rampRows} resolved={resolved} showGroups />
</Section>
</>

View File

@@ -5,7 +5,7 @@ import {
type Role,
} from "@/shared/tokens/registry";
import { Note, PageHeader, Section, Swatch } from "./primitives";
import { PageHeader, Section, Swatch } from "./primitives";
/**
* A role, sitting directly on the page.
@@ -34,7 +34,6 @@ function RoleRow({ role }: { role: Role }) {
</span>
) : null}
</div>
<p className="text-caption text-secondary">{role.use}</p>
{role.exception ? (
<p className="text-caption text-tertiary">
Exception: {role.exception}
@@ -48,23 +47,14 @@ function RoleRow({ role }: { role: Role }) {
export function ColourPage() {
return (
<>
<PageHeader
title="Colour"
intro="Three layers, and only the role layer is ever used when building a screen. Private ramps hold values; public roles hold meanings. Everything below is rendered from the token registry, so a token added there appears here automatically and this page cannot drift from the system."
/>
<PageHeader title="Colour" />
<Section
title="Layer 1 — private ramps"
description="A ramp is a contrast instrument, not an assignment. It gives values with known perceptual relationships, so a role encodes a distance rather than a colour — which is what stays true when the palette changes. Components never reference these."
>
<Section title="Layer 1 — private ramps">
<div className="flex flex-col gap-8">
{RAMPS.map((ramp) => (
<div key={ramp.id} className="flex flex-col gap-3">
<div className="flex flex-col gap-1">
<h3 className="text-label text-primary">{ramp.name}</h3>
<p className="max-w-2xl text-caption text-secondary">
{ramp.description}
</p>
</div>
<div
className={`grid gap-3 ${
@@ -88,18 +78,12 @@ export function ColourPage() {
</div>
</Section>
<Section
title="Layer 2 — public roles"
description="The only layer a screen may use. Every role points at a ramp step, so changing a theme is a change of values rather than a change of code."
>
<Section title="Layer 2 — public roles">
<div className="flex flex-col gap-8">
{ROLE_GROUPS.map((group) => (
<div key={group.id} className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<h3 className="text-label text-primary">{group.name}</h3>
<p className="max-w-2xl text-caption text-secondary">
{group.description}
</p>
</div>
<div className="mt-1 flex flex-col">
{group.roles.map((role) => (
@@ -111,10 +95,7 @@ export function ColourPage() {
</div>
</Section>
<Section
title="Deliberate exceptions"
description="Literal values exist only in the ramps, and nothing above a ramp holds one — except these. The list is short and complete on purpose: a vague exception policy is how a layered system quietly erodes."
>
<Section title="Deliberate exceptions">
{/* No swatch here to do the separating, so these entries keep a little
structure — the token name leads and the spacing groups it with its
reason. Still no card: this is prose, not data. */}
@@ -129,13 +110,6 @@ export function ColourPage() {
))}
</div>
</Section>
<Note>
Every dark value in this system is authored rather than observed — the
design exploration it was derived from is light-only. Toggle the mode in
the sidebar and treat anything that looks wrong as a finding, not a
given.
</Note>
</>
);
}

View File

@@ -42,10 +42,7 @@ export function ComponentPage({ entry }: { entry: CatalogEntry }) {
export function ComponentNotFound() {
return (
<>
<PageHeader
title="Component not found"
intro="This component page does not exist. Explore the catalog to find an example."
/>
<PageHeader title="Component not found" />
<Link to="/design/components" className="text-label text-accent">
Browse all components
</Link>

View File

@@ -26,10 +26,7 @@ export function ComponentsPage() {
);
return (
<>
<PageHeader
title="Components"
intro="A shared visual language, built in the open. Quiet surfaces, confident actions, and accessible behavior — all using the same Buzz roles."
/>
<PageHeader title="Components" />
<div className="bui-stack mb-10">
<Field.Root>
<Field.Label>Find a component</Field.Label>
@@ -77,9 +74,6 @@ export function ComponentsPage() {
<h2 id={`${entry.id}-title`} className="text-heading mt-1">
{entry.name}
</h2>
<p className="text-caption text-secondary mt-2">
{entry.description}
</p>
</div>
<div className="catalog-preview">
<entry.Preview />

View File

@@ -98,14 +98,8 @@ export function CompositionsPage() {
const [saved, setSaved] = useState(false);
return (
<>
<PageHeader
title="Built from the same pieces"
intro="Product screens and generated responses share one visual language. These examples compose the library directly, without a second set of styles or a desktop bridge."
/>
<Section
title="Project workspace"
description="Identity, progress, and focused actions. Color earns its place through status."
>
<PageHeader title="Built from the same pieces" />
<Section title="Project workspace">
<div className="grid gap-6 md:grid-cols-2">
<Card>
<div className="bui-inline">
@@ -151,10 +145,7 @@ export function CompositionsPage() {
</Card>
</div>
</Section>
<Section
title="Settings"
description="Labels, help, validation, and action emphasis form one consistent flow."
>
<Section title="Settings">
<Card>
<Tabs.Root defaultValue="profile">
<Tabs.List aria-label="Settings section">
@@ -202,10 +193,7 @@ export function CompositionsPage() {
</Tabs.Root>
</Card>
</Section>
<Section
title="Generated responses"
description="An agent supplies a versioned data snapshot. The host validates it and maps known actions to explicit callbacks."
>
<Section title="Generated responses">
<div className="bui-inline mb-6">
<Button
variant="outline"

View File

@@ -84,9 +84,6 @@ export function DesignSystemLayout() {
<Link to="/design" className="text-label text-primary">
Buzz Design System
</Link>
<p className="mt-1 text-caption text-tertiary">
Rendered from the tokens themselves
</p>
</div>
<div className="design-nav-sections flex min-h-0 max-h-80 flex-1 flex-col gap-6 overflow-y-auto lg:max-h-none lg:gap-7">

View File

@@ -1,14 +1,11 @@
import { ELEVATION } from "@/shared/tokens/registry";
import { Note, PageHeader, Section } from "./primitives";
import { PageHeader, Section } from "./primitives";
export function ElevationPage() {
return (
<>
<PageHeader
title="Elevation"
intro="Two values, taken from the design exploration unchanged. Depth comes from hairline borders and shadows at the threshold of perception rather than from many surface colours — if a shadow is obvious, it is too strong."
/>
<PageHeader title="Elevation" />
<Section title="The values">
<div className="flex flex-wrap gap-6 rounded-xl bg-app p-8">
@@ -27,14 +24,6 @@ export function ElevationPage() {
))}
</div>
</Section>
<Note>
Elevation is carried by shadow in light mode and by lightness in dark
mode. On a near-black background there is nothing darker for a shadow to
cast, so a floating surface becomes a step lighter instead — which is
why `bg-float` and `bg-panel` share a light value and diverge in dark.
Never reach for a stronger shadow to make something float in dark mode.
</Note>
</>
);
}

View File

@@ -1,7 +1,7 @@
import { Tabs } from "@buzz/ui";
import { BLUR, RAMPS } from "@/shared/tokens/registry";
import { Note, PageHeader, Section, Swatch } from "./primitives";
import { PageHeader, Section, Swatch } from "./primitives";
const GLASS = RAMPS.find((ramp) => ramp.id === "glass");
@@ -34,15 +34,9 @@ function ChromePill() {
export function GlassPage() {
return (
<>
<PageHeader
title="Glass"
intro="A translucent blurred surface is the same region in a different material, so it is named as that region plus its material rather than assembled per screen from a fill, a transparency, and a blur amount. Translucency and blur live inside the value."
/>
<PageHeader title="Glass" />
<Section
title="Live"
description="The chrome pill, rendered from the real tokens over the real backdrop. Select a tab to see the shared indicator slide; hover an unselected item to see the glass hover move one step up the ramp."
>
<Section title="Live">
<div className="texture-dots flex items-center justify-center rounded-xl bg-app px-8 py-12">
<ChromePill />
</div>
@@ -53,14 +47,13 @@ export function GlassPage() {
<code className="text-primary">blur-chrome</code>
</span>
<span>
selected <code className="text-primary">bg-chrome-selected</code> —
opaque where its container is glass
selected <code className="text-primary">bg-chrome-selected</code>
</span>
</div>
</Section>
{GLASS ? (
<Section title="The glass ramp" description={GLASS.description}>
<Section title="The glass ramp">
<div className="grid grid-cols-3 gap-3 rounded-xl bg-app p-4 sm:grid-cols-5">
{GLASS.steps.map((step) => (
<Swatch
@@ -75,10 +68,7 @@ export function GlassPage() {
</Section>
) : null}
<Section
title="Blur"
description="A separate axis from translucency: a glass role names one step on the glass ramp and one blur amount, so a quiet glass can still be heavily blurred."
>
<Section title="Blur">
<div className="flex flex-wrap gap-3 rounded-xl bg-app p-4">
{BLUR.map((blur) => (
<div
@@ -93,33 +83,13 @@ export function GlassPage() {
</div>
</Section>
<Section
title="The rim"
description="A deliberate exception: two literal values, not a ramp step and not one of the numbered gradients. Those are background treatments; this is a material detail. Real glass catches light along one edge and falls away on the opposite one, so the rim is a directional pair from one fixed light direction that every glass surface shares."
>
<Section title="The rim">
<div className="rounded-lg bg-inverse px-5 py-4">
<code className="whitespace-pre text-caption text-on-inverse">
{`box-shadow:\n inset 0 1px 0 var(--rim-lit),\n inset 0 -1px 0 var(--rim-shade);`}
</code>
</div>
<Note>
CSS borders accept only solid colours, so every gradient-border
technique is a workaround. `border-image` takes a gradient but ignores
`border-radius`, which is fatal on a pill. The two-background
`background-clip` trick respects radius but needs an opaque inner
fill, so it bleeds across a translucent surface. Two inset shadows
respect radius, cost nothing, and work over translucency. The known
limit is that each shadow is one solid colour, so a corner transitions
in two discrete edges rather than a smooth sweep.
</Note>
</Section>
<Note>
A glass hover changes opacity and never blur — re-blurring a large
surface every frame is expensive enough to feel. Glass surfaces use
their own `-glass-hover` rather than the shared `bg-hover`, which is a
neutral built to sit on an opaque surface.
</Note>
</>
);
}

View File

@@ -1,6 +1,6 @@
import { GROWTH_PROCEDURE } from "@/shared/tokens/registry";
import { Note, PageHeader, Section } from "./primitives";
import { PageHeader, Section } from "./primitives";
const AUDIT: Array<[string, string]> = [
[
@@ -39,15 +39,9 @@ const AUDIT: Array<[string, string]> = [
export function GrowthPage() {
return (
<>
<PageHeader
title="Growing the system"
intro="Need something the system doesn't have? Add it, mark it proposed, keep working. There is no gate and no separate mechanism for one-offs — the moment the legal path is slower than writing a raw value, the system starts being bypassed."
/>
<PageHeader title="Growing the system" />
<Section
title="The procedure"
description="Runs per change, by whoever needs the value. Nothing here needs permission, and every addition arrives in the same change that needed it, carrying its values, its description, and its owner."
>
<Section title="The procedure">
<ol className="flex flex-col gap-2 rounded-xl bg-inset px-5 py-4">
{GROWTH_PROCEDURE.map((step, index) => (
<li key={step} className="flex gap-3">
@@ -60,10 +54,7 @@ export function GrowthPage() {
</ol>
</Section>
<Section
title="The audit"
description="Runs on a schedule rather than per change. It reports rather than silently rewrites, except where the fix is unambiguous. Growth without pruning is how a system accumulates thirteen transparencies of one colour."
>
<Section title="The audit">
<div className="rounded-xl bg-inset px-5">
{AUDIT.map(([check, action]) => (
<div
@@ -78,12 +69,6 @@ export function GrowthPage() {
))}
</div>
</Section>
<Note>
Promotion from proposed to core is a metadata change, not a rename.
Renaming call sites would give promotion a migration cost, and anything
with a cost does not happen.
</Note>
</>
);
}

View File

@@ -8,11 +8,8 @@ export function MotionPage() {
if (!group) throw new Error("Missing foundation group");
return (
<>
<PageHeader title={group.name} intro={group.description} />
<Section
title="Try the feedback"
description="Press an action, or open a surface. The same shared durations and easing apply everywhere; your system’s reduced-motion preference removes movement."
>
<PageHeader title={group.name} />
<Section title="Try the feedback">
<ButtonsDemo />
<PopoverDemo />
</Section>

View File

@@ -31,10 +31,7 @@ const differences = [
export function OpenSourcePage() {
return (
<>
<PageHeader
title="Open by construction"
intro="BlockUI supplies the visual starting point. Buzz owns this implementation and can evolve it independently: public dependencies, local font assets, a portable stylesheet, and one documented component contract."
/>
<PageHeader title="Open by construction" />
<Section title="What changed">
<Table>
<caption className="bui-sr-only">
@@ -60,10 +57,8 @@ export function OpenSourcePage() {
</Section>
<Section title="The package boundary">
<p className="text-body text-secondary">
@buzz/design-tokens contains the role registry, light and dark values,
typography, geometry, and motion. @buzz/ui consumes those roles. This
catalog is a consumer of both packages; components do not import the
catalog, router, Tauri, or a private service.
@buzz/design-tokens defines the tokens; @buzz/ui provides the
components.
</p>
<pre className="text-code overflow-auto rounded-control bg-inset p-6">
<code>{`import '@fontsource-variable/inter/index.css';\nimport { Button, Field, Input } from '@buzz/ui';\n\n<Field.Root>\n <Field.Label>Project name</Field.Label>\n <Input required />\n</Field.Root>\n<Button>Continue</Button>`}</code>
@@ -71,11 +66,7 @@ export function OpenSourcePage() {
</Section>
<Section title="Run it anywhere">
<p className="text-body text-secondary">
Run the Vite catalog locally or build static files for a public host.
A host serving the catalog needs an SPA fallback to index.html for
nested routes. Storybook itself is open source and could be another
consumer later; it is not a runtime dependency or a hosting
requirement.
Static hosts need an index.html fallback for nested routes.
</p>
<pre className="text-code overflow-auto rounded-control bg-inset p-6">
<code>{`pnpm install --frozen-lockfile\npnpm --filter buzz-desktop-next dev\npnpm --filter buzz-desktop-next build\npnpm --filter buzz-desktop-next preview`}</code>
@@ -83,23 +74,15 @@ export function OpenSourcePage() {
</Section>
<Section title="What portability means">
<p className="text-body text-secondary">
This is a web and React foundation for desktop-next. It does not
replace Flutter widgets or migrate the existing desktop client. The
package is usable in the workspace and has a compiled distribution;
npm publication is a separate release step. Charts currently cover
categorical bars, and the carousel uses manual navigation. There is no
promise of API compatibility with shadcn or internal BlockUI
implementations.
React components for desktop-next, available as workspace packages and
compiled files.
</p>
</Section>
<Section title="Provenance and licenses">
<p className="text-body text-secondary">
No private implementation, font, icon, screenshot, or internal
documentation bundle is included. New Buzz code uses the repository
license. Inter and JetBrains Mono use the SIL Open Font License;
Lucide carries ISC and Feather-derived MIT notices; behavior
dependencies carry their own public licenses. Full notices live in
packages/ui/THIRD_PARTY_NOTICES.md.
Repository license for Buzz code; SIL OFL for fonts; ISC and MIT
notices for Lucide. See packages/ui/THIRD_PARTY_NOTICES.md for
dependency licenses.
</p>
<div className="bui-inline text-caption">
<a

View File

@@ -12,10 +12,7 @@ export function OverviewPage() {
<Badge>Buzz</Badge>
<Badge>Open-source design system</Badge>
</div>
<PageHeader
title="A shared language for building together."
intro="Quiet foundations. Expressive possibilities. A complete web component foundation for Buzz, with Inter, Lucide, and the same semantic roles from the smallest control to an agent’s response."
/>
<PageHeader title="A shared language for building together." />
<div className="bui-inline mb-12">
<Button
nativeButton={false}
@@ -55,10 +52,6 @@ export function OverviewPage() {
<Card>
<p className="text-caption text-secondary">Type with a purpose</p>
<p className="text-title">Make room for the next idea.</p>
<p className="text-body text-secondary">
Inter brings clarity to the interface. Every size, weight, and line
height belongs to a named role.
</p>
<code className="text-code text-tertiary">
text-title · text-body · text-caption
</code>
@@ -67,48 +60,23 @@ export function OverviewPage() {
</Link>
</Card>
</div>
<Section
title="One system, three layers"
description="A change to the foundation flows through every component and every composition."
>
<Section title="One system, three layers">
<div className="grid gap-8 md:grid-cols-3">
{[
[
"01",
"Foundations",
"A closed vocabulary for color, type, space, corners, depth, and motion.",
"/design/color",
],
[
"02",
"Components",
"Public behavior, Buzz styling, explicit variants, and accessible states.",
"/design/components",
],
[
"03",
"Compositions",
"Product patterns and validated generated responses made from the same parts.",
"/design/compositions",
],
].map(([number, title, description, to]) => (
["01", "Foundations", "/design/color"],
["02", "Components", "/design/components"],
["03", "Compositions", "/design/compositions"],
].map(([number, title, to]) => (
<div className="bui-stack" key={number}>
<p className="text-meta text-tertiary">{number}</p>
<h2 className="text-heading">
<Link to={to}>{title}</Link>
</h2>
<p className="text-body text-secondary">{description}</p>
</div>
))}
</div>
</Section>
<Section title="A visual starting point, room to evolve">
<p className="text-body text-secondary">
Inspired by BlockUI’s visual direction and implemented for Buzz’s
open-source architecture. Fonts and icons are public, assets are
self-hosted, and the documentation is part of the app. Switch modes to
inspect every example in light and dark.
</p>
<Link className="text-label text-accent" to="/design/open-source">
Read the adaptation guide →
</Link>

View File

@@ -6,7 +6,7 @@ export function RadiusPage() {
if (!group) throw new Error("Missing foundation group");
return (
<>
<PageHeader title={group.name} intro={group.description} />
<PageHeader title={group.name} />
<Section title="Corner roles">
<FoundationRoles name="Radius" />
</Section>

View File

@@ -6,14 +6,11 @@ export function SpacingPage() {
if (!group) throw new Error("Missing foundation group");
return (
<>
<PageHeader title={group.name} intro={group.description} />
<PageHeader title={group.name} />
<Section title="Spacing roles">
<FoundationRoles name="Spacing" />
</Section>
<Section
title="Controls and layers"
description="Shared dimensions, padding, and overlay stacking keep components in proportion."
>
<Section title="Controls and layers">
<FoundationRoles name="Controls" />
</Section>
</>

View File

@@ -5,7 +5,7 @@ import {
type TypeRole,
} from "@/shared/tokens/registry";
import { Note, PageHeader, Row, Rows, Section, Specimens } from "./primitives";
import { PageHeader, Row, Rows, Section, Specimens } from "./primitives";
/**
* Every specimen below is set in the role it documents, so the page is the
@@ -27,7 +27,6 @@ function RoleSpecimen({ role }: { role: TypeRole }) {
{role.size} / {role.lineHeight} / {role.tracking} / {role.weight}
</span>
</div>
<p className="max-w-xl text-caption text-secondary">{role.use}</p>
</div>
);
}
@@ -35,15 +34,9 @@ function RoleSpecimen({ role }: { role: TypeRole }) {
export function TypographyPage() {
return (
<>
<PageHeader
title="Typography"
intro="Nine sizes, ten roles, two faces. A role carries its whole setting — size, line height, letter spacing, weight — because those four are one decision rather than four, so text-body alone produces correctly set text and there is nothing left to get wrong."
/>
<PageHeader title="Typography" />
<Section
title="The faces"
description="Both already ship in every current Buzz client, so this is a decision to keep rather than to make. Inter is drawn for interface text at small sizes, which is most of this product."
>
<Section title="The faces">
<Specimens>
{TYPE_FAMILIES.map((family) => (
<div key={family.token} className="flex flex-col gap-1.5">
@@ -55,18 +48,12 @@ export function TypographyPage() {
{family.name}
</p>
<code className="text-code text-accent">{family.token}</code>
<p className="max-w-xl text-caption text-secondary">
{family.use}
</p>
</div>
))}
</Specimens>
</Section>
<Section
title="The roles"
description="Named for the job the text does, never for its size. text-title, not text-28 — a size name is a value in disguise and goes stale the moment the ramp moves. Each specimen is set in the role it documents."
>
<Section title="The roles">
<Specimens>
{TYPE_ROLES.map((role) => (
<RoleSpecimen key={role.token} role={role} />
@@ -75,7 +62,7 @@ export function TypographyPage() {
</Section>
{TYPE_RAMPS.map((ramp) => (
<Section key={ramp.id} title={ramp.name} description={ramp.description}>
<Section key={ramp.id} title={ramp.name}>
<Rows>
{ramp.steps.map((step) => (
<Row key={`${ramp.id}-${step.step}`}>
@@ -94,43 +81,20 @@ export function TypographyPage() {
</Section>
))}
<Section
title="Two rules"
description="Both are inherited rather than invented — the existing client learned each of them the expensive way."
>
<Section title="Two rules">
<Rows>
<Row>
<p className="text-label text-primary">
Every size is relative. Never px.
</p>
<p className="mt-1.5 max-w-xl text-caption text-secondary">
Fixed pixel text freezes against keyboard zoom and ignores the
person's font-size preference. The current client shipped a
message-timeline regression from exactly this and now has a CI
guard rejecting arbitrary size literals. This ramp derives
entirely from one virtual rem, so both dials work by construction.
</p>
</Row>
<Row>
<p className="text-label text-primary">
No all-caps, and no tracked-out labels.
</p>
<p className="mt-1.5 max-w-xl text-caption text-secondary">
A capitalised label is harder to read than the sentence-case
version and reads as enterprise chrome. Section labels earn their
quietness from size and colour — text-meta on text-tertiary —
rather than from being shouted. There is no uppercase utility in
this system.
</p>
</Row>
</Rows>
</Section>
<Note>
Inter Variable is bundled with the catalog, and JetBrains Mono covers
code. Fonts load from local assets. Every component uses these named
roles so type stays consistent as the system evolves.
</Note>
</>
);
}

View File

@@ -4,15 +4,12 @@ import {
VOCABULARY,
} from "@/shared/tokens/registry";
import { Note, PageHeader, Section } from "./primitives";
import { PageHeader, Section } from "./primitives";
export function VocabularyPage() {
return (
<>
<PageHeader
title="Vocabulary"
intro="One list. Every token in the system is built from these words. Combining them freely is routine and needs no permission; introducing a new word is the thing the audit reports on its own line, because a new word changes the shape of the system rather than adding to it."
/>
<PageHeader title="Vocabulary" />
<Section title="The words">
<div className="flex flex-col gap-0 rounded-xl bg-inset px-5 py-2">
@@ -39,10 +36,7 @@ export function VocabularyPage() {
</div>
</Section>
<Section
title="The grammar"
description="A name reads property, then role, then modifier, then material, then state. The order is fixed, so an agent writing a hover for a glass surface produces the same name every time instead of two equally plausible ones."
>
<Section title="The grammar">
<div className="rounded-lg bg-inverse px-5 py-4">
<code className="text-label text-on-inverse">{GRAMMAR}</code>
</div>
@@ -65,12 +59,6 @@ export function VocabularyPage() {
</div>
</div>
</Section>
<Note>
One modifier, one material, one state per name. A name that does not
parse under this grammar is reported by the audit with its correct
spelling.
</Note>
</>
);
}

View File

@@ -13,20 +13,17 @@ import type { ReactNode } from "react";
export function PageHeader({
title,
intro,
status,
}: {
title: string;
intro: string;
status?: string;
}) {
return (
<header className="mb-14 flex flex-col gap-4">
<header className="mb-8">
<div className="flex flex-wrap items-center gap-3">
<h1 className="text-title text-primary">{title}</h1>
{status ? <StatusPill>{status}</StatusPill> : null}
</div>
<p className="max-w-2xl text-body-lg text-secondary">{intro}</p>
</header>
);
}
@@ -41,52 +38,19 @@ export function StatusPill({ children }: { children: ReactNode }) {
export function Section({
title,
description,
children,
}: {
title: string;
description?: string;
children: ReactNode;
}) {
return (
<section className="mb-14 flex flex-col gap-5">
<div className="flex flex-col gap-2">
<h2 className="text-heading text-primary">{title}</h2>
{description ? (
<p className="max-w-2xl text-body text-secondary">{description}</p>
) : null}
</div>
<h2 className="text-heading text-primary">{title}</h2>
{children}
</section>
);
}
export function Note({ children }: { children: ReactNode }) {
return (
<p className="max-w-2xl rounded-xl bg-inset px-5 py-4 text-caption text-secondary">
{children}
</p>
);
}
export function Stub({ what, decide }: { what: string; decide: string[] }) {
return (
<div className="flex max-w-2xl flex-col gap-4 rounded-xl bg-inset px-6 py-5">
<p className="text-body text-secondary">{what}</p>
<div className="flex flex-col gap-2">
<p className="text-label text-tertiary">Still to decide</p>
<ul className="flex list-disc flex-col gap-1.5 pl-4">
{decide.map((item) => (
<li key={item} className="text-caption text-secondary">
{item}
</li>
))}
</ul>
</div>
</div>
);
}
/**
* A list of uniform rows — the tabular case, where every row looks alike and the
* eye needs a line to track along. Dividers, no container: the section heading

View File

@@ -39,7 +39,7 @@ test("density changes geometry throughout without changing type or losing a draf
await expect(page.locator(".catalog-grid")).toHaveCSS("row-gap", "32px");
await expect(page.locator("main header").first()).toHaveCSS(
"margin-bottom",
"42px",
"24px",
);
await expect(draft).toHaveCSS("min-height", "64px");