@designtools/docs 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -2
  4. package/dist/a11y-panel.d.ts +58 -0
  5. package/dist/a11y-panel.js +75 -0
  6. package/dist/adherence-summary.d.ts +9 -0
  7. package/dist/adherence-summary.js +27 -0
  8. package/dist/agent-view.d.ts +19 -0
  9. package/dist/agent-view.js +23 -0
  10. package/dist/anatomy.d.ts +12 -0
  11. package/dist/anatomy.js +38 -0
  12. package/dist/ask-claude.d.ts +14 -0
  13. package/dist/ask-claude.js +40 -0
  14. package/dist/cli.js +322 -0
  15. package/dist/code-view.d.ts +17 -0
  16. package/dist/code-view.js +42 -0
  17. package/dist/examples.d.ts +25 -0
  18. package/dist/examples.js +48 -0
  19. package/dist/glossary.d.ts +5 -0
  20. package/dist/glossary.js +14 -0
  21. package/dist/imagery.d.ts +6 -0
  22. package/dist/imagery.js +17 -0
  23. package/dist/index.d.ts +34 -0
  24. package/dist/index.js +33 -0
  25. package/dist/lib/adherence-types.d.ts +168 -0
  26. package/dist/lib/adherence-types.js +13 -0
  27. package/dist/lib/boundary.d.ts +16 -0
  28. package/dist/lib/boundary.js +15 -0
  29. package/dist/lib/contrast.d.ts +8 -0
  30. package/dist/lib/contrast.js +42 -0
  31. package/dist/lib/cx.d.ts +24 -0
  32. package/dist/lib/cx.js +26 -0
  33. package/dist/lib/jsx.d.ts +6 -0
  34. package/dist/lib/jsx.js +29 -0
  35. package/dist/lib/lookup.d.ts +35 -0
  36. package/dist/lib/lookup.js +41 -0
  37. package/dist/lib/manifest-types.d.ts +253 -0
  38. package/dist/lib/manifest-types.js +14 -0
  39. package/dist/lib/manifest.d.ts +98 -0
  40. package/dist/lib/manifest.js +173 -0
  41. package/dist/lib/markdown.d.ts +50 -0
  42. package/dist/lib/markdown.js +286 -0
  43. package/dist/lib/measure.d.ts +12 -0
  44. package/dist/lib/measure.js +62 -0
  45. package/dist/lib/pseudo.d.ts +11 -0
  46. package/dist/lib/pseudo.js +71 -0
  47. package/dist/lib/registry.d.ts +21 -0
  48. package/dist/lib/registry.js +11 -0
  49. package/dist/lib/search.d.ts +44 -0
  50. package/dist/lib/search.js +62 -0
  51. package/dist/lib/standards.d.ts +17 -0
  52. package/dist/lib/standards.js +81 -0
  53. package/dist/lib/status.d.ts +7 -0
  54. package/dist/lib/status.js +17 -0
  55. package/dist/lib/text.d.ts +8 -0
  56. package/dist/lib/text.js +9 -0
  57. package/dist/lib/ui.d.ts +100 -0
  58. package/dist/lib/ui.js +89 -0
  59. package/dist/lib/vision.d.ts +18 -0
  60. package/dist/lib/vision.js +27 -0
  61. package/dist/logo-usage.d.ts +14 -0
  62. package/dist/logo-usage.js +45 -0
  63. package/dist/next/config.d.ts +85 -0
  64. package/dist/next/config.js +1 -0
  65. package/dist/next/index.d.ts +62 -0
  66. package/dist/next/index.js +63 -0
  67. package/dist/next/markdown.d.ts +20 -0
  68. package/dist/next/markdown.js +95 -0
  69. package/dist/next/pages.d.ts +6 -0
  70. package/dist/next/pages.js +122 -0
  71. package/dist/next/prose.d.ts +9 -0
  72. package/dist/next/prose.js +37 -0
  73. package/dist/next/proxy.d.ts +19 -0
  74. package/dist/next/proxy.js +56 -0
  75. package/dist/next/site.d.ts +53 -0
  76. package/dist/next/site.js +136 -0
  77. package/dist/page-actions.d.ts +9 -0
  78. package/dist/page-actions.js +32 -0
  79. package/dist/pattern.d.ts +8 -0
  80. package/dist/pattern.js +19 -0
  81. package/dist/playground.d.ts +62 -0
  82. package/dist/playground.js +111 -0
  83. package/dist/preview-frame.d.ts +31 -0
  84. package/dist/preview-frame.js +63 -0
  85. package/dist/props-table.d.ts +10 -0
  86. package/dist/props-table.js +45 -0
  87. package/dist/rule.d.ts +12 -0
  88. package/dist/rule.js +32 -0
  89. package/dist/scale.d.ts +9 -0
  90. package/dist/scale.js +58 -0
  91. package/dist/search.d.ts +16 -0
  92. package/dist/search.js +46 -0
  93. package/dist/shell.d.ts +89 -0
  94. package/dist/shell.js +199 -0
  95. package/dist/standards.d.ts +13 -0
  96. package/dist/standards.js +19 -0
  97. package/dist/styles.css +3519 -0
  98. package/dist/swatches.d.ts +28 -0
  99. package/dist/swatches.js +94 -0
  100. package/dist/theme-switch.d.ts +20 -0
  101. package/dist/theme-switch.js +68 -0
  102. package/dist/type-ramp.d.ts +10 -0
  103. package/dist/type-ramp.js +31 -0
  104. package/dist/usage.d.ts +13 -0
  105. package/dist/usage.js +55 -0
  106. package/dist/variant-matrix.d.ts +15 -0
  107. package/dist/variant-matrix.js +53 -0
  108. package/dist/voice-terms.d.ts +5 -0
  109. package/dist/voice-terms.js +13 -0
  110. package/dist/workbench.d.ts +33 -0
  111. package/dist/workbench.js +273 -0
  112. package/package.json +107 -3
  113. package/src/a11y-panel.tsx +297 -0
  114. package/src/adherence-summary.tsx +134 -0
  115. package/src/agent-view.tsx +54 -0
  116. package/src/anatomy.tsx +114 -0
  117. package/src/ask-claude.tsx +90 -0
  118. package/src/code-view.tsx +112 -0
  119. package/src/css.d.ts +1 -0
  120. package/src/examples.tsx +141 -0
  121. package/src/glossary.tsx +71 -0
  122. package/src/imagery.tsx +64 -0
  123. package/src/index.ts +34 -0
  124. package/src/lib/adherence-types.ts +181 -0
  125. package/src/lib/boundary.tsx +21 -0
  126. package/src/lib/contrast.ts +44 -0
  127. package/src/lib/cx.ts +30 -0
  128. package/src/lib/jsx.ts +29 -0
  129. package/src/lib/lookup.tsx +49 -0
  130. package/src/lib/manifest-types.ts +284 -0
  131. package/src/lib/manifest.ts +228 -0
  132. package/src/lib/markdown.ts +288 -0
  133. package/src/lib/measure.tsx +101 -0
  134. package/src/lib/pseudo.ts +74 -0
  135. package/src/lib/registry.tsx +29 -0
  136. package/src/lib/search.ts +99 -0
  137. package/src/lib/standards.ts +82 -0
  138. package/src/lib/status.tsx +32 -0
  139. package/src/lib/text.tsx +27 -0
  140. package/src/lib/ui.tsx +291 -0
  141. package/src/lib/vision.tsx +44 -0
  142. package/src/logo-usage.tsx +140 -0
  143. package/src/next/config.ts +80 -0
  144. package/src/next/index.tsx +100 -0
  145. package/src/next/markdown.ts +119 -0
  146. package/src/next/pages.tsx +370 -0
  147. package/src/next/prose.tsx +87 -0
  148. package/src/next/proxy.ts +55 -0
  149. package/src/next/site.ts +173 -0
  150. package/src/page-actions.tsx +92 -0
  151. package/src/pattern.tsx +67 -0
  152. package/src/playground.tsx +352 -0
  153. package/src/preview-frame.tsx +131 -0
  154. package/src/props-table.tsx +154 -0
  155. package/src/rule.tsx +116 -0
  156. package/src/scale.tsx +175 -0
  157. package/src/search.tsx +155 -0
  158. package/src/shell.tsx +555 -0
  159. package/src/standards.tsx +58 -0
  160. package/src/styles.css +155 -0
  161. package/src/swatches.tsx +287 -0
  162. package/src/theme-switch.tsx +90 -0
  163. package/src/type-ramp.tsx +150 -0
  164. package/src/usage.tsx +148 -0
  165. package/src/variant-matrix.tsx +195 -0
  166. package/src/voice-terms.tsx +46 -0
  167. package/src/workbench.tsx +584 -0
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Menu } from "@base-ui/react/menu";
4
+ import { Braces, Check, ChevronDown, Copy, FileText, Files } from "lucide-react";
5
+ import { usePathname } from "next/navigation";
6
+ import { useState } from "react";
7
+ /**
8
+ * What a reader can take from a page, as one split button: copy the page as
9
+ * markdown, the same markdown any agent gets at `.md`, with a menu beside it to
10
+ * view that markdown, copy the whole system as one file, or open the manifest.
11
+ * Put it in a `DocsHeader`.
12
+ */
13
+ export function PageActions({ basePath = "/design/system" }) {
14
+ const path = usePathname().replace(/\/$/, "");
15
+ const [done, setDone] = useState(null);
16
+ const copyFrom = async (url, what) => {
17
+ try {
18
+ const res = await fetch(url);
19
+ if (!res.ok)
20
+ throw new Error(String(res.status));
21
+ await navigator.clipboard.writeText(await res.text());
22
+ setDone(what);
23
+ }
24
+ catch {
25
+ setDone("Could not copy");
26
+ }
27
+ setTimeout(() => setDone(null), 1600);
28
+ };
29
+ const item = "flex min-h-(--docs-target,2.75rem) cursor-pointer items-center gap-3 rounded-md px-3 text-sm outline-none select-none data-highlighted:bg-muted";
30
+ const icon = "size-4 shrink-0 text-muted-foreground";
31
+ return (_jsxs("div", { "data-slot": "page-actions", className: "inline-flex items-stretch rounded-lg border border-border bg-canvas text-sm shadow-xs", children: [_jsxs("button", { type: "button", onClick: () => copyFrom(`${path}.md`, "Page copied"), className: "inline-flex min-h-9 cursor-pointer items-center gap-2 rounded-l-lg py-1.5 pr-3 pl-3 font-medium hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring", children: [done === "Page copied" ? _jsx(Check, { "aria-hidden": true, className: "size-4" }) : _jsx(Copy, { "aria-hidden": true, className: icon }), done === "Page copied" ? "Copied" : "Copy page"] }), _jsxs(Menu.Root, { children: [_jsx(Menu.Trigger, { "aria-label": "More ways to take this page", className: "inline-flex min-h-9 w-9 cursor-pointer items-center justify-center rounded-r-lg border-l border-border text-muted-foreground hover:bg-muted hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring data-popup-open:bg-muted", children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-4" }) }), _jsx(Menu.Portal, { children: _jsx(Menu.Positioner, { "data-designtools": "", align: "end", sideOffset: 6, className: "z-50", children: _jsxs(Menu.Popup, { className: "w-72 rounded-xl border border-border bg-overlay p-1.5 text-overlay-foreground shadow-lg transition-[opacity,scale] duration-(--dt-duration-fast) data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0", children: [_jsxs(Menu.Item, { className: item, onClick: () => copyFrom(`${path}.md`, "Page copied"), children: [_jsx(Copy, { "aria-hidden": true, className: icon }), _jsxs("span", { className: "flex flex-col", children: ["Copy page", _jsx("span", { className: "text-xs text-muted-foreground", children: "As markdown, for an assistant" })] })] }), _jsxs(Menu.LinkItem, { className: item, href: `${path}.md`, target: "_blank", rel: "noreferrer", children: [_jsx(FileText, { "aria-hidden": true, className: icon }), _jsxs("span", { className: "flex flex-col", children: ["View as markdown", _jsx("span", { className: "text-xs text-muted-foreground", children: "What an agent reads at this address" })] })] }), _jsx(Menu.Separator, { className: "my-1.5 h-px bg-border" }), _jsxs(Menu.Item, { className: item, onClick: () => copyFrom(`${basePath}/context.md`, "System copied"), children: [_jsx(Files, { "aria-hidden": true, className: icon }), _jsxs("span", { className: "flex flex-col", children: ["Copy the whole system", _jsx("span", { className: "text-xs text-muted-foreground", children: "Every page in one markdown file" })] })] }), _jsxs(Menu.LinkItem, { className: item, href: `${basePath}/manifest/components.json`, target: "_blank", rel: "noreferrer", children: [_jsx(Braces, { "aria-hidden": true, className: icon }), _jsxs("span", { className: "flex flex-col", children: ["Open the manifest", _jsx("span", { className: "text-xs text-muted-foreground", children: "Components, tokens and rules as JSON" })] })] })] }) }) })] }), _jsx("span", { className: "sr-only", "aria-live": "polite", children: done ?? "" })] }));
32
+ }
@@ -0,0 +1,8 @@
1
+ import type { ComponentsManifest, PatternEntry } from "./lib/manifest-types.js";
2
+ /** A recurring screen composition: what it is made of, then each composition live in its frame, with its code. */
3
+ export declare function Pattern({ pattern, manifest, basePath, }: {
4
+ pattern: PatternEntry;
5
+ /** To link the components it uses to their pages. */
6
+ manifest?: ComponentsManifest;
7
+ basePath?: string;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ArrowUpRight } from "lucide-react";
4
+ import Link from "next/link";
5
+ import { ExampleFrame } from "./examples.js";
6
+ import { Missing, renderExample, useLookup } from "./lib/lookup.js";
7
+ import { slugOf } from "./lib/manifest.js";
8
+ import { StatusBadge } from "./lib/status.js";
9
+ /** A recurring screen composition: what it is made of, then each composition live in its frame, with its code. */
10
+ export function Pattern({ pattern, manifest, basePath = "/design/system", }) {
11
+ const lookup = useLookup();
12
+ const module = lookup.compositionsFor(pattern);
13
+ const known = new Map((manifest?.components ?? []).map((c) => [c.name, c]));
14
+ return (_jsxs("div", { "data-slot": "pattern", className: "flex flex-col gap-10", children: [_jsxs("div", { className: "flex flex-col gap-3 rounded-xl border border-border bg-surface p-4 text-surface-foreground sm:flex-row sm:items-center sm:gap-6", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Status" }), _jsx(StatusBadge, { status: pattern.status })] }), pattern.components.length > 0 && (_jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Made of" }), pattern.components.map((name) => {
15
+ const c = known.get(name);
16
+ const chip = "inline-flex min-h-8 items-center gap-1 rounded-full border border-border px-3 text-xs font-medium";
17
+ return c ? (_jsxs(Link, { href: `${basePath}/components/${slugOf(c)}`, className: `${chip} relative hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring after:absolute after:-inset-y-1.5 after:inset-x-0 after:content-['']`, children: [name, _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5 text-muted-foreground" })] }, name)) : (_jsx("span", { className: chip, children: name }, name));
18
+ })] }))] }), !module ? (_jsx(Missing, { name: pattern.name, what: "compositions" })) : (pattern.examples.map((ex, i) => (_jsx(ExampleFrame, { name: ex.name, kind: i === 0 ? "canonical" : "other", description: ex.description, code: ex.code, children: renderExample(module[ex.name], ex.name) }, ex.name))))] }));
19
+ }
@@ -0,0 +1,62 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ComponentEntry, VariantAxis } from "./lib/manifest-types.js";
3
+ /**
4
+ * The component, live, with controls generated from its variants and its own
5
+ * boolean props, and the JSX for exactly what is showing. Pass `props` for what
6
+ * it needs to render at all; the controls change everything else.
7
+ */
8
+ export declare function Playground({ entry, props: base, children: initialText, }: {
9
+ entry: ComponentEntry;
10
+ props?: Record<string, unknown>;
11
+ /** Text content, editable in the playground. Pass "" for a component without children. */
12
+ children?: string;
13
+ }): import("react").JSX.Element;
14
+ /** Props passed only so the component renders (handlers, data) are left out of the snippet unless they are short. */
15
+ export declare function visible(props: Record<string, unknown>): Record<string, unknown>;
16
+ /** `variant` → `Variant`, `aria-label` → `Aria label`: a control reads as a setting, not a prop name. */
17
+ export declare function title(name: string): string;
18
+ /** A control's label, with the prop it sets and what its options mean behind an info button. */
19
+ export declare function Label({ id, name, info, htmlFor }: {
20
+ id?: string;
21
+ name: string;
22
+ info?: ReactNode;
23
+ htmlFor?: string;
24
+ }): import("react").JSX.Element;
25
+ export declare function AxisControl({ axis, value, onChange }: {
26
+ axis: VariantAxis;
27
+ value: string;
28
+ onChange: (v: string) => void;
29
+ }): import("react").JSX.Element;
30
+ export declare function FlagControl({ name, checked, onChange }: {
31
+ name: string;
32
+ checked: boolean;
33
+ onChange: (v: boolean) => void;
34
+ }): import("react").JSX.Element;
35
+ export declare function TextControl({ value, onChange }: {
36
+ value: string;
37
+ onChange: (v: string) => void;
38
+ }): import("react").JSX.Element;
39
+ /** A number prop: a field with steppers. */
40
+ export declare function NumberControl({ name, value, onChange, info }: {
41
+ name: string;
42
+ value: number | undefined;
43
+ onChange: (v: number | undefined) => void;
44
+ info?: ReactNode;
45
+ }): import("react").JSX.Element;
46
+ /** A string prop: a text field. */
47
+ export declare function StringControl({ name, value, onChange, info }: {
48
+ name: string;
49
+ value: string;
50
+ onChange: (v: string) => void;
51
+ info?: ReactNode;
52
+ }): import("react").JSX.Element;
53
+ /**
54
+ * An array or object prop (a Select's options): JSON, applied while it parses,
55
+ * and said plainly when it does not, so a half-typed edit never breaks the preview.
56
+ */
57
+ export declare function JsonControl({ name, value, onChange, info }: {
58
+ name: string;
59
+ value: unknown;
60
+ onChange: (v: unknown) => void;
61
+ info?: ReactNode;
62
+ }): import("react").JSX.Element;
@@ -0,0 +1,111 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Select } from "@base-ui/react/select";
4
+ import { Switch } from "@base-ui/react/switch";
5
+ import { Toggle } from "@base-ui/react/toggle";
6
+ import { ToggleGroup } from "@base-ui/react/toggle-group";
7
+ import { Tooltip } from "@base-ui/react/tooltip";
8
+ import { Check, ChevronDown, Info, Moon, RotateCcw, SlidersHorizontal, Sun } from "lucide-react";
9
+ import { useId, useState } from "react";
10
+ import { CodeView } from "./code-view.js";
11
+ import { Boundary } from "./lib/boundary.js";
12
+ import { toJSX } from "./lib/jsx.js";
13
+ import { coerce, Missing, useLookup } from "./lib/lookup.js";
14
+ import { Frame, IconButton, Stage, Toolbar } from "./lib/ui.js";
15
+ /**
16
+ * The component, live, with controls generated from its variants and its own
17
+ * boolean props, and the JSX for exactly what is showing. Pass `props` for what
18
+ * it needs to render at all; the controls change everything else.
19
+ */
20
+ export function Playground({ entry, props: base = {}, children: initialText = entry.name, }) {
21
+ const lookup = useLookup();
22
+ const Component = lookup.componentFor(entry);
23
+ const axes = entry.variants?.axes ?? [];
24
+ const axisNames = new Set(axes.map((a) => a.name));
25
+ const flags = Object.entries(entry.props)
26
+ .filter(([name, p]) => !p.from && !axisNames.has(name) && p.type === "boolean" && p.deprecated === undefined)
27
+ .map(([name]) => name);
28
+ const [variants, setVariants] = useState({});
29
+ const [on, setOn] = useState({});
30
+ const [text, setText] = useState(initialText);
31
+ const [theme, setTheme] = useState();
32
+ if (!Component)
33
+ return _jsx(Missing, { name: entry.name });
34
+ const chosen = { ...coerce(variants), ...Object.fromEntries(Object.entries(on).filter(([, v]) => v)) };
35
+ const code = toJSX(entry.export, { ...visible(base), ...chosen }, text || undefined);
36
+ const changed = Object.keys(variants).length > 0 || Object.values(on).some(Boolean) || text !== initialText;
37
+ const reset = () => {
38
+ setVariants({});
39
+ setOn({});
40
+ setText(initialText);
41
+ };
42
+ return (_jsxs(Frame, { "data-slot": "playground", children: [_jsxs("div", { className: "grid lg:grid-cols-[minmax(0,1fr)_20rem]", children: [_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsxs(Toolbar, { children: [_jsxs("span", { className: "flex items-center gap-2 pl-2 text-xs font-medium text-muted-foreground", children: [_jsx(SlidersHorizontal, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), "Live"] }), _jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx(IconButton, { icon: theme === "dark" ? Sun : Moon, label: theme === "dark" ? "Show in light mode" : "Show in dark mode", pressed: theme !== undefined, onClick: () => setTheme(theme === "dark" ? "light" : "dark") }), _jsx(IconButton, { icon: RotateCcw, label: "Reset the controls", onClick: reset, disabled: !changed, className: "disabled:pointer-events-none disabled:opacity-40" })] })] }), _jsx(Stage, { theme: theme, "data-slot": "playground-preview", className: "min-h-72 flex-1", children: _jsx(Boundary, { label: entry.name, children: _jsx(Component, { ...base, ...chosen, children: text || undefined }) }) })] }), _jsxs("div", { "data-slot": "playground-controls", className: "flex flex-col gap-6 border-t border-border p-5 lg:border-t-0 lg:border-l", children: [axes.map((axis) => (_jsx(AxisControl, { axis: axis, value: variants[axis.name] ?? axis.default ?? "", onChange: (v) => setVariants((s) => ({ ...s, [axis.name]: v })) }, axis.name))), flags.map((name) => (_jsx(FlagControl, { name: name, checked: !!on[name], onChange: (v) => setOn((s) => ({ ...s, [name]: v })) }, name))), initialText !== "" && _jsx(TextControl, { value: text, onChange: setText }), axes.length === 0 && flags.length === 0 && initialText === "" && _jsx("p", { className: "text-sm text-muted-foreground", children: "Nothing to change: it has no variants or flags." })] })] }), _jsx("div", { className: "border-t border-border", children: _jsx(CodeView, { code: code, label: "JSX", bare: true }) })] }));
43
+ }
44
+ /** Props passed only so the component renders (handlers, data) are left out of the snippet unless they are short. */
45
+ export function visible(props) {
46
+ return Object.fromEntries(Object.entries(props).filter(([, v]) => typeof v !== "function" && JSON.stringify(v ?? "").length <= 40));
47
+ }
48
+ /** `variant` → `Variant`, `aria-label` → `Aria label`: a control reads as a setting, not a prop name. */
49
+ export function title(name) {
50
+ const words = name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[-_]/g, " ").toLowerCase();
51
+ return words.charAt(0).toUpperCase() + words.slice(1);
52
+ }
53
+ /** Options fit one segmented row when there are few and their names are short; otherwise a menu. */
54
+ const fitsSegments = (axis) => axis.options.length <= 4 && axis.options.reduce((n, o) => n + o.name.length, 0) <= 26;
55
+ /** A control's label, with the prop it sets and what its options mean behind an info button. */
56
+ export function Label({ id, name, info, htmlFor }) {
57
+ const text = (_jsx("span", { className: "text-sm font-medium", children: title(name) }));
58
+ return (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [htmlFor ? (_jsx("label", { id: id, htmlFor: htmlFor, children: text })) : (_jsx("span", { id: id, children: text })), _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { "aria-label": `About ${title(name)}`, className: "relative inline-flex size-6 cursor-pointer items-center justify-center rounded-full text-muted-foreground hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring after:absolute after:-inset-2.5 after:content-['']", children: _jsx(Info, { "aria-hidden": true, className: "size-4" }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Positioner, { "data-designtools": "", side: "left", sideOffset: 8, className: "z-50", children: _jsxs(Tooltip.Popup, { className: "flex max-w-64 flex-col gap-1.5 rounded-lg border border-border bg-overlay px-3 py-2.5 text-xs text-overlay-foreground shadow-md transition-opacity data-ending-style:opacity-0 data-starting-style:opacity-0", children: [_jsx("code", { className: "font-mono font-medium", children: name }), info] }) }) })] })] }));
59
+ }
60
+ function AxisInfo({ axis }) {
61
+ const described = axis.options.filter((o) => o.description);
62
+ return (_jsxs(_Fragment, { children: [axis.default && _jsxs("span", { className: "text-muted-foreground", children: ["Default: ", axis.default] }), described.map((o) => (_jsxs("span", { children: [_jsx("span", { className: "font-medium", children: o.name }), ": ", _jsx("span", { className: "text-muted-foreground", children: o.description })] }, o.name)))] }));
63
+ }
64
+ export function AxisControl({ axis, value, onChange }) {
65
+ const id = useId();
66
+ if (fitsSegments(axis)) {
67
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { id: id, name: axis.name, info: _jsx(AxisInfo, { axis: axis }) }), _jsx(ToggleGroup, { "aria-labelledby": id, value: value ? [value] : [], onValueChange: (next) => next[0] && onChange(String(next[0])), className: "grid grid-flow-col auto-cols-fr gap-0.5 rounded-lg bg-muted p-1", children: axis.options.map((o) => (_jsx(Toggle, { value: o.name, className: "relative h-8 min-w-0 cursor-pointer truncate rounded-md px-2 text-sm text-muted-foreground transition-colors duration-(--dt-duration-fast) after:absolute after:inset-x-0 after:-inset-y-1.5 after:content-[''] hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring data-[pressed]:bg-canvas data-[pressed]:font-medium data-[pressed]:text-canvas-foreground data-[pressed]:shadow-xs", children: title(o.name) }, o.name))) })] }));
68
+ }
69
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { id: id, name: axis.name, info: _jsx(AxisInfo, { axis: axis }) }), _jsxs(Select.Root, { value: value, onValueChange: (v) => typeof v === "string" && onChange(v), children: [_jsxs(Select.Trigger, { "aria-labelledby": id, className: "flex h-10 cursor-pointer items-center justify-between rounded-lg bg-muted px-3 text-sm font-medium hover:bg-muted/70 focus-visible:outline-2 focus-visible:outline-ring", children: [_jsx(Select.Value, { children: (v) => title(v) }), _jsx(Select.Icon, { "aria-hidden": true, children: _jsx(ChevronDown, { className: "size-4 text-muted-foreground" }) })] }), _jsx(Select.Portal, { children: _jsx(Select.Positioner, { "data-designtools": "", sideOffset: 4, alignItemWithTrigger: false, className: "z-50", children: _jsx(Select.Popup, { className: "min-w-(--anchor-width) rounded-lg border border-border bg-overlay p-1 text-overlay-foreground shadow-md", children: _jsx(Select.List, { children: axis.options.map((o) => (_jsxs(Select.Item, { value: o.name, className: "grid min-h-(--docs-target,2.75rem) cursor-pointer grid-cols-[1fr_auto] items-center gap-3 rounded-md px-3 text-sm outline-none data-[highlighted]:bg-muted", children: [_jsx(Select.ItemText, { children: title(o.name) }), _jsx(Select.ItemIndicator, { children: _jsx(Check, { "aria-hidden": true, className: "size-4" }) })] }, o.name))) }) }) }) })] })] }));
70
+ }
71
+ export function FlagControl({ name, checked, onChange }) {
72
+ const id = useId();
73
+ return (_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("label", { htmlFor: id, className: "text-sm font-medium", children: title(name) }), _jsx(Switch.Root, { id: id, checked: checked, onCheckedChange: onChange,
74
+ // drawn small, pressed large: the hit area reaches the docs' target without the switch growing
75
+ className: "relative h-6 w-10 shrink-0 cursor-pointer rounded-full bg-input p-0.5 transition-colors duration-(--dt-duration-fast) after:absolute after:-inset-2.5 after:content-[''] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[checked]:bg-primary", children: _jsx(Switch.Thumb, { className: "block size-5 rounded-full bg-primary-foreground shadow-xs transition-transform duration-(--dt-duration-fast) data-[checked]:translate-x-4" }) })] }));
76
+ }
77
+ export function TextControl({ value, onChange }) {
78
+ const id = useId();
79
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { name: "children", htmlFor: id, info: _jsx("span", { className: "text-muted-foreground", children: "The text inside it." }) }), _jsx("input", { id: id, value: value, onChange: (e) => onChange(e.target.value), className: "h-10 rounded-lg bg-muted px-3 text-sm focus-visible:outline-2 focus-visible:outline-ring" })] }));
80
+ }
81
+ /** A number prop: a field with steppers. */
82
+ export function NumberControl({ name, value, onChange, info }) {
83
+ const id = useId();
84
+ const step = (d) => onChange((value ?? 0) + d);
85
+ const button = "relative flex w-9 cursor-pointer items-center justify-center text-muted-foreground hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring after:absolute after:-inset-y-1 after:inset-x-0 after:content-['']";
86
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { name: name, htmlFor: id, info: info }), _jsxs("div", { className: "flex h-10 items-stretch overflow-hidden rounded-lg bg-muted", children: [_jsx("button", { type: "button", "aria-label": `Less ${title(name)}`, onClick: () => step(-1), className: button, children: "\u2212" }), _jsx("input", { id: id, type: "number", inputMode: "decimal", value: value ?? "", onChange: (e) => onChange(e.target.value === "" ? undefined : Number(e.target.value)), className: "min-w-0 flex-1 bg-transparent text-center font-mono text-sm tabular-nums outline-none [appearance:textfield] focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-inner-spin-button]:appearance-none" }), _jsx("button", { type: "button", "aria-label": `More ${title(name)}`, onClick: () => step(1), className: button, children: "+" })] })] }));
87
+ }
88
+ /** A string prop: a text field. */
89
+ export function StringControl({ name, value, onChange, info }) {
90
+ const id = useId();
91
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { name: name, htmlFor: id, info: info }), _jsx("input", { id: id, value: value, onChange: (e) => onChange(e.target.value), className: "h-10 rounded-lg bg-muted px-3 text-sm focus-visible:outline-2 focus-visible:outline-ring" })] }));
92
+ }
93
+ /**
94
+ * An array or object prop (a Select's options): JSON, applied while it parses,
95
+ * and said plainly when it does not, so a half-typed edit never breaks the preview.
96
+ */
97
+ export function JsonControl({ name, value, onChange, info }) {
98
+ const id = useId();
99
+ const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));
100
+ const [error, setError] = useState(null);
101
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { name: name, htmlFor: id, info: info }), _jsx("textarea", { id: id, value: text, rows: Math.min(10, text.split("\n").length), spellCheck: false, "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : undefined, onChange: (e) => {
102
+ setText(e.target.value);
103
+ try {
104
+ onChange(JSON.parse(e.target.value));
105
+ setError(null);
106
+ }
107
+ catch (err) {
108
+ setError(err instanceof Error ? err.message : "Not valid JSON");
109
+ }
110
+ }, className: "resize-y rounded-lg bg-muted px-3 py-2 font-mono text-xs leading-relaxed focus-visible:outline-2 focus-visible:outline-ring aria-invalid:outline-2 aria-invalid:outline-destructive" }), error && (_jsxs("p", { id: `${id}-error`, className: "text-xs text-destructive-subdued-foreground", children: ["Not applied: ", error] }))] }));
111
+ }
@@ -0,0 +1,31 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ComponentEntry, PatternEntry } from "./lib/manifest-types.js";
3
+ /**
4
+ * A preview route in an iframe, at set widths and in either mode, so a
5
+ * component is seen at a phone's width and its media queries actually fire.
6
+ */
7
+ export declare function PreviewFrame({ src, title, widths, height, }: {
8
+ /** The preview route, e.g. `/design/system/preview/badge/canonical`. */
9
+ src: string;
10
+ title: string;
11
+ widths?: number[];
12
+ height?: number;
13
+ }): import("react").JSX.Element;
14
+ /**
15
+ * What the preview route renders around a state: the page's canvas in the
16
+ * requested mode, set on the document too, so popups in portals match.
17
+ */
18
+ export declare function PreviewSurface({ theme, children }: {
19
+ theme?: string;
20
+ children: ReactNode;
21
+ }): import("react").JSX.Element;
22
+ /** One example on its own, for the preview route to render inside a PreviewSurface. */
23
+ export declare function PreviewExample({ entry, example }: {
24
+ entry: ComponentEntry;
25
+ example: string;
26
+ }): import("react").JSX.Element;
27
+ /** One pattern composition on its own. */
28
+ export declare function PreviewComposition({ pattern, composition }: {
29
+ pattern: PatternEntry;
30
+ composition: string;
31
+ }): import("react").JSX.Element;
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ExternalLink, Monitor, Moon, Smartphone, Sun, Tablet } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
+ import { Boundary } from "./lib/boundary.js";
6
+ import { cx } from "./lib/cx.js";
7
+ import { Missing, renderExample, useLookup } from "./lib/lookup.js";
8
+ import { Frame, IconButton, IconLink, Toolbar } from "./lib/ui.js";
9
+ /**
10
+ * A preview route in an iframe, at set widths and in either mode, so a
11
+ * component is seen at a phone's width and its media queries actually fire.
12
+ */
13
+ export function PreviewFrame({ src, title, widths = [375, 768, 1280], height = 360, }) {
14
+ const [width, setWidth] = useState(widths[0]);
15
+ const [theme, setTheme] = useState("light");
16
+ const url = `${src}${src.includes("?") ? "&" : "?"}theme=${theme}`;
17
+ const device = (w) => (w < 600 ? Smartphone : w < 1024 ? Tablet : Monitor);
18
+ return (_jsxs(Frame, { "data-slot": "preview-frame", children: [_jsxs(Toolbar, { children: [_jsx("div", { role: "group", "aria-label": "Preview width", className: "flex items-center gap-0.5 rounded-lg border border-border p-0.5", children: widths.map((w) => {
19
+ const Icon = device(w);
20
+ return (_jsxs("button", { type: "button", "aria-pressed": width === w, onClick: () => setWidth(w), className: cx("relative inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md px-2.5 font-mono text-xs text-muted-foreground transition-colors duration-(--dt-duration-fast) hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring aria-pressed:bg-muted aria-pressed:text-canvas-foreground", "after:absolute after:inset-x-0 after:-inset-y-[calc((var(--docs-target,2.75rem)-1.75rem)/2)] after:content-['']"), children: [_jsx(Icon, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), w] }, w));
21
+ }) }), _jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx(IconButton, { icon: theme === "dark" ? Sun : Moon, label: theme === "dark" ? "Show in light mode" : "Show in dark mode", pressed: theme === "dark", onClick: () => setTheme(theme === "dark" ? "light" : "dark") }), _jsx(IconLink, { icon: ExternalLink, label: "Open in a new tab", href: url, target: "_blank", rel: "noreferrer" })] })] }), _jsx("div", { className: "overflow-x-auto bg-muted/50 bg-[radial-gradient(color-mix(in_oklch,var(--border)_85%,transparent)_1px,transparent_1px)] [background-size:14px_14px] p-6", tabIndex: 0, role: "region", "aria-label": "Preview", children: _jsxs("div", { className: "mx-auto flex flex-col overflow-hidden rounded-lg border border-border bg-canvas shadow-md", style: { width, maxWidth: "none" }, children: [_jsxs("div", { "aria-hidden": true, className: "flex items-center gap-3 border-b border-border bg-surface px-3 py-2", children: [_jsxs("span", { className: "flex gap-1.5", children: [_jsx("span", { className: "size-2.5 rounded-full bg-border" }), _jsx("span", { className: "size-2.5 rounded-full bg-border" }), _jsx("span", { className: "size-2.5 rounded-full bg-border" })] }), _jsxs("span", { className: "min-w-0 flex-1 truncate rounded-md bg-muted px-2 py-0.5 text-center font-mono text-xs text-muted-foreground", children: [title, " \u00B7 ", width, "px"] })] }), _jsx("iframe", { title: `${title}, ${width}px wide, ${theme} mode`, src: url, "data-slot": "preview-iframe", className: "block w-full resize-y bg-canvas", style: { height } })] }) })] }));
22
+ }
23
+ /**
24
+ * What the preview route renders around a state: the page's canvas in the
25
+ * requested mode, set on the document too, so popups in portals match.
26
+ */
27
+ export function PreviewSurface({ theme, children }) {
28
+ const mode = theme === "dark" || theme === "light" ? theme : undefined;
29
+ useEffect(() => {
30
+ if (!mode)
31
+ return;
32
+ const root = document.documentElement;
33
+ const previous = root.dataset.theme;
34
+ root.dataset.theme = mode;
35
+ return () => {
36
+ if (previous === undefined)
37
+ delete root.dataset.theme;
38
+ else
39
+ root.dataset.theme = previous;
40
+ };
41
+ }, [mode]);
42
+ return (_jsx("div", { "data-designtools": "", "data-docs-stage": "", "data-slot": "preview-surface", "data-theme": mode, className: "min-h-dvh bg-canvas p-6 text-canvas-foreground", children: children }));
43
+ }
44
+ /** One example on its own, for the preview route to render inside a PreviewSurface. */
45
+ export function PreviewExample({ entry, example }) {
46
+ const lookup = useLookup();
47
+ const module = lookup.examplesFor(entry);
48
+ if (!module)
49
+ return _jsx(Missing, { name: entry.name, what: "examples" });
50
+ if (!(example in module))
51
+ return _jsxs("p", { className: "text-sm", children: [entry.name, " has no example called ", example, "."] });
52
+ return _jsx(Boundary, { label: example, children: renderExample(module[example], example) });
53
+ }
54
+ /** One pattern composition on its own. */
55
+ export function PreviewComposition({ pattern, composition }) {
56
+ const lookup = useLookup();
57
+ const module = lookup.compositionsFor(pattern);
58
+ if (!module)
59
+ return _jsx(Missing, { name: pattern.name, what: "compositions" });
60
+ if (!(composition in module))
61
+ return _jsxs("p", { className: "text-sm", children: [pattern.name, " has no composition called ", composition, "."] });
62
+ return _jsx(Boundary, { label: composition, children: renderExample(module[composition], composition) });
63
+ }
@@ -0,0 +1,10 @@
1
+ import type { ComponentEntry } from "./lib/manifest-types.js";
2
+ /**
3
+ * A component's props, as an API reference: name, type and default on one line,
4
+ * each row opening to its description. Its own props first, then those of the
5
+ * primitive it wraps, folded away. Deprecated props are struck through with the
6
+ * reason; what it inherits from an element or primitive is named underneath.
7
+ */
8
+ export declare function PropsTable({ entry }: {
9
+ entry: ComponentEntry;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ChevronDown } from "lucide-react";
3
+ import { cx } from "./lib/cx.js";
4
+ import { inline } from "./lib/text.js";
5
+ /**
6
+ * A component's props, as an API reference: name, type and default on one line,
7
+ * each row opening to its description. Its own props first, then those of the
8
+ * primitive it wraps, folded away. Deprecated props are struck through with the
9
+ * reason; what it inherits from an element or primitive is named underneath.
10
+ */
11
+ export function PropsTable({ entry }) {
12
+ const rows = Object.entries(entry.props);
13
+ const own = rows.filter(([, p]) => !p.from);
14
+ const byPackage = new Map();
15
+ for (const row of rows.filter(([, p]) => p.from)) {
16
+ byPackage.set(row[1].from, [...(byPackage.get(row[1].from) ?? []), row]);
17
+ }
18
+ return (_jsxs("div", { "data-slot": "props-table", className: "flex flex-col gap-4", children: [own.length > 0 ? _jsx(Table, { rows: own }) : _jsx("p", { className: "text-sm text-muted-foreground", children: "No props of its own." }), [...byPackage.entries()].map(([pkg, inherited]) => (_jsxs("details", { "data-slot": "props-inherited", className: "group/inherited overflow-hidden rounded-xl border border-border bg-surface text-surface-foreground", children: [_jsxs("summary", { className: "flex min-h-(--docs-target,2.75rem) cursor-pointer list-none items-center justify-between gap-3 px-4 text-sm hover:bg-muted [&::-webkit-details-marker]:hidden", children: [_jsxs("span", { children: [inherited.length, " more from ", _jsx("code", { className: "font-mono text-xs", children: pkg })] }), _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--dt-size-xs) text-muted-foreground transition-transform group-open/inherited:rotate-180" })] }), _jsx("div", { className: "border-t border-border", children: _jsx(Table, { rows: inherited, bare: true }) })] }, pkg))), entry.inherits && entry.inherits.length > 0 && (_jsxs("p", { "data-slot": "props-inherits", className: "text-sm text-muted-foreground", children: ["Also accepts every prop of", " ", entry.inherits.map((x, i) => (_jsxs("span", { children: [i > 0 && ", ", _jsx("code", { className: "rounded-sm bg-muted px-1 py-0.5 font-mono text-xs text-canvas-foreground", children: /^[a-z][\w-]*$/.test(x) ? `<${x}>` : x })] }, x))), "."] }))] }));
19
+ }
20
+ const COLS = "grid grid-cols-[minmax(0,1fr)_auto] gap-x-4 gap-y-1 md:grid-cols-[minmax(9rem,0.9fr)_minmax(0,1.6fr)_minmax(5rem,0.6fr)_1.25rem] md:items-center";
21
+ function Table({ rows, bare }) {
22
+ return (_jsxs("div", { className: cx("min-w-0 text-sm", !bare && "overflow-hidden rounded-xl border border-border bg-surface text-surface-foreground"), children: [_jsxs("div", { "aria-hidden": true, className: cx(COLS, "hidden border-b border-border bg-muted/50 px-4 py-2.5 text-xs font-medium text-muted-foreground md:grid"), children: [_jsx("span", { children: "Prop" }), _jsx("span", { children: "Type" }), _jsx("span", { children: "Default" }), _jsx("span", {})] }), _jsx("ul", { "aria-label": "Props", className: "divide-y divide-border", children: rows.map(([name, prop]) => (_jsx("li", { children: _jsx(Row, { name: name, prop: prop }) }, name))) })] }));
23
+ }
24
+ function Row({ name, prop }) {
25
+ const deprecated = prop.deprecated !== undefined;
26
+ const line = (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex min-w-0 items-center gap-1", children: [_jsx("code", { className: cx("truncate font-mono text-xs font-medium text-primary-subdued-foreground", deprecated && "line-through opacity-70"), children: name }), prop.required && (_jsxs("span", { "data-slot": "props-required", className: "font-mono text-xs text-destructive-subdued-foreground", title: "Required", children: ["*", _jsx("span", { className: "sr-only", children: " required" })] }))] }), _jsxs("span", { className: "col-span-2 min-w-0 md:col-span-1", children: [_jsx("span", { className: "sr-only", children: ", type " }), _jsx(Type, { prop: prop })] }), _jsx("span", { className: "hidden font-mono text-xs text-muted-foreground md:block", children: prop.default !== undefined ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "sr-only", children: ", default " }), prop.default] })) : (_jsx("span", { "aria-hidden": true, children: "\u2014" })) })] }));
27
+ const described = deprecated || !!prop.description;
28
+ if (!described)
29
+ return (_jsxs("div", { "data-slot": "props-row", className: cx(COLS, "px-4 py-3"), children: [line, _jsx("span", {})] }));
30
+ return (_jsxs("details", { "data-slot": "props-row", className: "group/row", children: [_jsxs("summary", { className: cx(COLS, "min-h-(--docs-target,2.75rem) cursor-pointer list-none px-4 py-3 hover:bg-muted/60 [&::-webkit-details-marker]:hidden"), children: [line, _jsx(ChevronDown, { "aria-hidden": true, className: "col-start-2 row-start-1 size-(--dt-size-xs) justify-self-end text-muted-foreground transition-transform group-open/row:rotate-180 md:col-start-4" })] }), _jsxs("div", { className: "flex flex-col gap-2 px-4 pb-4 text-sm text-muted-foreground", children: [prop.default !== undefined && (_jsxs("p", { className: "md:hidden", children: ["Default ", _jsx("code", { className: "font-mono text-xs text-canvas-foreground", children: prop.default })] })), deprecated && _jsxs("p", { className: "font-medium text-canvas-foreground", children: ["Deprecated", prop.deprecated ? _jsxs(_Fragment, { children: [": ", inline(prop.deprecated)] }) : "."] }), prop.description?.split("\n\n").map((p) => (_jsx("p", { className: "max-w-prose whitespace-pre-line", children: inline(p) }, p)))] })] }));
31
+ }
32
+ /** A type, coloured by kind: literals, primitives and functions read apart at a glance. */
33
+ function Type({ prop }) {
34
+ const parts = prop.values ?? prop.type.split(/\s*\|\s*/);
35
+ return (_jsx("span", { className: "flex flex-wrap items-center gap-x-1 gap-y-1 font-mono text-xs", children: parts.map((t, i) => (_jsxs("span", { className: "inline-flex items-center gap-1", children: [i > 0 && _jsx("span", { className: "text-muted-foreground", children: "|" }), _jsx("span", { className: cx("break-all", tone(t, !!prop.values)), children: prop.values ? `"${t}"` : t })] }, `${t}-${i}`))) }));
36
+ }
37
+ function tone(t, literal) {
38
+ if (literal || /^["'].*["']$/.test(t))
39
+ return "text-success-subdued-foreground";
40
+ if (/^(boolean|number|string|true|false|null|undefined)$/.test(t))
41
+ return "text-primary-subdued-foreground";
42
+ if (/=>|^\(|Function|Handler|EventHandler/.test(t))
43
+ return "text-destructive-subdued-foreground";
44
+ return "text-canvas-foreground";
45
+ }
package/dist/rule.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import type { RuleEntry } from "./lib/manifest-types.js";
3
+ /**
4
+ * One rule, brand or interface, written for people: the statement set large, the
5
+ * line it draws, what it applies to and the check that proves it, then why. A
6
+ * switch shows the same rule as an agent reads it. Pass a demonstration as
7
+ * children where the rule needs persuading; it is shown on a stage.
8
+ */
9
+ export declare function Rule({ rule, children }: {
10
+ rule: RuleEntry;
11
+ children?: ReactNode;
12
+ }): import("react").JSX.Element;
package/dist/rule.js ADDED
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Bot, Eye, Hash, ShieldCheck } from "lucide-react";
4
+ import { CodeView } from "./code-view.js";
5
+ import { cx } from "./lib/cx.js";
6
+ import { ruleMarkdown } from "./lib/markdown.js";
7
+ import { StatusBadge } from "./lib/status.js";
8
+ import { inline, Prose } from "./lib/text.js";
9
+ import { Eyebrow, Segmented, SegmentedList, SegmentedPanel, Stage } from "./lib/ui.js";
10
+ const CHECK = { test: "Tested", lint: "Linted", audit: "Audited", manual: "Checked by a person" };
11
+ /**
12
+ * One rule, brand or interface, written for people: the statement set large, the
13
+ * line it draws, what it applies to and the check that proves it, then why. A
14
+ * switch shows the same rule as an agent reads it. Pass a demonstration as
15
+ * children where the rule needs persuading; it is shown on a stage.
16
+ */
17
+ export function Rule({ rule, children }) {
18
+ 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))) }));
19
+ const facts = [];
20
+ if (rule.threshold)
21
+ facts.push(["Threshold", _jsx("span", { className: "font-medium", children: rule.threshold }, "t")]);
22
+ if (rule.appliesTo?.length)
23
+ facts.push(["Applies to", chips(rule.appliesTo)]);
24
+ if (rule.exceptions?.length)
25
+ facts.push(["Exceptions", _jsx("span", { children: rule.exceptions.join("; ") }, "e")]);
26
+ if (rule.tokens?.length)
27
+ facts.push(["Tokens", chips(rule.tokens, true)]);
28
+ return (_jsx("article", { id: rule.id, "data-slot": "rule", className: "scroll-mt-24 border-t border-border pt-8", children: _jsxs(Segmented, { defaultValue: "rendered", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [_jsx(StatusBadge, { status: rule.status }), _jsx("span", { className: "text-xs font-medium text-muted-foreground", children: rule.kind === "brand" ? "Brand rule" : "Interface rule" })] }), _jsxs("h3", { className: "group flex items-center gap-2 text-3xl tracking-tight", children: [rule.title, _jsx("a", { href: `#${rule.id}`, "aria-label": `Link to ${rule.title}`, className: "relative inline-flex size-7 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(SegmentedList, { label: `${rule.title}, two ways`, items: [
29
+ { value: "rendered", label: "For people", icon: Eye },
30
+ { value: "agent", label: "For agents", icon: Bot },
31
+ ] })] }), _jsxs(SegmentedPanel, { value: "rendered", className: "flex flex-col gap-6 pt-4", children: [_jsx("p", { "data-slot": "rule-statement", className: "max-w-3xl text-xl leading-snug tracking-tight", children: inline(rule.statement) }), children && (_jsx("div", { "data-slot": "rule-demonstration", className: "overflow-hidden rounded-xl border border-border", children: _jsx(Stage, { className: "min-h-40", children: children }) })), facts.length > 0 && (_jsx("dl", { className: "grid gap-x-10 gap-y-5 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.check && (_jsxs("div", { className: "flex items-start gap-2.5 text-sm", children: [_jsx(ShieldCheck, { "aria-hidden": true, className: "mt-0.5 size-(--dt-size-xs) shrink-0 text-muted-foreground" }), _jsxs("p", { 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) })] })] })), 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-sm text-muted-foreground", children: _jsx(Prose, { text: rule.rationale }) })] }))] }), _jsx(SegmentedPanel, { value: "agent", className: "pt-4", children: _jsx(CodeView, { code: ruleMarkdown(rule), lang: "markdown", label: `${rule.id}.md`, wrap: true }) })] }) }));
32
+ }
@@ -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;