@designtools/docs 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +155 -2
  4. package/dist/a11y-panel.d.ts +58 -0
  5. package/dist/a11y-panel.js +75 -0
  6. package/dist/adherence-summary.d.ts +9 -0
  7. package/dist/adherence-summary.js +27 -0
  8. package/dist/agent-view.d.ts +19 -0
  9. package/dist/agent-view.js +23 -0
  10. package/dist/anatomy.d.ts +12 -0
  11. package/dist/anatomy.js +39 -0
  12. package/dist/ask-claude.d.ts +14 -0
  13. package/dist/ask-claude.js +40 -0
  14. package/dist/cli.js +322 -0
  15. package/dist/code-view.d.ts +17 -0
  16. package/dist/code-view.js +42 -0
  17. package/dist/examples.d.ts +25 -0
  18. package/dist/examples.js +48 -0
  19. package/dist/glossary.d.ts +5 -0
  20. package/dist/glossary.js +21 -0
  21. package/dist/imagery.d.ts +6 -0
  22. package/dist/imagery.js +17 -0
  23. package/dist/index.d.ts +35 -0
  24. package/dist/index.js +34 -0
  25. package/dist/lib/adherence-types.d.ts +168 -0
  26. package/dist/lib/adherence-types.js +13 -0
  27. package/dist/lib/boundary.d.ts +16 -0
  28. package/dist/lib/boundary.js +15 -0
  29. package/dist/lib/contrast.d.ts +8 -0
  30. package/dist/lib/contrast.js +42 -0
  31. package/dist/lib/cx.d.ts +24 -0
  32. package/dist/lib/cx.js +26 -0
  33. package/dist/lib/jsx.d.ts +6 -0
  34. package/dist/lib/jsx.js +29 -0
  35. package/dist/lib/live.d.ts +10 -0
  36. package/dist/lib/live.js +10 -0
  37. package/dist/lib/lookup.d.ts +35 -0
  38. package/dist/lib/lookup.js +42 -0
  39. package/dist/lib/manifest-types.d.ts +253 -0
  40. package/dist/lib/manifest-types.js +14 -0
  41. package/dist/lib/manifest.d.ts +98 -0
  42. package/dist/lib/manifest.js +173 -0
  43. package/dist/lib/markdown.d.ts +50 -0
  44. package/dist/lib/markdown.js +286 -0
  45. package/dist/lib/measure.d.ts +12 -0
  46. package/dist/lib/measure.js +62 -0
  47. package/dist/lib/pseudo.d.ts +11 -0
  48. package/dist/lib/pseudo.js +71 -0
  49. package/dist/lib/registry.d.ts +21 -0
  50. package/dist/lib/registry.js +11 -0
  51. package/dist/lib/search.d.ts +44 -0
  52. package/dist/lib/search.js +62 -0
  53. package/dist/lib/standards.d.ts +17 -0
  54. package/dist/lib/standards.js +81 -0
  55. package/dist/lib/status.d.ts +6 -0
  56. package/dist/lib/status.js +17 -0
  57. package/dist/lib/terms.d.ts +15 -0
  58. package/dist/lib/terms.js +20 -0
  59. package/dist/lib/text.d.ts +8 -0
  60. package/dist/lib/text.js +9 -0
  61. package/dist/lib/ui.d.ts +100 -0
  62. package/dist/lib/ui.js +89 -0
  63. package/dist/lib/vision.d.ts +18 -0
  64. package/dist/lib/vision.js +27 -0
  65. package/dist/logo-usage.d.ts +14 -0
  66. package/dist/logo-usage.js +45 -0
  67. package/dist/next/config.d.ts +105 -0
  68. package/dist/next/config.js +1 -0
  69. package/dist/next/index.d.ts +65 -0
  70. package/dist/next/index.js +66 -0
  71. package/dist/next/markdown.d.ts +20 -0
  72. package/dist/next/markdown.js +95 -0
  73. package/dist/next/pages.d.ts +6 -0
  74. package/dist/next/pages.js +188 -0
  75. package/dist/next/prose.d.ts +9 -0
  76. package/dist/next/prose.js +39 -0
  77. package/dist/next/proxy.d.ts +19 -0
  78. package/dist/next/proxy.js +56 -0
  79. package/dist/next/site.d.ts +53 -0
  80. package/dist/next/site.js +136 -0
  81. package/dist/page-actions.d.ts +9 -0
  82. package/dist/page-actions.js +32 -0
  83. package/dist/pattern.d.ts +11 -0
  84. package/dist/pattern.js +22 -0
  85. package/dist/playground.d.ts +62 -0
  86. package/dist/playground.js +111 -0
  87. package/dist/preview-frame.d.ts +31 -0
  88. package/dist/preview-frame.js +63 -0
  89. package/dist/props-table.d.ts +10 -0
  90. package/dist/props-table.js +45 -0
  91. package/dist/rule.d.ts +16 -0
  92. package/dist/rule.js +37 -0
  93. package/dist/scale.d.ts +9 -0
  94. package/dist/scale.js +58 -0
  95. package/dist/search.d.ts +16 -0
  96. package/dist/search.js +46 -0
  97. package/dist/shell.d.ts +92 -0
  98. package/dist/shell.js +201 -0
  99. package/dist/standards.d.ts +13 -0
  100. package/dist/standards.js +19 -0
  101. package/dist/styles.css +3708 -0
  102. package/dist/swatches.d.ts +28 -0
  103. package/dist/swatches.js +100 -0
  104. package/dist/theme-switch.d.ts +20 -0
  105. package/dist/theme-switch.js +68 -0
  106. package/dist/type-ramp.d.ts +10 -0
  107. package/dist/type-ramp.js +31 -0
  108. package/dist/usage.d.ts +13 -0
  109. package/dist/usage.js +55 -0
  110. package/dist/variant-matrix.d.ts +15 -0
  111. package/dist/variant-matrix.js +58 -0
  112. package/dist/voice-terms.d.ts +5 -0
  113. package/dist/voice-terms.js +14 -0
  114. package/dist/workbench.d.ts +33 -0
  115. package/dist/workbench.js +274 -0
  116. package/package.json +107 -3
  117. package/src/a11y-panel.tsx +297 -0
  118. package/src/adherence-summary.tsx +134 -0
  119. package/src/agent-view.tsx +54 -0
  120. package/src/anatomy.tsx +119 -0
  121. package/src/ask-claude.tsx +90 -0
  122. package/src/code-view.tsx +112 -0
  123. package/src/css.d.ts +1 -0
  124. package/src/examples.tsx +141 -0
  125. package/src/glossary.tsx +70 -0
  126. package/src/imagery.tsx +64 -0
  127. package/src/index.ts +35 -0
  128. package/src/lib/adherence-types.ts +181 -0
  129. package/src/lib/boundary.tsx +21 -0
  130. package/src/lib/contrast.ts +44 -0
  131. package/src/lib/cx.ts +30 -0
  132. package/src/lib/jsx.ts +29 -0
  133. package/src/lib/live.tsx +15 -0
  134. package/src/lib/lookup.tsx +50 -0
  135. package/src/lib/manifest-types.ts +284 -0
  136. package/src/lib/manifest.ts +228 -0
  137. package/src/lib/markdown.ts +288 -0
  138. package/src/lib/measure.tsx +101 -0
  139. package/src/lib/pseudo.ts +74 -0
  140. package/src/lib/registry.tsx +29 -0
  141. package/src/lib/search.ts +99 -0
  142. package/src/lib/standards.ts +82 -0
  143. package/src/lib/status.tsx +32 -0
  144. package/src/lib/terms.tsx +53 -0
  145. package/src/lib/text.tsx +27 -0
  146. package/src/lib/ui.tsx +291 -0
  147. package/src/lib/vision.tsx +44 -0
  148. package/src/logo-usage.tsx +140 -0
  149. package/src/next/config.ts +101 -0
  150. package/src/next/index.tsx +103 -0
  151. package/src/next/markdown.ts +119 -0
  152. package/src/next/pages.tsx +483 -0
  153. package/src/next/prose.tsx +97 -0
  154. package/src/next/proxy.ts +55 -0
  155. package/src/next/site.ts +173 -0
  156. package/src/page-actions.tsx +92 -0
  157. package/src/pattern.tsx +72 -0
  158. package/src/playground.tsx +357 -0
  159. package/src/preview-frame.tsx +131 -0
  160. package/src/props-table.tsx +154 -0
  161. package/src/rule.tsx +165 -0
  162. package/src/scale.tsx +175 -0
  163. package/src/search.tsx +155 -0
  164. package/src/shell.tsx +569 -0
  165. package/src/standards.tsx +58 -0
  166. package/src/styles.css +157 -0
  167. package/src/swatches.tsx +298 -0
  168. package/src/theme-switch.tsx +90 -0
  169. package/src/type-ramp.tsx +150 -0
  170. package/src/usage.tsx +148 -0
  171. package/src/variant-matrix.tsx +178 -0
  172. package/src/voice-terms.tsx +37 -0
  173. package/src/workbench.tsx +587 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # @designtools/docs
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a8e0eee: Editorial pages can be components: `body` takes React, with `markdown` for agents, and `DocsSection`, `Prose` and `Live` are exported for composing them. `Live` marks the project's own components inside the docs, and the docs' CSS never matches inside it: the docs' utilities no longer beat a project's responsive utilities on its components (a hero's `hidden lg:flex`), and a project's utilities no longer beat the docs' on their own elements, whichever stylesheet loads last. A legacy `:before` in the scoped CSS no longer invalidates its rule.
8
+
9
+ The overview explains the system instead of listing every component: each part, why it exists, its count and a glimpse of it, then "Why this is agentic" for client and design leads (`overview` in the config tunes or drops it).
10
+
11
+ Layout: the colour page drops its nested cards for open rows with a panel per mode; rules have an index and a two-column layout with the check beside the statement; pattern pages show the compositions first, status in the header and "Made of" below; variants sit on one stage without grid lines; vocabulary and voice share a calm term row (say, not, why) with a quiet search and a correction callout; navigation links read as links and group headings recede; section numbers align to their titles; the page header loses its rule; previous and next come before where the page came from, which is last; the phone drawer has room above its first item; the props tooltip is ink and opens at once.
12
+
13
+ ## 0.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 637fc1f: First release. A design system's docs as a package: every page generated from the manifest, rendering the project's real components; nested navigation, search and a colour-mode switch; a workbench per component (viewports, zoom, backgrounds, colour-vision simulation, outline and measure, pinned states, generated controls, an actions log, shareable links, code, props, the agent view and a full accessibility report); editorial pages, a full-bleed hero, and markdown for agents on every page.
18
+
19
+ It ships compiled, with CSS scoped to `[data-designtools]` that reads the project's own tokens, so a project on @designtools/tokens is themed with nothing to set and any other sets a short list of variables, fonts and colours first. A Next adapter, `createDocs`, turns one config into the layout, the pages, their static params and metadata, the markdown and manifest routes and the preview route; `docsProxy` handles markdown negotiation and the production 404. The CLI writes the registry that reaches the project's components (`map`) and copies a block into the project when it needs more than the theme (`eject`, `status`, `show`).
20
+
21
+ Replaces @designtools/blocks, whose blocks were copied into each project.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Made by Many Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,156 @@
1
- # Temporary Holding Version
1
+ # @designtools/docs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A design system's docs as a package: the brand and the system in one place, for the people who make a product and the agents that build it with them. Every page is generated from [`@designtools/manifest`](../manifest)'s JSON and renders the project's real components.
4
+
5
+ - **Pages:** an overview (with an optional full-bleed hero), editorial brand pages, logo, imagery, voice, colour, type, scale, rules, patterns, standards, vocabulary, and a page per component.
6
+ - **A workbench per component:** live on a stage at any viewport, zoom, background, mode or direction, with colour-vision simulation, outline and measure overlays, and states pinned without a pointer. A properties panel generated from the prop types, and tabs for the code of what is showing, the props, a log of every handler call, what an agent reads, and a full accessibility report.
7
+ - **Navigation and search:** nested, collapsible, with a colour-mode switch, a phone drawer and an "On this page" rail; fuzzy search that finds a component by a wrong name.
8
+ - **Markdown for agents:** every page at `.md` or on `Accept: text/markdown`, the whole system as one file, and the manifest as JSON.
9
+
10
+ It ships compiled, with its own CSS scoped to the docs, and takes the brand from the project's tokens. A project writes a config and a handful of one-line route files; nothing is copied, so upgrading is a version bump.
11
+
12
+ ## Setup (Next App Router)
13
+
14
+ ```bash
15
+ pnpm add @designtools/docs @base-ui/react lucide-react
16
+ pnpm add -D @designtools/manifest
17
+ ```
18
+
19
+ `axe-core` (MPL-2.0) is optional: add it for the accessibility report. Without it the report says so.
20
+
21
+ 1. Build the manifest, then write the registry, the one generated file, which reaches the project's components, examples and assets:
22
+
23
+ ```bash
24
+ designtools-manifest build
25
+ designtools-docs map # writes app/(design)/design/system/_registry.tsx
26
+ ```
27
+
28
+ 2. Write the config, `app/(design)/design/system/docs.tsx`:
29
+
30
+ ```tsx
31
+ import components from "@ds/manifest/components.json";
32
+ import tokens from "@ds/manifest/tokens.json";
33
+ import rules from "@ds/manifest/rules.json";
34
+ // patterns, taxonomy, assets the same way
35
+ import { createDocs } from "@designtools/docs/next";
36
+ import Registry from "./_registry";
37
+
38
+ export const docs = createDocs({
39
+ title: "Acme design system",
40
+ registry: Registry,
41
+ manifest: { components, tokens, rules, patterns, taxonomy, assets },
42
+ logo: <img src={wordmark.src} alt="Acme" />,
43
+ editorial: [{ slug: "brand", title: "Principles", body: "## Principles\n\n- **Say the thing.** …", by: { author: "Studio", reviewed: "10 October 2026", status: "draft" } }],
44
+ });
45
+ ```
46
+
47
+ 3. Add the routes, each one line:
48
+
49
+ | File | Contents |
50
+ | --- | --- |
51
+ | `(docs)/layout.tsx` | `export default docs.Layout;` |
52
+ | `(docs)/[[...slug]]/page.tsx` | `export default docs.Page;` `export const generateStaticParams = docs.generateStaticParams;` `export const generateMetadata = docs.generateMetadata;` |
53
+ | `md/[[...path]]/route.ts` | `export const GET = docs.markdown;` |
54
+ | `manifest/[file]/route.ts` | `export const GET = docs.manifest;` `export const generateStaticParams = docs.manifestParams;` |
55
+ | `preview/[slug]/[example]/page.tsx` | `export default docs.Preview;` |
56
+
57
+ 4. In `proxy.ts`, the markdown negotiation and a 404 for every docs URL on production:
58
+
59
+ ```ts
60
+ import { docsProxy } from "@designtools/docs/next/proxy";
61
+ export const proxy = docsProxy({ basePath: "/design/system" });
62
+ export const config = { matcher: ["/design/system", "/design/system.md", "/design/system/:path*"] };
63
+ ```
64
+
65
+ Keep the `(design)` route group's layout calling `notFound()` on production too, for the pages.
66
+
67
+ 5. Give `<html>` `suppressHydrationWarning`: the colour-mode switch sets `data-theme` on it before the first paint.
68
+
69
+ **Standalone:** the same files in their own Next app (`apps/docs` in a workspace), with the manifest and the system imported from the system's package. `designtools-docs map --manifest <path>` reads another package's manifest and imports from its root.
70
+
71
+ ## Config
72
+
73
+ | Key | What |
74
+ | --- | --- |
75
+ | `title`, `basePath`, `logo` | The site's name, where it is served (`/design/system`), and the client's mark |
76
+ | `registry` | The generated registry from `designtools-docs map` |
77
+ | `manifest` | The manifest files, as imported JSON |
78
+ | `hero` | A full-bleed introduction across the top of the overview; without it the overview has a plain header and `lead` |
79
+ | `editorial` | Pages people write, each with its author and status, in the brand, foundations or standards group: markdown, or components (below) |
80
+ | `workbench` | Per component: starting `props`, text `children`, richer `content` (a Card's parts), and a `wrap` element for the provider or root it needs |
81
+ | `demonstrations` | Per rule id: an element shown on the rule, for one that needs persuading |
82
+ | `baselines`, `adherence`, `provenance` | The standards page's targets, the adherence report, and the line naming the version and commit |
83
+ | `overview` | `{ agentic: false }` drops the overview's "Why this is agentic"; `agenticLead` rewrites its opening line |
84
+ | `hide` | Built-in pages to leave out |
85
+ | `blocks` | Blocks replaced by the project's own (see Eject) |
86
+
87
+ ## Editorial pages with components
88
+
89
+ A page that has to show rather than tell (the brand on two surfaces, the status colours in their real jobs) takes React as its `body`, with `markdown` for agents:
90
+
91
+ ```tsx
92
+ import { DocsSection, Live, Prose } from "@designtools/docs/next";
93
+
94
+ editorial: [{
95
+ slug: "foundations/two-surfaces", section: "foundations", title: "One system, two surfaces", by,
96
+ body: (
97
+ <DocsSection title="Side by side">
98
+ <Prose text="The same tokens and components, set for two jobs." />
99
+ <Live><MarketingSlice /><AppSlice /></Live>
100
+ </DocsSection>
101
+ ),
102
+ markdown: "## Side by side\n\nThe same tokens and components, set for two jobs.",
103
+ }]
104
+ ```
105
+
106
+ `DocsSection` and `Prose` give the page the docs' own sections and type. Wrap the project's components in `Live`: the docs' styles never reach inside it, so the components look exactly as they do in the product. `markdown` is what the page's `.md` carries.
107
+
108
+ ## Theming
109
+
110
+ The docs read the project's own variables, so a project on [`@designtools/tokens`](../tokens) is themed with nothing to set. Any other project sets these on the docs (or anywhere above them):
111
+
112
+ | Variables | Used for |
113
+ | --- | --- |
114
+ | `--canvas`, `--surface`, `--overlay`, `--muted`, each with `-foreground` | Page, panels, popups, quiet fills and their text |
115
+ | `--border`, `--input`, `--ring`, `--shadow` | Lines, control edges, focus, shadow colour |
116
+ | `--primary`, `--primary-foreground`, `--primary-subdued`, `--primary-subdued-foreground`, `--primary-hover` | Action and emphasis |
117
+ | `--success`, `--destructive`, `--warning`, with `-foreground`, `-subdued`, `-subdued-foreground` | Status |
118
+ | `--docs-font-sans`, `--docs-font-mono` (else `--font-sans`, `--font-mono`) | The docs' type |
119
+ | `--docs-display` (else `--font-display`, else the sans) | Headings |
120
+ | `--docs-accent` (else `--primary`) | The current page's marker and the active tab: always a fill or a bar, never text |
121
+ | `--docs-radius-sm` … `--docs-radius-2xl` (else `--radius-*`) | Corners |
122
+ | `--docs-target` (44px) | The smallest pressable size |
123
+
124
+ Text sizes, leading, tracking, weights, shadows, control sizes and motion follow the project's `--text-*`, `--leading-*`, `--tracking-*`, `--font-weight-*`, `--shadow-*`, `--size-*`, `--duration-*` and `--ease-*` where it has them. Every value has a fallback, so the docs render on a project with none of it.
125
+
126
+ The CSS is scoped to `[data-designtools]`, so it styles the docs and nothing else. It never matches inside `Live` (`[data-docs-content]`), which marks everywhere the project's components render: examples, the workbench, variants, anatomy, rule demonstrations, the hero and the logo. There only the project's CSS applies, responsive utilities included. On the docs' own elements its rules carry one attribute more than a project utility of the same name, so neither side's utilities leak into the other, whichever stylesheet loads last.
127
+
128
+ ## Eject
129
+
130
+ When a block has to change more than the theme allows:
131
+
132
+ ```bash
133
+ designtools-docs eject usage
134
+ ```
135
+
136
+ copies its source into the project (`_blocks/usage.tsx` beside the registry), with its imports pointed at the package, and prints the line for the config: `blocks: { Usage: Usage }`. That block is the project's; every other block keeps upgrading. `designtools-docs status` compares ejected blocks with the installed version, and `designtools-docs show <block>` prints the new one.
137
+
138
+ ## CLI
139
+
140
+ ```
141
+ designtools-docs map [--check] Write the registry from the manifest
142
+ designtools-docs eject <block…> Copy blocks' source into the project
143
+ designtools-docs status Compare ejected blocks with this version
144
+ designtools-docs list List the blocks
145
+ designtools-docs show <block> Print this version of a block
146
+ ```
147
+
148
+ The registry's path is `docs.registry` in `designtools.json` (default `app/(design)/design/system/_registry.tsx`), ejected blocks go to `docs.blocks` (default `_blocks` beside it), and the manifest is found from `manifest.system`.
149
+
150
+ ## Composing pages yourself
151
+
152
+ Every block is exported from `@designtools/docs` (`DocsShell`, `Workbench`, `Usage`, `Examples`, `SemanticSwatches`, …) for a project that wants pages the adapter does not make. Render them inside the generated `Registry` and a `DocsShell`, and import `@designtools/docs/styles.css`.
153
+
154
+ ## Licence
155
+
156
+ MIT, Made by Many.
@@ -0,0 +1,58 @@
1
+ import { type ReactNode, type RefObject } from "react";
2
+ import type { ComponentEntry } from "./lib/manifest-types.js";
3
+ interface Node {
4
+ target: string;
5
+ html: string;
6
+ summary?: string;
7
+ }
8
+ interface Finding {
9
+ id: string;
10
+ impact: string | null;
11
+ help: string;
12
+ description: string;
13
+ helpUrl: string;
14
+ nodes: Node[];
15
+ }
16
+ export interface AxeResult {
17
+ violations: Finding[];
18
+ incomplete: Finding[];
19
+ passes: Finding[];
20
+ version: string;
21
+ }
22
+ type Status = "idle" | "running" | "done" | "failed";
23
+ /**
24
+ * Runs axe-core over an element and keeps the result: on demand, or again
25
+ * whenever `watch` changes, debounced. axe loads on the first run, so a page
26
+ * that never checks never ships it.
27
+ */
28
+ export declare function useAxe(target: RefObject<HTMLElement | null>, { auto, watch }?: {
29
+ auto?: boolean;
30
+ watch?: unknown;
31
+ }): {
32
+ status: Status;
33
+ result: AxeResult | null;
34
+ run: () => Promise<void>;
35
+ };
36
+ /**
37
+ * The whole of an axe run, as a report: what failed, on which elements and how
38
+ * to fix it; what axe could not decide and a person must; what passed; and the
39
+ * checks no tool makes, from the component's own `@role`, `@keyboard` and `@aria`.
40
+ */
41
+ export declare function AxeReport({ status, result, run, entry, scope, }: {
42
+ status: Status;
43
+ result: AxeResult | null;
44
+ run: () => void;
45
+ entry?: ComponentEntry;
46
+ /** What was checked, in a line: "Button, variant outline, in dark mode". */
47
+ scope?: string;
48
+ }): import("react").JSX.Element;
49
+ /**
50
+ * Runs axe-core over what it wraps, on demand, and shows the full report under
51
+ * it. A clean run is not a pass: axe catches roughly a third of WCAG problems,
52
+ * so the report lists the checks a person makes too.
53
+ */
54
+ export declare function A11yPanel({ entry, children }: {
55
+ entry?: ComponentEntry;
56
+ children: ReactNode;
57
+ }): import("react").JSX.Element;
58
+ export {};
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { ArrowUpRight, ChevronDown, CircleAlert, CircleCheck, CircleHelp, Hand, LoaderCircle, RotateCcw } from "lucide-react";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+ import { cx } from "./lib/cx.js";
6
+ /**
7
+ * Runs axe-core over an element and keeps the result: on demand, or again
8
+ * whenever `watch` changes, debounced. axe loads on the first run, so a page
9
+ * that never checks never ships it.
10
+ */
11
+ export function useAxe(target, { auto = false, watch } = {}) {
12
+ const [status, setStatus] = useState("idle");
13
+ const [result, setResult] = useState(null);
14
+ const run = useCallback(async () => {
15
+ if (!target.current)
16
+ return;
17
+ setStatus("running");
18
+ try {
19
+ const axe = (await import("axe-core")).default;
20
+ const r = await axe.run(target.current, { resultTypes: ["violations", "incomplete", "passes"] });
21
+ const map = (list) => list.map((v) => ({
22
+ id: v.id,
23
+ impact: v.impact ?? null,
24
+ help: v.help,
25
+ description: v.description,
26
+ helpUrl: v.helpUrl,
27
+ nodes: v.nodes.map((n) => ({ target: n.target.join(" "), html: n.html, summary: n.failureSummary })),
28
+ }));
29
+ setResult({ violations: map(r.violations), incomplete: map(r.incomplete), passes: map(r.passes), version: axe.version });
30
+ setStatus("done");
31
+ }
32
+ catch {
33
+ setStatus("failed");
34
+ }
35
+ }, [target]);
36
+ useEffect(() => {
37
+ if (!auto)
38
+ return;
39
+ const timer = setTimeout(run, 400);
40
+ return () => clearTimeout(timer);
41
+ }, [auto, run, watch]);
42
+ return { status, result, run };
43
+ }
44
+ const SEVERE = new Set(["critical", "serious"]);
45
+ /**
46
+ * The whole of an axe run, as a report: what failed, on which elements and how
47
+ * to fix it; what axe could not decide and a person must; what passed; and the
48
+ * checks no tool makes, from the component's own `@role`, `@keyboard` and `@aria`.
49
+ */
50
+ export function AxeReport({ status, result, run, entry, scope, }) {
51
+ return (_jsxs("div", { "data-slot": "a11y-report", className: "flex flex-col gap-8", children: [_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-6", children: [_jsxs("dl", { className: "flex flex-wrap gap-x-10 gap-y-4", children: [_jsx(Count, { label: "Violations", value: result?.violations.length, tone: result?.violations.length ? "bad" : "good" }), _jsx(Count, { label: "Needs a person", value: result?.incomplete.length }), _jsx(Count, { label: "Passed", value: result?.passes.length })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: status === "running" ? "Checking…" : status === "failed" ? "axe could not run. Is axe-core installed?" : result ? `axe-core ${result.version}` : "" }), _jsxs("button", { type: "button", onClick: run, disabled: status === "running", className: "inline-flex min-h-9 cursor-pointer items-center gap-2 rounded-lg border border-border px-3 text-sm font-medium hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring disabled:cursor-wait disabled:opacity-60", children: [status === "running" ? _jsx(LoaderCircle, { "aria-hidden": true, className: "size-4 animate-spin" }) : _jsx(RotateCcw, { "aria-hidden": true, className: "size-4" }), result ? "Check again" : "Run axe"] })] })] }), scope && _jsxs("p", { className: "-mt-4 text-sm text-muted-foreground", children: ["Checked: ", scope, "."] }), result && result.violations.length === 0 && (_jsxs("p", { className: "flex items-center gap-2 text-sm text-success-subdued-foreground", children: [_jsx(CircleCheck, { "aria-hidden": true, className: "size-4" }), "No violations in what axe can measure. That is about a third of WCAG; the rest is below."] })), result && result.violations.length > 0 && (_jsx(Group, { title: "Violations", icon: CircleAlert, children: result.violations.map((f) => (_jsx(FindingRow, { finding: f, open: true }, f.id))) })), result && result.incomplete.length > 0 && (_jsx(Group, { title: "Needs a person", icon: CircleHelp, lead: "axe found these but could not decide them: look at each.", children: result.incomplete.map((f) => (_jsx(FindingRow, { finding: f }, f.id))) })), _jsx(Group, { title: "Check by hand", icon: Hand, lead: "What no tool measures. Try each with a keyboard and a screen reader.", children: _jsxs("ul", { className: "flex flex-col divide-y divide-border border-y border-border text-sm", children: [_jsx(Manual, { children: "Tab reaches it, and the focus ring is visible on every background it sits on." }), entry?.a11y?.keyboard?.length ? (_jsxs(Manual, { children: ["It answers", " ", entry.a11y.keyboard.map((k, i) => (_jsxs("span", { children: [i > 0 && (i === entry.a11y.keyboard.length - 1 ? " and " : ", "), _jsx("kbd", { className: "rounded-sm border border-border px-1.5 font-mono text-xs", children: k })] }, k))), "."] })) : null, _jsxs(Manual, { children: ["A screen reader announces its name", entry?.a11y?.role ? (_jsxs(_Fragment, { children: [" ", "and its role, ", _jsx("code", { className: "font-mono text-xs", children: entry.a11y.role })] })) : (" and its role"), ", and any change of state."] }), _jsx(Manual, { children: "It still works at 200% zoom, and at a phone's width." }), entry?.aria && (_jsxs(Manual, { children: ["It follows its", " ", _jsxs("a", { href: entry.aria, className: "inline-flex items-center gap-0.5 font-medium underline decoration-border underline-offset-4 hover:decoration-current", children: ["ARIA pattern ", _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5" })] }), "."] }))] }) }), result && result.passes.length > 0 && (_jsxs("details", { className: "group", children: [_jsxs("summary", { className: "flex min-h-(--docs-target,2.75rem) w-fit cursor-pointer list-none items-center gap-2 text-sm font-medium [&::-webkit-details-marker]:hidden", children: [_jsx(CircleCheck, { "aria-hidden": true, className: "size-4 text-muted-foreground" }), result.passes.length, " checks passed", _jsx(ChevronDown, { "aria-hidden": true, className: "size-4 text-muted-foreground transition-transform group-open:rotate-180" })] }), _jsx("ul", { className: "mt-2 grid gap-x-8 gap-y-1.5 text-sm text-muted-foreground sm:grid-cols-2", children: result.passes.map((p) => (_jsxs("li", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { children: p.help }), _jsxs("span", { className: "font-mono text-xs tabular-nums", children: ["\u00D7", p.nodes.length] })] }, p.id))) })] }))] }));
52
+ }
53
+ function Count({ label, value, tone }) {
54
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: label }), _jsx("dd", { className: cx("text-4xl leading-none font-light tracking-tight tabular-nums", tone === "bad" && "text-destructive-subdued-foreground", value === undefined && "text-muted-foreground"), children: value ?? "–" })] }));
55
+ }
56
+ function Group({ title, icon: Icon, lead, children }) {
57
+ return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("h4", { className: "flex items-center gap-2 text-base font-medium", children: [_jsx(Icon, { "aria-hidden": true, className: "size-4 text-muted-foreground" }), title] }), lead && _jsx("p", { className: "text-sm text-muted-foreground", children: lead })] }), _jsx("div", { className: "flex flex-col gap-2", children: children })] }));
58
+ }
59
+ function Manual({ children }) {
60
+ return (_jsxs("li", { className: "flex items-start gap-3 py-3", children: [_jsx("span", { "aria-hidden": true, className: "mt-1.5 size-1.5 shrink-0 rounded-full bg-muted-foreground" }), _jsx("span", { children: children })] }));
61
+ }
62
+ function FindingRow({ finding: f, open }) {
63
+ const severe = SEVERE.has(f.impact ?? "");
64
+ return (_jsxs("details", { open: open, className: "group rounded-lg border border-border", children: [_jsxs("summary", { className: "flex min-h-(--docs-target,2.75rem) cursor-pointer list-none items-center gap-3 px-4 py-2 text-sm [&::-webkit-details-marker]:hidden", children: [_jsx("span", { className: cx("rounded-full px-2 py-0.5 text-xs font-medium capitalize", severe ? "bg-destructive-subdued text-destructive-subdued-foreground" : "bg-muted text-muted-foreground"), children: f.impact ?? "unrated" }), _jsx("span", { className: "min-w-0 flex-1 font-medium", children: f.help }), _jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [f.nodes.length, " ", f.nodes.length === 1 ? "element" : "elements"] }), _jsx(ChevronDown, { "aria-hidden": true, className: "size-4 text-muted-foreground transition-transform group-open:rotate-180" })] }), _jsxs("div", { className: "flex flex-col gap-3 border-t border-border px-4 py-3 text-sm", children: [_jsxs("p", { className: "text-muted-foreground", children: [f.description, " ", _jsxs("a", { href: f.helpUrl, className: "inline-flex items-center gap-0.5 font-medium text-canvas-foreground underline decoration-border underline-offset-4 hover:decoration-current", children: ["How to fix ", _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5" })] })] }), _jsx("ul", { className: "flex flex-col gap-2", children: f.nodes.map((n, i) => (_jsxs("li", { className: "flex flex-col gap-1.5 rounded-md bg-muted/60 p-3", children: [_jsx("code", { className: "font-mono text-xs text-muted-foreground", children: n.target }), _jsx("code", { className: "font-mono text-xs break-all", children: n.html }), n.summary && _jsx("p", { className: "text-xs whitespace-pre-line text-muted-foreground", children: n.summary })] }, `${n.target}-${i}`))) })] })] }));
65
+ }
66
+ /**
67
+ * Runs axe-core over what it wraps, on demand, and shows the full report under
68
+ * it. A clean run is not a pass: axe catches roughly a third of WCAG problems,
69
+ * so the report lists the checks a person makes too.
70
+ */
71
+ export function A11yPanel({ entry, children }) {
72
+ const box = useRef(null);
73
+ const axe = useAxe(box);
74
+ return (_jsxs("div", { "data-slot": "a11y-panel", className: "flex flex-col gap-6", children: [_jsx("div", { ref: box, "data-slot": "a11y-target", children: children }), _jsx(AxeReport, { ...axe, entry: entry })] }));
75
+ }
@@ -0,0 +1,9 @@
1
+ import type { AdherenceReport } from "./lib/adherence-types.js";
2
+ /**
3
+ * The latest adherence report: how much of what the system could render comes
4
+ * from it, the classes that bypass the tokens, and where a className fights a
5
+ * component's variants. It informs; it never blocks.
6
+ */
7
+ export declare function AdherenceSummary({ report }: {
8
+ report: AdherenceReport;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown } from "lucide-react";
3
+ import { cx } from "./lib/cx.js";
4
+ /**
5
+ * The latest adherence report: how much of what the system could render comes
6
+ * from it, the classes that bypass the tokens, and where a className fights a
7
+ * component's variants. It informs; it never blocks.
8
+ */
9
+ export function AdherenceSummary({ report }) {
10
+ const s = report.summary;
11
+ const stats = [
12
+ ["Components used", `${s.componentsUsed} of ${s.componentsUsed + s.componentsUnused.length}`],
13
+ ["Raw elements", String(s.rawElements)],
14
+ ["Off-system values", String(s.offSystemValues)],
15
+ ["Overrides", String(s.overrides)],
16
+ ];
17
+ const pct = s.share === null ? null : Math.round(s.share * 100);
18
+ return (_jsxs("div", { "data-slot": "adherence-summary", className: "flex flex-col gap-6", children: [_jsxs("div", { className: "grid gap-x-12 gap-y-8 border-t border-border pt-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.6fr)]", children: [_jsxs("div", { "data-slot": "adherence-share", className: "flex flex-col gap-4", children: [_jsx("span", { className: "text-sm font-medium text-muted-foreground", children: "From the system" }), _jsxs("span", { className: "flex flex-col gap-3", children: [_jsx("span", { className: "text-7xl leading-none font-light tracking-tighter tabular-nums", children: share(s.share) }), pct !== null && (_jsx("span", { role: "meter", "aria-label": "Share from the system", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, className: "block h-2 overflow-hidden rounded-full bg-muted", children: _jsx("span", { className: "block h-full rounded-full bg-primary", style: { width: `${pct}%` } }) }))] })] }), _jsx("dl", { className: "grid grid-cols-2 gap-x-8 gap-y-6 self-end", children: stats.map(([label, value]) => (_jsxs("div", { "data-slot": "adherence-stat", className: "flex flex-col gap-1", children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: label }), _jsx("dd", { className: "text-4xl font-light tracking-tight tabular-nums", children: value })] }, label))) })] }), s.replaceable.length === 0 && (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["No component declares ", _jsx("code", { className: "font-mono text-xs", children: "@replaces" }), ", so raw elements are not counted and the share is not yet meaningful."] })), report.routes.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-2xl border border-border bg-surface text-surface-foreground", tabIndex: 0, role: "region", "aria-label": "Adherence by route", children: _jsxs("table", { className: "w-full min-w-[32rem] border-collapse text-left text-sm", children: [_jsx("caption", { className: "sr-only", children: "Adherence by route" }), _jsx("thead", { children: _jsx("tr", { className: "border-b border-border bg-muted/50 text-xs text-muted-foreground", children: ["Route", "From the system", "Raw", "Off-system", "Overrides"].map((h) => (_jsx("th", { scope: "col", className: "px-4 py-2.5 font-medium", children: h }, h))) }) }), _jsx("tbody", { children: report.routes.map((r) => (_jsx(Row, { name: r.route, counts: r.counts }, r.route))) })] }) })), report.files.some((f) => f.raw.length || f.offSystem.length || f.overrides.length) && (_jsxs("details", { "data-slot": "adherence-findings", className: "group overflow-hidden rounded-2xl 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 px-4 text-sm font-medium hover:bg-muted [&::-webkit-details-marker]:hidden", children: ["Findings by file", _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--dt-size-xs) text-muted-foreground transition-transform group-open:rotate-180" })] }), _jsx("ul", { className: "flex flex-col divide-y divide-border border-t border-border text-sm", children: report.files
19
+ .filter((f) => f.raw.length || f.offSystem.length || f.overrides.length)
20
+ .map((f) => (_jsxs("li", { className: "flex flex-col gap-1.5 px-4 py-3", children: [_jsx("code", { className: "font-mono text-xs", children: f.file }), _jsxs("ul", { className: "flex flex-col gap-0.5 text-muted-foreground", children: [f.raw.map((r) => (_jsxs("li", { children: ["line ", r.line, ": a raw ", _jsxs("code", { className: "font-mono text-xs", children: ["<", r.element, ">"] }), " where ", r.replacedBy.join(" or "), " would do"] }, `r${r.line}:${r.column}`))), f.offSystem.map((o) => (_jsxs("li", { children: ["line ", o.line, ": ", _jsx("code", { className: "font-mono text-xs", children: o.class }), " is ", o.kind === "status-text" ? "a status fill used as text; use its subdued foreground" : o.kind === "palette" ? "a palette step, not a semantic token" : "an arbitrary value"] }, `o${o.line}:${o.column}:${o.class}`))), f.overrides.map((o) => (_jsxs("li", { children: ["line ", o.line, ": ", _jsx("code", { className: "font-mono text-xs", children: o.classes.map((c) => c.class).join(" ") }), " on ", o.component, " changes its", " ", [...new Set(o.classes.map((c) => c.kind))].join(" and "), ": is a variant missing?"] }, `v${o.line}:${o.column}`)))] })] }, f.file))) })] })), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["A report, never a gate: ", report.generator, ", over ", s.files, " files. Lookalikes (a div styled like a Card) are not detected."] })] }));
21
+ }
22
+ function Row({ name, counts }) {
23
+ return (_jsxs("tr", { className: "border-b border-border last:border-b-0", children: [_jsx("th", { scope: "row", className: "px-4 py-3 font-mono text-xs font-normal", children: name }), _jsx("td", { className: cx("px-4 py-3 tabular-nums", counts.share !== null && counts.share < 0.9 && "font-medium"), children: share(counts.share) }), _jsx("td", { className: "px-4 py-3 tabular-nums", children: counts.rawElements }), _jsx("td", { className: "px-4 py-3 tabular-nums", children: counts.offSystemValues }), _jsx("td", { className: "px-4 py-3 tabular-nums", children: counts.overrides })] }));
24
+ }
25
+ function share(value) {
26
+ return value === null ? "n/a" : `${Math.round(value * 1000) / 10}%`;
27
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ComponentEntry } from "./lib/manifest-types.js";
3
+ /**
4
+ * What an agent needs to know, as copyable markdown: a component's status,
5
+ * usage, canonical example, do and don't, props, variants and parts, or any
6
+ * other block's markdown form. Generated from the manifest; nothing in it is
7
+ * written twice and nothing is written by a model.
8
+ */
9
+ export declare function AgentInstructions({ entry, markdown }: {
10
+ entry?: ComponentEntry;
11
+ markdown?: string;
12
+ }): import("react").JSX.Element;
13
+ /** The same thing two ways, on a switch: as a person sees it, and as an agent reads it. Pass a component entry, or any block's markdown. */
14
+ export declare function AgentView({ entry, markdown, label, children, }: {
15
+ entry?: ComponentEntry;
16
+ markdown?: string;
17
+ label?: string;
18
+ children: ReactNode;
19
+ }): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Bot, Eye } from "lucide-react";
4
+ import { CodeView } from "./code-view.js";
5
+ import { componentMarkdown } from "./lib/markdown.js";
6
+ import { Segmented, SegmentedList, SegmentedPanel } from "./lib/ui.js";
7
+ /**
8
+ * What an agent needs to know, as copyable markdown: a component's status,
9
+ * usage, canonical example, do and don't, props, variants and parts, or any
10
+ * other block's markdown form. Generated from the manifest; nothing in it is
11
+ * written twice and nothing is written by a model.
12
+ */
13
+ export function AgentInstructions({ entry, markdown }) {
14
+ const code = markdown ?? (entry ? componentMarkdown(entry) : "");
15
+ return (_jsx("div", { "data-slot": "agent-instructions", className: "flex flex-col gap-2", children: _jsx(CodeView, { code: code, lang: "markdown", label: "For agents: paste into a prompt", wrap: true }) }));
16
+ }
17
+ /** The same thing two ways, on a switch: as a person sees it, and as an agent reads it. Pass a component entry, or any block's markdown. */
18
+ export function AgentView({ entry, markdown, label, children, }) {
19
+ return (_jsxs(Segmented, { defaultValue: "rendered", className: "gap-4", children: [_jsx("div", { "data-slot": "agent-view", className: "flex", children: _jsx(SegmentedList, { label: `${label ?? entry?.name ?? "This"}, two ways`, items: [
20
+ { value: "rendered", label: "For people", icon: Eye },
21
+ { value: "agent", label: "For agents", icon: Bot },
22
+ ] }) }), _jsx(SegmentedPanel, { value: "rendered", children: children }), _jsx(SegmentedPanel, { value: "agent", children: _jsx(AgentInstructions, { entry: entry, markdown: markdown }) })] }));
23
+ }
@@ -0,0 +1,12 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ComponentEntry } from "./lib/manifest-types.js";
3
+ /**
4
+ * A component's parts, by `data-slot`. Point at a part to outline every element
5
+ * carrying it in the example. A part that does not appear may render in a
6
+ * portal or only in some states.
7
+ */
8
+ export declare function Anatomy({ entry, children, }: {
9
+ entry: ComponentEntry;
10
+ /** The example to point into. Defaults to the canonical example, else the bare component. */
11
+ children?: ReactNode;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ import { Live } from "./lib/live.js";
4
+ import { Layers } from "lucide-react";
5
+ import { useEffect, useId, useRef, useState } from "react";
6
+ import { cx, target } from "./lib/cx.js";
7
+ import { renderExample, useLookup } from "./lib/lookup.js";
8
+ import { Frame, Stage, Toolbar } from "./lib/ui.js";
9
+ /**
10
+ * A component's parts, by `data-slot`. Point at a part to outline every element
11
+ * carrying it in the example. A part that does not appear may render in a
12
+ * portal or only in some states.
13
+ */
14
+ export function Anatomy({ entry, children, }) {
15
+ const lookup = useLookup();
16
+ const id = useId();
17
+ const box = useRef(null);
18
+ const [active, setActive] = useState(null);
19
+ const [counts, setCounts] = useState({});
20
+ useEffect(() => {
21
+ const node = box.current;
22
+ if (!node)
23
+ return;
24
+ const measure = () => setCounts(Object.fromEntries(entry.slots.map((s) => [s, node.querySelectorAll(`[data-slot="${CSS.escape(s)}"]`).length])));
25
+ measure();
26
+ const observer = new MutationObserver(measure);
27
+ observer.observe(node, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-slot"] });
28
+ return () => observer.disconnect();
29
+ }, [entry.slots]);
30
+ if (entry.slots.length === 0) {
31
+ return _jsxs("p", { className: "text-sm text-muted-foreground", children: [entry.name, " renders no data-slot parts."] });
32
+ }
33
+ const Component = lookup.componentFor(entry);
34
+ const module = lookup.examplesFor(entry);
35
+ const first = entry.examples?.canonical?.name ?? entry.examples?.do[0]?.name ?? entry.examples?.other[0]?.name;
36
+ const example = children ?? (module && first ? renderExample(module[first], first) : Component ? (_jsx(Live, { children: _jsx(Component, { children: entry.name }) })) : null);
37
+ const shown = entry.slots.filter((slot) => counts[slot]).length;
38
+ return (_jsxs(Frame, { "data-slot": "anatomy", children: [active && (_jsx("style", { children: `[data-anatomy="${id}"] [data-slot="${active.replace(/"/g, '\\"')}"] { outline: 2px solid var(--ring); outline-offset: 3px; border-radius: var(--radius-sm); background-color: color-mix(in oklch, var(--ring) 10%, transparent); }` })), _jsxs("div", { className: "grid lg:grid-cols-[minmax(0,1fr)_17rem]", children: [_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx(Toolbar, { children: _jsxs("span", { className: "flex items-center gap-2 pl-2 text-xs font-medium text-muted-foreground", children: [_jsx(Layers, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), entry.slots.length, " ", entry.slots.length === 1 ? "part" : "parts", shown < entry.slots.length && ` · ${shown} in this example`] }) }), _jsx(Stage, { className: "min-h-64 flex-1", children: _jsx("div", { ref: box, "data-anatomy": id, "data-slot": "anatomy-example", className: "min-w-0", children: example }) })] }), _jsxs("div", { className: "flex flex-col gap-2 border-t border-border p-3 lg:border-t-0 lg:border-l", children: [_jsx("p", { className: "px-2 pt-1 text-xs text-muted-foreground", children: "Point at a part to find it." }), _jsx("ol", { "data-slot": "anatomy-parts", className: "flex flex-col gap-0.5 text-sm", onMouseLeave: () => setActive(null), children: entry.slots.map((slot, i) => (_jsx("li", { children: _jsxs("button", { type: "button", onMouseEnter: () => setActive(slot), onFocus: () => setActive(slot), onBlur: () => setActive(null), onClick: () => setActive(slot), className: cx(target, "group flex w-full items-center gap-3 rounded-lg px-2 text-left hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring", active === slot && "bg-muted", !counts[slot] && "text-muted-foreground"), children: [_jsx("span", { "aria-hidden": true, className: cx("flex size-6 shrink-0 items-center justify-center rounded-full border border-border font-mono text-xs", active === slot && "border-transparent bg-primary text-primary-foreground"), children: i + 1 }), _jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: slot }), _jsx("span", { className: "text-xs text-muted-foreground", children: counts[slot] ? `×${counts[slot]}` : "not shown" })] }) }, slot))) }), entry.variants?.parts && (_jsxs("p", { className: "mt-auto px-2 pt-2 text-xs text-muted-foreground", children: ["Styled in ", _jsx("code", { className: "font-mono", children: entry.variants.config }), ": ", entry.variants.parts.join(", ")] }))] })] })] }));
39
+ }
@@ -0,0 +1,14 @@
1
+ import type { ComponentEntry } from "./lib/manifest-types.js";
2
+ /**
3
+ * Copies a ready-made prompt for Claude: the component, its source file, the
4
+ * variant and example in view, and what should change. Paste it into Claude
5
+ * Code in the project and it starts from the right file.
6
+ */
7
+ export declare function AskClaude({ entry, variant, example, }: {
8
+ entry: ComponentEntry;
9
+ /** The variant in view, e.g. `{ intent: "success" }`. */
10
+ variant?: Record<string, string>;
11
+ /** The example in view, by export name. */
12
+ example?: string;
13
+ }): import("react").JSX.Element;
14
+ export declare function promptFor(entry: ComponentEntry, request: string, variant?: Record<string, string>, example?: string): string;
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Check, Copy, Sparkles } from "lucide-react";
4
+ import { useId, useState } from "react";
5
+ import { importOf } from "./lib/manifest.js";
6
+ /**
7
+ * Copies a ready-made prompt for Claude: the component, its source file, the
8
+ * variant and example in view, and what should change. Paste it into Claude
9
+ * Code in the project and it starts from the right file.
10
+ */
11
+ export function AskClaude({ entry, variant, example, }) {
12
+ const id = useId();
13
+ const [request, setRequest] = useState("");
14
+ const [copied, setCopied] = useState(false);
15
+ const copy = async () => {
16
+ try {
17
+ await navigator.clipboard.writeText(promptFor(entry, request, variant, example));
18
+ setCopied(true);
19
+ setTimeout(() => setCopied(false), 1500);
20
+ }
21
+ catch {
22
+ // clipboard refused: nothing changes
23
+ }
24
+ };
25
+ return (_jsxs("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", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary-subdued text-primary-subdued-foreground", children: _jsx(Sparkles, { "aria-hidden": true, className: "size-(--dt-size-xs)" }) }), _jsxs("span", { className: "flex flex-col gap-0.5", children: [_jsxs("label", { htmlFor: id, className: "text-base font-semibold tracking-tight", children: ["Ask Claude to change ", entry.name] }), _jsxs("span", { className: "text-sm text-muted-foreground", children: ["Copies a prompt naming the file", example ? ", the example" : "", ", its parts and the variant, ready to paste into Claude Code."] })] })] }), _jsxs("div", { className: "flex flex-col overflow-hidden rounded-xl border border-input bg-canvas focus-within:outline-2 focus-within:outline-ring", children: [_jsx("textarea", { id: id, rows: 3, value: request, onChange: (e) => setRequest(e.target.value), placeholder: `What should change about ${entry.name}?`, className: "resize-none bg-transparent px-4 py-3 text-base outline-none placeholder:text-muted-foreground" }), _jsxs("div", { className: "flex items-center justify-between gap-3 px-2 pb-2 pl-4", children: [_jsx("code", { className: "truncate font-mono text-xs text-muted-foreground", children: entry.source }), _jsxs("button", { type: "button", onClick: copy, 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", children: [copied ? _jsx(Check, { "aria-hidden": true, className: "size-(--dt-size-xs)" }) : _jsx(Copy, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), copied ? "Copied" : "Copy prompt"] })] })] }), _jsx("span", { className: "sr-only", "aria-live": "polite", children: copied ? "Prompt copied" : "" })] }));
26
+ }
27
+ export function promptFor(entry, request, variant, example) {
28
+ const lines = [`Component: ${entry.name}, exported as ${entry.export} from ${entry.source} (imported from "${importOf(entry)}")`];
29
+ const shown = Object.entries(variant ?? {});
30
+ if (shown.length)
31
+ lines.push(`Variant in view: ${shown.map(([k, v]) => `${k}="${v}"`).join(", ")}`);
32
+ if (example && entry.examples)
33
+ lines.push(`Example in view: ${example}, from ${entry.examples.source}`);
34
+ if (entry.status && entry.status !== "stable")
35
+ lines.push(`Status: ${entry.status}${entry.statusNote ? `. ${entry.statusNote}` : ""}`);
36
+ if (entry.slots.length)
37
+ lines.push(`Its parts (data-slot): ${entry.slots.join(", ")}`);
38
+ lines.push("", request.trim() || "What should change: ");
39
+ return lines.join("\n");
40
+ }