trails/apps/planner/app/routes/dev.ui.tsx
Ullrich Schäfer 1bd5f18a33
feat(ui): topbar primitives — IconButton, SegmentedControl, Avatar
Adds the shared primitives the planner topbar redesign needs, on the
design-system tokens:

- IconButton — icon-only button (undo/redo), ghost/secondary variants,
  requires an accessible label.
- SegmentedControl — generic single-select toggle (color mode
  Plain/Elevation/Surface), radiogroup semantics.
- Avatar — colored initial circle for participants, per-user color with
  an accent fallback.

Co-located jsdom unit tests for each (roles, aria-checked, onChange,
disabled, color fallback). Extends the dev-only /dev/ui gallery with
sections for all three.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 23:43:14 +02:00

142 lines
4.5 KiB
TypeScript

import { useState, type ReactNode } from "react";
import {
Avatar,
Badge,
Button,
Card,
IconButton,
Input,
SegmentedControl,
} from "@trails-cool/ui";
const UndoIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="1 4 1 10 7 10" />
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" />
</svg>
);
const RedoIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="23 4 23 10 17 10" />
<path d="M20.49 15a9 9 0 1 1-2.13-9.36L23 10" />
</svg>
);
const COLOR_MODES = [
{ value: "plain", label: "Plain" },
{ value: "elevation", label: "Elevation" },
{ value: "surface", label: "Surface" },
] as const;
/**
* Dev-only gallery for the shared UI primitives. Renders every primitive in
* its variants/states on one page so components can be built and eyeballed in
* isolation — the lightweight alternative to Storybook. Registered only in
* non-production builds (see routes.ts).
*/
function Section({ title, children }: { title: string; children: ReactNode }) {
return (
<section className="space-y-3">
<h2 className="font-mono text-xs uppercase tracking-wider text-text-lo">
{title}
</h2>
<div className="flex flex-wrap items-center gap-3">{children}</div>
</section>
);
}
export default function DevUi() {
const [colorMode, setColorMode] =
useState<(typeof COLOR_MODES)[number]["value"]>("elevation");
return (
<main className="mx-auto max-w-3xl space-y-10 p-10">
<header className="space-y-1">
<h1 className="text-2xl font-semibold text-text-hi">UI primitives</h1>
<p className="text-sm text-text-md">
Shared <code className="font-mono">@trails-cool/ui</code> components on
the design-system tokens.
</p>
</header>
<Section title="Button · variants">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="primary" disabled>
Disabled
</Button>
</Section>
<Section title="Button · sizes">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
</Section>
<Section title="Badge · tones">
<Badge>Neutral</Badge>
<Badge tone="accent">Elevation</Badge>
<Badge tone="stop">NIGHT 2</Badge>
</Section>
<Section title="Input">
<div className="w-72">
<Input placeholder="Route name…" />
</div>
<div className="w-72">
<Input value="Alpenüberquerung" readOnly />
</div>
</Section>
<Section title="IconButton">
<IconButton label="Undo">
<UndoIcon />
</IconButton>
<IconButton label="Redo">
<RedoIcon />
</IconButton>
<IconButton label="Undo (disabled)" disabled>
<UndoIcon />
</IconButton>
<IconButton label="Undo" variant="secondary">
<UndoIcon />
</IconButton>
</Section>
<Section title="SegmentedControl · color mode">
<SegmentedControl
options={COLOR_MODES}
value={colorMode}
onChange={setColorMode}
label="Color mode"
/>
<span className="text-sm text-text-md">
selected: <code className="font-mono">{colorMode}</code>
</span>
</Section>
<Section title="Avatar · participants">
<Avatar name="ulle" color="#4A6B40" />
<Avatar name="Mara" color="#8B6D3A" />
<Avatar name="Sam" color="#C46040" />
<Avatar name="ulle" color="#4A6B40" size="sm" />
<span className="inline-flex items-center gap-1.5 text-sm text-text-hi">
<Avatar name="ulle" color="#4A6B40" size="sm" />
ulle
<Badge tone="accent">Host</Badge>
</span>
</Section>
<Section title="Card">
<Card className="w-64">
<h3 className="font-medium text-text-hi">Subtle card</h3>
<p className="mt-1 text-sm text-text-md">On a subtle surface tone.</p>
</Card>
<Card raised className="w-64">
<h3 className="font-medium text-text-hi">Raised card</h3>
<p className="mt-1 text-sm text-text-md">Elevated with a soft shadow.</p>
</Card>
</Section>
</main>
);
}