@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
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,89 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ComponentsManifest, PatternsManifest, RulesManifest, TaxonomyManifest } from "./lib/manifest-types.js";
3
+ export interface DocsLink {
4
+ label: string;
5
+ href: string;
6
+ /** Shown beneath it while you are on it or one of them: a component's parts. */
7
+ children?: DocsLink[];
8
+ }
9
+ /**
10
+ * The docs frame, in the site's order: overview, brand, foundations, rules,
11
+ * patterns, components by `@category`, standards. Editorial pages are the links
12
+ * you pass; rules, patterns and components come from the manifest.
13
+ *
14
+ * On a laptop the navigation is an inset panel down the left, each section
15
+ * folding open where the current page is, with search at the top and the colour
16
+ * mode at the foot; wide screens add an "On this page" rail. On a phone it is a
17
+ * bar with search and a drawer. Every page ends with the previous and next page,
18
+ * and `DocsHeader` names the section it sits in. Put it in the docs layout.
19
+ */
20
+ export declare function DocsShell({ manifest, rules, patterns, terms, search, themeSwitch, title, logo, basePath, brand, foundations, standards, hero, children, }: {
21
+ manifest: ComponentsManifest;
22
+ /** Listed under Rules, each linking to its anchor on `${basePath}/rules`. */
23
+ rules?: RulesManifest;
24
+ /** Listed under Patterns, each at `${basePath}/patterns/${id}`. */
25
+ patterns?: PatternsManifest;
26
+ /** The taxonomy, so search finds a component by a wrong name ("Dropdown" finds Select). */
27
+ terms?: TaxonomyManifest;
28
+ /** Search over components, parts, rules, patterns and pages, on ⌘K, Ctrl+K or /. On by default. */
29
+ search?: boolean;
30
+ /** System, light and dark, at the foot of the navigation. Off for a project with its own switch. */
31
+ themeSwitch?: boolean;
32
+ title?: string;
33
+ /** The client's logo, shown beside the title: the site is theirs. */
34
+ logo?: ReactNode;
35
+ /** Component pages live at `${basePath}/components/${slug}`. */
36
+ basePath?: string;
37
+ /** Brand pages: principles, design language, voice, logo, imagery. */
38
+ brand?: DocsLink[];
39
+ /** Foundations pages: colour, type, scale. */
40
+ foundations?: DocsLink[];
41
+ /** Standards pages: budgets, accessibility, adherence. */
42
+ standards?: DocsLink[];
43
+ /**
44
+ * A full-bleed introduction across the top of the overview, edge to edge of the
45
+ * content area: the brand's splash. The overview's own header can then be left out.
46
+ */
47
+ hero?: ReactNode;
48
+ children: ReactNode;
49
+ }): import("react").JSX.Element;
50
+ /**
51
+ * A page's heading: the section it sits in (from the navigation), the title, the
52
+ * lead, and the page's actions as children. Where the page comes from goes at the
53
+ * foot, in `DocsFooter`.
54
+ */
55
+ export declare function DocsHeader({ title, lead, eyebrow, children, }: {
56
+ title: string;
57
+ lead?: string;
58
+ /** Above the title. Defaults to the section the page sits in. */
59
+ eyebrow?: string;
60
+ /** The page's actions, beside the title: `PageActions`. */
61
+ children?: ReactNode;
62
+ }): import("react").JSX.Element;
63
+ /**
64
+ * Where a page comes from, at its foot: a generated page names the version and
65
+ * commit it was built from; an editorial page names its author, who reviewed it,
66
+ * when, and whether it is settled. Never both, and never a review date on a
67
+ * generated page, which would be invented. `source` names the file it documents.
68
+ */
69
+ export declare function DocsFooter({ provenance, editorial, source, }: {
70
+ /** For a generated page: `Generated from acme-web@1.4.0 at 3f2a9c1`, from `designtools-manifest provenance`. */
71
+ provenance?: string;
72
+ /** For an editorial page. `reviewer` when the person who signs it off is not the author. */
73
+ editorial?: {
74
+ author: string;
75
+ reviewer?: string;
76
+ reviewed: string;
77
+ status: "draft" | "published";
78
+ };
79
+ source?: string;
80
+ }): import("react").JSX.Element | null;
81
+ /**
82
+ * A titled section of a docs page, numbered in a gutter beside it (01, 02, …,
83
+ * counted by CSS in page order) and listed in "On this page".
84
+ */
85
+ export declare function DocsSection({ title, lead, children }: {
86
+ title: string;
87
+ lead?: string;
88
+ children: ReactNode;
89
+ }): import("react").JSX.Element;
package/dist/shell.js ADDED
@@ -0,0 +1,199 @@
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 { accent, cx, display, target } from "./lib/cx.js";
10
+ import { groupFamilies, slugOf } from "./lib/manifest.js";
11
+ import { searchIndex } from "./lib/search.js";
12
+ import { Prose } from "./lib/text.js";
13
+ import { DocsSearch } from "./search.js";
14
+ import { ThemeSwitch, useThemeScript } from "./theme-switch.js";
15
+ /** What the page knows from the navigation: the section it sits in, and its neighbours. */
16
+ const DocsPage = createContext({});
17
+ /**
18
+ * The docs frame, in the site's order: overview, brand, foundations, rules,
19
+ * patterns, components by `@category`, standards. Editorial pages are the links
20
+ * you pass; rules, patterns and components come from the manifest.
21
+ *
22
+ * On a laptop the navigation is an inset panel down the left, each section
23
+ * folding open where the current page is, with search at the top and the colour
24
+ * mode at the foot; wide screens add an "On this page" rail. On a phone it is a
25
+ * bar with search and a drawer. Every page ends with the previous and next page,
26
+ * and `DocsHeader` names the section it sits in. Put it in the docs layout.
27
+ */
28
+ export function DocsShell({ manifest, rules, patterns, terms, search = true, themeSwitch = true, title = "Design system", logo, basePath = "/design/system", brand = [], foundations = [], standards = [], hero, children, }) {
29
+ const current = usePathname();
30
+ const searchItems = useMemo(() => searchIndex({
31
+ manifest,
32
+ rules: rules?.rules,
33
+ patterns: patterns?.patterns,
34
+ terms: terms?.terms,
35
+ pages: [...brand, ...foundations, ...(rules?.rules.length ? [{ label: "Rules", href: `${basePath}/rules` }] : []), ...standards],
36
+ basePath,
37
+ }), [manifest, rules, patterns, terms, brand, foundations, standards, basePath]);
38
+ const sections = useMemo(() => {
39
+ const families = groupFamilies(manifest);
40
+ const all = [
41
+ { id: "overview", label: "Overview", icon: House, href: basePath, groups: [] },
42
+ { id: "brand", label: "Brand", icon: BookOpen, groups: [{ links: brand }] },
43
+ { id: "foundations", label: "Foundations", icon: Palette, groups: [{ links: foundations }] },
44
+ ...(rules?.rules.length ? [{ id: "rules", label: "Rules", icon: Scale, href: `${basePath}/rules`, groups: [] }] : []),
45
+ {
46
+ id: "patterns",
47
+ label: "Patterns",
48
+ icon: LayoutTemplate,
49
+ groups: [{ links: (patterns?.patterns ?? []).map((p) => ({ label: p.name, href: `${basePath}/patterns/${p.id}` })) }],
50
+ },
51
+ {
52
+ id: "components",
53
+ label: "Components",
54
+ icon: Component,
55
+ groups: families.map((g) => ({
56
+ heading: families.length > 1 ? g.category : undefined,
57
+ links: g.families.map(({ head, parts }) => ({
58
+ label: head.name,
59
+ href: `${basePath}/components/${slugOf(head)}`,
60
+ children: parts.map((p) => ({ label: p.name, href: `${basePath}/components/${slugOf(p)}` })),
61
+ })),
62
+ })),
63
+ },
64
+ { id: "standards", label: "Standards", icon: Gauge, groups: [{ links: standards }] },
65
+ ];
66
+ return all.filter((s) => s.href || s.groups.some((g) => g.links.length));
67
+ }, [manifest, rules, patterns, brand, foundations, standards, basePath]);
68
+ // every page in navigation order, for the section a page sits in and its neighbours
69
+ const page = useMemo(() => {
70
+ // a section of one page has no section above it: the page is the section
71
+ const order = [];
72
+ for (const s of sections) {
73
+ if (s.href)
74
+ order.push({ link: { label: s.label, href: s.href } });
75
+ for (const g of s.groups)
76
+ for (const l of g.links)
77
+ order.push({ link: l, section: s.label });
78
+ }
79
+ const at = order.findIndex(({ link }) => link.href === current || link.children?.some((c) => c.href === current));
80
+ if (at < 0)
81
+ return {};
82
+ return { section: order[at].section, prev: order[at - 1]?.link, next: order[at + 1]?.link };
83
+ }, [sections, current]);
84
+ // a hook, so it runs whether or not the switch is shown; the script only reads a saved choice
85
+ useThemeScript();
86
+ const [menu, setMenu] = useState(false);
87
+ useEffect(() => setMenu(false), [current]);
88
+ 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: logo })), _jsx("span", { className: cx(logo ? "sr-only" : "truncate text-base font-semibold tracking-tight"), children: title })] }));
89
+ 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 }), 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: 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 })] })] })] }) }));
90
+ }
91
+ 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";
92
+ /** The sections, each a link or a fold that opens where the current page is. */
93
+ function Nav({ sections, current, title }) {
94
+ const holds = (s) => s.groups.some((g) => g.links.some((l) => l.href === current || l.children?.some((c) => c.href === current)));
95
+ const [open, setOpen] = useState(() => Object.fromEntries(sections.map((s) => [s.id, holds(s)])));
96
+ // moving to a page opens its section; nothing closes on its own
97
+ useEffect(() => {
98
+ const here = sections.find(holds);
99
+ if (here)
100
+ setOpen((o) => (o[here.id] ? o : { ...o, [here.id]: true }));
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [current, sections]);
103
+ return (_jsx("nav", { "aria-label": title, "data-slot": "docs-nav", className: "min-h-0 flex-1 overflow-y-auto px-3 pb-4 text-sm", children: _jsx("ul", { className: "flex flex-col gap-0.5", children: sections.map((s) => {
104
+ const Icon = s.icon;
105
+ 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");
106
+ const icon = _jsx(Icon, { "aria-hidden": true, className: "size-(--dt-size-xs) shrink-0 text-muted-foreground group-hover:text-canvas-foreground" });
107
+ if (s.href) {
108
+ const active = current === s.href;
109
+ 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));
110
+ }
111
+ 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-1 pb-1 text-xs font-medium tracking-wide text-muted-foreground uppercase", children: g.heading })), _jsx("ul", { className: "flex flex-col", children: g.links.map((link) => {
112
+ const here = current === link.href || link.children?.some((c) => c.href === current);
113
+ 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));
114
+ }) })] }, g.heading ?? i))) }) })] }) }, s.id));
115
+ }) }) }));
116
+ }
117
+ function NavLink({ link, current }) {
118
+ const active = current === link.href;
119
+ 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-muted-foreground 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] }));
120
+ }
121
+ /** The page's sections, beside it on a wide screen, marking the one being read. */
122
+ function OnThisPage({ current }) {
123
+ const [items, setItems] = useState([]);
124
+ const [active, setActive] = useState();
125
+ useEffect(() => {
126
+ const headings = [...document.querySelectorAll('[data-slot="docs-main"] [data-slot="docs-section-title"][id]')];
127
+ setItems(headings.map((h) => ({ id: h.id, label: h.textContent ?? "" })));
128
+ setActive(headings[0]?.id);
129
+ if (!headings.length)
130
+ return;
131
+ // the section being read is the last whose heading has passed the top quarter of the window
132
+ let frame = 0;
133
+ const update = () => {
134
+ frame = 0;
135
+ const line = window.innerHeight * 0.25;
136
+ let at = headings[0];
137
+ for (const h of headings)
138
+ if (h.getBoundingClientRect().top <= line)
139
+ at = h;
140
+ // at the foot of the page the last section is the one in view, however short
141
+ if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4)
142
+ at = headings[headings.length - 1];
143
+ setActive(at.id);
144
+ };
145
+ const onScroll = () => {
146
+ if (!frame)
147
+ frame = requestAnimationFrame(update);
148
+ };
149
+ update();
150
+ window.addEventListener("scroll", onScroll, { passive: true });
151
+ window.addEventListener("resize", onScroll);
152
+ return () => {
153
+ cancelAnimationFrame(frame);
154
+ window.removeEventListener("scroll", onScroll);
155
+ window.removeEventListener("resize", onScroll);
156
+ };
157
+ }, [current]);
158
+ if (items.length < 2)
159
+ return null;
160
+ 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))) })] }));
161
+ }
162
+ /** The previous and next page in the navigation's order. */
163
+ function Pager({ prev, next }) {
164
+ if (!prev && !next)
165
+ return null;
166
+ 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");
167
+ return (_jsxs("nav", { "aria-label": "Previous and next", "data-slot": "docs-pager", className: "grid gap-3 border-t border-border pt-10 sm:grid-cols-2", 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 })] }))] }));
168
+ }
169
+ /**
170
+ * A page's heading: the section it sits in (from the navigation), the title, the
171
+ * lead, and the page's actions as children. Where the page comes from goes at the
172
+ * foot, in `DocsFooter`.
173
+ */
174
+ export function DocsHeader({ title, lead, eyebrow, children, }) {
175
+ const { section } = useContext(DocsPage);
176
+ const over = eyebrow ?? (section && section !== title ? section : undefined);
177
+ return (_jsxs("header", { "data-slot": "docs-header", className: "flex flex-col gap-6 border-b border-border pb-12 lg:pb-16", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", 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 })) : (_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 }) }))] }));
178
+ }
179
+ /**
180
+ * Where a page comes from, at its foot: a generated page names the version and
181
+ * commit it was built from; an editorial page names its author, who reviewed it,
182
+ * when, and whether it is settled. Never both, and never a review date on a
183
+ * generated page, which would be invented. `source` names the file it documents.
184
+ */
185
+ export function DocsFooter({ provenance, editorial, source, }) {
186
+ if (!provenance && !editorial && !source)
187
+ return null;
188
+ return (_jsxs("footer", { "data-slot": "docs-footer", className: "flex flex-wrap items-center gap-x-6 gap-y-2 text-xs text-muted-foreground", 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
189
+ ? `Written by ${editorial.author} · ${editorial.reviewer ? `reviewed by ${editorial.reviewer}, ` : "last reviewed "}${editorial.reviewed} · ${editorial.status}`
190
+ : `${provenance} · generated from the code`] })), source && (_jsx("p", { "data-slot": "docs-source", className: "font-mono", children: source }))] }));
191
+ }
192
+ /**
193
+ * A titled section of a docs page, numbered in a gutter beside it (01, 02, …,
194
+ * counted by CSS in page order) and listed in "On this page".
195
+ */
196
+ export function DocsSection({ title, lead, children }) {
197
+ const id = title.toLowerCase().replace(/[^a-z0-9]+/g, "-");
198
+ 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: "pt-2 font-mono text-xs tracking-[var(--tracking-widest,0.16em)] text-muted-foreground 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 md:text-5xl"), children: title }), lead && _jsx("p", { className: "max-w-2xl text-lg leading-relaxed text-muted-foreground", children: lead })] }), children] })] }));
199
+ }
@@ -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
+ }