@designtools/blocks 0.1.0 → 0.2.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +13 -9
  3. package/dist/cli.js +6 -2
  4. package/package.json +4 -2
  5. package/reference/README.md +20 -0
  6. package/reference/app/(design)/design/system/(docs)/brand/imagery/page.tsx +15 -0
  7. package/reference/app/(design)/design/system/(docs)/brand/logo/page.tsx +15 -0
  8. package/reference/app/(design)/design/system/(docs)/brand/page.tsx +16 -0
  9. package/reference/app/(design)/design/system/(docs)/brand/voice/page.tsx +15 -0
  10. package/reference/app/(design)/design/system/(docs)/colour/page.tsx +26 -0
  11. package/reference/app/(design)/design/system/(docs)/components/[slug]/page.tsx +86 -0
  12. package/reference/app/(design)/design/system/(docs)/layout.tsx +35 -0
  13. package/reference/app/(design)/design/system/(docs)/page.tsx +48 -0
  14. package/reference/app/(design)/design/system/(docs)/patterns/[id]/page.tsx +23 -0
  15. package/reference/app/(design)/design/system/(docs)/rules/page.tsx +42 -0
  16. package/reference/app/(design)/design/system/(docs)/scale/page.tsx +15 -0
  17. package/reference/app/(design)/design/system/(docs)/standards/page.tsx +21 -0
  18. package/reference/app/(design)/design/system/(docs)/type/page.tsx +15 -0
  19. package/reference/app/(design)/design/system/(docs)/vocabulary/page.tsx +15 -0
  20. package/reference/app/(design)/design/system/_components/copy-page.tsx +32 -0
  21. package/reference/app/(design)/design/system/_components/prose.tsx +26 -0
  22. package/reference/app/(design)/design/system/_data.ts +53 -0
  23. package/reference/app/(design)/design/system/manifest/[file]/route.ts +15 -0
  24. package/reference/app/(design)/design/system/md/[[...path]]/route.ts +94 -0
  25. package/reference/app/(design)/design/system/preview/[slug]/[example]/page.tsx +23 -0
  26. package/reference/app/(design)/layout.tsx +16 -0
  27. package/reference/app/globals.css +14 -0
  28. package/reference/app/layout.tsx +16 -0
  29. package/reference/designtools.json +14 -0
  30. package/reference/package.json +38 -0
  31. package/reference/proxy.ts +55 -0
  32. package/registry/a11y-panel.tsx +3 -3
  33. package/registry/adherence-summary.tsx +4 -4
  34. package/registry/agent-view.tsx +2 -1
  35. package/registry/anatomy.tsx +3 -2
  36. package/registry/ask-claude.tsx +4 -2
  37. package/registry/code-view.tsx +6 -3
  38. package/registry/examples.tsx +7 -6
  39. package/registry/glossary.tsx +2 -1
  40. package/registry/lib/adherence-types.ts +3 -1
  41. package/registry/lib/cx.ts +8 -0
  42. package/registry/lib/manifest-types.ts +13 -2
  43. package/registry/lib/manifest.ts +46 -0
  44. package/registry/lib/markdown.ts +30 -25
  45. package/registry/lib/search.ts +99 -0
  46. package/registry/lib/standards.ts +82 -0
  47. package/registry/lib/status.tsx +3 -1
  48. package/registry/logo-usage.tsx +44 -19
  49. package/registry/pattern.tsx +3 -2
  50. package/registry/playground.tsx +7 -6
  51. package/registry/preview-frame.tsx +3 -3
  52. package/registry/props-table.tsx +4 -4
  53. package/registry/rule.tsx +3 -2
  54. package/registry/search.tsx +134 -0
  55. package/registry/shell.tsx +86 -23
  56. package/registry/standards.tsx +22 -26
  57. package/registry/swatches.tsx +82 -3
  58. package/registry/usage.tsx +2 -1
  59. package/registry/variant-matrix.tsx +1 -1
  60. package/registry/voice-terms.tsx +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @designtools/blocks
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3da728f: From the first end-to-end run:
8
+
9
+ - Component markdown imports through the alias the manifest found (`@ds/button/button`), so the line an agent copies resolves; `importOf` gives the same specifier to the blocks.
10
+ - `componentMarkdown` and `patternMarkdown` take a heading level: `1` for a page of its own, so it no longer opens with the same heading twice. Do and don't examples come out in pairs, as the page shows them.
11
+ - Every pressable thing in the blocks is at least 44 × 44 CSS px, and a project sets `--docs-target` for its own rule (`3rem` for 48px).
12
+ - A component's parts fold under it in the navigation (`groupFamilies`), instead of each taking a line.
13
+ - The standards block titles its sections and rows, shows notes as sentences, leaves out values nobody has set, and titles thresholds beyond the schema from their keys; its markdown matches.
14
+ - The logo's minimum-size sample is painted on the logo's own surface, so a reversed logo is visible.
15
+ - The demo's docs app ships in `reference/`: `proxy.ts`, the data module, the markdown, manifest and preview routes, and every page.
16
+ - `add` defaults to `app/(design)/design/system/_blocks`, beside the docs in the `(design)` route group, unless the docs are already in `app/design/system`.
17
+ - `search` (and the shell, by default): find a component, part, rule, pattern or page by name, fuzzily, or by a wrong name from the taxonomy, on ⌘K, Ctrl+K or `/`. Base UI's Autocomplete in a Dialog, ranked by `fuzzysort`; the manifest is the index. Pass `terms` to the shell for the wrong names.
18
+ - The shell takes the client's `logo`. Editorial headers take a `reviewer` as well as an author.
19
+ - Accessibility, from the second run: scrolling tables and code are keyboard-reachable regions; no text below `text-xs` (the props table's "required" and the ramp labels were 10px); do and don't labels and a failing contrast grade use measured subdued pairs, not fill colours as text; rule anchors and "instead" links meet the target size.
20
+ - The logo page paints surfaces in their light-mode colours whatever the docs' mode, lists rules for every logo once above them, and drops the faded surface label.
21
+ - `reference/` gates the docs in production in every format (the `(design)` layout and `proxy.ts`), shows parts on their component's overview tile, puts the logo in the shell, and pins its packages exactly.
22
+ - `PageContrast` in the swatches block: each colour on each background (canvas, surface, overlay and every named `--surface-*`), per mode, measured on what the browser paints and marked as fine for text, as a shape, or neither.
23
+ - The status badge shows `measured` (a rule generated from the tokens) apart from confirmed and assumed ones.
24
+
3
25
  ## 0.1.0
4
26
 
5
27
  ### Minor Changes
package/README.md CHANGED
@@ -17,14 +17,14 @@ The blocks expect a Next app on the suite stack: React, Tailwind v4 over tokens
17
17
  ```json
18
18
  {
19
19
  "manifest": { "system": "src/ds", "tokens": ["app/styles/tokens.css", "app/styles/scale.css"] },
20
- "blocks": { "dir": "app/design/system/_blocks" }
20
+ "blocks": { "dir": "app/(design)/design/system/_blocks" }
21
21
  }
22
22
  ```
23
23
 
24
- `add` records `blocks.dir` the first time if you have not, defaulting to `app/design/system/_blocks` (or under `src/app` when the project has one). The leading underscore keeps the folder out of Next's routing.
24
+ `add` records `blocks.dir` the first time if you have not, defaulting to `app/(design)/design/system/_blocks`, beside the docs in the `(design)` route group (or under `src/app` when the project has one; `app/design/system/_blocks` when the docs already live outside the group). The leading underscore keeps the folder out of Next's routing.
25
25
 
26
26
  1. `designtools-manifest build` writes `components.json` and `tokens.json`.
27
- 2. `designtools-blocks add <block…>` copies the blocks, the shared files they import, and nothing else. It says which packages they need that the project lacks: `@base-ui/react` for the shell and playground, `shiki` for the code view, `axe-core` for the accessibility panel.
27
+ 2. `designtools-blocks add <block…>` copies the blocks, the shared files they import, and nothing else. It says which packages they need that the project lacks: `@base-ui/react` for the shell, search and playground, `fuzzysort` (MIT) for search, `shiki` (MIT) for the code view, `axe-core` for the accessibility panel. Pin them exactly. `axe-core` is MPL-2.0: check it against the project's licence list before adding `a11y-panel`, and leave the panel out if the list will not take it; nothing else needs it.
28
28
  3. `designtools-blocks map` writes `registry.generated.tsx`: every component, examples module, pattern and brand asset in the manifest, imported by path. Run it after each manifest build; `map --check` exits 1 when it is stale, for a pre-commit hook.
29
29
 
30
30
  Blocks that render components are client components, and a server page cannot pass a component to one. So a page passes the manifest entry, which is plain data, and the block finds the component in the generated map.
@@ -60,17 +60,18 @@ export default async function Page({ params }: { params: Promise<{ slug: string
60
60
  }
61
61
  ```
62
62
 
63
- `readComponents` and `readTokens` type a JSON import and check its schema version. [`examples/demo`](../../examples/demo) has the full site: brand, logo, imagery, voice, colour, type, scale, rules, a pattern, a component page using every component block, standards and vocabulary, the preview route, and the markdown route.
63
+ `readComponents` and `readTokens` type a JSON import and check its schema version. The full site ships with the package, in `reference/` (`node_modules/@designtools/blocks/reference/`): brand, logo, imagery, voice, colour, type, scale, rules, a pattern, a component page using every component block, standards and vocabulary, the preview route, the markdown and manifest routes, and `proxy.ts`. It is a copy of the designtools repo's `examples/demo`, taken at build, so it matches this version of the blocks. Read it; do not import from it.
64
64
 
65
65
  ## Blocks
66
66
 
67
67
  | Block | Shows | Reads |
68
68
  | --- | --- | --- |
69
- | `shell` | `DocsShell`: navigation in the site's order (brand, foundations, rules, patterns, components by `@category`, standards). `DocsHeader` with the provenance of a generated page or the author and review date of an editorial one. `DocsSection` | `components.json`, `rules.json`, `patterns.json` |
70
- | `logo-usage` | Each logo on its surfaces, at minimum size, with clear space drawn and its brand rules | `assets.json`, `rules.json`, the map |
69
+ | `shell` | `DocsShell`: the client's `logo` and title, search, and navigation in the site's order (brand, foundations, rules, patterns, components by `@category` with their parts folded under them, standards). `DocsHeader` with the provenance of a generated page, or the author, reviewer and review date of an editorial one. `DocsSection` | `components.json`, `rules.json`, `patterns.json`, `taxonomy.json` |
70
+ | `search` | `DocsSearch`: find a component, part, rule, pattern or page by name, fuzzily (`buton` finds Button), or by a wrong name from the taxonomy (`dropdown` finds Select, and says so). ⌘K, Ctrl+K or `/` opens it; arrows and Enter go. Navigation only, not page text: the manifest is the index, so there is nothing to build. The shell includes it; `search={false}` leaves it out | the same, through `searchIndex` in `lib/search` |
71
+ | `logo-usage` | Each logo on its surfaces, at minimum size, with clear space drawn. Surfaces are painted in their light-mode colours whatever the docs' mode, because a brand book's surfaces are fixed. Rules for every logo once, above them; a rule naming one logo, under it | `assets.json`, `rules.json`, the map |
71
72
  | `imagery` | The approved icons and imagery with their usage notes | `assets.json`, the map |
72
73
  | `voice-terms` | Preferred terms beside their wrong forms | `taxonomy.json` |
73
- | `swatches` | Each semantic colour in light and dark side by side, contrast measured on the painted colours; the ramps | `tokens.json` |
74
+ | `swatches` | Each semantic colour in light and dark side by side, contrast measured on the painted colours; every colour on every background (`PageContrast`); the ramps | `tokens.json` |
74
75
  | `type-ramp` | Each `--text-*` size with its leading and tracking, then families, weights, leading, tracking | `tokens.json` |
75
76
  | `scale` | Spacing, size, radius, shadow, duration and easing, drawn the way each is used | `tokens.json` |
76
77
  | `rule` | A rule for people, and as an agent reads it, with its check and a slot for a demonstration | `rules.json` |
@@ -89,11 +90,14 @@ export default async function Page({ params }: { params: Promise<{ slug: string
89
90
  | `ask-claude` | Copies a prompt naming the component, its file, the variant and the example | an entry |
90
91
  | `agent-view` | Any block's markdown form, copyable, in a tab beside the rendered one | an entry, or markdown |
91
92
 
93
+
94
+ Every pressable thing in the blocks (navigation, tabs, buttons, summaries, the playground's controls) is at least 44 by 44 CSS pixels, WCAG 2.5.5's size. A client whose own rule is larger sets `--docs-target` on the docs layout (`style={{ "--docs-target": "3rem" }}` for 48px), so the site that states the rule meets it. A component's parts (`CardHeader`, `CardTitle`) are folded under it in the navigation and open when you are on one of them.
95
+
92
96
  ## Markdown
93
97
 
94
- `lib/markdown.ts` holds the markdown form of every block, generated from the manifest rather than converted from HTML: `componentMarkdown`, `coloursMarkdown`, `typeMarkdown`, `scaleMarkdown`, `ruleMarkdown`, `patternMarkdown`, `glossaryMarkdown`, `assetsMarkdown`, `standardsMarkdown`, and `systemMarkdown` for everything in one file. Structure survives as typed fences (```` ```rule ````, ```` ```example ````, ```` ```do ````, ```` ```dont ````), and status travels with everything. `generatedHeader` opens a generated page with its version and commit; `editorialHeader` opens a written one with its author and review date.
98
+ `lib/markdown.ts` holds the markdown form of every block, generated from the manifest rather than converted from HTML: `componentMarkdown`, `coloursMarkdown`, `typeMarkdown`, `scaleMarkdown`, `ruleMarkdown`, `patternMarkdown`, `glossaryMarkdown`, `assetsMarkdown`, `standardsMarkdown`, and `systemMarkdown` for everything in one file. Structure survives as typed fences (```` ```rule ````, ```` ```example ````, ```` ```do ````, ```` ```dont ````), and status travels with everything. `generatedHeader` opens a generated page with its version and commit; `editorialHeader` opens a written one with its author and review date. Pass `1` as the second argument to `componentMarkdown` or `patternMarkdown` when it is the whole page, so the page has one heading; inside `systemMarkdown` they sit at level 2. Do and don't examples come out in pairs, as the page shows them, and the import line uses the alias the manifest found (`@ds/button/button`).
95
99
 
96
- Serving it is per-app wiring, so it is not packaged: the demo's `proxy.ts` answers `Accept: text/markdown` and `.md` with these, sets `Vary: Accept` and the canonical `Link`, and returns a markdown 404 for a page that does not exist.
100
+ Serving it is per-app wiring, so it is copied rather than imported: `reference/proxy.ts` answers `Accept: text/markdown` and `.md` with these, sets `Vary: Accept` and the canonical `Link`, and returns a markdown 404 for a page that does not exist.
97
101
 
98
102
  ## Styling
99
103
 
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ var REGISTRY_DIR = join(dirname(fileURLToPath(import.meta.url)), "../registry");
13
13
  var GENERATED = "registry.generated.tsx";
14
14
  var BLOCKS = [
15
15
  { name: "shell", file: "shell.tsx", description: "Docs frame: brand, foundations, rules, patterns, components, standards; page header and sections" },
16
+ { name: "search", file: "search.tsx", description: "Fuzzy search over components, parts, rules, patterns and pages, wrong names included; \u2318K, Ctrl+K or /" },
16
17
  { name: "logo-usage", file: "logo-usage.tsx", description: "Each logo on its surfaces, at minimum size, with clear space and its brand rules" },
17
18
  { name: "imagery", file: "imagery.tsx", description: "The approved icons and imagery with their usage notes" },
18
19
  { name: "voice-terms", file: "voice-terms.tsx", description: "Preferred terms beside their wrong forms" },
@@ -142,7 +143,8 @@ Usage
142
143
 
143
144
  Options
144
145
  --dir <dir> Where blocks live (default: blocks.dir in designtools.json,
145
- else app/design/system/_blocks, or src/app/\u2026 when src/app exists)
146
+ else app/(design)/design/system/_blocks, or src/app/\u2026 when
147
+ src/app exists; app/design/system/_blocks if the docs are there)
146
148
  --manifest <file> components.json (default: from manifest.system in designtools.json)
147
149
  --root <dir> Project root (default: the current directory)
148
150
  --force add: overwrite files that already exist
@@ -206,7 +208,9 @@ function readConfig() {
206
208
  function blocksDir(config) {
207
209
  if (values.dir) return values.dir;
208
210
  if (config.blocks?.dir) return config.blocks.dir;
209
- return existsSync2(join2(root, "src/app")) ? "src/app/design/system/_blocks" : "app/design/system/_blocks";
211
+ const app = existsSync2(join2(root, "src/app")) ? "src/app" : "app";
212
+ if (existsSync2(join2(root, app, "design/system")) && !existsSync2(join2(root, app, "(design)"))) return `${app}/design/system/_blocks`;
213
+ return `${app}/(design)/design/system/_blocks`;
210
214
  }
211
215
  function manifestPath(config) {
212
216
  if (values.manifest) return values.manifest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@designtools/blocks",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Unstyled, accessible React blocks for design system docs, copied into your repo: swatches, type ramp, scale, variant matrix, state grid, props table, anatomy, playground, code view, accessibility panel, preview frame. Each reads the @designtools/manifest JSON and renders your real components.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  "files": [
21
21
  "dist",
22
22
  "registry",
23
+ "reference",
23
24
  "README.md",
24
25
  "CHANGELOG.md",
25
26
  "LICENSE"
@@ -44,6 +45,7 @@
44
45
  "@types/react": "^19.3.0",
45
46
  "@types/react-dom": "^19.3.0",
46
47
  "axe-core": "^4.14.0",
48
+ "fuzzysort": "4.0.2",
47
49
  "next": "16.3.0",
48
50
  "react": "19.2.7",
49
51
  "react-dom": "19.2.7",
@@ -54,7 +56,7 @@
54
56
  "vitest": "^4.1.11"
55
57
  },
56
58
  "scripts": {
57
- "build": "tsup src/cli.ts --format esm --clean",
59
+ "build": "node scripts/reference.mjs && tsup src/cli.ts --format esm --clean",
58
60
  "typecheck": "tsc --noEmit && tsc --noEmit -p registry",
59
61
  "test": "vitest run",
60
62
  "cli": "tsx src/cli.ts"
@@ -0,0 +1,20 @@
1
+ # Reference: a docs site wired end to end
2
+
3
+ A copy of `examples/demo` in the designtools repo, taken when this version of
4
+ `@designtools/blocks` was built: everything a project writes per app, around the
5
+ blocks it copies. Read it; do not import from it.
6
+
7
+ | File | What it shows |
8
+ | --- | --- |
9
+ | `proxy.ts` | Markdown on `Accept: text/markdown` or `.md`, `Vary: Accept`, the alternate `Link`, and a 404 for every docs URL on production, route handlers included |
10
+ | `app/(design)/layout.tsx` | The 404 and `noindex` for every design page on production |
11
+ | `app/(design)/design/system/_data.ts` | One data module that the pages and the markdown route both read |
12
+ | `app/(design)/design/system/md/[[...path]]/route.ts` | Every page as markdown, with the canonical `Link` and a markdown 404 |
13
+ | `app/(design)/design/system/manifest/[file]/route.ts` | The manifest served as JSON |
14
+ | `app/(design)/design/system/preview/[slug]/[example]/page.tsx` | One example alone, for the preview frame and visual regression |
15
+ | `app/(design)/design/system/(docs)/` | The pages, composing the blocks: brand, foundations, rules, patterns, components, standards, vocabulary |
16
+ | `package.json`, `designtools.json`, `app/globals.css` | The setup scripts, the config, and the `@source` line Tailwind needs for the blocks |
17
+
18
+ Paths match a project that keeps its system in `src/ds` and its docs in the
19
+ `(design)` route group. Blocks are imported relatively from `_blocks`, where
20
+ `designtools-blocks add` puts them.
@@ -0,0 +1,15 @@
1
+ import { CopyPage } from "../../../_components/copy-page";
2
+ import { Imagery } from "../../../_blocks/imagery";
3
+ import { DocsHeader } from "../../../_blocks/shell";
4
+ import { assets, provenance } from "../../../_data";
5
+
6
+ export default function ImageryPage() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Imagery and icons" lead="The approved icons and imagery, and how to use each." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <Imagery assets={assets.assets} />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ import { CopyPage } from "../../../_components/copy-page";
2
+ import { LogoUsage } from "../../../_blocks/logo-usage";
3
+ import { DocsHeader } from "../../../_blocks/shell";
4
+ import { assets, BASE, provenance, rules } from "../../../_data";
5
+
6
+ export default function Logo() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Logo" lead="Each logo on the surfaces it may sit on, at its smallest, with its clear space." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <LogoUsage assets={assets.assets} rules={rules.rules} rulesHref={`${BASE}/rules`} />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,16 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { Markdown } from "../../_components/prose";
3
+ import { DocsHeader } from "../../_blocks/shell";
4
+ import { editorial } from "../../_data";
5
+
6
+ export default function Brand() {
7
+ const page = editorial.brand;
8
+ return (
9
+ <>
10
+ <DocsHeader title={page.title} lead={page.lead} editorial={page.by}>
11
+ <CopyPage />
12
+ </DocsHeader>
13
+ <Markdown text={page.body} />
14
+ </>
15
+ );
16
+ }
@@ -0,0 +1,15 @@
1
+ import { CopyPage } from "../../../_components/copy-page";
2
+ import { DocsHeader } from "../../../_blocks/shell";
3
+ import { VoiceTerms } from "../../../_blocks/voice-terms";
4
+ import { provenance, taxonomy } from "../../../_data";
5
+
6
+ export default function Voice() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Voice and terms" lead="The words we use, beside the ones we do not." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <VoiceTerms terms={taxonomy.terms} />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,26 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { DocsHeader, DocsSection } from "../../_blocks/shell";
3
+ import { PageContrast, RampSwatches, SemanticSwatches } from "../../_blocks/swatches";
4
+ import { provenance, tokens } from "../../_data";
5
+
6
+ export default function Colour() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Colour" lead="Generated from one brand colour by @designtools/tokens. Write the semantic names in components; the ramps are what they point at." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <DocsSection title="Semantic colours" lead="Each pair is measured on the colours the browser paints, in each mode.">
13
+ <SemanticSwatches tokens={tokens} />
14
+ </DocsSection>
15
+ <DocsSection
16
+ title="On the page"
17
+ lead="Each colour on each background. A label is measured on its own fill above; this is the rest: a colour used as text on the page needs 4.5:1, as a shape (a meter fill, an icon without words) 3:1."
18
+ >
19
+ <PageContrast tokens={tokens} />
20
+ </DocsSection>
21
+ <DocsSection title="Ramps">
22
+ <RampSwatches tokens={tokens} />
23
+ </DocsSection>
24
+ </>
25
+ );
26
+ }
@@ -0,0 +1,86 @@
1
+ import { notFound } from "next/navigation";
2
+ import { CopyPage } from "../../../_components/copy-page";
3
+ import { A11yPanel } from "../../../_blocks/a11y-panel";
4
+ import { AgentView } from "../../../_blocks/agent-view";
5
+ import { Anatomy } from "../../../_blocks/anatomy";
6
+ import { AskClaude } from "../../../_blocks/ask-claude";
7
+ import { Examples } from "../../../_blocks/examples";
8
+ import { Playground } from "../../../_blocks/playground";
9
+ import { PreviewFrame } from "../../../_blocks/preview-frame";
10
+ import { PropsTable } from "../../../_blocks/props-table";
11
+ import { DocsHeader, DocsSection } from "../../../_blocks/shell";
12
+ import { Usage } from "../../../_blocks/usage";
13
+ import { VariantMatrix } from "../../../_blocks/variant-matrix";
14
+ import { findBySlug, slugOf } from "../../../_blocks/lib/manifest";
15
+ import { BASE, components, provenance } from "../../../_data";
16
+
17
+ /** What a component needs to render at all. The rest comes from the controls. */
18
+ const needs: Record<string, { props?: Record<string, unknown>; children?: string }> = {
19
+ Select: {
20
+ props: { label: "Project", value: "beacon", options: [{ value: "atlas", label: "Atlas" }, { value: "beacon", label: "Beacon" }] },
21
+ children: "",
22
+ },
23
+ Switch: { props: { "aria-label": "Notifications" }, children: "" },
24
+ Alert: { props: { title: "Timesheets close on Friday" } },
25
+ };
26
+
27
+ export function generateStaticParams() {
28
+ return components.components.map((c) => ({ slug: slugOf(c) }));
29
+ }
30
+
31
+ export default async function ComponentPage({ params }: { params: Promise<{ slug: string }> }) {
32
+ const { slug } = await params;
33
+ const entry = findBySlug(components, slug);
34
+ if (!entry) notFound();
35
+ const need = needs[entry.name] ?? {};
36
+ const preview = `${BASE}/preview/${slug}`;
37
+ const first = entry.examples?.canonical?.name;
38
+
39
+ return (
40
+ <>
41
+ <DocsHeader title={entry.name} lead={entry.description} source={entry.source} provenance={provenance}>
42
+ <CopyPage />
43
+ </DocsHeader>
44
+
45
+ <DocsSection title="Usage">
46
+ <Usage entry={entry} manifest={components} basePath={BASE} />
47
+ </DocsSection>
48
+
49
+ <DocsSection title="Examples" lead="Each is the real component. Copy the first one.">
50
+ <A11yPanel entry={entry}>
51
+ <Examples entry={entry} previewHref={preview} />
52
+ </A11yPanel>
53
+ </DocsSection>
54
+
55
+ <DocsSection title="Playground">
56
+ <AgentView entry={entry}>
57
+ <Playground entry={entry} props={need.props} children={need.children} />
58
+ </AgentView>
59
+ </DocsSection>
60
+
61
+ {!!entry.variants?.axes.length && (
62
+ <DocsSection title="Variants" lead={`From the ${entry.variants.from === "props" ? "props' types" : `${entry.variants.from}() config`}.`}>
63
+ <VariantMatrix entry={entry} props={need.props}>
64
+ {need.children === "" ? undefined : entry.name}
65
+ </VariantMatrix>
66
+ </DocsSection>
67
+ )}
68
+
69
+ {first && (
70
+ <DocsSection title="At each width">
71
+ <PreviewFrame src={`${preview}/${first}`} title={`${entry.name}: ${first}`} />
72
+ </DocsSection>
73
+ )}
74
+
75
+ <DocsSection title="Anatomy">
76
+ <Anatomy entry={entry} />
77
+ </DocsSection>
78
+
79
+ <DocsSection title="Props">
80
+ <PropsTable entry={entry} />
81
+ </DocsSection>
82
+
83
+ <AskClaude entry={entry} />
84
+ </>
85
+ );
86
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from "react";
2
+ import wordmark from "@ds/brand/logos/wordmark.svg";
3
+ import { DocsShell } from "../_blocks/shell";
4
+ import { BASE, components, patterns, rules, taxonomy, TITLE } from "../_data";
5
+
6
+ export default function DocsLayout({ children }: { children: ReactNode }) {
7
+ return (
8
+ <DocsShell
9
+ manifest={components}
10
+ rules={rules}
11
+ patterns={patterns}
12
+ terms={taxonomy}
13
+ title={TITLE}
14
+ logo={<img src={wordmark.src} alt="" className="h-6 w-auto" />}
15
+ basePath={BASE}
16
+ brand={[
17
+ { label: "Principles and language", href: `${BASE}/brand` },
18
+ { label: "Logo", href: `${BASE}/brand/logo` },
19
+ { label: "Imagery and icons", href: `${BASE}/brand/imagery` },
20
+ { label: "Voice and terms", href: `${BASE}/brand/voice` },
21
+ ]}
22
+ foundations={[
23
+ { label: "Colour", href: `${BASE}/colour` },
24
+ { label: "Type", href: `${BASE}/type` },
25
+ { label: "Scale", href: `${BASE}/scale` },
26
+ ]}
27
+ standards={[
28
+ { label: "Standards", href: `${BASE}/standards` },
29
+ { label: "Vocabulary", href: `${BASE}/vocabulary` },
30
+ ]}
31
+ >
32
+ {children}
33
+ </DocsShell>
34
+ );
35
+ }
@@ -0,0 +1,48 @@
1
+ import Link from "next/link";
2
+ import { CopyPage } from "../_components/copy-page";
3
+ import { DocsHeader, DocsSection } from "../_blocks/shell";
4
+ import { groupFamilies, slugOf } from "../_blocks/lib/manifest";
5
+ import { StatusBadge } from "../_blocks/lib/status";
6
+ import { BASE, components, provenance, TITLE } from "../_data";
7
+
8
+ export default function Overview() {
9
+ return (
10
+ <>
11
+ <DocsHeader
12
+ title={TITLE}
13
+ lead="The brand and the design system in one place, for the people who make the product and the agents that build it with them. Every page is built from the code."
14
+ provenance={provenance}
15
+ >
16
+ <p className="text-sm">
17
+ For agents: <a href={`${BASE}.md`} className="underline underline-offset-2">this site as markdown</a>, the whole
18
+ system in <a href={`${BASE}/md/context`} className="underline underline-offset-2">one file</a>, or the{" "}
19
+ <a href={`${BASE}/manifest/components.json`} className="underline underline-offset-2">manifest as data</a>. Add{" "}
20
+ <code className="font-mono text-xs">.md</code> to any page.
21
+ </p>
22
+ <CopyPage />
23
+ </DocsHeader>
24
+ {/* a component's parts (CardHeader, CardTitle) are named on its tile, not given tiles of their own */}
25
+ {groupFamilies(components).map((group) => (
26
+ <DocsSection key={group.category} title={group.category}>
27
+ <ul className="grid gap-3 sm:grid-cols-2">
28
+ {group.families.map(({ head: c, parts }) => (
29
+ <li key={c.source + c.export}>
30
+ <Link
31
+ href={`${BASE}/components/${slugOf(c)}`}
32
+ className="flex h-full flex-col gap-2 rounded-lg border border-border p-4 hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring"
33
+ >
34
+ <span className="flex items-center justify-between gap-2">
35
+ <span className="font-medium">{c.name}</span>
36
+ <StatusBadge status={c.status} />
37
+ </span>
38
+ {c.description && <span className="line-clamp-2 text-sm text-muted-foreground">{c.description}</span>}
39
+ {parts.length > 0 && <span className="font-mono text-xs text-muted-foreground">with {parts.map((p) => p.name).join(", ")}</span>}
40
+ </Link>
41
+ </li>
42
+ ))}
43
+ </ul>
44
+ </DocsSection>
45
+ ))}
46
+ </>
47
+ );
48
+ }
@@ -0,0 +1,23 @@
1
+ import { notFound } from "next/navigation";
2
+ import { CopyPage } from "../../../_components/copy-page";
3
+ import { Pattern } from "../../../_blocks/pattern";
4
+ import { DocsHeader } from "../../../_blocks/shell";
5
+ import { BASE, components, patterns, provenance } from "../../../_data";
6
+
7
+ export function generateStaticParams() {
8
+ return patterns.patterns.map((p) => ({ id: p.id }));
9
+ }
10
+
11
+ export default async function PatternPage({ params }: { params: Promise<{ id: string }> }) {
12
+ const { id } = await params;
13
+ const pattern = patterns.patterns.find((p) => p.id === id);
14
+ if (!pattern) notFound();
15
+ return (
16
+ <>
17
+ <DocsHeader title={pattern.name} lead={pattern.description} source={pattern.source} provenance={provenance}>
18
+ <CopyPage />
19
+ </DocsHeader>
20
+ <Pattern pattern={pattern} manifest={components} basePath={BASE} />
21
+ </>
22
+ );
23
+ }
@@ -0,0 +1,42 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { Rule } from "../../_blocks/rule";
3
+ import { DocsHeader, DocsSection } from "../../_blocks/shell";
4
+ import { provenance, rules } from "../../_data";
5
+
6
+ /** A demonstration for the rule that needs persuading: the same control, too small and big enough. */
7
+ function TouchTargets() {
8
+ return (
9
+ <div className="flex flex-wrap items-end gap-8 rounded-lg bg-muted p-6">
10
+ {[32, 48].map((size) => (
11
+ <figure key={size} className="flex flex-col items-center gap-2">
12
+ <span className="flex items-center justify-center border border-dashed border-destructive" style={{ width: size, height: size }}>
13
+ <span className="size-4 rounded-full bg-primary" />
14
+ </span>
15
+ <figcaption className="text-xs text-muted-foreground">
16
+ {size} px {size < 48 ? "is missed" : "is hit"}
17
+ </figcaption>
18
+ </figure>
19
+ ))}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ export default function Rules() {
25
+ const groups = (["brand", "interface"] as const).map((kind) => ({ kind, rules: rules.rules.filter((r) => r.kind === kind) }));
26
+ return (
27
+ <>
28
+ <DocsHeader title="Rules" lead="Brand and interface rules in one format, each with the check that proves it. Written for people; agents read the same rule." provenance={provenance}>
29
+ <CopyPage />
30
+ </DocsHeader>
31
+ {groups.map((g) => (
32
+ <DocsSection key={g.kind} title={g.kind === "brand" ? "Brand" : "Interface"}>
33
+ {g.rules.map((r) => (
34
+ <Rule key={r.id} rule={r}>
35
+ {r.id === "touch-target" ? <TouchTargets /> : null}
36
+ </Rule>
37
+ ))}
38
+ </DocsSection>
39
+ ))}
40
+ </>
41
+ );
42
+ }
@@ -0,0 +1,15 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { Scale } from "../../_blocks/scale";
3
+ import { DocsHeader } from "../../_blocks/shell";
4
+ import { provenance, tokens } from "../../_data";
5
+
6
+ export default function ScalePage() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Scale" lead="Space, size, radius, elevation and motion. Hover a motion row to play it." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <Scale tokens={tokens} />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,21 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { DocsHeader, DocsSection } from "../../_blocks/shell";
3
+ import { AdherenceSummary } from "../../_blocks/adherence-summary";
4
+ import { Standards } from "../../_blocks/standards";
5
+ import { adherence, baselines, provenance } from "../../_data";
6
+
7
+ export default function StandardsPage() {
8
+ return (
9
+ <>
10
+ <DocsHeader title="Standards" lead="The budgets and targets the project holds itself to, read from .mxa/stack.json, where the checks read them too." provenance={provenance}>
11
+ <CopyPage />
12
+ </DocsHeader>
13
+ <DocsSection title="Targets">
14
+ <Standards baselines={baselines} />
15
+ </DocsSection>
16
+ <DocsSection title="Adherence" lead="How closely the product follows the system, from @designtools/adherence. It informs; it never blocks.">
17
+ <AdherenceSummary report={adherence} />
18
+ </DocsSection>
19
+ </>
20
+ );
21
+ }
@@ -0,0 +1,15 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { DocsHeader } from "../../_blocks/shell";
3
+ import { TypeRamp } from "../../_blocks/type-ramp";
4
+ import { provenance, tokens } from "../../_data";
5
+
6
+ export default function Type() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Type" lead="A size is never set without its leading." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <TypeRamp tokens={tokens} />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ import { CopyPage } from "../../_components/copy-page";
2
+ import { Glossary } from "../../_blocks/glossary";
3
+ import { DocsHeader } from "../../_blocks/shell";
4
+ import { provenance, taxonomy } from "../../_data";
5
+
6
+ export default function Vocabulary() {
7
+ return (
8
+ <>
9
+ <DocsHeader title="Vocabulary" lead="Every name in the system. Search by a wrong name and it finds the right one." provenance={provenance}>
10
+ <CopyPage />
11
+ </DocsHeader>
12
+ <Glossary terms={taxonomy.terms} />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { usePathname } from "next/navigation";
4
+ import { useState } from "react";
5
+
6
+ /** Copies this page's markdown, for people handing it to an assistant. The same markdown any agent gets at `.md`. */
7
+ export function CopyPage() {
8
+ const path = usePathname();
9
+ const [state, setState] = useState<"idle" | "copied" | "failed">("idle");
10
+ const copy = async () => {
11
+ try {
12
+ const res = await fetch(`${path.replace(/\/$/, "")}.md`);
13
+ await navigator.clipboard.writeText(await res.text());
14
+ setState("copied");
15
+ } catch {
16
+ setState("failed");
17
+ }
18
+ setTimeout(() => setState("idle"), 1500);
19
+ };
20
+ return (
21
+ <button
22
+ type="button"
23
+ onClick={copy}
24
+ className="min-h-[var(--docs-target,2.75rem)] self-start rounded-md border border-border px-2.5 py-1 text-xs hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring"
25
+ >
26
+ {state === "copied" ? "Copied" : state === "failed" ? "Could not copy" : "Copy page as markdown"}
27
+ <span className="sr-only" aria-live="polite">
28
+ {state === "copied" ? "Page copied" : ""}
29
+ </span>
30
+ </button>
31
+ );
32
+ }