@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,90 @@
1
+ "use client";
2
+
3
+ import { Check, Copy, Sparkles } from "lucide-react";
4
+ import { useId, useState } from "react";
5
+ import { importOf } from "./lib/manifest";
6
+ import type { ComponentEntry } from "./lib/manifest-types";
7
+
8
+ /**
9
+ * Copies a ready-made prompt for Claude: the component, its source file, the
10
+ * variant and example in view, and what should change. Paste it into Claude
11
+ * Code in the project and it starts from the right file.
12
+ */
13
+ export function AskClaude({
14
+ entry,
15
+ variant,
16
+ example,
17
+ }: {
18
+ entry: ComponentEntry;
19
+ /** The variant in view, e.g. `{ intent: "success" }`. */
20
+ variant?: Record<string, string>;
21
+ /** The example in view, by export name. */
22
+ example?: string;
23
+ }) {
24
+ const id = useId();
25
+ const [request, setRequest] = useState("");
26
+ const [copied, setCopied] = useState(false);
27
+
28
+ const copy = async () => {
29
+ try {
30
+ await navigator.clipboard.writeText(promptFor(entry, request, variant, example));
31
+ setCopied(true);
32
+ setTimeout(() => setCopied(false), 1500);
33
+ } catch {
34
+ // clipboard refused: nothing changes
35
+ }
36
+ };
37
+
38
+ return (
39
+ <div data-slot="ask-claude" className="flex flex-col gap-4 rounded-2xl border border-border bg-surface p-5 text-surface-foreground shadow-xs sm:p-6">
40
+ <div className="flex items-start gap-3">
41
+ <span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary-subdued text-primary-subdued-foreground">
42
+ <Sparkles aria-hidden className="size-(--dt-size-xs)" />
43
+ </span>
44
+ <span className="flex flex-col gap-0.5">
45
+ <label htmlFor={id} className="text-base font-semibold tracking-tight">
46
+ Ask Claude to change {entry.name}
47
+ </label>
48
+ <span className="text-sm text-muted-foreground">
49
+ Copies a prompt naming the file{example ? ", the example" : ""}, its parts and the variant, ready to paste into Claude Code.
50
+ </span>
51
+ </span>
52
+ </div>
53
+ <div className="flex flex-col overflow-hidden rounded-xl border border-input bg-canvas focus-within:outline-2 focus-within:outline-ring">
54
+ <textarea
55
+ id={id}
56
+ rows={3}
57
+ value={request}
58
+ onChange={(e) => setRequest(e.target.value)}
59
+ placeholder={`What should change about ${entry.name}?`}
60
+ className="resize-none bg-transparent px-4 py-3 text-base outline-none placeholder:text-muted-foreground"
61
+ />
62
+ <div className="flex items-center justify-between gap-3 px-2 pb-2 pl-4">
63
+ <code className="truncate font-mono text-xs text-muted-foreground">{entry.source}</code>
64
+ <button
65
+ type="button"
66
+ onClick={copy}
67
+ className="inline-flex min-h-(--docs-target,2.75rem) shrink-0 cursor-pointer items-center gap-2 rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground hover:bg-primary-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
68
+ >
69
+ {copied ? <Check aria-hidden className="size-(--dt-size-xs)" /> : <Copy aria-hidden className="size-(--dt-size-xs)" />}
70
+ {copied ? "Copied" : "Copy prompt"}
71
+ </button>
72
+ </div>
73
+ </div>
74
+ <span className="sr-only" aria-live="polite">
75
+ {copied ? "Prompt copied" : ""}
76
+ </span>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ export function promptFor(entry: ComponentEntry, request: string, variant?: Record<string, string>, example?: string): string {
82
+ const lines = [`Component: ${entry.name}, exported as ${entry.export} from ${entry.source} (imported from "${importOf(entry)}")`];
83
+ const shown = Object.entries(variant ?? {});
84
+ if (shown.length) lines.push(`Variant in view: ${shown.map(([k, v]) => `${k}="${v}"`).join(", ")}`);
85
+ if (example && entry.examples) lines.push(`Example in view: ${example}, from ${entry.examples.source}`);
86
+ if (entry.status && entry.status !== "stable") lines.push(`Status: ${entry.status}${entry.statusNote ? `. ${entry.statusNote}` : ""}`);
87
+ if (entry.slots.length) lines.push(`Its parts (data-slot): ${entry.slots.join(", ")}`);
88
+ lines.push("", request.trim() || "What should change: ");
89
+ return lines.join("\n");
90
+ }
@@ -0,0 +1,112 @@
1
+ "use client";
2
+
3
+ import { ChevronDown, FileCode } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
+ import { cx } from "./lib/cx";
6
+ import { CopyButton } from "./lib/ui";
7
+
8
+ /* Shiki writes both themes as CSS variables; these pick the one for the page's mode, and number the lines. */
9
+ const THEME_CSS = `
10
+ [data-slot="code-view"] .shiki { background: transparent !important; counter-reset: line; }
11
+ [data-slot="code-view"][data-numbered] .shiki .line::before { counter-increment: line; content: counter(line); display: inline-block; width: 2ch; margin-right: 1.25rem; text-align: right; color: var(--muted-foreground); opacity: .6; }
12
+ [data-theme="dark"] [data-slot="code-view"] .shiki, [data-theme="dark"] [data-slot="code-view"] .shiki span { color: var(--shiki-dark) !important; }
13
+ @media (prefers-color-scheme: dark) {
14
+ :root:not([data-theme="light"]) [data-slot="code-view"] .shiki, :root:not([data-theme="light"]) [data-slot="code-view"] .shiki span { color: var(--shiki-dark) !important; }
15
+ }`;
16
+
17
+ /** Lines past which the code folds, with a fade and a "Show all". */
18
+ const FOLD = 14;
19
+
20
+ /**
21
+ * Code, highlighted once Shiki has loaded (it loads on first use, so pages
22
+ * without code never pay for it) and readable as plain text before then. A file
23
+ * header with a copy button, numbered lines, and long code folded behind "Show
24
+ * all". `bare` drops the frame, for code inside another frame.
25
+ */
26
+ export function CodeView({
27
+ code,
28
+ lang = "tsx",
29
+ label = "Code",
30
+ wrap = false,
31
+ bare = false,
32
+ numbered,
33
+ }: {
34
+ code: string;
35
+ lang?: string;
36
+ label?: string;
37
+ /** Wrap long lines, for prose such as markdown, instead of scrolling them. */
38
+ wrap?: boolean;
39
+ /** No border or header of its own: for code inside an example or playground frame. */
40
+ bare?: boolean;
41
+ /** Line numbers. On by default for more than one line of code, off for prose. */
42
+ numbered?: boolean;
43
+ }) {
44
+ const [html, setHtml] = useState<string | null>(null);
45
+ const lines = code.split("\n").length;
46
+ const [open, setOpen] = useState(lines <= FOLD);
47
+ const numbers = numbered ?? (!wrap && lines > 1);
48
+
49
+ useEffect(() => {
50
+ let live = true;
51
+ import("shiki")
52
+ .then(({ codeToHtml }) => codeToHtml(code, { lang, themes: { light: "github-light", dark: "github-dark" } }))
53
+ .then((out) => live && setHtml(out))
54
+ .catch(() => live && setHtml(null));
55
+ return () => {
56
+ live = false;
57
+ };
58
+ }, [code, lang]);
59
+
60
+ const body = cx(
61
+ "overflow-x-auto px-4 py-3.5 font-mono text-sm leading-relaxed",
62
+ wrap && "[&_pre]:break-words [&_pre]:whitespace-pre-wrap",
63
+ // room for the copy button floating at the top right
64
+ bare && "pr-12",
65
+ !open && "max-h-96 overflow-y-hidden",
66
+ );
67
+
68
+ return (
69
+ <figure
70
+ data-slot="code-view"
71
+ data-numbered={numbers || undefined}
72
+ className={cx("relative min-w-0", !bare && "overflow-hidden rounded-xl border border-border bg-surface text-surface-foreground")}
73
+ >
74
+ <style>{THEME_CSS}</style>
75
+ {!bare && (
76
+ <figcaption className="flex min-h-11 items-center justify-between gap-3 border-b border-border bg-muted/40 py-1 pr-1.5 pl-4 text-xs text-muted-foreground">
77
+ <span className="flex min-w-0 items-center gap-2">
78
+ <FileCode aria-hidden className="size-(--dt-size-xs) shrink-0" />
79
+ <span className="truncate font-medium text-canvas-foreground">{label}</span>
80
+ </span>
81
+ <CopyButton text={code} label="Copy code" />
82
+ </figcaption>
83
+ )}
84
+ <div className="relative">
85
+ {bare && (
86
+ <span className="absolute top-1.5 right-1.5 z-10">
87
+ <CopyButton text={code} label="Copy code" />
88
+ </span>
89
+ )}
90
+ {html ? (
91
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki's output for code from the manifest, escaped by Shiki
92
+ <div className={body} tabIndex={0} role="region" aria-label={label} dangerouslySetInnerHTML={{ __html: html }} />
93
+ ) : (
94
+ <pre tabIndex={0} aria-label={label} className={cx(body, wrap && "break-words whitespace-pre-wrap")}>
95
+ <code>{code}</code>
96
+ </pre>
97
+ )}
98
+ {!open && (
99
+ <div className="absolute inset-x-0 bottom-0 flex h-24 items-end justify-center bg-linear-to-t from-surface via-surface/80 to-transparent pb-3">
100
+ <button
101
+ type="button"
102
+ onClick={() => setOpen(true)}
103
+ className="inline-flex min-h-(--docs-target,2.75rem) cursor-pointer items-center gap-1.5 rounded-full border border-border bg-surface px-4 text-xs font-medium text-surface-foreground shadow-sm hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring"
104
+ >
105
+ Show all {lines} lines <ChevronDown aria-hidden className="size-(--dt-size-xs)" />
106
+ </button>
107
+ </div>
108
+ )}
109
+ </div>
110
+ </figure>
111
+ );
112
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module "*.css";
@@ -0,0 +1,141 @@
1
+ "use client";
2
+
3
+ import { CircleCheck, CircleX, Code, Eye, Maximize2, Moon, Smartphone, Sun } from "lucide-react";
4
+ import { type ReactNode, useState } from "react";
5
+ import { CodeView } from "./code-view";
6
+ import { Boundary } from "./lib/boundary";
7
+ import { cx } from "./lib/cx";
8
+ import { Missing, renderExample, useLookup } from "./lib/lookup";
9
+ import type { ComponentEntry, ExampleRef } from "./lib/manifest-types";
10
+ import { inline } from "./lib/text";
11
+ import { CopyButton, IconButton, IconLink, Segmented, SegmentedList, SegmentedPanel, Stage, Toolbar } from "./lib/ui";
12
+
13
+ /**
14
+ * The canonical example first, the one to copy, then each do beside its don't.
15
+ * Every example is the real component; the code behind it is the code that drew it.
16
+ */
17
+ export function Examples({ entry, previewHref }: { entry: ComponentEntry; previewHref?: string }) {
18
+ const lookup = useLookup();
19
+ const ex = entry.examples;
20
+ if (!ex) {
21
+ return (
22
+ <p data-slot="examples-empty" className="rounded-xl border border-dashed border-border p-6 text-sm text-muted-foreground">
23
+ No examples yet. Add <code className="font-mono text-xs">{entry.source.replace(/\.tsx$/, ".examples.tsx")}</code> with a{" "}
24
+ <code className="font-mono text-xs">canonical</code> export and do and don&apos;t pairs.
25
+ </p>
26
+ );
27
+ }
28
+ const module = lookup.examplesFor(entry);
29
+ if (!module) return <Missing name={entry.name} what="examples" />;
30
+
31
+ const pairs = Array.from({ length: Math.max(ex.do.length, ex.dont.length) }, (_, i) => [ex.do[i], ex.dont[i]] as const);
32
+ const frame = (ref: ExampleRef, kind: Kind) => (
33
+ <ExampleFrame
34
+ key={ref.name}
35
+ name={ref.name}
36
+ kind={kind}
37
+ description={ref.description}
38
+ code={ref.code}
39
+ href={previewHref && `${previewHref}/${encodeURIComponent(ref.name)}`}
40
+ >
41
+ {renderExample(module[ref.name], ref.name)}
42
+ </ExampleFrame>
43
+ );
44
+
45
+ return (
46
+ <div data-slot="examples" className="flex flex-col gap-10">
47
+ {ex.canonical && frame(ex.canonical, "canonical")}
48
+ {pairs.length > 0 && (
49
+ <div className="flex flex-col gap-6">
50
+ {pairs.map(([d, n], i) => (
51
+ <div key={d?.name ?? n?.name ?? i} data-slot="examples-pair" className="grid grid-cols-1 gap-4 lg:grid-cols-2">
52
+ {d ? frame(d, "do") : <span />}
53
+ {n ? frame(n, "dont") : <span />}
54
+ </div>
55
+ ))}
56
+ </div>
57
+ )}
58
+ {ex.other.length > 0 && <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">{ex.other.map((o) => frame(o, "other"))}</div>}
59
+ </div>
60
+ );
61
+ }
62
+
63
+ type Kind = "canonical" | "do" | "dont" | "other";
64
+
65
+ const MARK: Record<Kind, { label: string; icon?: typeof CircleCheck; tone: string }> = {
66
+ // the canonical example is simply first, the usual use; it needs no badge to say so
67
+ canonical: { label: "", tone: "" },
68
+ // a measured pairing, never a fill colour used as text (text-success read 3.9:1 on the canvas)
69
+ do: { label: "Do", icon: CircleCheck, tone: "bg-success-subdued text-success-subdued-foreground" },
70
+ dont: { label: "Don't", icon: CircleX, tone: "bg-destructive-subdued text-destructive-subdued-foreground" },
71
+ other: { label: "", tone: "" },
72
+ };
73
+
74
+ /**
75
+ * One example in its frame: Preview and Code on a switch, the stage in either mode
76
+ * or at a phone's width, a copy button, and a link to it alone. The caption says
77
+ * what kind of example it is and why. Patterns use it for their compositions too.
78
+ */
79
+ export function ExampleFrame({
80
+ name,
81
+ kind = "other",
82
+ description,
83
+ code,
84
+ href,
85
+ children,
86
+ }: {
87
+ name: string;
88
+ kind?: Kind;
89
+ description?: string;
90
+ code?: string;
91
+ href?: string;
92
+ children: ReactNode;
93
+ }) {
94
+ const [theme, setTheme] = useState<"light" | "dark" | undefined>();
95
+ const [narrow, setNarrow] = useState(false);
96
+ const mark = MARK[kind];
97
+ const Icon = mark.icon;
98
+ const views = [{ value: "preview", label: "Preview", icon: Eye }, ...(code ? [{ value: "code", label: "Code", icon: Code }] : [])];
99
+
100
+ return (
101
+ <figure data-slot="example" data-kind={kind} className="flex min-w-0 flex-col gap-3">
102
+ <Segmented defaultValue="preview" className="overflow-hidden rounded-xl border border-border bg-surface text-surface-foreground shadow-xs">
103
+ <Toolbar>
104
+ <SegmentedList items={views} label={`${name}: preview or code`} />
105
+ <span className="flex items-center gap-0.5">
106
+ <IconButton icon={Smartphone} label="Phone width" pressed={narrow} onClick={() => setNarrow(!narrow)} />
107
+ <IconButton
108
+ icon={theme === "dark" ? Sun : Moon}
109
+ label={theme === "dark" ? "Show in light mode" : "Show in dark mode"}
110
+ pressed={theme !== undefined}
111
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
112
+ />
113
+ {code && <CopyButton text={code} label="Copy code" />}
114
+ {href && <IconLink icon={Maximize2} label="Open on its own" href={href} />}
115
+ </span>
116
+ </Toolbar>
117
+ <SegmentedPanel value="preview">
118
+ <Stage theme={theme} width={narrow ? 375 : undefined} className={kind === "canonical" ? "min-h-64" : "min-h-48"}>
119
+ <Boundary label={name}>{children}</Boundary>
120
+ </Stage>
121
+ </SegmentedPanel>
122
+ {code && (
123
+ <SegmentedPanel value="code">
124
+ <CodeView code={code} label={name} bare />
125
+ </SegmentedPanel>
126
+ )}
127
+ </Segmented>
128
+ {(mark.label || description) && (
129
+ <figcaption className="flex items-start gap-2 px-1">
130
+ {mark.label && (
131
+ <span className={cx("inline-flex h-6 shrink-0 items-center gap-1.5 rounded-full pr-2.5 pl-2 text-xs font-medium", mark.tone)}>
132
+ {Icon && <Icon aria-hidden className="size-3.5" />}
133
+ {mark.label}
134
+ </span>
135
+ )}
136
+ {description && <span className="pt-0.5 text-sm">{inline(description)}</span>}
137
+ </figcaption>
138
+ )}
139
+ </figure>
140
+ );
141
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { Search } from "lucide-react";
4
+ import { useId, useState } from "react";
5
+ import type { TermEntry } from "./lib/manifest-types";
6
+ import { inline } from "./lib/text";
7
+
8
+ /** Every name in the system, searchable by its wrong forms too, so a wrong name finds the right one. */
9
+ export function Glossary({ terms }: { terms: TermEntry[] }) {
10
+ const id = useId();
11
+ const [query, setQuery] = useState("");
12
+ const q = query.trim().toLowerCase();
13
+ const matches = q ? terms.filter((t) => t.name.toLowerCase().includes(q) || t.wrong?.some((w) => w.toLowerCase().includes(q))) : terms;
14
+ const corrected = q ? terms.find((t) => t.wrong?.some((w) => w.toLowerCase() === q)) : undefined;
15
+
16
+ return (
17
+ <div data-slot="glossary" className="flex flex-col gap-5">
18
+ <div className="flex flex-col gap-2">
19
+ <label htmlFor={id} className="sr-only">
20
+ Find a term
21
+ </label>
22
+ <div className="relative max-w-xl">
23
+ <Search aria-hidden className="pointer-events-none absolute top-1/2 left-4 size-(--dt-size-sm) -translate-y-1/2 text-muted-foreground" />
24
+ <input
25
+ id={id}
26
+ type="search"
27
+ value={query}
28
+ onChange={(e) => setQuery(e.target.value)}
29
+ placeholder="Find a term, or a name you half remember"
30
+ className="h-(--dt-size-2xl) w-full rounded-lg border border-border bg-canvas pr-4 pl-12 text-base placeholder:text-muted-foreground hover:border-input focus-visible:outline-2 focus-visible:outline-ring"
31
+ />
32
+ </div>
33
+ <p aria-live="polite" className="text-sm text-muted-foreground">
34
+ {corrected ? (
35
+ <>
36
+ The system calls “{query.trim()}” <strong className="text-canvas-foreground">{corrected.name}</strong>.
37
+ </>
38
+ ) : q ? (
39
+ `${matches.length} ${matches.length === 1 ? "term" : "terms"}`
40
+ ) : (
41
+ `${terms.length} terms`
42
+ )}
43
+ </p>
44
+ </div>
45
+ <dl data-slot="glossary-list" className="grid gap-x-12 sm:grid-cols-2">
46
+ {matches.map((t) => (
47
+ <div key={`${t.kind}:${t.name}`} className="flex flex-col gap-1.5 border-t border-border py-4">
48
+ <dt className="flex items-center justify-between gap-2">
49
+ <span className="text-lg">{t.name}</span>
50
+ <span className="font-mono text-xs text-muted-foreground">{t.kind}</span>
51
+ </dt>
52
+ <dd className="flex flex-col gap-1 text-sm text-muted-foreground">
53
+ {t.wrong?.length ? (
54
+ <span className="flex flex-wrap items-center gap-1">
55
+ Not
56
+ {t.wrong.map((w) => (
57
+ <s key={w} className="decoration-destructive">
58
+ {w}
59
+ </s>
60
+ ))}
61
+ </span>
62
+ ) : null}
63
+ {t.note && <span>{inline(t.note)}</span>}
64
+ {t.ref && <code className="font-mono text-xs">{t.ref}</code>}
65
+ </dd>
66
+ </div>
67
+ ))}
68
+ </dl>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { useLookup } from "./lib/lookup";
4
+ import type { AssetEntry } from "./lib/manifest-types";
5
+ import { inline } from "./lib/text";
6
+ import { Stage } from "./lib/ui";
7
+
8
+ /** The approved icons and imagery, each on a stage with how to use it; an icon is shown at its three sizes too. */
9
+ export function Imagery({ assets, kinds = ["icon", "image", "other"] }: { assets: AssetEntry[]; kinds?: AssetEntry["kind"][] }) {
10
+ const lookup = useLookup();
11
+ const shown = assets.filter((a) => kinds.includes(a.kind));
12
+ if (shown.length === 0) return <p className="text-sm text-muted-foreground">No icons or imagery in the brand folder yet.</p>;
13
+ return (
14
+ <ul data-slot="imagery" className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
15
+ {shown.map((a) => {
16
+ const src = lookup.assetUrl(a);
17
+ const icon = a.kind === "icon";
18
+ return (
19
+ <li key={a.file} className="min-w-0">
20
+ <figure data-slot="asset" className="flex h-full flex-col overflow-hidden rounded-2xl border border-border bg-surface text-surface-foreground shadow-xs">
21
+ <Stage className="h-44 p-6">
22
+ {src ? (
23
+ icon ? (
24
+ // icons drawn in currentColor show in the stage's text colour, in either mode
25
+ <span
26
+ aria-hidden={!a.alt}
27
+ role={a.alt ? "img" : undefined}
28
+ aria-label={a.alt || undefined}
29
+ className="block size-14 bg-current"
30
+ style={{ mask: `url(${src}) center / contain no-repeat`, WebkitMask: `url(${src}) center / contain no-repeat` }}
31
+ />
32
+ ) : (
33
+ <img src={src} alt={a.alt ?? ""} className="max-h-full max-w-full rounded-md object-contain" />
34
+ )
35
+ ) : (
36
+ <span className="font-mono text-xs text-muted-foreground">{a.file}</span>
37
+ )}
38
+ </Stage>
39
+ <figcaption className="flex flex-1 flex-col gap-2 border-t border-border p-4 text-sm">
40
+ <span className="flex items-center justify-between gap-2">
41
+ <span className="text-lg">{a.name}</span>
42
+ {icon && src ? (
43
+ <span aria-hidden className="flex items-end gap-2 text-muted-foreground">
44
+ {[16, 20, 24].map((size) => (
45
+ <span key={size} className="block bg-current" style={{ width: size, height: size, mask: `url(${src}) center / contain no-repeat`, WebkitMask: `url(${src}) center / contain no-repeat` }} />
46
+ ))}
47
+ </span>
48
+ ) : (
49
+ <span className="rounded-full bg-muted px-2 py-0.5 font-mono text-xs text-muted-foreground">{a.kind}</span>
50
+ )}
51
+ </span>
52
+ <span className="text-muted-foreground">{a.usage ? inline(a.usage) : "No usage notes yet."}</span>
53
+ <span className="mt-auto pt-1 font-mono text-xs text-muted-foreground">
54
+ {a.file}
55
+ {a.width && a.height ? ` · ${a.width} × ${a.height}` : ""}
56
+ </span>
57
+ </figcaption>
58
+ </figure>
59
+ </li>
60
+ );
61
+ })}
62
+ </ul>
63
+ );
64
+ }
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @designtools/docs: every block, for a project composing its own pages. Most
3
+ * projects use `@designtools/docs/next` instead, which composes them.
4
+ */
5
+ export { A11yPanel, AxeReport, useAxe } from "./a11y-panel";
6
+ export { AdherenceSummary } from "./adherence-summary";
7
+ export { AgentInstructions, AgentView } from "./agent-view";
8
+ export { Anatomy } from "./anatomy";
9
+ export { AskClaude } from "./ask-claude";
10
+ export { CodeView } from "./code-view";
11
+ export { ExampleFrame, Examples } from "./examples";
12
+ export { Glossary } from "./glossary";
13
+ export { Imagery } from "./imagery";
14
+ export { LogoUsage } from "./logo-usage";
15
+ export { PageActions } from "./page-actions";
16
+ export { Pattern } from "./pattern";
17
+ export { Playground } from "./playground";
18
+ export { PreviewComposition, PreviewExample, PreviewFrame, PreviewSurface } from "./preview-frame";
19
+ export { PropsTable } from "./props-table";
20
+ export { Rule } from "./rule";
21
+ export { Scale } from "./scale";
22
+ export { DocsSearch } from "./search";
23
+ export { type DocsLink, DocsFooter, DocsHeader, DocsSection, DocsShell } from "./shell";
24
+ export { Standards } from "./standards";
25
+ export { PageContrast, RampSwatches, SemanticSwatches } from "./swatches";
26
+ export { ThemeSwitch, themeScript, useThemeScript } from "./theme-switch";
27
+ export { TypeRamp } from "./type-ramp";
28
+ export { Usage } from "./usage";
29
+ export { VariantMatrix } from "./variant-matrix";
30
+ export { VoiceTerms } from "./voice-terms";
31
+ export { Workbench } from "./workbench";
32
+ export { type Registry, RegistryProvider, useRegistry } from "./lib/registry";
33
+ export * from "./lib/manifest-types";
34
+ export type { AdherenceReport } from "./lib/adherence-types";