@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.
Files changed (167) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -2
  4. package/dist/a11y-panel.d.ts +58 -0
  5. package/dist/a11y-panel.js +75 -0
  6. package/dist/adherence-summary.d.ts +9 -0
  7. package/dist/adherence-summary.js +27 -0
  8. package/dist/agent-view.d.ts +19 -0
  9. package/dist/agent-view.js +23 -0
  10. package/dist/anatomy.d.ts +12 -0
  11. package/dist/anatomy.js +38 -0
  12. package/dist/ask-claude.d.ts +14 -0
  13. package/dist/ask-claude.js +40 -0
  14. package/dist/cli.js +322 -0
  15. package/dist/code-view.d.ts +17 -0
  16. package/dist/code-view.js +42 -0
  17. package/dist/examples.d.ts +25 -0
  18. package/dist/examples.js +48 -0
  19. package/dist/glossary.d.ts +5 -0
  20. package/dist/glossary.js +14 -0
  21. package/dist/imagery.d.ts +6 -0
  22. package/dist/imagery.js +17 -0
  23. package/dist/index.d.ts +34 -0
  24. package/dist/index.js +33 -0
  25. package/dist/lib/adherence-types.d.ts +168 -0
  26. package/dist/lib/adherence-types.js +13 -0
  27. package/dist/lib/boundary.d.ts +16 -0
  28. package/dist/lib/boundary.js +15 -0
  29. package/dist/lib/contrast.d.ts +8 -0
  30. package/dist/lib/contrast.js +42 -0
  31. package/dist/lib/cx.d.ts +24 -0
  32. package/dist/lib/cx.js +26 -0
  33. package/dist/lib/jsx.d.ts +6 -0
  34. package/dist/lib/jsx.js +29 -0
  35. package/dist/lib/lookup.d.ts +35 -0
  36. package/dist/lib/lookup.js +41 -0
  37. package/dist/lib/manifest-types.d.ts +253 -0
  38. package/dist/lib/manifest-types.js +14 -0
  39. package/dist/lib/manifest.d.ts +98 -0
  40. package/dist/lib/manifest.js +173 -0
  41. package/dist/lib/markdown.d.ts +50 -0
  42. package/dist/lib/markdown.js +286 -0
  43. package/dist/lib/measure.d.ts +12 -0
  44. package/dist/lib/measure.js +62 -0
  45. package/dist/lib/pseudo.d.ts +11 -0
  46. package/dist/lib/pseudo.js +71 -0
  47. package/dist/lib/registry.d.ts +21 -0
  48. package/dist/lib/registry.js +11 -0
  49. package/dist/lib/search.d.ts +44 -0
  50. package/dist/lib/search.js +62 -0
  51. package/dist/lib/standards.d.ts +17 -0
  52. package/dist/lib/standards.js +81 -0
  53. package/dist/lib/status.d.ts +7 -0
  54. package/dist/lib/status.js +17 -0
  55. package/dist/lib/text.d.ts +8 -0
  56. package/dist/lib/text.js +9 -0
  57. package/dist/lib/ui.d.ts +100 -0
  58. package/dist/lib/ui.js +89 -0
  59. package/dist/lib/vision.d.ts +18 -0
  60. package/dist/lib/vision.js +27 -0
  61. package/dist/logo-usage.d.ts +14 -0
  62. package/dist/logo-usage.js +45 -0
  63. package/dist/next/config.d.ts +85 -0
  64. package/dist/next/config.js +1 -0
  65. package/dist/next/index.d.ts +62 -0
  66. package/dist/next/index.js +63 -0
  67. package/dist/next/markdown.d.ts +20 -0
  68. package/dist/next/markdown.js +95 -0
  69. package/dist/next/pages.d.ts +6 -0
  70. package/dist/next/pages.js +122 -0
  71. package/dist/next/prose.d.ts +9 -0
  72. package/dist/next/prose.js +37 -0
  73. package/dist/next/proxy.d.ts +19 -0
  74. package/dist/next/proxy.js +56 -0
  75. package/dist/next/site.d.ts +53 -0
  76. package/dist/next/site.js +136 -0
  77. package/dist/page-actions.d.ts +9 -0
  78. package/dist/page-actions.js +32 -0
  79. package/dist/pattern.d.ts +8 -0
  80. package/dist/pattern.js +19 -0
  81. package/dist/playground.d.ts +62 -0
  82. package/dist/playground.js +111 -0
  83. package/dist/preview-frame.d.ts +31 -0
  84. package/dist/preview-frame.js +63 -0
  85. package/dist/props-table.d.ts +10 -0
  86. package/dist/props-table.js +45 -0
  87. package/dist/rule.d.ts +12 -0
  88. package/dist/rule.js +32 -0
  89. package/dist/scale.d.ts +9 -0
  90. package/dist/scale.js +58 -0
  91. package/dist/search.d.ts +16 -0
  92. package/dist/search.js +46 -0
  93. package/dist/shell.d.ts +89 -0
  94. package/dist/shell.js +199 -0
  95. package/dist/standards.d.ts +13 -0
  96. package/dist/standards.js +19 -0
  97. package/dist/styles.css +3519 -0
  98. package/dist/swatches.d.ts +28 -0
  99. package/dist/swatches.js +94 -0
  100. package/dist/theme-switch.d.ts +20 -0
  101. package/dist/theme-switch.js +68 -0
  102. package/dist/type-ramp.d.ts +10 -0
  103. package/dist/type-ramp.js +31 -0
  104. package/dist/usage.d.ts +13 -0
  105. package/dist/usage.js +55 -0
  106. package/dist/variant-matrix.d.ts +15 -0
  107. package/dist/variant-matrix.js +53 -0
  108. package/dist/voice-terms.d.ts +5 -0
  109. package/dist/voice-terms.js +13 -0
  110. package/dist/workbench.d.ts +33 -0
  111. package/dist/workbench.js +273 -0
  112. package/package.json +107 -3
  113. package/src/a11y-panel.tsx +297 -0
  114. package/src/adherence-summary.tsx +134 -0
  115. package/src/agent-view.tsx +54 -0
  116. package/src/anatomy.tsx +114 -0
  117. package/src/ask-claude.tsx +90 -0
  118. package/src/code-view.tsx +112 -0
  119. package/src/css.d.ts +1 -0
  120. package/src/examples.tsx +141 -0
  121. package/src/glossary.tsx +71 -0
  122. package/src/imagery.tsx +64 -0
  123. package/src/index.ts +34 -0
  124. package/src/lib/adherence-types.ts +181 -0
  125. package/src/lib/boundary.tsx +21 -0
  126. package/src/lib/contrast.ts +44 -0
  127. package/src/lib/cx.ts +30 -0
  128. package/src/lib/jsx.ts +29 -0
  129. package/src/lib/lookup.tsx +49 -0
  130. package/src/lib/manifest-types.ts +284 -0
  131. package/src/lib/manifest.ts +228 -0
  132. package/src/lib/markdown.ts +288 -0
  133. package/src/lib/measure.tsx +101 -0
  134. package/src/lib/pseudo.ts +74 -0
  135. package/src/lib/registry.tsx +29 -0
  136. package/src/lib/search.ts +99 -0
  137. package/src/lib/standards.ts +82 -0
  138. package/src/lib/status.tsx +32 -0
  139. package/src/lib/text.tsx +27 -0
  140. package/src/lib/ui.tsx +291 -0
  141. package/src/lib/vision.tsx +44 -0
  142. package/src/logo-usage.tsx +140 -0
  143. package/src/next/config.ts +80 -0
  144. package/src/next/index.tsx +100 -0
  145. package/src/next/markdown.ts +119 -0
  146. package/src/next/pages.tsx +370 -0
  147. package/src/next/prose.tsx +87 -0
  148. package/src/next/proxy.ts +55 -0
  149. package/src/next/site.ts +173 -0
  150. package/src/page-actions.tsx +92 -0
  151. package/src/pattern.tsx +67 -0
  152. package/src/playground.tsx +352 -0
  153. package/src/preview-frame.tsx +131 -0
  154. package/src/props-table.tsx +154 -0
  155. package/src/rule.tsx +116 -0
  156. package/src/scale.tsx +175 -0
  157. package/src/search.tsx +155 -0
  158. package/src/shell.tsx +555 -0
  159. package/src/standards.tsx +58 -0
  160. package/src/styles.css +155 -0
  161. package/src/swatches.tsx +287 -0
  162. package/src/theme-switch.tsx +90 -0
  163. package/src/type-ramp.tsx +150 -0
  164. package/src/usage.tsx +148 -0
  165. package/src/variant-matrix.tsx +195 -0
  166. package/src/voice-terms.tsx +46 -0
  167. package/src/workbench.tsx +584 -0
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "../styles.css";
3
+ import { notFound } from "next/navigation";
4
+ import { findBySlug, slugOf } from "../lib/manifest.js";
5
+ import { PreviewExample, PreviewSurface } from "../preview-frame.js";
6
+ import { DocsShell } from "../shell.js";
7
+ import { manifestFiles, manifestRoute, markdownRoute } from "./markdown.js";
8
+ import { DocsPageView } from "./pages.js";
9
+ import { allSlugs, metaOf, navOf, resolve, siteOf } from "./site.js";
10
+ export { markdownFor } from "./markdown.js";
11
+ export { docsMatcher, docsProxy, prefersMarkdown } from "./proxy.js";
12
+ /**
13
+ * A whole docs site from one config, for the Next App Router. Each export is
14
+ * what one route file re-exports, so a project's docs are a handful of
15
+ * one-line files:
16
+ *
17
+ * - `(docs)/layout.tsx`: `export default docs.Layout`
18
+ * - `(docs)/[[...slug]]/page.tsx`: `export default docs.Page`, with `generateStaticParams` and `generateMetadata`
19
+ * - `md/[[...path]]/route.ts`: `export const GET = docs.markdown`
20
+ * - `manifest/[file]/route.ts`: `export const GET = docs.manifest`, with `generateStaticParams = docs.manifestParams`
21
+ * - `preview/[slug]/[example]/page.tsx`: `export default docs.Preview`
22
+ *
23
+ * The same files serve the docs inside a product's app or as a standalone app.
24
+ */
25
+ export function createDocs(config) {
26
+ const site = siteOf(config);
27
+ const nav = navOf(site);
28
+ const Registry = config.registry;
29
+ function Layout({ children }) {
30
+ return (_jsx(Registry, { children: _jsx(DocsShell, { manifest: site.components, rules: site.rules, patterns: site.patterns, terms: site.taxonomy, title: site.title, logo: config.logo, basePath: site.base, brand: nav.brand, foundations: nav.foundations, standards: nav.standards, hero: config.hero, children: children }) }));
31
+ }
32
+ async function Page({ params }) {
33
+ const page = resolve(site, (await params).slug);
34
+ if (!page)
35
+ notFound();
36
+ return _jsx(DocsPageView, { site: site, page: page });
37
+ }
38
+ async function generateMetadata({ params }) {
39
+ const page = resolve(site, (await params).slug);
40
+ return page ? metaOf(site, page) : {};
41
+ }
42
+ /** One example on its own, outside the shell: what the preview frame shows, and the target for visual regression. */
43
+ async function Preview({ params, searchParams }) {
44
+ const { slug, example } = await params;
45
+ const { theme } = await searchParams;
46
+ const entry = findBySlug(site.components, slug);
47
+ if (!entry?.examples)
48
+ notFound();
49
+ return (_jsx(Registry, { children: _jsx(PreviewSurface, { theme: theme, children: _jsx(PreviewExample, { entry: entry, example: decodeURIComponent(example) }) }) }));
50
+ }
51
+ return {
52
+ site,
53
+ Layout,
54
+ Page,
55
+ generateMetadata,
56
+ generateStaticParams: () => allSlugs(site).map((slug) => ({ slug })),
57
+ Preview,
58
+ previewParams: () => site.components.components.flatMap((c) => c.examples ? [c.examples.canonical, ...c.examples.do, ...c.examples.dont, ...c.examples.other].filter(Boolean).map((e) => ({ slug: slugOf(c), example: e.name })) : []),
59
+ markdown: markdownRoute(site),
60
+ manifest: manifestRoute(site),
61
+ manifestParams: () => Object.keys(manifestFiles(site)).map((name) => ({ file: `${name}.json` })),
62
+ };
63
+ }
@@ -0,0 +1,20 @@
1
+ import { type Site } from "./site.js";
2
+ /**
3
+ * The markdown form of a docs page, generated from the manifest rather than
4
+ * converted from HTML, or nothing when there is no such page. `context` is the
5
+ * whole system in one file.
6
+ */
7
+ export declare function markdownFor(site: Site, path: string): string | undefined;
8
+ /** The route handler for `md/[[...path]]`: the page's markdown with `Vary: Accept` and its canonical link, or a markdown 404. */
9
+ export declare function markdownRoute(site: Site): (_: Request, { params }: {
10
+ params: Promise<{
11
+ path?: string[];
12
+ }>;
13
+ }) => Promise<Response>;
14
+ /** The route handler for `manifest/[file]`: each manifest file as JSON, so an agent outside the repo gets the exact data. */
15
+ export declare function manifestRoute(site: Site): (_: Request, { params }: {
16
+ params: Promise<{
17
+ file: string;
18
+ }>;
19
+ }) => Promise<Response>;
20
+ export declare function manifestFiles(site: Site): Record<string, unknown>;
@@ -0,0 +1,95 @@
1
+ import { groupByCategory, slugOf } from "../lib/manifest.js";
2
+ import { adherenceMarkdown, assetsMarkdown, coloursMarkdown, componentMarkdown, editorialHeader, generatedHeader, glossaryMarkdown, manifestLinks, patternMarkdown, ruleMarkdown, scaleMarkdown, standardsMarkdown, systemMarkdown, typeMarkdown, } from "../lib/markdown.js";
3
+ import { navOf, resolve } from "./site.js";
4
+ /**
5
+ * The markdown form of a docs page, generated from the manifest rather than
6
+ * converted from HTML, or nothing when there is no such page. `context` is the
7
+ * whole system in one file.
8
+ */
9
+ export function markdownFor(site, path) {
10
+ const { base, title, components, tokens, rules, patterns, taxonomy, assets, config } = site;
11
+ const generated = (md) => generatedHeader(config.provenance ?? "") + md;
12
+ if (path === "context")
13
+ return systemMarkdown({ components, tokens, rules: rules.rules, patterns: patterns.patterns, terms: taxonomy.terms }, title, config.provenance ?? "");
14
+ const page = resolve(site, path ? path.split("/") : []);
15
+ if (!page)
16
+ return undefined;
17
+ switch (page.kind) {
18
+ case "overview": {
19
+ const nav = navOf(site);
20
+ const links = (list) => list.map((l) => `[${l.label}](${l.href}.md)`).join(", ");
21
+ return generated([
22
+ `# ${title}`,
23
+ "",
24
+ "The brand and the design system in one place. Every page here has a markdown form: add `.md` to its URL.",
25
+ "",
26
+ "## Pages",
27
+ "",
28
+ ...(nav.brand.length ? [`- Brand: ${links(nav.brand)}`] : []),
29
+ `- Foundations: ${links(nav.foundations)}`,
30
+ ...(rules.rules.length ? [`- [Rules](${base}/rules.md)`] : []),
31
+ ...patterns.patterns.map((p) => `- Pattern: [${p.name}](${base}/patterns/${p.id}.md)`),
32
+ ...groupByCategory(components).flatMap((g) => g.components.map((c) => `- ${g.category}: [${c.name}](${base}/components/${slugOf(c)}.md), ${c.status ?? "status not set"}`)),
33
+ ...(nav.standards.length ? [`- Standards: ${links(nav.standards)}`] : []),
34
+ `- Everything in [one file](${base}/context.md)`,
35
+ "",
36
+ manifestLinks(`${base}/manifest`),
37
+ ].join("\n"));
38
+ }
39
+ case "editorial":
40
+ return `${editorialHeader(page.page.by)}# ${page.page.title}\n\n${page.page.lead ? `${page.page.lead}\n\n` : ""}${page.page.body}\n`;
41
+ case "logo":
42
+ return generated(assetsMarkdown(assets.assets.filter((a) => a.kind === "logo")) +
43
+ "\n" +
44
+ rules.rules
45
+ .filter((r) => r.kind === "brand")
46
+ .map(ruleMarkdown)
47
+ .join("\n"));
48
+ case "imagery":
49
+ return generated(assetsMarkdown(assets.assets.filter((a) => a.kind !== "logo")));
50
+ case "voice":
51
+ return generated(glossaryMarkdown(taxonomy.terms.filter((t) => t.kind === "product" || t.wrong?.length)));
52
+ case "colour":
53
+ return generated(coloursMarkdown(tokens));
54
+ case "type":
55
+ return generated(typeMarkdown(tokens));
56
+ case "scale":
57
+ return generated(scaleMarkdown(tokens));
58
+ case "rules":
59
+ return generated(["# Rules", "", ...rules.rules.map(ruleMarkdown)].join("\n"));
60
+ case "pattern":
61
+ return generated(patternMarkdown(page.pattern, 1));
62
+ case "component":
63
+ return generated(componentMarkdown(page.entry, 1));
64
+ case "standards":
65
+ return generated(`${config.baselines ? standardsMarkdown(config.baselines) : ""}\n${config.adherence ? adherenceMarkdown(config.adherence) : ""}`);
66
+ case "vocabulary":
67
+ return generated(glossaryMarkdown(taxonomy.terms));
68
+ }
69
+ }
70
+ /** The route handler for `md/[[...path]]`: the page's markdown with `Vary: Accept` and its canonical link, or a markdown 404. */
71
+ export function markdownRoute(site) {
72
+ return async function GET(_, { params }) {
73
+ const path = ((await params).path ?? []).join("/").replace(/\.md$/, "");
74
+ const page = markdownFor(site, path);
75
+ const canonical = `${site.base}${path ? `/${path}` : ""}`;
76
+ return new Response(page ?? `# Not found\n\nThere is no page at \`${canonical}\`. The index is at [${site.base}.md](${site.base}.md).\n`, {
77
+ status: page ? 200 : 404,
78
+ headers: { "content-type": "text/markdown; charset=utf-8", vary: "Accept", link: `<${canonical}>; rel="canonical"` },
79
+ });
80
+ };
81
+ }
82
+ /** The route handler for `manifest/[file]`: each manifest file as JSON, so an agent outside the repo gets the exact data. */
83
+ export function manifestRoute(site) {
84
+ const files = manifestFiles(site);
85
+ return async function GET(_, { params }) {
86
+ const { file } = await params;
87
+ const data = files[file.replace(/\.json$/, "")];
88
+ if (!data)
89
+ return new Response(`# Not found\n\nNo manifest file called ${file}.\n`, { status: 404, headers: { "content-type": "text/markdown; charset=utf-8" } });
90
+ return Response.json(data);
91
+ };
92
+ }
93
+ export function manifestFiles(site) {
94
+ return Object.fromEntries(Object.entries(site.config.manifest).filter(([, v]) => v !== undefined));
95
+ }
@@ -0,0 +1,6 @@
1
+ import { type PageRef, type Site } from "./site.js";
2
+ /** The page for a reference, composed of blocks. */
3
+ export declare function DocsPageView({ site, page }: {
4
+ site: Site;
5
+ page: PageRef;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowRight, ArrowUpRight } from "lucide-react";
3
+ import Link from "next/link";
4
+ import { AdherenceSummary } from "../adherence-summary.js";
5
+ import { Anatomy } from "../anatomy.js";
6
+ import { Examples } from "../examples.js";
7
+ import { Glossary } from "../glossary.js";
8
+ import { Imagery } from "../imagery.js";
9
+ import { groupFamilies, slugOf } from "../lib/manifest.js";
10
+ import { StatusBadge } from "../lib/status.js";
11
+ import { LogoUsage } from "../logo-usage.js";
12
+ import { PageActions } from "../page-actions.js";
13
+ import { Pattern } from "../pattern.js";
14
+ import { PreviewFrame } from "../preview-frame.js";
15
+ import { PropsTable } from "../props-table.js";
16
+ import { Rule } from "../rule.js";
17
+ import { Scale } from "../scale.js";
18
+ import { DocsFooter, DocsHeader, DocsSection } from "../shell.js";
19
+ import { Standards } from "../standards.js";
20
+ import { PageContrast, RampSwatches, SemanticSwatches } from "../swatches.js";
21
+ import { TypeRamp } from "../type-ramp.js";
22
+ import { Usage } from "../usage.js";
23
+ import { VariantMatrix } from "../variant-matrix.js";
24
+ import { VoiceTerms } from "../voice-terms.js";
25
+ import { Workbench } from "../workbench.js";
26
+ import { Markdown } from "./prose.js";
27
+ import { COPY } from "./site.js";
28
+ /** Every block a page renders, by the name `config.blocks` replaces it under. */
29
+ const DEFAULT_BLOCKS = {
30
+ AdherenceSummary,
31
+ Anatomy,
32
+ Examples,
33
+ Glossary,
34
+ Imagery,
35
+ LogoUsage,
36
+ PageContrast,
37
+ Pattern,
38
+ PreviewFrame,
39
+ PropsTable,
40
+ RampSwatches,
41
+ Rule,
42
+ Scale,
43
+ SemanticSwatches,
44
+ Standards,
45
+ TypeRamp,
46
+ Usage,
47
+ VariantMatrix,
48
+ VoiceTerms,
49
+ Workbench,
50
+ };
51
+ function blocksOf(site) {
52
+ return { ...DEFAULT_BLOCKS, ...site.config.blocks };
53
+ }
54
+ /** The page for a reference, composed of blocks. */
55
+ export function DocsPageView({ site, page }) {
56
+ const B = blocksOf(site);
57
+ const { base, config } = site;
58
+ const footer = _jsx(DocsFooter, { provenance: config.provenance });
59
+ const header = (title, lead) => (_jsx(DocsHeader, { title: title, lead: lead, children: _jsx(PageActions, { basePath: base }) }));
60
+ switch (page.kind) {
61
+ case "overview":
62
+ return _jsx(Overview, { site: site });
63
+ case "editorial":
64
+ return (_jsxs(_Fragment, { children: [header(page.page.title, page.page.lead), _jsx(Markdown, { text: page.page.body }), _jsx(DocsFooter, { editorial: page.page.by })] }));
65
+ case "logo":
66
+ return (_jsxs(_Fragment, { children: [header(COPY.logo.title, COPY.logo.lead), _jsx(B.LogoUsage, { assets: site.assets.assets, rules: site.rules.rules, rulesHref: `${base}/rules` }), footer] }));
67
+ case "imagery":
68
+ return (_jsxs(_Fragment, { children: [header(COPY.imagery.title, COPY.imagery.lead), _jsx(B.Imagery, { assets: site.assets.assets }), footer] }));
69
+ case "voice":
70
+ return (_jsxs(_Fragment, { children: [header(COPY.voice.title, COPY.voice.lead), _jsx(B.VoiceTerms, { terms: site.taxonomy.terms }), footer] }));
71
+ case "colour":
72
+ return (_jsxs(_Fragment, { children: [header(COPY.colour.title, COPY.colour.lead), _jsx(DocsSection, { title: "Semantic colours", lead: "Each pair is measured on the colours the browser paints, in each mode.", children: _jsx(B.SemanticSwatches, { tokens: site.tokens }) }), _jsx(DocsSection, { title: "On the page", 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.", children: _jsx(B.PageContrast, { tokens: site.tokens }) }), _jsx(DocsSection, { title: "Ramps", children: _jsx(B.RampSwatches, { tokens: site.tokens }) }), footer] }));
73
+ case "type":
74
+ return (_jsxs(_Fragment, { children: [header(COPY.type.title, COPY.type.lead), _jsx(B.TypeRamp, { tokens: site.tokens }), footer] }));
75
+ case "scale":
76
+ return (_jsxs(_Fragment, { children: [header(COPY.scale.title, COPY.scale.lead), _jsx(B.Scale, { tokens: site.tokens }), footer] }));
77
+ case "rules":
78
+ return (_jsxs(_Fragment, { children: [header(COPY.rules.title, COPY.rules.lead), ["brand", "interface"].map((kind) => {
79
+ const rules = site.rules.rules.filter((r) => r.kind === kind);
80
+ return rules.length ? (_jsx(DocsSection, { title: kind === "brand" ? "Brand" : "Interface", children: rules.map((r) => (_jsx(B.Rule, { rule: r, children: config.demonstrations?.[r.id] }, r.id))) }, kind)) : null;
81
+ }), footer] }));
82
+ case "standards":
83
+ return (_jsxs(_Fragment, { children: [header(COPY.standards.title, COPY.standards.lead), config.baselines && (_jsx(DocsSection, { title: "Targets", children: _jsx(B.Standards, { baselines: config.baselines }) })), config.adherence && (_jsx(DocsSection, { title: "Adherence", lead: "How closely the product follows the system, from @designtools/adherence. It informs; it never blocks.", children: _jsx(B.AdherenceSummary, { report: config.adherence }) })), footer] }));
84
+ case "vocabulary":
85
+ return (_jsxs(_Fragment, { children: [header(COPY.vocabulary.title, COPY.vocabulary.lead), _jsx(B.Glossary, { terms: site.taxonomy.terms }), footer] }));
86
+ case "pattern":
87
+ return (_jsxs(_Fragment, { children: [header(page.pattern.name, page.pattern.description), _jsx(B.Pattern, { pattern: page.pattern, manifest: site.components, basePath: base }), _jsx(DocsFooter, { source: page.pattern.source, provenance: config.provenance })] }));
88
+ case "component":
89
+ return _jsx(ComponentPage, { site: site, blocks: B, entry: page.entry });
90
+ }
91
+ }
92
+ function ComponentPage({ site, blocks: B, entry }) {
93
+ const { base, config } = site;
94
+ // A part (TabsPanel, CardHeader) renders inside its family's root, so it is shown there, never alone:
95
+ // a part rendered on its own can throw for want of the root's context.
96
+ const head = entry.category ? undefined : site.components.components.find((c) => c.source === entry.source && c.category);
97
+ if (head)
98
+ return (_jsxs(_Fragment, { children: [_jsx(DocsHeader, { title: entry.name, lead: entry.description ?? `A part of ${head.name}, used inside it.`, children: _jsx(PageActions, { basePath: base }) }), _jsx(DocsSection, { title: "Part of", lead: `Its examples, variants and anatomy are on ${head.name}'s page, where it renders inside the whole.`, children: _jsxs(Link, { href: `${base}/components/${slugOf(head)}`, className: "group flex max-w-md items-center justify-between gap-3 border-t border-border py-5 focus-visible:outline-2 focus-visible:outline-ring", children: [_jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-xl tracking-tight underline-offset-4 group-hover:underline", children: head.name }), head.description && _jsx("span", { className: "line-clamp-2 text-sm text-muted-foreground", children: head.description })] }), _jsx(ArrowRight, { "aria-hidden": true, className: "size-5 shrink-0 text-muted-foreground" })] }) }), _jsx(DocsSection, { title: "Props", children: _jsx(B.PropsTable, { entry: entry }) }), _jsx(DocsFooter, { source: entry.source, provenance: config.provenance })] }));
99
+ const setup = config.workbench?.[entry.name] ?? {};
100
+ const preview = `${base}/preview/${slugOf(entry)}`;
101
+ const first = entry.examples?.canonical?.name;
102
+ // the brand's named backgrounds (--surface-<name>), offered on the workbench's stage
103
+ const backgrounds = site.tokens.tokens.map((t) => t.name.slice(2)).filter((n) => /^surface-[a-z]/.test(n) && !n.endsWith("-foreground"));
104
+ return (_jsxs(_Fragment, { children: [_jsx(DocsHeader, { title: entry.name, lead: entry.description, children: _jsx(PageActions, { basePath: base }) }), _jsx(DocsSection, { title: "Usage", children: _jsx(B.Usage, { entry: entry, manifest: site.components, basePath: base }) }), _jsx(B.Workbench, { entry: entry, props: setup.props, content: setup.content, wrap: setup.wrap, backgrounds: backgrounds, previewHref: first && `${preview}/${first}`, children: setup.children }), _jsx(DocsSection, { title: "Anatomy", children: _jsx(B.Anatomy, { entry: entry }) }), _jsx(DocsSection, { title: "Examples", lead: "The usual use first, then each do beside its don't. Every one is the real component.", children: _jsx(B.Examples, { entry: entry, previewHref: preview }) }), !!entry.variants?.axes.length && (_jsx(DocsSection, { title: "Variants", lead: `Every combination, from the ${entry.variants.from === "props" ? "props' types" : `${entry.variants.from}() config`}, then each state.`, children: _jsx(B.VariantMatrix, { entry: entry, props: setup.props, children: setup.children === "" ? undefined : (setup.content ?? entry.name) }) })), first && (_jsx(DocsSection, { title: "At each width", children: _jsx(B.PreviewFrame, { src: `${preview}/${first}`, title: `${entry.name}: ${first}` }) })), _jsx(DocsFooter, { source: entry.source, provenance: config.provenance })] }));
105
+ }
106
+ const row = "group flex h-full flex-col gap-2 border-t border-border py-5 focus-visible:outline-2 focus-visible:outline-ring";
107
+ /** The overview: where to start, then every component by category. Below the hero when there is one. */
108
+ function Overview({ site }) {
109
+ const { base, config } = site;
110
+ const families = groupFamilies(site.components);
111
+ const count = families.reduce((n, g) => n + g.families.length, 0);
112
+ const brandFirst = site.editorial.find((p) => (p.section ?? "brand") === "brand");
113
+ const starts = [
114
+ brandFirst || site.shows("logo") ? { title: "Brand", href: `${base}/${brandFirst?.slug ?? "brand/logo"}`, line: "Principles, the logo, imagery and the words we use." } : null,
115
+ { title: "Foundations", href: `${base}/colour`, line: "Colour, type and the scale, rendered from the tokens." },
116
+ site.rules.rules.length ? { title: "Rules", href: `${base}/rules`, line: `${site.rules.rules.length} rules, brand and interface, each with its check.` } : null,
117
+ site.patterns.patterns.length
118
+ ? { title: "Patterns", href: `${base}/patterns/${site.patterns.patterns[0].id}`, line: `${site.patterns.patterns.length} recurring screens, made of the real components.` }
119
+ : null,
120
+ ].filter((s) => !!s);
121
+ return (_jsxs(_Fragment, { children: [!config.hero && (_jsx(DocsHeader, { title: site.title, lead: config.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.", children: _jsx(PageActions, { basePath: base }) })), _jsx("ol", { className: "flex max-w-3xl flex-col", children: starts.map(({ title, href, line }, n) => (_jsx("li", { className: "border-t border-border first:border-t-0", children: _jsxs(Link, { href: href, className: "group grid grid-cols-[3rem_minmax(0,1fr)_auto] items-baseline gap-x-4 py-6 focus-visible:outline-2 focus-visible:outline-ring sm:grid-cols-[4rem_minmax(0,1fr)_auto]", children: [_jsx("span", { className: "text-lg text-muted-foreground tabular-nums", children: String(n + 1).padStart(2, "0") }), _jsxs("span", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "text-2xl leading-snug tracking-tight underline-offset-[6px] group-hover:underline md:text-3xl", children: title }), _jsx("span", { className: "text-lg leading-relaxed text-muted-foreground", children: line })] }), _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-5 self-center text-muted-foreground transition-transform duration-(--dt-duration-fast) group-hover:translate-x-0.5 group-hover:-translate-y-0.5" })] }) }, title))) }), site.shows("components") && (_jsx(DocsSection, { title: "Components", lead: `${count} components, grouped as the navigation groups them. Each page has its usage, a workbench, anatomy, examples and variants.`, children: _jsx("div", { className: "flex flex-col gap-12", children: families.map((group) => (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h3", { className: "text-xs font-medium tracking-wide text-muted-foreground uppercase", children: group.category }), _jsx("ul", { className: "grid gap-x-10 sm:grid-cols-2 xl:grid-cols-3", children: group.families.map(({ head: c, parts }) => (_jsx("li", { children: _jsxs(Link, { href: `${base}/components/${slugOf(c)}`, className: row, children: [_jsxs("span", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xl tracking-tight underline-offset-4 group-hover:underline", children: c.name }), _jsx(StatusBadge, { status: c.status })] }), c.description && _jsx("span", { className: "line-clamp-2 text-sm text-muted-foreground", children: c.description }), parts.length > 0 && _jsxs("span", { className: "mt-auto font-mono text-xs text-muted-foreground", children: ["with ", parts.map((p) => p.name).join(", ")] })] }) }, c.source + c.export))) })] }, group.category))) }) })), _jsx(DocsFooter, { provenance: config.provenance })] }));
122
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The few markdown shapes editorial pages use: `##` sections, paragraphs and bullet lists, with bold
3
+ * and code. Each `##` is a docs section, so it reads at the same size as a generated page and is
4
+ * listed in "On this page". A list whose every item opens in bold ("**Say it plainly.** …") is a set
5
+ * of principles, numbered, each a heading over its sentence.
6
+ */
7
+ export declare function Markdown({ text }: {
8
+ text: string;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DocsSection } from "../shell.js";
3
+ /**
4
+ * The few markdown shapes editorial pages use: `##` sections, paragraphs and bullet lists, with bold
5
+ * and code. Each `##` is a docs section, so it reads at the same size as a generated page and is
6
+ * listed in "On this page". A list whose every item opens in bold ("**Say it plainly.** …") is a set
7
+ * of principles, numbered, each a heading over its sentence.
8
+ */
9
+ export function Markdown({ text }) {
10
+ const sections = [{ blocks: [] }];
11
+ for (const b of text.split(/\n{2,}/)) {
12
+ if (b.startsWith("## "))
13
+ sections.push({ title: b.slice(3), blocks: [] });
14
+ else
15
+ sections[sections.length - 1].blocks.push(b);
16
+ }
17
+ return (_jsx(_Fragment, { children: sections.map((s, i) => s.title ? (_jsx(DocsSection, { title: s.title, children: _jsx(Blocks, { blocks: s.blocks }) }, s.title)) : (s.blocks.length > 0 && _jsx(Blocks, { blocks: s.blocks }, i))) }));
18
+ }
19
+ function Blocks({ blocks }) {
20
+ return (_jsx("div", { className: "flex flex-col gap-5", children: blocks.map((b, i) => {
21
+ const lines = b.split("\n");
22
+ if (lines.every((l) => l.startsWith("- "))) {
23
+ const items = lines.map((l) => l.slice(2));
24
+ const principles = items.every((l) => /^\*\*[^*]+\*\*/.test(l));
25
+ if (principles)
26
+ return (_jsx("ol", { className: "flex max-w-3xl flex-col gap-12", children: items.map((l, n) => {
27
+ const [, head, rest] = /^\*\*([^*]+)\*\*\s*(.*)$/.exec(l) ?? [];
28
+ return (_jsxs("li", { className: "grid grid-cols-[3rem_minmax(0,1fr)] gap-x-4 gap-y-3 sm:grid-cols-[4rem_minmax(0,1fr)]", children: [_jsx("span", { className: "pt-1 text-lg text-muted-foreground tabular-nums", children: String(n + 1).padStart(2, "0") }), _jsxs("span", { className: "flex flex-col gap-3", children: [_jsx("span", { className: "text-2xl leading-snug tracking-tight md:text-3xl", children: head.replace(/\.$/, "") }), _jsx("span", { className: "text-lg leading-relaxed text-canvas-foreground/75", children: inline(rest) })] })] }, l));
29
+ }) }, i));
30
+ return (_jsx("ul", { className: "flex max-w-3xl list-disc flex-col gap-2 pl-5 text-lg leading-relaxed", children: items.map((l) => (_jsx("li", { children: inline(l) }, l))) }, i));
31
+ }
32
+ return (_jsx("p", { className: "max-w-3xl text-lg leading-relaxed text-canvas-foreground/80", children: inline(b) }, i));
33
+ }) }));
34
+ }
35
+ function inline(text) {
36
+ return text.split(/(\*\*[^*]+\*\*|`[^`]+`)/).map((part, i) => part.startsWith("**") ? (_jsx("strong", { className: "font-semibold", children: part.slice(2, -2) }, i)) : part.startsWith("`") ? (_jsx("code", { className: "font-mono text-[0.9em]", children: part.slice(1, -1) }, i)) : (part));
37
+ }
@@ -0,0 +1,19 @@
1
+ import { type NextRequest, NextResponse } from "next/server";
2
+ /**
3
+ * One URL, two formats, and never public. A docs URL answers `Accept:
4
+ * text/markdown`, or the same path with `.md`, with markdown generated from the
5
+ * manifest; negotiation is on Accept only, never the user agent, so responses say
6
+ * `Vary: Accept` and the HTML links to its markdown. On a production deployment
7
+ * (`VERCEL_ENV`, or `production` you pass) every docs URL, in every format, is a
8
+ * 404, the route handlers included, which no layout wraps.
9
+ *
10
+ * In `proxy.ts`: `export const proxy = docsProxy(); export const config = { matcher: docsMatcher() };`
11
+ */
12
+ export declare function docsProxy({ basePath, production }?: {
13
+ basePath?: string;
14
+ production?: () => boolean;
15
+ }): (request: NextRequest) => NextResponse<unknown>;
16
+ /** The proxy's matcher for a base path. Next reads `config.matcher` statically, so write the result out where it must be a literal. */
17
+ export declare function docsMatcher(basePath?: string): string[];
18
+ /** text/markdown ranked above text/html in the Accept header. */
19
+ export declare function prefersMarkdown(accept: string | null): boolean;
@@ -0,0 +1,56 @@
1
+ import { NextResponse } from "next/server";
2
+ /**
3
+ * One URL, two formats, and never public. A docs URL answers `Accept:
4
+ * text/markdown`, or the same path with `.md`, with markdown generated from the
5
+ * manifest; negotiation is on Accept only, never the user agent, so responses say
6
+ * `Vary: Accept` and the HTML links to its markdown. On a production deployment
7
+ * (`VERCEL_ENV`, or `production` you pass) every docs URL, in every format, is a
8
+ * 404, the route handlers included, which no layout wraps.
9
+ *
10
+ * In `proxy.ts`: `export const proxy = docsProxy(); export const config = { matcher: docsMatcher() };`
11
+ */
12
+ export function docsProxy({ basePath = "/design/system", production } = {}) {
13
+ const base = basePath.replace(/\/$/, "");
14
+ const isProduction = production ?? (() => process.env.VERCEL_ENV === "production");
15
+ return function proxy(request) {
16
+ const { pathname } = request.nextUrl;
17
+ if (isProduction())
18
+ return new NextResponse("Not found\n", { status: 404, headers: { "x-robots-tag": "noindex" } });
19
+ if (pathname.startsWith(`${base}/md`) || pathname.startsWith(`${base}/manifest/`) || pathname.startsWith(`${base}/preview/`))
20
+ return NextResponse.next();
21
+ const page = pathname.replace(/\.md$/, "").replace(/\/$/, "");
22
+ if (pathname.endsWith(".md") || prefersMarkdown(request.headers.get("accept"))) {
23
+ const url = request.nextUrl.clone();
24
+ url.pathname = `${base}/md${page.slice(base.length)}`;
25
+ return NextResponse.rewrite(url, { headers: { vary: "Accept", "x-robots-tag": "noindex" } });
26
+ }
27
+ // Next 16 replaces Vary on page responses with its own, so this one does not survive on HTML;
28
+ // the markdown responses keep theirs.
29
+ const res = NextResponse.next();
30
+ res.headers.set("vary", "Accept");
31
+ res.headers.set("link", `<${page}.md>; rel="alternate"; type="text/markdown"`);
32
+ res.headers.set("x-robots-tag", "noindex");
33
+ return res;
34
+ };
35
+ }
36
+ /** The proxy's matcher for a base path. Next reads `config.matcher` statically, so write the result out where it must be a literal. */
37
+ export function docsMatcher(basePath = "/design/system") {
38
+ const base = basePath.replace(/\/$/, "");
39
+ return [base, `${base}.md`, `${base}/:path*`];
40
+ }
41
+ /** text/markdown ranked above text/html in the Accept header. */
42
+ export function prefersMarkdown(accept) {
43
+ if (!accept)
44
+ return false;
45
+ const q = (type) => {
46
+ for (const part of accept.split(",")) {
47
+ const [name, ...params] = part.trim().split(";");
48
+ if (name.trim() !== type)
49
+ continue;
50
+ const weight = params.map((p) => p.trim()).find((p) => p.startsWith("q="));
51
+ return weight ? Number(weight.slice(2)) : 1;
52
+ }
53
+ return 0;
54
+ };
55
+ return q("text/markdown") > 0 && q("text/markdown") >= q("text/html");
56
+ }
@@ -0,0 +1,53 @@
1
+ import type { DocsLink } from "../shell.js";
2
+ import type { AssetsManifest, ComponentEntry, ComponentsManifest, PatternEntry, PatternsManifest, RulesManifest, TaxonomyManifest, TokensManifest } from "../lib/manifest-types.js";
3
+ import type { BuiltInPage, DocsConfig, EditorialPage } from "./config.js";
4
+ /** The config, read once: the manifest typed and checked, and what is shown. */
5
+ export interface Site {
6
+ config: DocsConfig;
7
+ base: string;
8
+ title: string;
9
+ components: ComponentsManifest;
10
+ tokens: TokensManifest;
11
+ rules: RulesManifest;
12
+ patterns: PatternsManifest;
13
+ taxonomy: TaxonomyManifest;
14
+ assets: AssetsManifest;
15
+ editorial: EditorialPage[];
16
+ shows: (page: BuiltInPage) => boolean;
17
+ }
18
+ export declare function siteOf(config: DocsConfig): Site;
19
+ /** One page of the docs, by what it shows. */
20
+ export type PageRef = {
21
+ kind: "overview";
22
+ } | {
23
+ kind: "editorial";
24
+ page: EditorialPage;
25
+ } | {
26
+ kind: "logo" | "imagery" | "voice" | "colour" | "type" | "scale" | "rules" | "standards" | "vocabulary";
27
+ } | {
28
+ kind: "component";
29
+ entry: ComponentEntry;
30
+ } | {
31
+ kind: "pattern";
32
+ pattern: PatternEntry;
33
+ };
34
+ /** The page at a path under the docs, or nothing. */
35
+ export declare function resolve(site: Site, slug?: string[]): PageRef | undefined;
36
+ /** Every page's path, for static generation. */
37
+ export declare function allSlugs(site: Site): string[][];
38
+ /** The navigation's editorial and generated groups: brand, foundations, standards. Rules, patterns and components the shell adds from the manifest. */
39
+ export declare function navOf(site: Site): {
40
+ brand: DocsLink[];
41
+ foundations: DocsLink[];
42
+ standards: DocsLink[];
43
+ };
44
+ /** Each built-in page's title and lead: what the page says it is, which is also its description. */
45
+ export declare const COPY: Record<Exclude<PageRef["kind"], "overview" | "editorial" | "component" | "pattern">, {
46
+ title: string;
47
+ lead: string;
48
+ }>;
49
+ /** A page's title and description, for its metadata. */
50
+ export declare function metaOf(site: Site, ref: PageRef): {
51
+ title: string;
52
+ description?: string;
53
+ };
@@ -0,0 +1,136 @@
1
+ import { findBySlug, readComponents, readTokens, slugOf } from "../lib/manifest.js";
2
+ export function siteOf(config) {
3
+ const hidden = new Set(config.hide ?? []);
4
+ return {
5
+ config,
6
+ base: (config.basePath ?? "/design/system").replace(/\/$/, ""),
7
+ title: config.title,
8
+ components: readComponents(config.manifest.components),
9
+ tokens: readTokens(config.manifest.tokens),
10
+ rules: config.manifest.rules ?? { rules: [] },
11
+ patterns: config.manifest.patterns ?? { patterns: [] },
12
+ taxonomy: config.manifest.taxonomy ?? { terms: [] },
13
+ assets: config.manifest.assets ?? { assets: [] },
14
+ editorial: config.editorial ?? [],
15
+ shows: (page) => !hidden.has(page),
16
+ };
17
+ }
18
+ const BUILT_IN = {
19
+ "brand/logo": "logo",
20
+ "brand/imagery": "imagery",
21
+ "brand/voice": "voice",
22
+ colour: "colour",
23
+ type: "type",
24
+ scale: "scale",
25
+ rules: "rules",
26
+ standards: "standards",
27
+ vocabulary: "vocabulary",
28
+ };
29
+ /** Whether a built-in page has anything to show, as well as not being hidden. */
30
+ function has(site, kind) {
31
+ return site.shows(kind) && KIND_NEEDS[kind](site);
32
+ }
33
+ const KIND_NEEDS = {
34
+ logo: (s) => s.assets.assets.some((a) => a.kind === "logo"),
35
+ imagery: (s) => s.assets.assets.some((a) => a.kind !== "logo"),
36
+ voice: (s) => s.taxonomy.terms.some((t) => t.kind === "product" || t.wrong?.length),
37
+ colour: () => true,
38
+ type: () => true,
39
+ scale: () => true,
40
+ rules: (s) => s.rules.rules.length > 0,
41
+ standards: (s) => !!s.config.baselines || !!s.config.adherence,
42
+ vocabulary: (s) => s.taxonomy.terms.length > 0,
43
+ };
44
+ /** The page at a path under the docs, or nothing. */
45
+ export function resolve(site, slug = []) {
46
+ const path = slug.join("/");
47
+ if (!path)
48
+ return { kind: "overview" };
49
+ const editorial = site.editorial.find((p) => p.slug === path);
50
+ if (editorial)
51
+ return { kind: "editorial", page: editorial };
52
+ const builtIn = BUILT_IN[path];
53
+ if (builtIn)
54
+ return has(site, builtIn) ? { kind: builtIn } : undefined;
55
+ const [section, id] = slug;
56
+ if (section === "components" && id && site.shows("components")) {
57
+ const entry = findBySlug(site.components, id);
58
+ return entry ? { kind: "component", entry } : undefined;
59
+ }
60
+ if (section === "patterns" && id && site.shows("patterns")) {
61
+ const pattern = site.patterns.patterns.find((p) => p.id === id);
62
+ return pattern ? { kind: "pattern", pattern } : undefined;
63
+ }
64
+ return undefined;
65
+ }
66
+ /** Every page's path, for static generation. */
67
+ export function allSlugs(site) {
68
+ const out = [[]];
69
+ for (const p of site.editorial)
70
+ out.push(p.slug.split("/"));
71
+ for (const [path, kind] of Object.entries(BUILT_IN))
72
+ if (has(site, kind))
73
+ out.push(path.split("/"));
74
+ if (site.shows("components"))
75
+ for (const c of site.components.components)
76
+ out.push(["components", slugOf(c)]);
77
+ if (site.shows("patterns"))
78
+ for (const p of site.patterns.patterns)
79
+ out.push(["patterns", p.id]);
80
+ return out;
81
+ }
82
+ /** The navigation's editorial and generated groups: brand, foundations, standards. Rules, patterns and components the shell adds from the manifest. */
83
+ export function navOf(site) {
84
+ const link = (label, path) => ({ label, href: `${site.base}/${path}` });
85
+ const written = (section) => site.editorial.filter((p) => (p.section ?? "brand") === section).map((p) => link(p.nav ?? p.title, p.slug));
86
+ return {
87
+ brand: [
88
+ ...written("brand"),
89
+ ...(has(site, "logo") ? [link("Logo", "brand/logo")] : []),
90
+ ...(has(site, "imagery") ? [link("Imagery and icons", "brand/imagery")] : []),
91
+ ...(has(site, "voice") ? [link("Voice and terms", "brand/voice")] : []),
92
+ ],
93
+ foundations: [...written("foundations"), link("Colour", "colour"), link("Type", "type"), link("Scale", "scale")].filter((l) => !["colour", "type", "scale"].some((k) => l.href.endsWith(`/${k}`) && !site.shows(k))),
94
+ standards: [
95
+ ...written("standards"),
96
+ ...(has(site, "standards") ? [link("Standards", "standards")] : []),
97
+ ...(has(site, "vocabulary") ? [link("Vocabulary", "vocabulary")] : []),
98
+ ],
99
+ };
100
+ }
101
+ /** Each built-in page's title and lead: what the page says it is, which is also its description. */
102
+ export const COPY = {
103
+ logo: { title: "Logo", lead: "Each logo on the surfaces it may sit on, at its smallest, with its clear space." },
104
+ imagery: { title: "Imagery and icons", lead: "The approved icons and imagery, and how to use each." },
105
+ voice: { title: "Voice and terms", lead: "The words we use, beside the ones we do not." },
106
+ colour: {
107
+ title: "Colour",
108
+ lead: "Generated from the brand colour by @designtools/tokens. Write the semantic names in components; the ramps are what they point at.",
109
+ },
110
+ type: { title: "Type", lead: "Each family, then the scale: a size is never set without its leading." },
111
+ scale: { title: "Scale", lead: "Space, size, radius, elevation and motion, each drawn the way it is used." },
112
+ rules: {
113
+ title: "Rules",
114
+ lead: "Brand and interface rules in one format, each with the check that proves it. Written for people; agents read the same rule.",
115
+ },
116
+ standards: {
117
+ title: "Standards",
118
+ lead: "The budgets and targets the project holds itself to, read from .mxa/stack.json, where the checks read them too.",
119
+ },
120
+ vocabulary: { title: "Vocabulary", lead: "Every name in the system. Search by a wrong name and it finds the right one." },
121
+ };
122
+ /** A page's title and description, for its metadata. */
123
+ export function metaOf(site, ref) {
124
+ switch (ref.kind) {
125
+ case "overview":
126
+ return { title: site.title, description: site.config.lead ?? `${site.title}: the brand and the design system in one place.` };
127
+ case "editorial":
128
+ return { title: ref.page.title, description: ref.page.lead };
129
+ case "component":
130
+ return { title: `${ref.entry.name} · Components`, description: ref.entry.description };
131
+ case "pattern":
132
+ return { title: `${ref.pattern.name} · Patterns`, description: ref.pattern.description };
133
+ default:
134
+ return { title: COPY[ref.kind].title, description: COPY[ref.kind].lead };
135
+ }
136
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * What a reader can take from a page, as one split button: copy the page as
3
+ * markdown, the same markdown any agent gets at `.md`, with a menu beside it to
4
+ * view that markdown, copy the whole system as one file, or open the manifest.
5
+ * Put it in a `DocsHeader`.
6
+ */
7
+ export declare function PageActions({ basePath }: {
8
+ basePath?: string;
9
+ }): import("react").JSX.Element;