@designtools/docs 0.0.0-stage → 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 (173) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +155 -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 +39 -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 +21 -0
  21. package/dist/imagery.d.ts +6 -0
  22. package/dist/imagery.js +17 -0
  23. package/dist/index.d.ts +35 -0
  24. package/dist/index.js +34 -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/live.d.ts +10 -0
  36. package/dist/lib/live.js +10 -0
  37. package/dist/lib/lookup.d.ts +35 -0
  38. package/dist/lib/lookup.js +42 -0
  39. package/dist/lib/manifest-types.d.ts +253 -0
  40. package/dist/lib/manifest-types.js +14 -0
  41. package/dist/lib/manifest.d.ts +98 -0
  42. package/dist/lib/manifest.js +173 -0
  43. package/dist/lib/markdown.d.ts +50 -0
  44. package/dist/lib/markdown.js +286 -0
  45. package/dist/lib/measure.d.ts +12 -0
  46. package/dist/lib/measure.js +62 -0
  47. package/dist/lib/pseudo.d.ts +11 -0
  48. package/dist/lib/pseudo.js +71 -0
  49. package/dist/lib/registry.d.ts +21 -0
  50. package/dist/lib/registry.js +11 -0
  51. package/dist/lib/search.d.ts +44 -0
  52. package/dist/lib/search.js +62 -0
  53. package/dist/lib/standards.d.ts +17 -0
  54. package/dist/lib/standards.js +81 -0
  55. package/dist/lib/status.d.ts +6 -0
  56. package/dist/lib/status.js +17 -0
  57. package/dist/lib/terms.d.ts +15 -0
  58. package/dist/lib/terms.js +20 -0
  59. package/dist/lib/text.d.ts +8 -0
  60. package/dist/lib/text.js +9 -0
  61. package/dist/lib/ui.d.ts +100 -0
  62. package/dist/lib/ui.js +89 -0
  63. package/dist/lib/vision.d.ts +18 -0
  64. package/dist/lib/vision.js +27 -0
  65. package/dist/logo-usage.d.ts +14 -0
  66. package/dist/logo-usage.js +45 -0
  67. package/dist/next/config.d.ts +105 -0
  68. package/dist/next/config.js +1 -0
  69. package/dist/next/index.d.ts +65 -0
  70. package/dist/next/index.js +66 -0
  71. package/dist/next/markdown.d.ts +20 -0
  72. package/dist/next/markdown.js +95 -0
  73. package/dist/next/pages.d.ts +6 -0
  74. package/dist/next/pages.js +188 -0
  75. package/dist/next/prose.d.ts +9 -0
  76. package/dist/next/prose.js +39 -0
  77. package/dist/next/proxy.d.ts +19 -0
  78. package/dist/next/proxy.js +56 -0
  79. package/dist/next/site.d.ts +53 -0
  80. package/dist/next/site.js +136 -0
  81. package/dist/page-actions.d.ts +9 -0
  82. package/dist/page-actions.js +32 -0
  83. package/dist/pattern.d.ts +11 -0
  84. package/dist/pattern.js +22 -0
  85. package/dist/playground.d.ts +62 -0
  86. package/dist/playground.js +111 -0
  87. package/dist/preview-frame.d.ts +31 -0
  88. package/dist/preview-frame.js +63 -0
  89. package/dist/props-table.d.ts +10 -0
  90. package/dist/props-table.js +45 -0
  91. package/dist/rule.d.ts +16 -0
  92. package/dist/rule.js +37 -0
  93. package/dist/scale.d.ts +9 -0
  94. package/dist/scale.js +58 -0
  95. package/dist/search.d.ts +16 -0
  96. package/dist/search.js +46 -0
  97. package/dist/shell.d.ts +92 -0
  98. package/dist/shell.js +201 -0
  99. package/dist/standards.d.ts +13 -0
  100. package/dist/standards.js +19 -0
  101. package/dist/styles.css +3708 -0
  102. package/dist/swatches.d.ts +28 -0
  103. package/dist/swatches.js +100 -0
  104. package/dist/theme-switch.d.ts +20 -0
  105. package/dist/theme-switch.js +68 -0
  106. package/dist/type-ramp.d.ts +10 -0
  107. package/dist/type-ramp.js +31 -0
  108. package/dist/usage.d.ts +13 -0
  109. package/dist/usage.js +55 -0
  110. package/dist/variant-matrix.d.ts +15 -0
  111. package/dist/variant-matrix.js +58 -0
  112. package/dist/voice-terms.d.ts +5 -0
  113. package/dist/voice-terms.js +14 -0
  114. package/dist/workbench.d.ts +33 -0
  115. package/dist/workbench.js +274 -0
  116. package/package.json +107 -3
  117. package/src/a11y-panel.tsx +297 -0
  118. package/src/adherence-summary.tsx +134 -0
  119. package/src/agent-view.tsx +54 -0
  120. package/src/anatomy.tsx +119 -0
  121. package/src/ask-claude.tsx +90 -0
  122. package/src/code-view.tsx +112 -0
  123. package/src/css.d.ts +1 -0
  124. package/src/examples.tsx +141 -0
  125. package/src/glossary.tsx +70 -0
  126. package/src/imagery.tsx +64 -0
  127. package/src/index.ts +35 -0
  128. package/src/lib/adherence-types.ts +181 -0
  129. package/src/lib/boundary.tsx +21 -0
  130. package/src/lib/contrast.ts +44 -0
  131. package/src/lib/cx.ts +30 -0
  132. package/src/lib/jsx.ts +29 -0
  133. package/src/lib/live.tsx +15 -0
  134. package/src/lib/lookup.tsx +50 -0
  135. package/src/lib/manifest-types.ts +284 -0
  136. package/src/lib/manifest.ts +228 -0
  137. package/src/lib/markdown.ts +288 -0
  138. package/src/lib/measure.tsx +101 -0
  139. package/src/lib/pseudo.ts +74 -0
  140. package/src/lib/registry.tsx +29 -0
  141. package/src/lib/search.ts +99 -0
  142. package/src/lib/standards.ts +82 -0
  143. package/src/lib/status.tsx +32 -0
  144. package/src/lib/terms.tsx +53 -0
  145. package/src/lib/text.tsx +27 -0
  146. package/src/lib/ui.tsx +291 -0
  147. package/src/lib/vision.tsx +44 -0
  148. package/src/logo-usage.tsx +140 -0
  149. package/src/next/config.ts +101 -0
  150. package/src/next/index.tsx +103 -0
  151. package/src/next/markdown.ts +119 -0
  152. package/src/next/pages.tsx +483 -0
  153. package/src/next/prose.tsx +97 -0
  154. package/src/next/proxy.ts +55 -0
  155. package/src/next/site.ts +173 -0
  156. package/src/page-actions.tsx +92 -0
  157. package/src/pattern.tsx +72 -0
  158. package/src/playground.tsx +357 -0
  159. package/src/preview-frame.tsx +131 -0
  160. package/src/props-table.tsx +154 -0
  161. package/src/rule.tsx +165 -0
  162. package/src/scale.tsx +175 -0
  163. package/src/search.tsx +155 -0
  164. package/src/shell.tsx +569 -0
  165. package/src/standards.tsx +58 -0
  166. package/src/styles.css +157 -0
  167. package/src/swatches.tsx +298 -0
  168. package/src/theme-switch.tsx +90 -0
  169. package/src/type-ramp.tsx +150 -0
  170. package/src/usage.tsx +148 -0
  171. package/src/variant-matrix.tsx +178 -0
  172. package/src/voice-terms.tsx +37 -0
  173. package/src/workbench.tsx +587 -0
