@designtools/docs 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/a11y-panel.d.ts +58 -0
- package/dist/a11y-panel.js +75 -0
- package/dist/adherence-summary.d.ts +9 -0
- package/dist/adherence-summary.js +27 -0
- package/dist/agent-view.d.ts +19 -0
- package/dist/agent-view.js +23 -0
- package/dist/anatomy.d.ts +12 -0
- package/dist/anatomy.js +38 -0
- package/dist/ask-claude.d.ts +14 -0
- package/dist/ask-claude.js +40 -0
- package/dist/cli.js +322 -0
- package/dist/code-view.d.ts +17 -0
- package/dist/code-view.js +42 -0
- package/dist/examples.d.ts +25 -0
- package/dist/examples.js +48 -0
- package/dist/glossary.d.ts +5 -0
- package/dist/glossary.js +14 -0
- package/dist/imagery.d.ts +6 -0
- package/dist/imagery.js +17 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +33 -0
- package/dist/lib/adherence-types.d.ts +168 -0
- package/dist/lib/adherence-types.js +13 -0
- package/dist/lib/boundary.d.ts +16 -0
- package/dist/lib/boundary.js +15 -0
- package/dist/lib/contrast.d.ts +8 -0
- package/dist/lib/contrast.js +42 -0
- package/dist/lib/cx.d.ts +24 -0
- package/dist/lib/cx.js +26 -0
- package/dist/lib/jsx.d.ts +6 -0
- package/dist/lib/jsx.js +29 -0
- package/dist/lib/lookup.d.ts +35 -0
- package/dist/lib/lookup.js +41 -0
- package/dist/lib/manifest-types.d.ts +253 -0
- package/dist/lib/manifest-types.js +14 -0
- package/dist/lib/manifest.d.ts +98 -0
- package/dist/lib/manifest.js +173 -0
- package/dist/lib/markdown.d.ts +50 -0
- package/dist/lib/markdown.js +286 -0
- package/dist/lib/measure.d.ts +12 -0
- package/dist/lib/measure.js +62 -0
- package/dist/lib/pseudo.d.ts +11 -0
- package/dist/lib/pseudo.js +71 -0
- package/dist/lib/registry.d.ts +21 -0
- package/dist/lib/registry.js +11 -0
- package/dist/lib/search.d.ts +44 -0
- package/dist/lib/search.js +62 -0
- package/dist/lib/standards.d.ts +17 -0
- package/dist/lib/standards.js +81 -0
- package/dist/lib/status.d.ts +7 -0
- package/dist/lib/status.js +17 -0
- package/dist/lib/text.d.ts +8 -0
- package/dist/lib/text.js +9 -0
- package/dist/lib/ui.d.ts +100 -0
- package/dist/lib/ui.js +89 -0
- package/dist/lib/vision.d.ts +18 -0
- package/dist/lib/vision.js +27 -0
- package/dist/logo-usage.d.ts +14 -0
- package/dist/logo-usage.js +45 -0
- package/dist/next/config.d.ts +85 -0
- package/dist/next/config.js +1 -0
- package/dist/next/index.d.ts +62 -0
- package/dist/next/index.js +63 -0
- package/dist/next/markdown.d.ts +20 -0
- package/dist/next/markdown.js +95 -0
- package/dist/next/pages.d.ts +6 -0
- package/dist/next/pages.js +122 -0
- package/dist/next/prose.d.ts +9 -0
- package/dist/next/prose.js +37 -0
- package/dist/next/proxy.d.ts +19 -0
- package/dist/next/proxy.js +56 -0
- package/dist/next/site.d.ts +53 -0
- package/dist/next/site.js +136 -0
- package/dist/page-actions.d.ts +9 -0
- package/dist/page-actions.js +32 -0
- package/dist/pattern.d.ts +8 -0
- package/dist/pattern.js +19 -0
- package/dist/playground.d.ts +62 -0
- package/dist/playground.js +111 -0
- package/dist/preview-frame.d.ts +31 -0
- package/dist/preview-frame.js +63 -0
- package/dist/props-table.d.ts +10 -0
- package/dist/props-table.js +45 -0
- package/dist/rule.d.ts +12 -0
- package/dist/rule.js +32 -0
- package/dist/scale.d.ts +9 -0
- package/dist/scale.js +58 -0
- package/dist/search.d.ts +16 -0
- package/dist/search.js +46 -0
- package/dist/shell.d.ts +89 -0
- package/dist/shell.js +199 -0
- package/dist/standards.d.ts +13 -0
- package/dist/standards.js +19 -0
- package/dist/styles.css +3519 -0
- package/dist/swatches.d.ts +28 -0
- package/dist/swatches.js +94 -0
- package/dist/theme-switch.d.ts +20 -0
- package/dist/theme-switch.js +68 -0
- package/dist/type-ramp.d.ts +10 -0
- package/dist/type-ramp.js +31 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +55 -0
- package/dist/variant-matrix.d.ts +15 -0
- package/dist/variant-matrix.js +53 -0
- package/dist/voice-terms.d.ts +5 -0
- package/dist/voice-terms.js +13 -0
- package/dist/workbench.d.ts +33 -0
- package/dist/workbench.js +273 -0
- package/package.json +107 -3
- package/src/a11y-panel.tsx +297 -0
- package/src/adherence-summary.tsx +134 -0
- package/src/agent-view.tsx +54 -0
- package/src/anatomy.tsx +114 -0
- package/src/ask-claude.tsx +90 -0
- package/src/code-view.tsx +112 -0
- package/src/css.d.ts +1 -0
- package/src/examples.tsx +141 -0
- package/src/glossary.tsx +71 -0
- package/src/imagery.tsx +64 -0
- package/src/index.ts +34 -0
- package/src/lib/adherence-types.ts +181 -0
- package/src/lib/boundary.tsx +21 -0
- package/src/lib/contrast.ts +44 -0
- package/src/lib/cx.ts +30 -0
- package/src/lib/jsx.ts +29 -0
- package/src/lib/lookup.tsx +49 -0
- package/src/lib/manifest-types.ts +284 -0
- package/src/lib/manifest.ts +228 -0
- package/src/lib/markdown.ts +288 -0
- package/src/lib/measure.tsx +101 -0
- package/src/lib/pseudo.ts +74 -0
- package/src/lib/registry.tsx +29 -0
- package/src/lib/search.ts +99 -0
- package/src/lib/standards.ts +82 -0
- package/src/lib/status.tsx +32 -0
- package/src/lib/text.tsx +27 -0
- package/src/lib/ui.tsx +291 -0
- package/src/lib/vision.tsx +44 -0
- package/src/logo-usage.tsx +140 -0
- package/src/next/config.ts +80 -0
- package/src/next/index.tsx +100 -0
- package/src/next/markdown.ts +119 -0
- package/src/next/pages.tsx +370 -0
- package/src/next/prose.tsx +87 -0
- package/src/next/proxy.ts +55 -0
- package/src/next/site.ts +173 -0
- package/src/page-actions.tsx +92 -0
- package/src/pattern.tsx +67 -0
- package/src/playground.tsx +352 -0
- package/src/preview-frame.tsx +131 -0
- package/src/props-table.tsx +154 -0
- package/src/rule.tsx +116 -0
- package/src/scale.tsx +175 -0
- package/src/search.tsx +155 -0
- package/src/shell.tsx +555 -0
- package/src/standards.tsx +58 -0
- package/src/styles.css +155 -0
- package/src/swatches.tsx +287 -0
- package/src/theme-switch.tsx +90 -0
- package/src/type-ramp.tsx +150 -0
- package/src/usage.tsx +148 -0
- package/src/variant-matrix.tsx +195 -0
- package/src/voice-terms.tsx +46 -0
- package/src/workbench.tsx +584 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Mode } from "./lib/manifest.js";
|
|
2
|
+
import type { TokensManifest } from "./lib/manifest-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Colour, painted from the tokens rather than restated. Each colour with a
|
|
5
|
+
* subdued pair is a card: its fill, subdued, hover and press, in light and dark
|
|
6
|
+
* side by side. Then the surfaces, then the utility colours. Every sample sits
|
|
7
|
+
* inside its own `data-theme`, so dark is shown as the dark tokens resolve, and
|
|
8
|
+
* every pair's contrast is measured on the colours the browser actually paints.
|
|
9
|
+
*/
|
|
10
|
+
export declare function SemanticSwatches({ tokens, modes, }: {
|
|
11
|
+
tokens: TokensManifest;
|
|
12
|
+
modes?: Mode[];
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
/**
|
|
15
|
+
* Each colour on each background, per mode: a label is measured on its own fill, and this is
|
|
16
|
+
* everything else, red text on the page, a brand fill on a dark section. Backgrounds are the
|
|
17
|
+
* canvas, surface and overlay and every named `--surface-<name>`; colours are each semantic fill
|
|
18
|
+
* and muted text. T: fine as text (4.5:1); S: fine as a shape only (3:1); none: neither.
|
|
19
|
+
* Measured on what the browser paints, inside each mode's `data-theme`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function PageContrast({ tokens, modes }: {
|
|
22
|
+
tokens: TokensManifest;
|
|
23
|
+
modes?: Mode[];
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
/** The primitive ramps, one row per colour family, each step painted from its own token. */
|
|
26
|
+
export declare function RampSwatches({ tokens }: {
|
|
27
|
+
tokens: TokensManifest;
|
|
28
|
+
}): import("react").JSX.Element;
|
package/dist/swatches.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { contrastRatio, wcagGrade } from "./lib/contrast.js";
|
|
5
|
+
import { cx } from "./lib/cx.js";
|
|
6
|
+
import { ramps, semanticColours, valueIn } from "./lib/manifest.js";
|
|
7
|
+
/**
|
|
8
|
+
* Colour, painted from the tokens rather than restated. Each colour with a
|
|
9
|
+
* subdued pair is a card: its fill, subdued, hover and press, in light and dark
|
|
10
|
+
* side by side. Then the surfaces, then the utility colours. Every sample sits
|
|
11
|
+
* inside its own `data-theme`, so dark is shown as the dark tokens resolve, and
|
|
12
|
+
* every pair's contrast is measured on the colours the browser actually paints.
|
|
13
|
+
*/
|
|
14
|
+
export function SemanticSwatches({ tokens, modes = ["light", "dark"], }) {
|
|
15
|
+
const colours = semanticColours(tokens);
|
|
16
|
+
const byName = new Map(colours.map((c) => [c.name, c]));
|
|
17
|
+
const families = colours.filter((c) => byName.has(`${c.name}-subdued`)).map((c) => c.name);
|
|
18
|
+
const inFamily = new Set(families.flatMap((f) => [f, `${f}-subdued`, `${f}-hover`, `${f}-active`]));
|
|
19
|
+
const surfaces = colours.filter((c) => !inFamily.has(c.name) && c.foreground).map((c) => c.name);
|
|
20
|
+
const utility = colours.filter((c) => !inFamily.has(c.name) && !c.foreground).map((c) => c.name);
|
|
21
|
+
const step = (name, mode) => {
|
|
22
|
+
const token = byName.get(name)?.background;
|
|
23
|
+
return token && pointsAt(valueIn(token, mode));
|
|
24
|
+
};
|
|
25
|
+
return (_jsxs("div", { "data-slot": "swatches", className: "flex flex-col gap-10", children: [_jsx("div", { className: "grid gap-4 xl:grid-cols-2", children: families.map((f) => (_jsxs("article", { "data-slot": "swatch-family", className: "flex flex-col overflow-hidden rounded-xl border border-border bg-surface text-surface-foreground", children: [_jsxs("header", { className: "flex items-baseline justify-between gap-3 px-4 py-3", children: [_jsx("h3", { className: "text-lg capitalize", children: f.slice(2).replace(/-/g, " ") }), _jsx("code", { className: "font-mono text-xs text-muted-foreground", children: f })] }), _jsx("div", { className: cx("grid border-t border-border", modes.length > 1 && "sm:grid-cols-2"), children: modes.map((mode) => (_jsxs("div", { "data-theme": mode, className: "flex flex-col gap-2 bg-canvas p-3 text-canvas-foreground not-first:border-t not-first:border-border sm:not-first:border-t-0 sm:not-first:border-l", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground capitalize", children: mode }), _jsx(Chip, { name: f, label: "fill", step: step(f, mode), size: "lg" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(Chip, { name: `${f}-subdued`, label: "subdued", step: step(`${f}-subdued`, mode) }), byName.has(`${f}-hover`) && _jsx(Chip, { name: `${f}-hover`, foreground: `${f}-foreground`, label: "hover" }), byName.has(`${f}-active`) && _jsx(Chip, { name: `${f}-active`, foreground: `${f}-foreground`, label: "press" })] })] }, mode))) })] }, f))) }), surfaces.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("h3", { className: "text-2xl tracking-tight", children: "Surfaces" }), _jsx("div", { className: cx("grid gap-4", modes.length > 1 && "lg:grid-cols-2"), children: modes.map((mode) => (_jsxs("div", { "data-theme": mode, className: "flex flex-col gap-2 rounded-xl border border-border bg-canvas p-3 text-canvas-foreground", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground capitalize", children: mode }), _jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3", children: surfaces.map((name) => (_jsx(Chip, { name: name, label: name.slice(2), step: step(name, mode), outlined: true }, name))) })] }, mode))) })] })), utility.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("h3", { className: "text-2xl tracking-tight", children: "Lines, focus and shadow" }), _jsx("div", { className: cx("grid gap-4", modes.length > 1 && "lg:grid-cols-2"), children: modes.map((mode) => (_jsxs("div", { "data-theme": mode, className: "flex flex-col gap-2 rounded-xl border border-border bg-canvas p-3 text-canvas-foreground", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground capitalize", children: mode }), _jsx("ul", { className: "grid grid-cols-2 gap-2", children: utility.map((name) => (_jsxs("li", { "data-slot": "swatch", className: "flex items-center gap-2 rounded-lg bg-surface p-2 text-surface-foreground", children: [_jsx("span", { "aria-hidden": true, className: "size-8 shrink-0 rounded-md border border-border", style: { background: `var(${name})` } }), _jsxs("span", { className: "flex min-w-0 flex-col font-mono text-xs", children: [_jsx("span", { className: "truncate font-medium", children: name.slice(2) }), _jsx("span", { className: "truncate text-muted-foreground", children: step(name, mode) })] })] }, name))) })] }, mode))) })] }))] }));
|
|
26
|
+
}
|
|
27
|
+
/** `var(--color-primary-500)` → `primary-500`: the step it points at, which is what a person compares. */
|
|
28
|
+
function pointsAt(value) {
|
|
29
|
+
const m = value && /^var\(--color-([\w-]+)\)$/.exec(value);
|
|
30
|
+
if (m)
|
|
31
|
+
return m[1];
|
|
32
|
+
return value?.startsWith("oklch(") ? "generated" : value;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One background with its foreground on it, labelled in that foreground, with the
|
|
36
|
+
* contrast measured on what the browser paints. It reads its mode from the nearest
|
|
37
|
+
* `data-theme` around it.
|
|
38
|
+
*/
|
|
39
|
+
function Chip({ name, foreground = `${name}-foreground`, label, step, size = "md", outlined, }) {
|
|
40
|
+
const chip = useRef(null);
|
|
41
|
+
const [ratio, setRatio] = useState(null);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const node = chip.current;
|
|
44
|
+
if (!node)
|
|
45
|
+
return;
|
|
46
|
+
const style = getComputedStyle(node);
|
|
47
|
+
setRatio(contrastRatio(style.color, style.backgroundColor));
|
|
48
|
+
}, [name, foreground]);
|
|
49
|
+
const grade = ratio === null ? null : wcagGrade(ratio);
|
|
50
|
+
const fails = grade === "Fail";
|
|
51
|
+
const measured = grade && ratio !== null ? `${ratio.toFixed(1)}:1 ${grade}` : undefined;
|
|
52
|
+
return (_jsxs("div", { ref: chip, "data-slot": "swatch", title: `${name} with ${foreground}${step ? ` · ${step}` : ""}${measured ? ` · ${measured}` : ""}`, className: cx("flex min-w-0 flex-col justify-between gap-3 rounded-lg p-3", size === "lg" ? "min-h-28" : "min-h-20", outlined && "border border-border"), style: { background: `var(${name})`, color: `var(${foreground})` }, children: [size === "lg" ? (_jsxs("span", { className: "flex items-start justify-between gap-2", children: [_jsx("span", { className: "text-3xl leading-none tracking-tight", children: "Aa" }), measured && (_jsx("span", { "data-slot": "swatch-contrast", className: cx("rounded-full px-2 py-0.5 font-mono text-xs whitespace-nowrap", fails ? "bg-destructive font-medium text-destructive-foreground" : "ring-1 ring-current/30"), children: measured }))] })) : (_jsx("span", { className: "text-base leading-none font-medium", children: "Aa" })), _jsxs("span", { className: "flex min-w-0 flex-col font-mono text-xs leading-snug", children: [_jsx("span", { className: "truncate font-medium", children: label }), size === "lg" ? (step && _jsx("span", { className: "truncate opacity-75", children: step })) : (measured && (_jsx("span", { "data-slot": "swatch-contrast", className: cx("truncate", fails ? "w-fit rounded-sm bg-destructive px-1 text-destructive-foreground" : "opacity-75"), children: measured })))] })] }));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Each colour on each background, per mode: a label is measured on its own fill, and this is
|
|
56
|
+
* everything else, red text on the page, a brand fill on a dark section. Backgrounds are the
|
|
57
|
+
* canvas, surface and overlay and every named `--surface-<name>`; colours are each semantic fill
|
|
58
|
+
* and muted text. T: fine as text (4.5:1); S: fine as a shape only (3:1); none: neither.
|
|
59
|
+
* Measured on what the browser paints, inside each mode's `data-theme`.
|
|
60
|
+
*/
|
|
61
|
+
export function PageContrast({ tokens, modes = ["light", "dark"] }) {
|
|
62
|
+
const names = new Set(semanticColours(tokens).map((c) => c.name));
|
|
63
|
+
const colours = [...names].filter((n) => names.has(`${n}-subdued`)).concat(names.has("--muted") ? ["--muted-foreground"] : []);
|
|
64
|
+
const grounds = ["--canvas", "--surface", "--overlay", ...[...names].filter((n) => n.startsWith("--surface-") && !n.endsWith("-foreground") && n !== "--surface-foreground")];
|
|
65
|
+
return (_jsx("div", { "data-slot": "page-contrast", className: "flex flex-col gap-6", children: modes.map((mode) => (_jsx(PageGrid, { mode: mode, colours: colours, grounds: grounds }, mode))) }));
|
|
66
|
+
}
|
|
67
|
+
function PageGrid({ mode, colours, grounds }) {
|
|
68
|
+
const probe = useRef(null);
|
|
69
|
+
const [ratios, setRatios] = useState({});
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const node = probe.current;
|
|
72
|
+
if (!node)
|
|
73
|
+
return;
|
|
74
|
+
const paint = (v) => {
|
|
75
|
+
node.style.color = `var(${v})`;
|
|
76
|
+
return getComputedStyle(node).color;
|
|
77
|
+
};
|
|
78
|
+
const painted = Object.fromEntries([...colours, ...grounds].map((v) => [v, paint(v)]));
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const c of colours)
|
|
81
|
+
for (const g of grounds)
|
|
82
|
+
out[`${c}|${g}`] = contrastRatio(painted[c], painted[g]);
|
|
83
|
+
setRatios(out);
|
|
84
|
+
}, [colours, grounds]);
|
|
85
|
+
return (_jsxs("div", { className: "overflow-x-auto rounded-2xl border border-border bg-surface text-surface-foreground", tabIndex: 0, role: "region", "aria-label": `Colours on each background, ${mode} mode`, children: [_jsx("div", { ref: probe, "data-theme": mode, hidden: true }), _jsxs("table", { className: "w-full min-w-[32rem] border-collapse text-left text-sm", children: [_jsx("caption", { className: "border-b border-border px-4 py-3 text-left text-sm font-semibold capitalize", children: mode }), _jsx("thead", { children: _jsxs("tr", { className: "border-b border-border bg-muted/50 text-xs text-muted-foreground", children: [_jsx("th", { scope: "col", className: "px-4 py-2.5 font-medium", children: "Colour" }), grounds.map((g) => (_jsx("th", { scope: "col", className: "px-4 py-2.5 font-mono font-medium", children: g.slice(2) }, g)))] }) }), _jsx("tbody", { children: colours.map((c) => (_jsxs("tr", { className: "border-b border-border last:border-b-0", children: [_jsx("th", { scope: "row", className: "px-4 py-2.5 font-mono text-xs font-medium", children: c.slice(2) }), grounds.map((g) => {
|
|
86
|
+
const r = ratios[`${c}|${g}`];
|
|
87
|
+
const mark = r == null ? "" : r >= 4.5 ? "text" : r >= 3 ? "shape" : "none";
|
|
88
|
+
return (_jsx("td", { className: "px-4 py-2.5", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { "data-theme": mode, className: "flex h-9 w-12 items-center justify-center rounded-lg border border-(--border) text-base font-bold", style: { background: `var(${g})`, color: `var(${c})` }, "aria-hidden": true, children: "Aa" }), r != null && (_jsxs("span", { className: cx("inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-mono text-xs", mark === "text" && "bg-success-subdued text-success-subdued-foreground", mark === "shape" && "bg-muted text-muted-foreground", mark === "none" && "bg-destructive-subdued text-destructive-subdued-foreground"), children: [r.toFixed(1), " ", mark] }))] }) }, g));
|
|
89
|
+
})] }, c))) })] })] }));
|
|
90
|
+
}
|
|
91
|
+
/** The primitive ramps, one row per colour family, each step painted from its own token. */
|
|
92
|
+
export function RampSwatches({ tokens }) {
|
|
93
|
+
return (_jsx("div", { "data-slot": "ramps", className: "flex flex-col gap-5 overflow-x-auto", tabIndex: 0, role: "region", "aria-label": "Ramps", children: ramps(tokens).map((ramp) => (_jsxs("div", { "data-slot": "ramp", className: "grid min-w-[36rem] grid-cols-[6rem_1fr] items-center gap-4", children: [_jsx("p", { className: "font-mono text-xs font-medium capitalize", children: ramp.family }), _jsx("ol", { className: "grid grid-flow-col auto-cols-fr gap-1", children: ramp.steps.map(({ step, token }) => (_jsxs("li", { "data-slot": "ramp-step", title: `${token.name}: ${token.values.default ?? ""}`, className: "flex flex-col gap-1.5", children: [_jsx("span", { "aria-hidden": true, className: "h-12 rounded-md ring-1 ring-border ring-inset", style: { background: `var(${token.name})` } }), _jsx("span", { className: "text-center font-mono text-xs text-muted-foreground", children: step })] }, step))) })] }, ramp.family))) }));
|
|
94
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs before the first paint, so a saved choice never flashes the other mode. It
|
|
3
|
+
* sets `data-theme` on <html>, which the generated tokens read, so give <html>
|
|
4
|
+
* `suppressHydrationWarning`. System sets nothing and leaves it to the media query.
|
|
5
|
+
*/
|
|
6
|
+
export declare const themeScript = "try{var t=localStorage.getItem(\"docs-theme\");if(t===\"light\"||t===\"dark\")document.documentElement.dataset.theme=t}catch(e){}";
|
|
7
|
+
/**
|
|
8
|
+
* Writes `themeScript` into the server's HTML, once, ahead of the page. Through
|
|
9
|
+
* Next's `useServerInsertedHTML`, because React will not run a <script> a client
|
|
10
|
+
* component renders, and says so. The shell calls it; call it once yourself if the
|
|
11
|
+
* switch lives somewhere else.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useThemeScript(): void;
|
|
14
|
+
/**
|
|
15
|
+
* System, light or dark for the whole site, as three icons in a track. Kept per
|
|
16
|
+
* viewer. Each is a 44px target however small it is drawn.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ThemeSwitch({ className }: {
|
|
19
|
+
className?: string;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Monitor, Moon, Sun } from "lucide-react";
|
|
4
|
+
import { useServerInsertedHTML } from "next/navigation";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { cx } from "./lib/cx.js";
|
|
7
|
+
const MODES = [
|
|
8
|
+
{ mode: "system", label: "System", Icon: Monitor },
|
|
9
|
+
{ mode: "light", label: "Light", Icon: Sun },
|
|
10
|
+
{ mode: "dark", label: "Dark", Icon: Moon },
|
|
11
|
+
];
|
|
12
|
+
const KEY = "docs-theme";
|
|
13
|
+
/**
|
|
14
|
+
* Runs before the first paint, so a saved choice never flashes the other mode. It
|
|
15
|
+
* sets `data-theme` on <html>, which the generated tokens read, so give <html>
|
|
16
|
+
* `suppressHydrationWarning`. System sets nothing and leaves it to the media query.
|
|
17
|
+
*/
|
|
18
|
+
export const themeScript = `try{var t=localStorage.getItem("${KEY}");if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}`;
|
|
19
|
+
/**
|
|
20
|
+
* Writes `themeScript` into the server's HTML, once, ahead of the page. Through
|
|
21
|
+
* Next's `useServerInsertedHTML`, because React will not run a <script> a client
|
|
22
|
+
* component renders, and says so. The shell calls it; call it once yourself if the
|
|
23
|
+
* switch lives somewhere else.
|
|
24
|
+
*/
|
|
25
|
+
export function useThemeScript() {
|
|
26
|
+
const done = useRef(false);
|
|
27
|
+
useServerInsertedHTML(() => {
|
|
28
|
+
if (done.current)
|
|
29
|
+
return null;
|
|
30
|
+
done.current = true;
|
|
31
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: a fixed string, written here, with nothing from the request in it
|
|
32
|
+
return _jsx("script", { dangerouslySetInnerHTML: { __html: themeScript } });
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function apply(mode) {
|
|
36
|
+
const root = document.documentElement;
|
|
37
|
+
if (mode === "system")
|
|
38
|
+
delete root.dataset.theme;
|
|
39
|
+
else
|
|
40
|
+
root.dataset.theme = mode;
|
|
41
|
+
try {
|
|
42
|
+
if (mode === "system")
|
|
43
|
+
localStorage.removeItem(KEY);
|
|
44
|
+
else
|
|
45
|
+
localStorage.setItem(KEY, mode);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// private windows and blocked storage: the choice lasts for this page only
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* System, light or dark for the whole site, as three icons in a track. Kept per
|
|
53
|
+
* viewer. Each is a 44px target however small it is drawn.
|
|
54
|
+
*/
|
|
55
|
+
export function ThemeSwitch({ className }) {
|
|
56
|
+
const [mode, setMode] = useState("system");
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const t = document.documentElement.dataset.theme;
|
|
59
|
+
if (t === "light" || t === "dark")
|
|
60
|
+
setMode(t);
|
|
61
|
+
}, []);
|
|
62
|
+
return (_jsx("div", { role: "group", "aria-label": "Colour mode", "data-slot": "docs-theme-switch", className: cx("flex items-center rounded-full bg-muted p-0.5", className), children: MODES.map(({ mode: m, label, Icon }) => (_jsx("button", { type: "button", "aria-pressed": mode === m, "aria-label": label, title: label, onClick: () => {
|
|
63
|
+
setMode(m);
|
|
64
|
+
apply(m);
|
|
65
|
+
},
|
|
66
|
+
// drawn at 32px; the transparent ring of padding makes each a 44px target
|
|
67
|
+
className: "relative -my-1.5 inline-flex size-(--docs-target,2.75rem) cursor-pointer items-center justify-center rounded-full p-1.5 text-muted-foreground transition-colors duration-(--dt-duration-fast) first:-ml-1.5 last:-mr-1.5 hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring aria-pressed:text-canvas-foreground", children: _jsx("span", { "aria-hidden": true, className: cx("flex size-full items-center justify-center rounded-full transition-colors duration-(--dt-duration-fast)", mode === m && "bg-surface shadow-xs ring-1 ring-border"), children: _jsx(Icon, { className: "size-(--dt-size-xs)" }) }) }, m))) }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TokensManifest } from "./lib/manifest-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Type, set in its own tokens: each family as a specimen, then the scale, each
|
|
4
|
+
* size with the leading and tracking Tailwind pairs with it, then weights,
|
|
5
|
+
* leading and tracking on their own. Every sample reads its token; nothing is restated.
|
|
6
|
+
*/
|
|
7
|
+
export declare function TypeRamp({ tokens, sample, }: {
|
|
8
|
+
tokens: TokensManifest;
|
|
9
|
+
sample?: string;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { shortName, tokensOfTier, typeScale, withGroupDescriptions } from "./lib/manifest.js";
|
|
3
|
+
/** `1.125rem` → `18px`, so a person reads the size they know. */
|
|
4
|
+
function px(value) {
|
|
5
|
+
const m = value && /^(\d*\.?\d+)rem$/.exec(value.trim());
|
|
6
|
+
return m ? `${Math.round(Number(m[1]) * 16 * 100) / 100}px` : undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Type, set in its own tokens: each family as a specimen, then the scale, each
|
|
10
|
+
* size with the leading and tracking Tailwind pairs with it, then weights,
|
|
11
|
+
* leading and tracking on their own. Every sample reads its token; nothing is restated.
|
|
12
|
+
*/
|
|
13
|
+
export function TypeRamp({ tokens, sample = "The quick brown fox jumps over the lazy dog", }) {
|
|
14
|
+
const steps = withGroupDescriptions(typeScale(tokens).map((s) => ({ ...s, description: s.size.description })));
|
|
15
|
+
const families = tokensOfTier(tokens, "font");
|
|
16
|
+
const weights = tokensOfTier(tokens, "font-weight");
|
|
17
|
+
const leading = tokensOfTier(tokens, "leading");
|
|
18
|
+
const tracking = tokensOfTier(tokens, "tracking");
|
|
19
|
+
return (_jsxs("div", { "data-slot": "type-ramp", className: "flex flex-col gap-14", children: [families.length > 0 && (_jsx("div", { className: "grid gap-x-12 gap-y-10 md:grid-cols-2", children: families.map((f) => (_jsxs("figure", { "data-slot": "type-family", className: "flex flex-col gap-5 border-t border-border pt-6", children: [_jsxs("div", { className: "flex flex-col gap-6", style: { fontFamily: `var(${f.name})` }, children: [_jsx("span", { className: "text-8xl leading-none font-light tracking-tighter", children: "Aa" }), _jsx("span", { className: "text-sm leading-relaxed break-all text-muted-foreground", children: "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 &?!" })] }), _jsxs("figcaption", { className: "flex flex-col gap-0.5", children: [_jsx("code", { className: "font-mono text-xs font-medium", children: shortName(f, "font") }), _jsx("span", { className: "truncate font-mono text-xs text-muted-foreground", title: f.values.default, children: f.description ?? f.values.default })] })] }, f.name))) })), _jsx("ol", { "data-slot": "type-scale", className: "flex flex-col", children: steps.map((step) => (_jsxs("li", { "data-slot": "type-step", className: "grid grid-cols-1 gap-3 border-t border-border py-5 md:grid-cols-[11rem_minmax(0,1fr)] md:items-center md:gap-6", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("span", { className: "flex items-baseline gap-2", children: [_jsx("code", { className: "font-mono text-xs font-semibold", children: shortName(step.size, "text") }), _jsx("span", { className: "font-mono text-xs text-muted-foreground", children: px(step.size.values.default) ?? step.size.values.default })] }), _jsx("span", { className: "font-mono text-xs text-muted-foreground", children: [step.lineHeight && `lh ${step.lineHeight.values.default}`, step.letterSpacing && `tr ${step.letterSpacing.values.default}`].filter(Boolean).join(" · ") }), step.description && _jsx("span", { className: "text-xs text-muted-foreground", children: step.description })] }), _jsx("p", { className: "min-w-0 truncate", style: {
|
|
20
|
+
fontSize: `var(${step.name})`,
|
|
21
|
+
lineHeight: step.lineHeight ? `var(${step.lineHeight.name})` : undefined,
|
|
22
|
+
letterSpacing: step.letterSpacing ? `var(${step.letterSpacing.name})` : undefined,
|
|
23
|
+
fontWeight: step.fontWeight ? `var(${step.fontWeight.name})` : undefined,
|
|
24
|
+
}, children: sample })] }, step.name))) }), weights.length > 0 && (_jsx(Group, { title: "Weights", children: _jsx("ul", { className: "grid grid-cols-2 gap-x-10 gap-y-8 sm:grid-cols-4", children: weights.map((t) => (_jsxs("li", { className: "flex flex-col gap-3 border-t border-border pt-4", children: [_jsx("span", { className: "text-4xl leading-none tracking-tight", style: { fontWeight: `var(${t.name})` }, children: "Aa" }), _jsx(Meta, { token: t, prefix: "font-weight" })] }, t.name))) }) })), (leading.length > 0 || tracking.length > 0) && (_jsxs("div", { className: "grid gap-10 lg:grid-cols-2", children: [leading.length > 0 && (_jsx(Group, { title: "Leading", children: _jsx("ul", { className: "flex flex-col", children: leading.map((t) => (_jsxs("li", { className: "grid grid-cols-[6rem_minmax(0,1fr)] gap-4 border-t border-border py-4", children: [_jsx(Meta, { token: t, prefix: "leading" }), _jsxs("span", { className: "line-clamp-2 text-sm", style: { lineHeight: `var(${t.name})` }, children: [sample, ". ", sample, "."] })] }, t.name))) }) })), tracking.length > 0 && (_jsx(Group, { title: "Tracking", children: _jsx("ul", { className: "flex flex-col", children: tracking.map((t) => (_jsxs("li", { className: "grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-4 border-t border-border py-4", children: [_jsx(Meta, { token: t, prefix: "tracking" }), _jsx("span", { className: "truncate text-lg font-medium", style: { letterSpacing: `var(${t.name})` }, children: sample })] }, t.name))) }) }))] }))] }));
|
|
25
|
+
}
|
|
26
|
+
function Group({ title, children }) {
|
|
27
|
+
return (_jsxs("div", { "data-slot": "type-table", className: "flex flex-col gap-4", children: [_jsx("h3", { className: "text-2xl tracking-tight", children: title }), children] }));
|
|
28
|
+
}
|
|
29
|
+
function Meta({ token, prefix }) {
|
|
30
|
+
return (_jsxs("span", { className: "flex min-w-0 flex-col font-mono text-xs", children: [_jsx("span", { className: "font-medium", children: shortName(token, prefix) }), _jsx("span", { className: "truncate text-muted-foreground", title: token.values.default, children: token.values.default })] }));
|
|
31
|
+
}
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentEntry, ComponentsManifest } from "./lib/manifest-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* When to use a component and when not, side by side, with what to use instead;
|
|
4
|
+
* then its facts in one strip: status, role, keys, the primitive it wraps and the
|
|
5
|
+
* elements it replaces. Written by people as JSDoc (`@use`, `@avoid`, `@instead`,
|
|
6
|
+
* `@status`, `@role`, `@keyboard`); a component without them says so.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Usage({ entry, manifest, basePath, }: {
|
|
9
|
+
entry: ComponentEntry;
|
|
10
|
+
/** To link `instead` names that are components. */
|
|
11
|
+
manifest?: ComponentsManifest;
|
|
12
|
+
basePath?: string;
|
|
13
|
+
}): import("react").JSX.Element;
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowUpRight, CircleCheck, CircleX } from "lucide-react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { cx } from "./lib/cx.js";
|
|
5
|
+
import { slugOf } from "./lib/manifest.js";
|
|
6
|
+
import { StatusBadge } from "./lib/status.js";
|
|
7
|
+
import { inline } from "./lib/text.js";
|
|
8
|
+
/**
|
|
9
|
+
* When to use a component and when not, side by side, with what to use instead;
|
|
10
|
+
* then its facts in one strip: status, role, keys, the primitive it wraps and the
|
|
11
|
+
* elements it replaces. Written by people as JSDoc (`@use`, `@avoid`, `@instead`,
|
|
12
|
+
* `@status`, `@role`, `@keyboard`); a component without them says so.
|
|
13
|
+
*/
|
|
14
|
+
export function Usage({ entry, manifest, basePath = "/design/system", }) {
|
|
15
|
+
const usage = entry.usage;
|
|
16
|
+
const known = new Map((manifest?.components ?? []).map((c) => [c.name, c]));
|
|
17
|
+
const instead = usage?.instead?.length ? (_jsxs("p", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground", children: ["Use instead", usage.instead.map((name) => {
|
|
18
|
+
const c = known.get(name);
|
|
19
|
+
return c ? (_jsxs(Link, { href: `${basePath}/components/${slugOf(c)}`, className: "relative inline-flex items-center gap-0.5 text-canvas-foreground underline decoration-border underline-offset-4 hover:decoration-current focus-visible:outline-2 focus-visible:outline-ring after:absolute after:-inset-3 after:content-['']", children: [name, _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5 text-muted-foreground" })] }, name)) : (_jsx("span", { className: "font-medium text-canvas-foreground", children: name }, name));
|
|
20
|
+
})] })) : null;
|
|
21
|
+
const facts = [["Status", _jsx(Status, { entry: entry }, "s")]];
|
|
22
|
+
if (entry.a11y?.role)
|
|
23
|
+
facts.push(["Role", _jsx("code", { className: "font-mono text-xs", children: entry.a11y.role }, "r")]);
|
|
24
|
+
if (entry.a11y?.keyboard?.length)
|
|
25
|
+
facts.push([
|
|
26
|
+
"Keyboard",
|
|
27
|
+
_jsx("span", { className: "flex flex-wrap gap-1", children: entry.a11y.keyboard.map((k) => (_jsx("kbd", { className: "rounded-sm border border-border px-1.5 font-mono text-xs leading-5", children: k }, k))) }, "k"),
|
|
28
|
+
]);
|
|
29
|
+
if (entry.primitive)
|
|
30
|
+
facts.push(["Built on", _jsx(Source, { href: entry.primitive }, "p")]);
|
|
31
|
+
if (entry.replaces?.length)
|
|
32
|
+
facts.push([
|
|
33
|
+
"Replaces",
|
|
34
|
+
_jsx("span", { className: "flex flex-wrap gap-1", children: entry.replaces.map((r) => (_jsx("code", { className: "font-mono text-xs", children: `<${r}>` }, r))) }, "x"),
|
|
35
|
+
]);
|
|
36
|
+
return (_jsxs("div", { "data-slot": "usage", className: "flex flex-col gap-10", children: [usage?.use || usage?.avoid ? (_jsxs("div", { className: "grid gap-10 md:grid-cols-2 md:gap-12", children: [_jsx(Column, { tone: "do", title: "Use it for", children: usage?.use ? inline(usage.use) : _jsx("span", { className: "text-muted-foreground", children: "Not written yet." }) }), _jsx(Column, { tone: "dont", title: "Not for", footer: instead, children: usage?.avoid ? inline(usage.avoid) : _jsx("span", { className: "text-muted-foreground", children: "Not written yet." }) })] })) : (_jsxs("p", { className: "text-base text-muted-foreground", children: ["No usage guidance yet. Add ", _jsx("code", { className: "font-mono text-sm", children: "@use" }), ", ", _jsx("code", { className: "font-mono text-sm", children: "@avoid" }), " and", " ", _jsx("code", { className: "font-mono text-sm", children: "@instead" }), " to its JSDoc."] })), _jsx("dl", { "data-slot": "usage-facts", className: "flex flex-wrap gap-x-12 gap-y-6", children: facts.map(([term, detail]) => (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: term }), _jsx("dd", { className: "text-sm", children: detail })] }, term))) })] }));
|
|
37
|
+
}
|
|
38
|
+
/** One side of when to use it: a hairline, a small label with its icon in the status colour, and the guidance at reading size. */
|
|
39
|
+
function Column({ tone, title, footer, children }) {
|
|
40
|
+
const Icon = tone === "do" ? CircleCheck : CircleX;
|
|
41
|
+
return (_jsxs("div", { className: "flex flex-col gap-3 border-t border-border pt-5", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Icon, { "aria-hidden": true, className: cx("size-4", tone === "do" ? "text-success-subdued-foreground" : "text-destructive-subdued-foreground") }), title] }), _jsx("p", { className: "text-lg leading-relaxed text-canvas-foreground/85 [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-[0.875em]", children: children }), footer] }));
|
|
42
|
+
}
|
|
43
|
+
function Status({ entry }) {
|
|
44
|
+
return (_jsxs("span", { className: "flex flex-col items-start gap-1.5", children: [_jsx(StatusBadge, { status: entry.status }), entry.statusNote && _jsx("span", { className: "text-xs text-muted-foreground", children: inline(entry.statusNote) })] }));
|
|
45
|
+
}
|
|
46
|
+
/** A primitive: a link names the site and the page (`Base UI · avatar`), a package path shows as code. */
|
|
47
|
+
function Source({ href }) {
|
|
48
|
+
if (!/^https?:/.test(href))
|
|
49
|
+
return _jsx("code", { className: "font-mono text-xs", children: href });
|
|
50
|
+
const url = new URL(href);
|
|
51
|
+
const name = url.pathname.split("/").filter(Boolean).pop() ?? "";
|
|
52
|
+
const host = url.host.replace(/^www\./, "");
|
|
53
|
+
const site = host === "base-ui.com" ? "Base UI" : host.replace(/\.[a-z]+$/, "");
|
|
54
|
+
return (_jsxs("a", { href: href, title: href, className: "group inline-flex items-center gap-1 whitespace-nowrap hover:underline hover:underline-offset-4 focus-visible:outline-2 focus-visible:outline-ring", children: [site, name && _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", name] }), _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5 text-muted-foreground" })] }));
|
|
55
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ComponentEntry } from "./lib/manifest-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Every variant, rendered live. The first two axes form a grid; each further
|
|
5
|
+
* axis gets its own row, with the others at their defaults. Pass `props` for
|
|
6
|
+
* anything the component needs to render (an `options` list, a `value`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function VariantMatrix({ entry, props, children, axes: only, }: {
|
|
9
|
+
entry: ComponentEntry;
|
|
10
|
+
props?: Record<string, unknown>;
|
|
11
|
+
/** What to render inside each instance. Defaults to the component's name. */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
/** Limit and order the axes shown. */
|
|
14
|
+
axes?: string[];
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Grid3x3, Moon, Sun } from "lucide-react";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { cx } from "./lib/cx.js";
|
|
6
|
+
import { coerce, Missing, useLookup } from "./lib/lookup.js";
|
|
7
|
+
import { installPseudoStates } from "./lib/pseudo.js";
|
|
8
|
+
import { Frame, IconButton, Toolbar } from "./lib/ui.js";
|
|
9
|
+
/**
|
|
10
|
+
* Every variant, rendered live. The first two axes form a grid; each further
|
|
11
|
+
* axis gets its own row, with the others at their defaults. Pass `props` for
|
|
12
|
+
* anything the component needs to render (an `options` list, a `value`).
|
|
13
|
+
*/
|
|
14
|
+
export function VariantMatrix({ entry, props = {}, children, axes: only, }) {
|
|
15
|
+
const lookup = useLookup();
|
|
16
|
+
const Component = lookup.componentFor(entry);
|
|
17
|
+
const [theme, setTheme] = useState();
|
|
18
|
+
const all = entry.variants?.axes ?? [];
|
|
19
|
+
const axes = only ? only.map((n) => all.find((a) => a.name === n)).filter((a) => !!a) : all;
|
|
20
|
+
if (!Component)
|
|
21
|
+
return _jsx(Missing, { name: entry.name });
|
|
22
|
+
if (axes.length === 0)
|
|
23
|
+
return _jsxs("p", { className: "text-sm text-muted-foreground", children: [entry.name, " has no variants."] });
|
|
24
|
+
const content = children ?? entry.name;
|
|
25
|
+
const render = (extra) => (_jsx(Component, { ...props, ...coerce(extra), children: content }));
|
|
26
|
+
const [rows, cols, ...rest] = axes;
|
|
27
|
+
const toggle = (_jsx(IconButton, { icon: theme === "dark" ? Sun : Moon, label: theme === "dark" ? "Show in light mode" : "Show in dark mode", pressed: theme !== undefined, onClick: () => setTheme(theme === "dark" ? "light" : "dark") }));
|
|
28
|
+
return (_jsxs("div", { "data-slot": "variant-matrix", className: "flex flex-col gap-6", children: [cols ? (_jsxs(Frame, { children: [_jsxs(Toolbar, { children: [_jsxs("span", { className: "flex items-center gap-2 pl-2 font-mono text-xs text-muted-foreground", children: [_jsx(Grid3x3, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), rows.name, " \u00D7 ", cols.name] }), toggle] }), _jsx("div", { "data-docs-stage": "", "data-theme": theme, className: "overflow-x-auto bg-canvas text-canvas-foreground", tabIndex: 0, role: "region", "aria-label": "Variants", children: _jsxs("table", { className: "w-full border-collapse text-sm", children: [_jsxs("caption", { className: "sr-only", children: [entry.name, ": ", rows.name, " by ", cols.name] }), _jsx("thead", { children: _jsxs("tr", { className: "border-b border-border", children: [_jsx("th", { scope: "col", className: "w-0 p-0" }), cols.options.map((c) => (_jsx("th", { scope: "col", title: c.description, className: "px-4 py-3 text-center font-mono text-xs font-medium text-muted-foreground", children: _jsx(OptionName, { axis: cols, option: c.name }) }, c.name)))] }) }), _jsx("tbody", { children: rows.options.map((r) => (_jsxs("tr", { className: "border-b border-border last:border-b-0", children: [_jsx("th", { scope: "row", title: r.description, className: "border-r border-border px-4 py-5 text-left font-mono text-xs font-medium whitespace-nowrap text-muted-foreground", children: _jsx(OptionName, { axis: rows, option: r.name }) }), cols.options.map((c) => (_jsx("td", { "data-slot": "variant-cell", className: "px-4 py-5 text-center align-middle", children: _jsx("span", { className: "inline-flex justify-center", children: render({ [rows.name]: r.name, [cols.name]: c.name }) }) }, c.name)))] }, r.name))) })] }) })] })) : (_jsx(AxisRow, { axis: rows, render: render, theme: theme, toggle: toggle })), rest.map((axis) => (_jsx(AxisRow, { axis: axis, render: render, theme: theme, toggle: toggle }, axis.name))), _jsx(States, { render: render, theme: theme, toggle: toggle, canDisable: "disabled" in entry.props })] }));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The component at its defaults in each state at once: at rest, hovered, focused,
|
|
32
|
+
* pressed and disabled, pinned without a pointer or a keyboard, so states are
|
|
33
|
+
* reviewed side by side rather than by mousing over each.
|
|
34
|
+
*/
|
|
35
|
+
function States({ render, theme, toggle, canDisable, }) {
|
|
36
|
+
useEffect(() => installPseudoStates(), []);
|
|
37
|
+
const states = [
|
|
38
|
+
{ name: "rest" },
|
|
39
|
+
{ name: "hover", pseudo: "hover" },
|
|
40
|
+
{ name: "focus", pseudo: "focus" },
|
|
41
|
+
{ name: "press", pseudo: "active" },
|
|
42
|
+
...(canDisable ? [{ name: "disabled", extra: { disabled: "true" } }] : []),
|
|
43
|
+
];
|
|
44
|
+
return (_jsxs(Frame, { "data-slot": "variant-states", children: [_jsxs(Toolbar, { children: [_jsx("span", { className: "pl-2 font-mono text-xs text-muted-foreground", children: "state" }), toggle] }), _jsx("div", { "data-docs-stage": "", "data-theme": theme, className: "flex flex-wrap items-end justify-center gap-x-10 gap-y-6 bg-canvas px-6 py-8 text-canvas-foreground", children: states.map((s) => (_jsxs("figure", { className: "flex flex-col items-center gap-3", children: [_jsx("div", { "data-pseudo": s.pseudo, children: render(s.extra ?? {}) }), _jsx("figcaption", { className: "font-mono text-xs text-muted-foreground", children: s.name })] }, s.name))) })] }));
|
|
45
|
+
}
|
|
46
|
+
function AxisRow({ axis, render, theme, toggle, }) {
|
|
47
|
+
return (_jsxs(Frame, { "data-slot": "variant-axis", children: [_jsxs(Toolbar, { children: [_jsx("span", { className: "pl-2 font-mono text-xs text-muted-foreground", children: axis.name }), toggle] }), _jsx("div", { "data-docs-stage": "", "data-theme": theme, className: "flex flex-wrap items-end justify-center gap-x-10 gap-y-6 bg-canvas px-6 py-8 text-canvas-foreground", children: axis.options.map((o) => (_jsxs("figure", { className: "flex flex-col items-center gap-3", children: [render({ [axis.name]: o.name }), _jsx("figcaption", { className: "font-mono text-xs text-muted-foreground", title: o.description, children: _jsx(OptionName, { axis: axis, option: o.name }) })] }, o.name))) })] }));
|
|
48
|
+
}
|
|
49
|
+
/** An option's name, with the default marked by a dot and said for screen readers. */
|
|
50
|
+
function OptionName({ axis, option }) {
|
|
51
|
+
const isDefault = axis.default === option;
|
|
52
|
+
return (_jsxs("span", { className: cx("inline-flex items-center gap-1.5", isDefault && "text-canvas-foreground"), children: [option, isDefault && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, className: "size-1.5 rounded-full bg-[var(--docs-accent,var(--primary))]" }), _jsx("span", { className: "sr-only", children: "(default)" })] }))] }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TermEntry } from "./lib/manifest-types.js";
|
|
2
|
+
/** The words to use, beside the ones not to: the product's words first, then the system's names that have wrong forms. */
|
|
3
|
+
export declare function VoiceTerms({ terms }: {
|
|
4
|
+
terms: TermEntry[];
|
|
5
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { inline } from "./lib/text.js";
|
|
3
|
+
/** The words to use, beside the ones not to: the product's words first, then the system's names that have wrong forms. */
|
|
4
|
+
export function VoiceTerms({ terms }) {
|
|
5
|
+
const product = terms.filter((t) => t.kind === "product");
|
|
6
|
+
const system = terms.filter((t) => t.kind !== "product" && t.wrong?.length);
|
|
7
|
+
if (product.length + system.length === 0)
|
|
8
|
+
return _jsx("p", { className: "text-sm text-muted-foreground", children: "No terms yet. Add them to the taxonomy file." });
|
|
9
|
+
return (_jsxs("div", { "data-slot": "voice-terms", className: "flex flex-col gap-12", children: [product.length > 0 && _jsx(Group, { title: "In the product", terms: product }), system.length > 0 && _jsx(Group, { title: "In the system", terms: system })] }));
|
|
10
|
+
}
|
|
11
|
+
function Group({ title, terms }) {
|
|
12
|
+
return (_jsxs("section", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-xs font-medium tracking-wide text-muted-foreground uppercase", children: title }), _jsx("dl", { className: "grid gap-x-12 sm:grid-cols-2", children: terms.map((t) => (_jsxs("div", { "data-slot": "term", className: "flex flex-col gap-1.5 border-t border-border py-5", children: [_jsxs("dt", { className: "text-3xl tracking-tight", children: [t.name, _jsx("span", { className: "sr-only", children: ", say this" })] }), (t.wrong ?? []).length > 0 && (_jsxs("dd", { className: "flex flex-wrap gap-x-3 text-base text-muted-foreground", children: [_jsx("span", { className: "sr-only", children: "not " }), (t.wrong ?? []).map((w) => (_jsx("s", { className: "decoration-destructive decoration-2", children: w }, w)))] })), (t.note || t.kind !== "product") && (_jsx("dd", { className: "text-sm text-muted-foreground", children: t.note ? inline(t.note) : `The ${t.kind}'s name in the code.` }))] }, `${t.kind}:${t.name}`))) })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
+
import type { ComponentEntry } from "./lib/manifest-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A component's one main area. On the stage: the component live, at any viewport
|
|
5
|
+
* and zoom, on any background, in either mode or direction, as people with
|
|
6
|
+
* different colour vision see it, with its boxes outlined or measured, and pinned
|
|
7
|
+
* at rest, hovered, focused, pressed or disabled. Beside it: a property for each
|
|
8
|
+
* variant and each prop, generated from their types. Under it: the code for
|
|
9
|
+
* exactly what is showing, the props, a log of every handler that fires, what an
|
|
10
|
+
* agent reads, and a full accessibility report that re-runs as anything changes.
|
|
11
|
+
* Every setting is in the URL, so a configuration can be shared as a link.
|
|
12
|
+
*
|
|
13
|
+
* Pass `props` for what it needs to render at all (its starting values), `content`
|
|
14
|
+
* for children richer than text (a Card's parts), and `wrap` for what it must sit
|
|
15
|
+
* inside to work (a provider, the root a part belongs to): an element, which the
|
|
16
|
+
* component is rendered into.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Workbench({ entry, props: base, children: initialText, content, wrap, backgrounds, previewHref, }: {
|
|
19
|
+
entry: ComponentEntry;
|
|
20
|
+
props?: Record<string, unknown>;
|
|
21
|
+
/** Text content, editable here. Pass "" for a component without children. */
|
|
22
|
+
children?: string;
|
|
23
|
+
/** Children richer than text; replaces the text control. */
|
|
24
|
+
content?: ReactNode;
|
|
25
|
+
/** An element to render the component inside: a provider, or the root a part needs. */
|
|
26
|
+
wrap?: ReactElement<{
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
}>;
|
|
29
|
+
/** Named backgrounds the brand has (`surface-stone`), offered on the stage beside canvas, surface and muted. */
|
|
30
|
+
backgrounds?: string[];
|
|
31
|
+
/** The preview route for its canonical example, opened from the toolbar. */
|
|
32
|
+
previewHref?: string;
|
|
33
|
+
}): import("react").JSX.Element;
|