mirror of
https://github.com/block/buzz.git
synced 2026-11-13 21:01:53 +00:00
fix(design-system): remove secondary page descriptions
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user