site/views.tsx
141 lines and 17 functions in DrDustinEdwards/capsomer, at commit d3966cc on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| ScaleTable | 8 to 35 | 3 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| show | 10 to 10 | 6 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:23> | 23 to 30 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| TypeView | 37 to 71 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:45> | 45 to 45 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:56> | 56 to 56 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:63> | 63 to 63 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| MotionView | 73 to 98 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:81> | 81 to 81 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:81> | 81 to 81 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:83> | 83 to 90 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| TestsView | 100 to 123 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:102> | 102 to 102 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:115> | 115 to 119 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:116> | 116 to 116 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| MarkdownView | 125 to 131 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| SettingsView | 133 to 148 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
Imports
It imports site/data.ts, site/markdown.ts, site/parts.tsx. Imported by site/main.tsx.
Source
// The site's type, motion, tests, defaults, changes and settings views (main.tsx).
import { useState, type ReactNode } from "react";
import { COMPONENTS, SCALES } from "./data.ts";
import { renderMarkdown } from "./markdown.ts";
import { Panel, TestList, Verdict, paint, useResults } from "./parts.tsx";
function ScaleTable({ group, render }: { group: string; render?: (name: string, value: unknown) => ReactNode }) {
const rows = Object.entries(SCALES[group] ?? {});
const show = (v: unknown) => (typeof v === "object" && v && "value" in (v as object) ? `${(v as { value: number }).value}${(v as { unit?: string }).unit ?? ""}` : Array.isArray(v) ? `cubic-bezier(${v.join(", ")})` : String(v));
return (
<div className="cap-table-wrap" role="region" tabIndex={0} aria-label={`${group} tokens`}>
<table className="cap-table">
<thead>
<tr>
<th scope="col">Token</th>
<th scope="col">Value</th>
{render ? <th scope="col">Sample</th> : null}
<th scope="col">For</th>
</tr>
</thead>
<tbody>
{rows.map(([name, t]) => (
<tr key={name}>
<td className="cap-mono">--{name}</td>
<td className="cap-mono">{show(t.$value)}</td>
{render ? <td>{render(name, t.$value)}</td> : null}
<td className="cap-muted">{t.$description}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export function TypeView() {
return (
<div className="site-page">
<div className="site-head">
<h1>Type and space</h1>
<p>Schibsted Grotesk for app chrome, Martian Mono for identifiers, Source Serif 4 for long-form text. App body text is 13 px; a reading page's body is 17 to 19 px with a 66-character measure.</p>
</div>
<Panel title="Type" id="type">
<ScaleTable group="type" render={(name) => (name.startsWith("fs-") ? <span className="site-type-sample" ref={paint("font-size", `var(--${name})`)}>Phage λ and φX174</span> : null)} />
</Panel>
<Panel title="Reading context" id="prose">
<div data-context="prose" className="site-prose-sample">
<h2>How a temperate phage decides</h2>
<p>
A temperate phage such as λ does not always kill the cell it enters. In <i>Escherichia coli</i> the outcome turns on two proteins, CI and Cro, each of which shuts off the gene for the other. Small lytic phages such as φX174 have no such switch. Sample text.
</p>
</div>
</Panel>
<Panel title="Space" id="space">
<ScaleTable group="space" render={(name) => <i className="site-space" ref={paint("--w", `var(--${name})`)} />} />
</Panel>
<Panel title="Density" id="density" src="data-density on the root or any element; compact is the default">
<ScaleTable group="density-compact" />
<ScaleTable group="density-comfortable" />
</Panel>
<Panel title="Radius, control size, layers, focus, layout" id="other">
<ScaleTable group="radius" render={(name) => <i className="site-radius" ref={paint("border-radius", `var(--${name})`)} />} />
<ScaleTable group="control" />
<ScaleTable group="layer" />
<ScaleTable group="focus" />
<ScaleTable group="layout" />
</Panel>
</div>
);
}
export function MotionView() {
const [played, setPlayed] = useState(false);
return (
<div className="site-page">
<div className="site-head">
<h1>Motion</h1>
<p>Motion explains a change. Three durations and three easings: things arrive on the long ease-out and leave faster than they came, a press springs back, and pages and themes crossfade. With reduced motion on, the dots below jump instead of travelling, and nothing in Capsomer moves at all.</p>
</div>
<Panel title="Durations" id="durations" src={<button type="button" className="cap-btn" onClick={() => setPlayed((p) => !p)} aria-pressed={played}>Play</button>}>
<div className="site-motion" data-played={played ? "" : undefined}>
{["dur-fast", "dur-base", "dur-slow"].map((d) => (
<div key={d}>
<span className="cap-mono">--{d}</span>
<div className="site-track" ref={paint("--d", `var(--${d})`)}>
<i />
</div>
</div>
))}
</div>
</Panel>
<Panel title="Every motion token" id="motion">
<ScaleTable group="motion" />
</Panel>
</div>
);
}
export function TestsView() {
const RESULTS = useResults();
const entries = Object.entries(RESULTS.components).sort(([, a], [, b]) => b.keyboard.failed + b.accessibility.failed + b.behaviour.failed - (a.keyboard.failed + a.accessibility.failed + a.behaviour.failed));
return (
<div className="site-page">
<div className="site-head">
<h1>Tests</h1>
<p>Every component's keyboard, accessibility and behavior tests from the last run, in both themes, failures first. Accessibility and keyboard failures block a merge; nothing about a color or a layout does.</p>
</div>
{!RESULTS.available ? (
<div className="cap-empty" data-kind="nothing-yet">
<b>No test run is recorded in this build</b>
<span className="cap-muted">CI runs the tests on every push to main and builds this page from the results.</span>
</div>
) : (
entries.map(([name, r]) => (
<Panel key={name} title={COMPONENTS.find((c) => c.name === name)?.title ?? name} id={`t-${name}`} src={<Verdict passed={r.keyboard.passed + r.accessibility.passed + r.behaviour.passed} failed={r.keyboard.failed + r.accessibility.failed + r.behaviour.failed} />}>
<TestList rows={r.tests} />
</Panel>
))
)}
</div>
);
}
export function MarkdownView({ title, md }: { title: string; md: string }) {
return (
<div className="site-page">
<article className="site-doc" dangerouslySetInnerHTML={{ __html: renderMarkdown(md || `# ${title}\n\nNothing yet.`, 0) }} />
</div>
);
}
export function SettingsView() {
return (
<div className="site-page">
<div className="site-head">
<h1>Settings</h1>
<p>Preferences for this browser only. Nothing here is sent anywhere.</p>
</div>
<Panel title="Theme" id="theme">
<p>The Light and Dark switch at the right of the top bar changes the theme. The first visit follows your device; your first choice is remembered in this browser.</p>
</Panel>
<Panel title="Menu" id="rail">
<p>The button at the top of the left strip collapses the menu to icons, and the [ key does the same. This browser remembers the choice.</p>
</Panel>
</div>
);
}