package/dist/rule.js ADDED
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Live } from "./lib/live.js";
4
+ import { Bot, Eye, Hash, ShieldCheck } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { CodeView } from "./code-view.js";
7
+ import { cx } from "./lib/cx.js";
8
+ import { ruleMarkdown } from "./lib/markdown.js";
9
+ import { StatusBadge } from "./lib/status.js";
10
+ import { inline, Prose } from "./lib/text.js";
11
+ import { Eyebrow } from "./lib/ui.js";
12
+ const CHECK = { test: "Tested", lint: "Linted", audit: "Audited", manual: "Checked by a person" };
13
+ /**
14
+ * One rule, brand or interface, written for people. On a wide screen, its name, status and the check
15
+ * that proves it hold the left column while the right reads: the statement set large, the
16
+ * demonstration (pass it as children, for a rule that needs persuading), the facts, then why. A small
17
+ * switch shows the same rule as an agent reads it.
18
+ */
19
+ export function Rule({ rule, children }) {
20
+ const [view, setView] = useState("people");
21
+ const chips = (items, mono = false) => (_jsx("span", { className: "flex flex-wrap gap-1.5", children: items.map((t) => (_jsx("span", { className: cx("inline-flex min-h-6 items-center rounded-sm bg-muted px-1.5 text-xs", mono && "font-mono"), children: t }, t))) }));
22
+ const facts = [];
23
+ if (rule.threshold)
24
+ facts.push(["Threshold", _jsx("span", { className: "font-medium", children: rule.threshold }, "t")]);
25
+ if (rule.appliesTo?.length)
26
+ facts.push(["Applies to", chips(rule.appliesTo)]);
27
+ if (rule.exceptions?.length)
28
+ facts.push(["Exceptions", _jsx("span", { children: rule.exceptions.join("; ") }, "e")]);
29
+ if (rule.tokens?.length)
30
+ facts.push(["Tokens", chips(rule.tokens, true)]);
31
+ const toggle = (value, label, Icon) => (_jsxs("button", { type: "button", "aria-pressed": view === value, onClick: () => setView(value), className: cx("inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-md px-2 text-xs font-medium transition-colors duration-(--dt-duration-fast) focus-visible:outline-2 focus-visible:outline-ring", view === value ? "bg-muted text-canvas-foreground" : "text-muted-foreground hover:text-canvas-foreground"), children: [_jsx(Icon, { "aria-hidden": true, className: "size-3.5" }), label] }));
32
+ return (_jsxs("article", { id: rule.id, "data-slot": "rule", className: "grid scroll-mt-24 gap-x-12 gap-y-6 border-t border-border pt-10 xl:grid-cols-[15rem_minmax(0,1fr)]", children: [_jsxs("header", { "data-slot": "rule-header", className: "flex flex-col gap-4 xl:sticky xl:top-24 xl:self-start", children: [_jsxs("div", { className: "flex flex-col gap-2.5", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: rule.kind === "brand" ? "Brand rule" : "Interface rule" }), _jsxs("h3", { className: "group flex items-start gap-1 text-2xl leading-tight tracking-tight", children: [rule.title, _jsx("a", { href: `#${rule.id}`, "aria-label": `Link to ${rule.title}`, className: "relative mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-muted focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-ring after:absolute after:-inset-2 after:content-['']", children: _jsx(Hash, { "aria-hidden": true, className: "size-4" }) })] }), _jsx("span", { children: _jsx(StatusBadge, { status: rule.status }) })] }), rule.check && (_jsxs("p", { "data-slot": "rule-check", className: "flex items-start gap-2 text-sm", children: [_jsx(ShieldCheck, { "aria-hidden": true, className: "mt-0.5 size-(--dt-size-xs) shrink-0 text-muted-foreground" }), _jsxs("span", { className: "flex flex-col gap-0.5", children: [_jsxs("span", { className: "font-medium", children: [CHECK[rule.check.kind] ?? rule.check.kind, rule.check.ref && (_jsxs(_Fragment, { children: [" ", "by ", _jsx("code", { className: "font-mono text-xs", children: rule.check.ref })] }))] }), rule.check.description && _jsx("span", { className: "text-muted-foreground", children: inline(rule.check.description) })] })] })), _jsxs("div", { role: "group", "aria-label": `Read ${rule.title}`, className: "-ml-2 flex gap-1", children: [toggle("people", "For people", Eye), toggle("agents", "For agents", Bot)] })] }), view === "people" ? (_jsxs("div", { "data-slot": "rule-body", className: "flex min-w-0 flex-col gap-8", children: [_jsx("p", { "data-slot": "rule-statement", className: "max-w-3xl text-2xl leading-snug tracking-tight", children: inline(rule.statement) }), children && (_jsx("div", { "data-slot": "rule-demonstration", "data-docs-stage": "", className: "flex min-h-40 items-center justify-center rounded-2xl bg-muted/60 p-8 sm:p-10", children: _jsx(Live, { children: children }) })), facts.length > 0 && (_jsx("dl", { className: "grid max-w-3xl gap-x-10 gap-y-4 sm:grid-cols-2", children: facts.map(([term, detail]) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("dt", { className: "text-xs font-medium text-muted-foreground", children: term }), _jsx("dd", { className: "text-sm", children: detail })] }, term))) })), rule.rationale && (_jsxs("div", { className: "flex max-w-3xl flex-col gap-2", children: [_jsx(Eyebrow, { children: "Why" }), _jsx("div", { className: "flex flex-col gap-2 text-base leading-relaxed text-muted-foreground", children: _jsx(Prose, { text: rule.rationale }) })] }))] })) : (_jsx("div", { className: "min-w-0", children: _jsx(CodeView, { code: ruleMarkdown(rule), lang: "markdown", label: `${rule.id}.md`, wrap: true }) }))] }));
33
+ }
34
+ /** Every rule on one screen, a line each, linking to the rule: what it is, how settled, how it is checked. */
35
+ export function RuleIndex({ rules }) {
36
+ return (_jsx("div", { "data-slot": "rule-index", className: "-mx-1 overflow-x-auto px-1", children: _jsxs("table", { className: "w-full min-w-[34rem] border-collapse text-left text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-xs text-muted-foreground", children: [_jsx("th", { scope: "col", className: "pr-4 pb-3 font-medium", children: "Rule" }), _jsx("th", { scope: "col", className: "px-4 pb-3 font-medium", children: "Kind" }), _jsx("th", { scope: "col", className: "px-4 pb-3 font-medium", children: "Status" }), _jsx("th", { scope: "col", className: "pb-3 pl-4 font-medium", children: "Checked" })] }) }), _jsx("tbody", { children: rules.map((r) => (_jsxs("tr", { className: "border-t border-border", children: [_jsx("th", { scope: "row", className: "py-3 pr-4 font-normal", children: _jsx("a", { href: `#${r.id}`, className: "font-medium underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-ring", children: r.title }) }), _jsx("td", { className: "px-4 py-3 text-muted-foreground capitalize", children: r.kind }), _jsx("td", { className: "px-4 py-3", children: _jsx(StatusBadge, { status: r.status }) }), _jsx("td", { className: "py-3 pl-4 text-muted-foreground", children: r.check ? (CHECK[r.check.kind] ?? r.check.kind) : "Not yet" })] }, r.id))) })] }) }));
37
+ }
@@ -0,0 +1,9 @@
1
+ import type { TokenTier, TokensManifest } from "./lib/manifest-types.js";
2
+ /**
3
+ * Everything that is not colour or type: space, size, radius, elevation and
4
+ * motion, each tier drawn the way it is used. Pass `tiers` to show only some, in your order.
5
+ */
6
+ export declare function Scale({ tokens, tiers, }: {
7
+ tokens: TokensManifest;
8
+ tiers?: TokenTier[];
9
+ }): import("react").JSX.Element;
package/dist/scale.js ADDED
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { shortName, tokensOfTier, withGroupDescriptions } from "./lib/manifest.js";
3
+ /** Steps drawn for a single `--spacing` multiplier, as Tailwind's p-1, p-2, … would use it. */
4
+ const SPACING_STEPS = [1, 2, 3, 4, 6, 8, 12, 16, 24];
5
+ const TITLE = {
6
+ spacing: ["Space", "The gap between things: padding, margin and gap."],
7
+ size: ["Size", "The extent of a thing: icons, avatars and control heights."],
8
+ radius: ["Radius", "Corners, scaled to the element: bigger box, bigger step."],
9
+ shadow: ["Elevation", "How far a thing lifts from the page."],
10
+ duration: ["Duration", "How long a change takes. Point at a row to play it."],
11
+ ease: ["Easing", "How a change moves. Point at a row to play it."],
12
+ };
13
+ /**
14
+ * Everything that is not colour or type: space, size, radius, elevation and
15
+ * motion, each tier drawn the way it is used. Pass `tiers` to show only some, in your order.
16
+ */
17
+ export function Scale({ tokens, tiers = ["spacing", "size", "radius", "shadow", "duration", "ease"], }) {
18
+ return (_jsx("div", { "data-slot": "scale", className: "flex flex-col gap-14", children: tiers.map((tier) => {
19
+ const rows = tokensOfTier(tokens, tier);
20
+ if (rows.length === 0)
21
+ return null;
22
+ const [title, lead] = TITLE[tier] ?? [tier, ""];
23
+ return (_jsxs("section", { "data-slot": "scale-tier", className: "flex flex-col gap-4", children: [_jsxs("header", { className: "flex flex-col gap-1", children: [_jsx("h3", { className: "text-2xl tracking-tight capitalize", children: title }), lead && _jsx("p", { className: "text-sm text-muted-foreground", children: lead })] }), tier === "spacing" ? (_jsx(Spacing, { rows: rows })) : tier === "duration" || tier === "ease" ? (_jsx(MotionRows, { rows: rows, tier: tier })) : (_jsx(Tiles, { rows: rows, tier: tier }))] }, tier));
24
+ }) }));
25
+ }
26
+ const card = "rounded-2xl border border-border bg-surface text-surface-foreground";
27
+ function Spacing({ rows }) {
28
+ const multiplier = rows.find((t) => t.name === "--spacing");
29
+ const named = rows.filter((t) => t !== multiplier);
30
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [multiplier && (_jsxs("div", { className: `${card} flex flex-col gap-1 p-5`, children: [_jsxs("p", { className: "mb-3 text-xs text-muted-foreground", children: [multiplier.description ?? "One multiplier", " \u00B7 ", _jsxs("code", { className: "font-mono", children: ["--spacing: ", multiplier.values.default] })] }), _jsx("ul", { className: "flex flex-col gap-2", children: SPACING_STEPS.map((n) => (_jsxs("li", { className: "grid grid-cols-[3rem_4rem_1fr] items-center gap-3 font-mono text-xs", children: [_jsx("span", { className: "font-medium", children: n }), _jsxs("span", { className: "text-muted-foreground", children: [n * 4, "px"] }), _jsx("span", { "data-slot": "scale-bar", className: "h-5 rounded-sm border border-primary-subdued-foreground/40 bg-primary-subdued", style: { width: `calc(var(--spacing) * ${n})` } })] }, n))) })] })), named.length > 0 && _jsx(Tiles, { rows: named, tier: "spacing" })] }));
31
+ }
32
+ function Tiles({ rows, tier }) {
33
+ return (_jsx("ul", { className: tier === "shadow" ? "grid grid-cols-2 gap-4 lg:grid-cols-3" : "grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4", children: rows.map((t) => (_jsxs("li", { "data-slot": "scale-row", className: `${card} flex flex-col overflow-hidden`, children: [_jsx("div", { className: tier === "shadow"
34
+ ? "flex h-36 items-center justify-center bg-canvas p-6"
35
+ : "flex h-28 items-center justify-center bg-[radial-gradient(color-mix(in_oklch,var(--border)_85%,transparent)_1px,transparent_1px)] [background-size:14px_14px] p-4", children: sample(t, tier) }), _jsxs("div", { className: "flex flex-col gap-0.5 border-t border-border px-4 py-3 font-mono text-xs", children: [_jsx("span", { className: "font-medium", children: shortName(t, tier) }), tier !== "shadow" && (_jsx("span", { className: "truncate text-muted-foreground", title: t.values.default, children: t.values.default })), t.description && (_jsx("span", { className: "truncate font-sans text-muted-foreground", title: t.description, children: t.description }))] })] }, t.name))) }));
36
+ }
37
+ function MotionRows({ rows, tier }) {
38
+ return (_jsx("ul", { className: `${card} flex flex-col divide-y divide-border overflow-hidden`, children: withGroupDescriptions(rows).map((t) => (_jsxs("li", { "data-slot": "scale-row", tabIndex: 0, className: "group grid grid-cols-1 items-center gap-3 px-5 py-4 outline-none hover:bg-muted/50 focus-visible:bg-muted/50 md:grid-cols-[12rem_minmax(0,1fr)]", children: [_jsxs("span", { className: "flex min-w-0 flex-col font-mono text-xs", children: [_jsx("span", { className: "font-medium", children: shortName(t, tier) }), _jsx("span", { className: "truncate text-muted-foreground", title: t.values.default, children: t.values.default }), t.description && _jsx("span", { className: "truncate font-sans text-muted-foreground", children: t.description })] }), _jsx("span", { className: "min-w-0", children: tier === "duration" ? (_jsx(Motion, { duration: `var(${t.name})`, easing: "var(--dt-ease-out, ease-out)" })) : (_jsx(Motion, { duration: "var(--dt-duration-slow, 400ms)", easing: `var(${t.name})` })) })] }, t.name))) }));
39
+ }
40
+ function sample(t, tier) {
41
+ const v = `var(${t.name})`;
42
+ switch (tier) {
43
+ case "spacing":
44
+ return _jsx("span", { className: "block h-5 rounded-sm bg-primary-subdued", style: { width: v } });
45
+ case "size":
46
+ return _jsx("span", { className: "block rounded-md border border-primary-subdued-foreground/40 bg-primary-subdued", style: { width: v, height: v } });
47
+ case "radius":
48
+ return _jsx("span", { className: "block size-16 border-2 border-primary-subdued-foreground/60 bg-primary-subdued", style: { borderRadius: v } });
49
+ case "shadow":
50
+ return _jsx("span", { className: "block h-16 w-28 rounded-lg bg-surface", style: { boxShadow: v } });
51
+ default:
52
+ return _jsx("span", { className: "font-mono text-xs", children: t.values.default });
53
+ }
54
+ }
55
+ /** A dot that crosses its track when the row is pointed at or focused. Reduced motion jumps instead. */
56
+ function Motion({ duration, easing }) {
57
+ return (_jsx("span", { "data-slot": "scale-motion", className: "relative block h-6 w-full rounded-full bg-muted", children: _jsx("span", { className: "absolute inset-y-1 right-5 left-1 block transition-transform group-hover:translate-x-full group-focus-visible:translate-x-full motion-reduce:transition-none", style: { transitionDuration: duration, transitionTimingFunction: easing }, children: _jsx("span", { className: "block size-4 rounded-full bg-primary shadow-sm" }) }) }));
58
+ }
@@ -0,0 +1,16 @@
1
+ import { type SearchItem } from "./lib/search.js";
2
+ /**
3
+ * Find a component, a part, a rule, a pattern or a page by name, fuzzily, and go
4
+ * there. A wrong name finds the right thing ("Dropdown" finds Select, and says
5
+ * so). Opens with ⌘K or Ctrl+K anywhere, or / when you are not typing. Build the
6
+ * items with `searchIndex` from `lib/search`; the shell does, when you pass it the
7
+ * taxonomy. Navigation only: it does not search page text.
8
+ */
9
+ export declare function DocsSearch({ items, shortcuts, variant, className, }: {
10
+ items: SearchItem[];
11
+ /** Listen for ⌘K, Ctrl+K and /. Give one search on a page the shortcuts. */
12
+ shortcuts?: boolean;
13
+ /** A field with its shortcut, or an icon on its own for a phone's top bar. */
14
+ variant?: "field" | "icon";
15
+ className?: string;
16
+ }): import("react").JSX.Element;
package/dist/search.js ADDED
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Autocomplete } from "@base-ui/react/autocomplete";
4
+ import { Dialog } from "@base-ui/react/dialog";
5
+ import { Search } from "lucide-react";
6
+ import { useRouter } from "next/navigation";
7
+ import { useEffect, useId, useMemo, useState } from "react";
8
+ import { cx, target } from "./lib/cx.js";
9
+ import { search } from "./lib/search.js";
10
+ const PLURAL = { Component: "Components", Rule: "Rules", Pattern: "Patterns", Page: "Pages" };
11
+ /**
12
+ * Find a component, a part, a rule, a pattern or a page by name, fuzzily, and go
13
+ * there. A wrong name finds the right thing ("Dropdown" finds Select, and says
14
+ * so). Opens with ⌘K or Ctrl+K anywhere, or / when you are not typing. Build the
15
+ * items with `searchIndex` from `lib/search`; the shell does, when you pass it the
16
+ * taxonomy. Navigation only: it does not search page text.
17
+ */
18
+ export function DocsSearch({ items, shortcuts = true, variant = "field", className, }) {
19
+ const router = useRouter();
20
+ const [open, setOpen] = useState(false);
21
+ const [query, setQuery] = useState("");
22
+ const [shortcut, setShortcut] = useState("Ctrl K");
23
+ const hintId = useId();
24
+ const groups = useMemo(() => search(items, query), [items, query]);
25
+ useEffect(() => {
26
+ if (/Mac|iPhone|iPad/.test(navigator.platform))
27
+ setShortcut("⌘K");
28
+ if (!shortcuts)
29
+ return;
30
+ const onKey = (e) => {
31
+ const typing = e.target instanceof HTMLElement && (e.target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName));
32
+ if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || (e.key === "/" && !typing && !e.metaKey && !e.ctrlKey && !e.altKey)) {
33
+ e.preventDefault();
34
+ setOpen(true);
35
+ }
36
+ };
37
+ window.addEventListener("keydown", onKey);
38
+ return () => window.removeEventListener("keydown", onKey);
39
+ }, [shortcuts]);
40
+ const go = (item) => {
41
+ setOpen(false);
42
+ setQuery("");
43
+ router.push(item.href);
44
+ };
45
+ return (_jsxs(Dialog.Root, { open: open, onOpenChange: (next) => (setOpen(next), next || setQuery("")), children: [variant === "icon" ? (_jsx(Dialog.Trigger, { "data-slot": "docs-search-trigger", "aria-label": "Search the design system", className: cx("inline-flex size-(--docs-target,2.75rem) shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring", className), children: _jsx(Search, { "aria-hidden": true, className: "size-(--dt-size-sm)" }) })) : (_jsxs(Dialog.Trigger, { "data-slot": "docs-search-trigger", className: cx(target, "flex w-full items-center gap-2.5 rounded-md border border-border bg-canvas px-3 text-sm text-muted-foreground transition-colors duration-(--dt-duration-fast) hover:border-input hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", className), children: [_jsx(Search, { "aria-hidden": true, className: "size-(--dt-size-xs) shrink-0" }), _jsx("span", { className: "flex-1 text-left", children: "Search" }), _jsx("kbd", { className: "rounded-sm border border-border bg-surface px-1.5 py-0.5 font-mono text-xs pointer-coarse:hidden", children: shortcut })] })), _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Backdrop, { "data-designtools": "", className: "fixed inset-0 z-40 bg-canvas-foreground/30 backdrop-blur-xs transition-opacity duration-(--dt-duration-base) data-ending-style:opacity-0 data-starting-style:opacity-0" }), _jsx(Dialog.Viewport, { "data-designtools": "", className: "fixed inset-0 z-50 flex items-start justify-center px-4 pt-[12dvh]", children: _jsx(Dialog.Popup, { "data-slot": "docs-search", "aria-label": "Search the design system", className: "flex max-h-[min(36rem,80dvh)] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-border bg-overlay text-overlay-foreground shadow-xl transition-[opacity,scale] duration-(--dt-duration-base) ease-(--dt-ease-out) data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-starting-style:scale-[0.98] data-starting-style:opacity-0", children: _jsxs(Autocomplete.Root, { open: true, inline: true, items: groups, filter: null, value: query, onValueChange: (next) => setQuery(next), itemToStringValue: (item) => item.label, autoHighlight: "always", keepHighlight: true, children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-border px-4", children: [_jsx(Search, { "aria-hidden": true, className: "size-(--dt-size-sm) shrink-0 text-muted-foreground" }), _jsx(Autocomplete.Input, { "aria-label": "Search components, rules, patterns and pages", "aria-describedby": hintId, placeholder: "Search components, rules, patterns\u2026", className: "h-(--dt-size-3xl) w-full bg-transparent text-lg outline-none placeholder:text-muted-foreground" })] }), _jsx(Dialog.Close, { className: "sr-only", children: "Close search" }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-2", children: [_jsx(Autocomplete.Empty, { children: _jsx("p", { className: "px-4 py-6 text-sm text-muted-foreground", children: query.trim() ? `Nothing called “${query.trim()}”.` : "Type a name, or a name you half remember." }) }), _jsx(Autocomplete.List, { children: (group) => (_jsxs(Autocomplete.Group, { items: group.items, className: "not-last:mb-2", children: [_jsx(Autocomplete.GroupLabel, { className: "px-3 pt-2 pb-1 text-xs font-medium tracking-wide text-muted-foreground uppercase", children: PLURAL[group.kind] }), _jsx(Autocomplete.Collection, { children: (item) => (_jsxs(Autocomplete.Item, { value: item, onClick: () => go(item), className: cx(target, "flex cursor-pointer items-center justify-between gap-3 rounded-md px-3 py-2 text-sm outline-none select-none data-highlighted:bg-muted"), children: [_jsx("span", { className: "min-w-0 truncate font-medium", children: item.label }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: item.matched ? `for “${item.matched}”` : item.detail })] }, item.href)) })] }, group.kind)) })] }), _jsx("p", { id: hintId, className: "border-t border-border px-4 py-2 text-xs text-muted-foreground", children: "Arrow keys to move, Enter to go, Escape to close." })] }) }) })] })] }));
46
+ }
@@ -0,0 +1,92 @@
1
+ import { type ReactNode } from "react";
2
+ import { type Status } from "./lib/status.js";
3
+ import type { ComponentsManifest, PatternsManifest, RulesManifest, TaxonomyManifest } from "./lib/manifest-types.js";
4
+ export interface DocsLink {
5
+ label: string;
6
+ href: string;
7
+ /** Shown beneath it while you are on it or one of them: a component's parts. */
8
+ children?: DocsLink[];
9
+ }
10
+ /**
11
+ * The docs frame, in the site's order: overview, brand, foundations, rules,
12
+ * patterns, components by `@category`, standards. Editorial pages are the links
13
+ * you pass; rules, patterns and components come from the manifest.
14
+ *
15
+ * On a laptop the navigation is an inset panel down the left, each section
16
+ * folding open where the current page is, with search at the top and the colour
17
+ * mode at the foot; wide screens add an "On this page" rail. On a phone it is a
18
+ * bar with search and a drawer. Every page ends with the previous and next page,
19
+ * and `DocsHeader` names the section it sits in. Put it in the docs layout.
20
+ */
21
+ export declare function DocsShell({ manifest, rules, patterns, terms, search, themeSwitch, title, logo, basePath, brand, foundations, standards, hero, children, }: {
22
+ manifest: ComponentsManifest;
23
+ /** Listed under Rules, each linking to its anchor on `${basePath}/rules`. */
24
+ rules?: RulesManifest;
25
+ /** Listed under Patterns, each at `${basePath}/patterns/${id}`. */
26
+ patterns?: PatternsManifest;
27
+ /** The taxonomy, so search finds a component by a wrong name ("Dropdown" finds Select). */
28
+ terms?: TaxonomyManifest;
29
+ /** Search over components, parts, rules, patterns and pages, on ⌘K, Ctrl+K or /. On by default. */
30
+ search?: boolean;
31
+ /** System, light and dark, at the foot of the navigation. Off for a project with its own switch. */
32
+ themeSwitch?: boolean;
33
+ title?: string;
34
+ /** The client's logo, shown beside the title: the site is theirs. */
35
+ logo?: ReactNode;
36
+ /** Component pages live at `${basePath}/components/${slug}`. */
37
+ basePath?: string;
38
+ /** Brand pages: principles, design language, voice, logo, imagery. */
39
+ brand?: DocsLink[];
40
+ /** Foundations pages: colour, type, scale. */
41
+ foundations?: DocsLink[];
42
+ /** Standards pages: budgets, accessibility, adherence. */
43
+ standards?: DocsLink[];
44
+ /**
45
+ * A full-bleed introduction across the top of the overview, edge to edge of the
46
+ * content area: the brand's splash. The overview's own header can then be left out.
47
+ */
48
+ hero?: ReactNode;
49
+ children: ReactNode;
50
+ }): import("react").JSX.Element;
51
+ /**
52
+ * A page's heading: the section it sits in (from the navigation), the title, the
53
+ * lead, and the page's actions as children. Where the page comes from goes at the
54
+ * foot, in `DocsFooter`.
55
+ */
56
+ export declare function DocsHeader({ title, lead, eyebrow, status, children, }: {
57
+ title: string;
58
+ lead?: string;
59
+ /** Above the title. Defaults to the section the page sits in. */
60
+ eyebrow?: string;
61
+ /** How settled the thing the page documents is, beside the eyebrow: a pattern's status. */
62
+ status?: Status;
63
+ /** The page's actions, beside the title: `PageActions`. */
64
+ children?: ReactNode;
65
+ }): import("react").JSX.Element;
66
+ /**
67
+ * Where a page comes from, at its foot: a generated page names the version and
68
+ * commit it was built from; an editorial page names its author, who reviewed it,
69
+ * when, and whether it is settled. Never both, and never a review date on a
70
+ * generated page, which would be invented. `source` names the file it documents.
71
+ */
72
+ export declare function DocsFooter({ provenance, editorial, source, }: {
73
+ /** For a generated page: `Generated from acme-web@1.4.0 at 3f2a9c1`, from `designtools-manifest provenance`. */
74
+ provenance?: string;
75
+ /** For an editorial page. `reviewer` when the person who signs it off is not the author. */
76
+ editorial?: {
77
+ author: string;
78
+ reviewer?: string;
79
+ reviewed: string;
80
+ status: "draft" | "published";
81
+ };
82
+ source?: string;
83
+ }): import("react").JSX.Element | null;
84
+ /**
85
+ * A titled section of a docs page, numbered in a gutter beside it (01, 02, …,
86
+ * counted by CSS in page order) and listed in "On this page".
87
+ */
88
+ export declare function DocsSection({ title, lead, children }: {
89
+ title: string;
90
+ lead?: string;
91
+ children: ReactNode;
92
+ }): import("react").JSX.Element;
package/dist/shell.js ADDED
@@ -0,0 +1,201 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Collapsible } from "@base-ui/react/collapsible";
4
+ import { Dialog } from "@base-ui/react/dialog";
5
+ import { ArrowLeft, ArrowRight, BookOpen, ChevronRight, Component, Gauge, House, LayoutTemplate, Menu, Palette, Scale, X } from "lucide-react";
6
+ import Link from "next/link";
7
+ import { usePathname } from "next/navigation";
8
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
9
+ import { Live } from "./lib/live.js";
10
+ import { accent, cx, display, target } from "./lib/cx.js";
11
+ import { StatusBadge } from "./lib/status.js";
12
+ import { groupFamilies, slugOf } from "./lib/manifest.js";
13
+ import { searchIndex } from "./lib/search.js";
14
+ import { Prose } from "./lib/text.js";
15
+ import { DocsSearch } from "./search.js";
16
+ import { ThemeSwitch, useThemeScript } from "./theme-switch.js";
17
+ /** What the page knows from the navigation: the section it sits in, and its neighbours. */
18
+ const DocsPage = createContext({});
19
+ /**
20
+ * The docs frame, in the site's order: overview, brand, foundations, rules,
21
+ * patterns, components by `@category`, standards. Editorial pages are the links
22
+ * you pass; rules, patterns and components come from the manifest.
23
+ *
24
+ * On a laptop the navigation is an inset panel down the left, each section
25
+ * folding open where the current page is, with search at the top and the colour
26
+ * mode at the foot; wide screens add an "On this page" rail. On a phone it is a
27
+ * bar with search and a drawer. Every page ends with the previous and next page,
28
+ * and `DocsHeader` names the section it sits in. Put it in the docs layout.
29
+ */
30
+ export function DocsShell({ manifest, rules, patterns, terms, search = true, themeSwitch = true, title = "Design system", logo, basePath = "/design/system", brand = [], foundations = [], standards = [], hero, children, }) {
31
+ const current = usePathname();
32
+ const searchItems = useMemo(() => searchIndex({
33
+ manifest,
34
+ rules: rules?.rules,
35
+ patterns: patterns?.patterns,
36
+ terms: terms?.terms,
37
+ pages: [...brand, ...foundations, ...(rules?.rules.length ? [{ label: "Rules", href: `${basePath}/rules` }] : []), ...standards],
38
+ basePath,
39
+ }), [manifest, rules, patterns, terms, brand, foundations, standards, basePath]);
40
+ const sections = useMemo(() => {
41
+ const families = groupFamilies(manifest);
42
+ const all = [
43
+ { id: "overview", label: "Overview", icon: House, href: basePath, groups: [] },
44
+ { id: "brand", label: "Brand", icon: BookOpen, groups: [{ links: brand }] },
45
+ { id: "foundations", label: "Foundations", icon: Palette, groups: [{ links: foundations }] },
46
+ ...(rules?.rules.length ? [{ id: "rules", label: "Rules", icon: Scale, href: `${basePath}/rules`, groups: [] }] : []),
47
+ {
48
+ id: "patterns",
49
+ label: "Patterns",
50
+ icon: LayoutTemplate,
51
+ groups: [{ links: (patterns?.patterns ?? []).map((p) => ({ label: p.name, href: `${basePath}/patterns/${p.id}` })) }],
52
+ },
53
+ {
54
+ id: "components",
55
+ label: "Components",
56
+ icon: Component,
57
+ groups: families.map((g) => ({
58
+ heading: families.length > 1 ? g.category : undefined,
59
+ links: g.families.map(({ head, parts }) => ({
60
+ label: head.name,
61
+ href: `${basePath}/components/${slugOf(head)}`,
62
+ children: parts.map((p) => ({ label: p.name, href: `${basePath}/components/${slugOf(p)}` })),
63
+ })),
64
+ })),
65
+ },
66
+ { id: "standards", label: "Standards", icon: Gauge, groups: [{ links: standards }] },
67
+ ];
68
+ return all.filter((s) => s.href || s.groups.some((g) => g.links.length));
69
+ }, [manifest, rules, patterns, brand, foundations, standards, basePath]);
70
+ // every page in navigation order, for the section a page sits in and its neighbours
71
+ const page = useMemo(() => {
72
+ // a section of one page has no section above it: the page is the section
73
+ const order = [];
74
+ for (const s of sections) {
75
+ if (s.href)
76
+ order.push({ link: { label: s.label, href: s.href } });
77
+ for (const g of s.groups)
78
+ for (const l of g.links)
79
+ order.push({ link: l, section: s.label });
80
+ }
81
+ const at = order.findIndex(({ link }) => link.href === current || link.children?.some((c) => c.href === current));
82
+ if (at < 0)
83
+ return {};
84
+ return { section: order[at].section, prev: order[at - 1]?.link, next: order[at + 1]?.link };
85
+ }, [sections, current]);
86
+ // a hook, so it runs whether or not the switch is shown; the script only reads a saved choice
87
+ useThemeScript();
88
+ const [menu, setMenu] = useState(false);
89
+ useEffect(() => setMenu(false), [current]);
90
+ const brandLink = (_jsxs(Link, { href: basePath, "data-slot": "docs-title", className: cx(target, "flex min-w-0 items-center gap-3 rounded-md focus-visible:outline-2 focus-visible:outline-ring"), children: [logo && (_jsx("span", { "data-slot": "docs-logo", className: "shrink-0", children: _jsx(Live, { children: logo }) })), _jsx("span", { className: cx(logo ? "sr-only" : "truncate text-base font-semibold tracking-tight"), children: title })] }));
91
+ return (_jsx(DocsPage.Provider, { value: page, children: _jsxs("div", { "data-designtools": "", "data-slot": "docs-shell", className: "min-h-dvh bg-canvas text-canvas-foreground lg:grid lg:grid-cols-[19rem_minmax(0,1fr)]", children: [_jsx("aside", { "data-slot": "docs-sidebar", className: "sticky top-0 hidden h-dvh p-3 lg:block", children: _jsxs("div", { className: "flex h-full flex-col overflow-hidden rounded-xl border border-border bg-surface text-surface-foreground shadow-xs", children: [_jsxs("div", { className: "flex flex-col gap-3 p-4 pb-3", children: [brandLink, search && _jsx(DocsSearch, { items: searchItems })] }), _jsx(Nav, { sections: sections, current: current, title: title }), themeSwitch && (_jsxs("div", { "data-slot": "docs-sidebar-foot", className: "flex items-center justify-between gap-3 border-t border-border px-4 py-3", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Appearance" }), _jsx(ThemeSwitch, {})] }))] }) }), _jsxs("div", { className: "min-w-0", children: [_jsxs("header", { "data-slot": "docs-topbar", className: "sticky top-0 z-20 flex h-16 items-center gap-2 border-b border-border bg-canvas/85 px-3 backdrop-blur-md lg:hidden", children: [_jsxs(Dialog.Root, { open: menu, onOpenChange: setMenu, children: [_jsx(Dialog.Trigger, { "aria-label": "Open the navigation", className: iconButton, children: _jsx(Menu, { "aria-hidden": true, className: "size-(--dt-size-sm)" }) }), _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Backdrop, { "data-designtools": "", className: "fixed inset-0 z-40 bg-canvas-foreground/30 transition-opacity duration-(--dt-duration-base) data-ending-style:opacity-0 data-starting-style:opacity-0" }), _jsxs(Dialog.Popup, { "data-designtools": "", "data-slot": "docs-drawer", "aria-label": title, className: "fixed inset-y-0 left-0 z-50 flex w-[min(21rem,88vw)] flex-col bg-surface text-surface-foreground shadow-xl transition-transform duration-(--dt-duration-slow) ease-(--dt-ease-out) data-ending-style:-translate-x-full data-starting-style:-translate-x-full", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border p-3 pl-4", children: [brandLink, _jsx(Dialog.Close, { "aria-label": "Close the navigation", className: iconButton, children: _jsx(X, { "aria-hidden": true, className: "size-(--dt-size-sm)" }) })] }), _jsx(Nav, { sections: sections, current: current, title: title, className: "pt-4" }), themeSwitch && (_jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-border px-4 py-3", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Appearance" }), _jsx(ThemeSwitch, {})] }))] })] })] }), _jsx("div", { className: "min-w-0 flex-1", children: brandLink }), search && _jsx(DocsSearch, { items: searchItems, shortcuts: false, variant: "icon" })] }), hero && current === basePath && (_jsx("div", { "data-slot": "docs-hero", className: "w-full", children: _jsx(Live, { children: hero }) })), _jsxs("div", { className: "mx-auto flex w-full max-w-[90rem] gap-16 px-5 pt-12 pb-20 sm:px-8 lg:px-12 lg:pt-20 xl:px-20", children: [_jsxs("main", { "data-slot": "docs-main", className: "flex min-w-0 flex-1 flex-col gap-16 [counter-reset:docs-section] lg:gap-24", children: [children, _jsx(Pager, { prev: page.prev, next: page.next })] }), _jsx(OnThisPage, { current: current })] })] })] }) }));
92
+ }
93
+ const iconButton = "inline-flex size-(--docs-target,2.75rem) shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring";
94
+ /** The sections, each a link or a fold that opens where the current page is. */
95
+ function Nav({ sections, current, title, className }) {
96
+ const holds = (s) => s.groups.some((g) => g.links.some((l) => l.href === current || l.children?.some((c) => c.href === current)));
97
+ const [open, setOpen] = useState(() => Object.fromEntries(sections.map((s) => [s.id, holds(s)])));
98
+ // moving to a page opens its section; nothing closes on its own
99
+ useEffect(() => {
100
+ const here = sections.find(holds);
101
+ if (here)
102
+ setOpen((o) => (o[here.id] ? o : { ...o, [here.id]: true }));
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ }, [current, sections]);
105
+ return (_jsx("nav", { "aria-label": title, "data-slot": "docs-nav", className: cx("min-h-0 flex-1 overflow-y-auto px-3 pb-4 text-sm", className), children: _jsx("ul", { className: "flex flex-col gap-0.5", children: sections.map((s) => {
106
+ const Icon = s.icon;
107
+ const row = cx(target, "group flex w-full items-center gap-3 rounded-md px-2.5 font-medium text-canvas-foreground hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring");
108
+ const icon = _jsx(Icon, { "aria-hidden": true, className: "size-(--dt-size-xs) shrink-0 text-muted-foreground group-hover:text-canvas-foreground" });
109
+ if (s.href) {
110
+ const active = current === s.href;
111
+ return (_jsx("li", { children: _jsxs(Link, { href: s.href, "aria-current": active ? "page" : undefined, "data-slot": "docs-nav-section", className: cx(row, active && "bg-muted"), children: [icon, s.label] }) }, s.id));
112
+ }
113
+ return (_jsx("li", { children: _jsxs(Collapsible.Root, { open: open[s.id] ?? false, onOpenChange: (next) => setOpen((o) => ({ ...o, [s.id]: next })), children: [_jsxs(Collapsible.Trigger, { "data-slot": "docs-nav-section", className: row, children: [icon, _jsx("span", { className: "flex-1 text-left", children: s.label }), _jsx(ChevronRight, { "aria-hidden": true, className: "size-(--dt-size-xs) text-muted-foreground transition-transform duration-(--dt-duration-fast) group-data-panel-open:rotate-90" })] }), _jsx(Collapsible.Panel, { "data-slot": "docs-nav-panel", className: "h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-(--dt-duration-base) ease-(--dt-ease-out) data-ending-style:h-0 data-starting-style:h-0", children: _jsx("div", { className: "ml-[1.1875rem] flex flex-col gap-3 border-l border-border py-1 pl-3", children: s.groups.map((g, i) => (_jsxs("div", { className: "flex flex-col", children: [g.heading && (_jsx("p", { "data-slot": "docs-nav-heading", className: "px-2.5 pt-3 pb-1.5 text-[0.6875rem] font-semibold tracking-[0.08em] text-muted-foreground uppercase first:pt-1", children: g.heading })), _jsx("ul", { className: "flex flex-col", children: g.links.map((link) => {
114
+ const here = current === link.href || link.children?.some((c) => c.href === current);
115
+ return (_jsxs("li", { children: [_jsx(NavLink, { link: link, current: current }), here && link.children?.length ? (_jsx("ul", { "data-slot": "docs-nav-parts", "aria-label": `${link.label} parts`, className: "ml-3 flex flex-col border-l border-border pl-2", children: link.children.map((child) => (_jsx("li", { children: _jsx(NavLink, { link: child, current: current }) }, child.href))) })) : null] }, link.href));
116
+ }) })] }, g.heading ?? i))) }) })] }) }, s.id));
117
+ }) }) }));
118
+ }
119
+ function NavLink({ link, current }) {
120
+ const active = current === link.href;
121
+ return (_jsxs(Link, { href: link.href, "aria-current": active ? "page" : undefined, "data-slot": "docs-nav-link", className: cx(target, "relative flex items-center rounded-md px-2.5 text-canvas-foreground/80 hover:bg-muted hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring", active && "bg-muted font-medium text-canvas-foreground"), children: [active && _jsx("span", { "aria-hidden": true, "data-slot": "docs-nav-marker", className: cx(accent, "absolute inset-y-2.5 -left-[0.8125rem] w-0.5 rounded-full") }), link.label] }));
122
+ }
123
+ /** The page's sections, beside it on a wide screen, marking the one being read. */
124
+ function OnThisPage({ current }) {
125
+ const [items, setItems] = useState([]);
126
+ const [active, setActive] = useState();
127
+ useEffect(() => {
128
+ const headings = [...document.querySelectorAll('[data-slot="docs-main"] [data-slot="docs-section-title"][id]')];
129
+ setItems(headings.map((h) => ({ id: h.id, label: h.textContent ?? "" })));
130
+ setActive(headings[0]?.id);
131
+ if (!headings.length)
132
+ return;
133
+ // the section being read is the last whose heading has passed the top quarter of the window
134
+ let frame = 0;
135
+ const update = () => {
136
+ frame = 0;
137
+ const line = window.innerHeight * 0.25;
138
+ let at = headings[0];
139
+ for (const h of headings)
140
+ if (h.getBoundingClientRect().top <= line)
141
+ at = h;
142
+ // at the foot of the page the last section is the one in view, however short
143
+ if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4)
144
+ at = headings[headings.length - 1];
145
+ setActive(at.id);
146
+ };
147
+ const onScroll = () => {
148
+ if (!frame)
149
+ frame = requestAnimationFrame(update);
150
+ };
151
+ update();
152
+ window.addEventListener("scroll", onScroll, { passive: true });
153
+ window.addEventListener("resize", onScroll);
154
+ return () => {
155
+ cancelAnimationFrame(frame);
156
+ window.removeEventListener("scroll", onScroll);
157
+ window.removeEventListener("resize", onScroll);
158
+ };
159
+ }, [current]);
160
+ if (items.length < 2)
161
+ return null;
162
+ return (_jsxs("nav", { "aria-label": "On this page", "data-slot": "docs-toc", className: "sticky top-16 hidden h-fit w-48 shrink-0 2xl:block", children: [_jsx("p", { className: "mb-3 text-xs font-medium tracking-wide text-muted-foreground uppercase", children: "On this page" }), _jsx("ul", { className: "flex flex-col border-l border-border text-sm", children: items.map((item) => (_jsx("li", { children: _jsxs("a", { href: `#${item.id}`, "aria-current": active === item.id ? "location" : undefined, className: cx("relative -ml-px block border-l border-transparent py-1.5 pl-4 text-muted-foreground hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring", active === item.id && "font-medium text-canvas-foreground"), children: [active === item.id && _jsx("span", { "aria-hidden": true, className: cx(accent, "absolute inset-y-1.5 -left-px w-0.5 rounded-full") }), item.label] }) }, item.id))) })] }));
163
+ }
164
+ /** The previous and next page in the navigation's order. */
165
+ function Pager({ prev, next }) {
166
+ if (!prev && !next)
167
+ return null;
168
+ const card = cx(target, "group flex flex-col gap-1 rounded-lg border border-border p-5 transition-colors duration-(--dt-duration-fast) hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring");
169
+ return (_jsxs("nav", { "aria-label": "Previous and next", "data-slot": "docs-pager", className: "-mt-4 grid gap-3 sm:grid-cols-2 lg:-mt-8", children: [prev ? (_jsxs(Link, { href: prev.href, className: card, children: [_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(ArrowLeft, { "aria-hidden": true, className: "size-(--dt-size-xs) transition-transform group-hover:-translate-x-0.5" }), " Previous"] }), _jsx("span", { className: "text-base font-medium", children: prev.label })] })) : (_jsx("span", {})), next && (_jsxs(Link, { href: next.href, className: cx(card, "sm:items-end sm:text-right"), children: [_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: ["Next ", _jsx(ArrowRight, { "aria-hidden": true, className: "size-(--dt-size-xs) transition-transform group-hover:translate-x-0.5" })] }), _jsx("span", { className: "text-base font-medium", children: next.label })] }))] }));
170
+ }
171
+ /**
172
+ * A page's heading: the section it sits in (from the navigation), the title, the
173
+ * lead, and the page's actions as children. Where the page comes from goes at the
174
+ * foot, in `DocsFooter`.
175
+ */
176
+ export function DocsHeader({ title, lead, eyebrow, status, children, }) {
177
+ const { section } = useContext(DocsPage);
178
+ const over = eyebrow ?? (section && section !== title ? section : undefined);
179
+ return (_jsxs("header", { "data-slot": "docs-header", className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [over || status ? (_jsxs("span", { className: "flex flex-wrap items-center gap-3", children: [over && (_jsx("p", { "data-slot": "docs-eyebrow", className: "text-xs font-medium tracking-[var(--tracking-widest,0.16em)] text-muted-foreground uppercase", children: over })), status && _jsx(StatusBadge, { status: status })] })) : (_jsx("span", {})), children && (_jsx("div", { "data-slot": "docs-header-actions", className: "flex flex-wrap items-center gap-2", children: children }))] }), _jsx("h1", { className: cx(display, "text-5xl leading-[1.05] tracking-tight md:text-7xl"), children: title }), lead && (_jsx("div", { "data-slot": "docs-lead", className: "flex max-w-3xl flex-col gap-3 text-xl leading-snug font-light text-muted-foreground md:text-2xl md:leading-snug [&_code]:bg-transparent [&_code]:p-0", children: _jsx(Prose, { text: lead }) }))] }));
180
+ }
181
+ /**
182
+ * Where a page comes from, at its foot: a generated page names the version and
183
+ * commit it was built from; an editorial page names its author, who reviewed it,
184
+ * when, and whether it is settled. Never both, and never a review date on a
185
+ * generated page, which would be invented. `source` names the file it documents.
186
+ */
187
+ export function DocsFooter({ provenance, editorial, source, }) {
188
+ if (!provenance && !editorial && !source)
189
+ return null;
190
+ return (_jsxs("footer", { "data-slot": "docs-footer", className: "order-last -mt-8 flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-border pt-6 text-xs text-muted-foreground lg:-mt-14", children: [(provenance || editorial) && (_jsxs("p", { "data-slot": "docs-provenance", className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: cx("size-1.5 shrink-0 rounded-full", editorial ? (editorial.status === "published" ? "bg-success" : "bg-warning") : "bg-primary") }), editorial
191
+ ? `Written by ${editorial.author} · ${editorial.reviewer ? `reviewed by ${editorial.reviewer}, ` : "last reviewed "}${editorial.reviewed} · ${editorial.status}`
192
+ : `${provenance} · generated from the code`] })), source && (_jsx("p", { "data-slot": "docs-source", className: "font-mono", children: source }))] }));
193
+ }
194
+ /**
195
+ * A titled section of a docs page, numbered in a gutter beside it (01, 02, …,
196
+ * counted by CSS in page order) and listed in "On this page".
197
+ */
198
+ export function DocsSection({ title, lead, children }) {
199
+ const id = title.toLowerCase().replace(/[^a-z0-9]+/g, "-");
200
+ return (_jsxs("section", { "data-slot": "docs-section", "aria-labelledby": id, className: "grid gap-x-10 gap-y-8 [counter-increment:docs-section] xl:grid-cols-[5rem_minmax(0,1fr)]", children: [_jsx("span", { "aria-hidden": true, className: "font-mono text-xs tracking-[var(--tracking-widest,0.16em)] text-muted-foreground [text-box:trim-both_cap_alphabetic] before:content-[counter(docs-section,decimal-leading-zero)]" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-10", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("h2", { id: id, "data-slot": "docs-section-title", className: cx(display, "scroll-mt-24 text-4xl leading-[1.1] tracking-tight [text-box:trim-both_cap_alphabetic] md:text-5xl"), children: title }), lead && _jsx("p", { className: "max-w-2xl text-lg leading-relaxed text-muted-foreground", children: lead })] }), children] })] }));
201
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The project's performance budgets, accessibility target and support baseline,
4
+ * rendered from the thresholds it already records (`.mxa/stack.json`
5
+ * `baselines`), so the docs and the checks cannot disagree. A value nobody has
6
+ * set is left out rather than shown as `null`; a note is shown as a sentence.
7
+ * Thresholds a client agrees beyond the schema (`textSize`, `touchTargets`) are
8
+ * titled from their keys.
9
+ */
10
+ export declare function Standards({ baselines, children }: {
11
+ baselines: Record<string, unknown>;
12
+ children?: ReactNode;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Gauge, Info, Monitor, PersonStanding, Ruler } from "lucide-react";
3
+ import { standardsSections } from "./lib/standards.js";
4
+ const ICON = { a11y: PersonStanding, performance: Gauge, support: Monitor };
5
+ /**
6
+ * The project's performance budgets, accessibility target and support baseline,
7
+ * rendered from the thresholds it already records (`.mxa/stack.json`
8
+ * `baselines`), so the docs and the checks cannot disagree. A value nobody has
9
+ * set is left out rather than shown as `null`; a note is shown as a sentence.
10
+ * Thresholds a client agrees beyond the schema (`textSize`, `touchTargets`) are
11
+ * titled from their keys.
12
+ */
13
+ export function Standards({ baselines, children }) {
14
+ return (_jsxs("div", { "data-slot": "standards", className: "flex flex-col gap-6", children: [_jsx("div", { className: "grid gap-x-12 gap-y-10 md:grid-cols-2 xl:grid-cols-3", children: standardsSections(baselines).map((section) => {
15
+ const Icon = ICON[section.key] ?? Ruler;
16
+ const single = section.rows.length === 1 && !section.rows[0].label;
17
+ return (_jsxs("section", { "data-slot": "standard", className: "flex flex-col gap-4 border-t border-border pt-5", children: [_jsxs("h3", { className: "flex items-center gap-2 text-sm font-medium text-muted-foreground", children: [_jsx(Icon, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), section.title] }), single ? (_jsx("p", { className: "text-5xl font-light tracking-tight", children: section.rows[0].value })) : section.rows.length ? (_jsx("dl", { className: "flex flex-col gap-3", children: section.rows.map((row) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: row.label }), _jsx("dd", { className: "text-2xl font-light tracking-tight tabular-nums", children: row.value })] }, row.label))) })) : null, section.notes.map((note) => (_jsx("p", { className: "text-sm text-muted-foreground", children: note }, note)))] }, section.key));
18
+ }) }), typeof baselines.$note === "string" && (_jsxs("p", { className: "flex items-start gap-2 text-sm text-muted-foreground", children: [_jsx(Info, { "aria-hidden": true, className: "mt-0.5 size-(--dt-size-xs) shrink-0" }), baselines.$note] })), children] }));
19
+ }