@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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +155 -2
- package/dist/a11y-panel.d.ts +58 -0
- package/dist/a11y-panel.js +75 -0
- package/dist/adherence-summary.d.ts +9 -0
- package/dist/adherence-summary.js +27 -0
- package/dist/agent-view.d.ts +19 -0
- package/dist/agent-view.js +23 -0
- package/dist/anatomy.d.ts +12 -0
- package/dist/anatomy.js +39 -0
- package/dist/ask-claude.d.ts +14 -0
- package/dist/ask-claude.js +40 -0
- package/dist/cli.js +322 -0
- package/dist/code-view.d.ts +17 -0
- package/dist/code-view.js +42 -0
- package/dist/examples.d.ts +25 -0
- package/dist/examples.js +48 -0
- package/dist/glossary.d.ts +5 -0
- package/dist/glossary.js +21 -0
- package/dist/imagery.d.ts +6 -0
- package/dist/imagery.js +17 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +34 -0
- package/dist/lib/adherence-types.d.ts +168 -0
- package/dist/lib/adherence-types.js +13 -0
- package/dist/lib/boundary.d.ts +16 -0
- package/dist/lib/boundary.js +15 -0
- package/dist/lib/contrast.d.ts +8 -0
- package/dist/lib/contrast.js +42 -0
- package/dist/lib/cx.d.ts +24 -0
- package/dist/lib/cx.js +26 -0
- package/dist/lib/jsx.d.ts +6 -0
- package/dist/lib/jsx.js +29 -0
- package/dist/lib/live.d.ts +10 -0
- package/dist/lib/live.js +10 -0
- package/dist/lib/lookup.d.ts +35 -0
- package/dist/lib/lookup.js +42 -0
- package/dist/lib/manifest-types.d.ts +253 -0
- package/dist/lib/manifest-types.js +14 -0
- package/dist/lib/manifest.d.ts +98 -0
- package/dist/lib/manifest.js +173 -0
- package/dist/lib/markdown.d.ts +50 -0
- package/dist/lib/markdown.js +286 -0
- package/dist/lib/measure.d.ts +12 -0
- package/dist/lib/measure.js +62 -0
- package/dist/lib/pseudo.d.ts +11 -0
- package/dist/lib/pseudo.js +71 -0
- package/dist/lib/registry.d.ts +21 -0
- package/dist/lib/registry.js +11 -0
- package/dist/lib/search.d.ts +44 -0
- package/dist/lib/search.js +62 -0
- package/dist/lib/standards.d.ts +17 -0
- package/dist/lib/standards.js +81 -0
- package/dist/lib/status.d.ts +6 -0
- package/dist/lib/status.js +17 -0
- package/dist/lib/terms.d.ts +15 -0
- package/dist/lib/terms.js +20 -0
- package/dist/lib/text.d.ts +8 -0
- package/dist/lib/text.js +9 -0
- package/dist/lib/ui.d.ts +100 -0
- package/dist/lib/ui.js +89 -0
- package/dist/lib/vision.d.ts +18 -0
- package/dist/lib/vision.js +27 -0
- package/dist/logo-usage.d.ts +14 -0
- package/dist/logo-usage.js +45 -0
- package/dist/next/config.d.ts +105 -0
- package/dist/next/config.js +1 -0
- package/dist/next/index.d.ts +65 -0
- package/dist/next/index.js +66 -0
- package/dist/next/markdown.d.ts +20 -0
- package/dist/next/markdown.js +95 -0
- package/dist/next/pages.d.ts +6 -0
- package/dist/next/pages.js +188 -0
- package/dist/next/prose.d.ts +9 -0
- package/dist/next/prose.js +39 -0
- package/dist/next/proxy.d.ts +19 -0
- package/dist/next/proxy.js +56 -0
- package/dist/next/site.d.ts +53 -0
- package/dist/next/site.js +136 -0
- package/dist/page-actions.d.ts +9 -0
- package/dist/page-actions.js +32 -0
- package/dist/pattern.d.ts +11 -0
- package/dist/pattern.js +22 -0
- package/dist/playground.d.ts +62 -0
- package/dist/playground.js +111 -0
- package/dist/preview-frame.d.ts +31 -0
- package/dist/preview-frame.js +63 -0
- package/dist/props-table.d.ts +10 -0
- package/dist/props-table.js +45 -0
- package/dist/rule.d.ts +16 -0
- package/dist/rule.js +37 -0
- package/dist/scale.d.ts +9 -0
- package/dist/scale.js +58 -0
- package/dist/search.d.ts +16 -0
- package/dist/search.js +46 -0
- package/dist/shell.d.ts +92 -0
- package/dist/shell.js +201 -0
- package/dist/standards.d.ts +13 -0
- package/dist/standards.js +19 -0
- package/dist/styles.css +3708 -0
- package/dist/swatches.d.ts +28 -0
- package/dist/swatches.js +100 -0
- package/dist/theme-switch.d.ts +20 -0
- package/dist/theme-switch.js +68 -0
- package/dist/type-ramp.d.ts +10 -0
- package/dist/type-ramp.js +31 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +55 -0
- package/dist/variant-matrix.d.ts +15 -0
- package/dist/variant-matrix.js +58 -0
- package/dist/voice-terms.d.ts +5 -0
- package/dist/voice-terms.js +14 -0
- package/dist/workbench.d.ts +33 -0
- package/dist/workbench.js +274 -0
- package/package.json +107 -3
- package/src/a11y-panel.tsx +297 -0
- package/src/adherence-summary.tsx +134 -0
- package/src/agent-view.tsx +54 -0
- package/src/anatomy.tsx +119 -0
- package/src/ask-claude.tsx +90 -0
- package/src/code-view.tsx +112 -0
- package/src/css.d.ts +1 -0
- package/src/examples.tsx +141 -0
- package/src/glossary.tsx +70 -0
- package/src/imagery.tsx +64 -0
- package/src/index.ts +35 -0
- package/src/lib/adherence-types.ts +181 -0
- package/src/lib/boundary.tsx +21 -0
- package/src/lib/contrast.ts +44 -0
- package/src/lib/cx.ts +30 -0
- package/src/lib/jsx.ts +29 -0
- package/src/lib/live.tsx +15 -0
- package/src/lib/lookup.tsx +50 -0
- package/src/lib/manifest-types.ts +284 -0
- package/src/lib/manifest.ts +228 -0
- package/src/lib/markdown.ts +288 -0
- package/src/lib/measure.tsx +101 -0
- package/src/lib/pseudo.ts +74 -0
- package/src/lib/registry.tsx +29 -0
- package/src/lib/search.ts +99 -0
- package/src/lib/standards.ts +82 -0
- package/src/lib/status.tsx +32 -0
- package/src/lib/terms.tsx +53 -0
- package/src/lib/text.tsx +27 -0
- package/src/lib/ui.tsx +291 -0
- package/src/lib/vision.tsx +44 -0
- package/src/logo-usage.tsx +140 -0
- package/src/next/config.ts +101 -0
- package/src/next/index.tsx +103 -0
- package/src/next/markdown.ts +119 -0
- package/src/next/pages.tsx +483 -0
- package/src/next/prose.tsx +97 -0
- package/src/next/proxy.ts +55 -0
- package/src/next/site.ts +173 -0
- package/src/page-actions.tsx +92 -0
- package/src/pattern.tsx +72 -0
- package/src/playground.tsx +357 -0
- package/src/preview-frame.tsx +131 -0
- package/src/props-table.tsx +154 -0
- package/src/rule.tsx +165 -0
- package/src/scale.tsx +175 -0
- package/src/search.tsx +155 -0
- package/src/shell.tsx +569 -0
- package/src/standards.tsx +58 -0
- package/src/styles.css +157 -0
- package/src/swatches.tsx +298 -0
- package/src/theme-switch.tsx +90 -0
- package/src/type-ramp.tsx +150 -0
- package/src/usage.tsx +148 -0
- package/src/variant-matrix.tsx +178 -0
- package/src/voice-terms.tsx +37 -0
- package/src/workbench.tsx +587 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Live } from "./lib/live.js";
|
|
4
|
+
import { Tabs } from "@base-ui/react/tabs";
|
|
5
|
+
import { Toggle } from "@base-ui/react/toggle";
|
|
6
|
+
import { ToggleGroup } from "@base-ui/react/toggle-group";
|
|
7
|
+
import { Bot, Braces, Check, Code, Eye, ExternalLink, Link2, Maximize2, Minimize2, Minus, Monitor, Moon, PaintBucket, PersonStanding, Plus, RotateCcw, Ruler, SquareDashed, Sun, Trash2, Zap, } from "lucide-react";
|
|
8
|
+
import { cloneElement, isValidElement, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
9
|
+
import { AxeReport, useAxe } from "./a11y-panel.js";
|
|
10
|
+
import { CodeView } from "./code-view.js";
|
|
11
|
+
import { Boundary } from "./lib/boundary.js";
|
|
12
|
+
import { cx } from "./lib/cx.js";
|
|
13
|
+
import { toJSX } from "./lib/jsx.js";
|
|
14
|
+
import { coerce, Missing, useLookup } from "./lib/lookup.js";
|
|
15
|
+
import { importOf } from "./lib/manifest.js";
|
|
16
|
+
import { componentMarkdown } from "./lib/markdown.js";
|
|
17
|
+
import { Measure } from "./lib/measure.js";
|
|
18
|
+
import { installPseudoStates } from "./lib/pseudo.js";
|
|
19
|
+
import { IconButton, IconLink, Toolbar, ToolbarDivider, ToolbarMenu } from "./lib/ui.js";
|
|
20
|
+
import { filterFor, VISIONS, VisionFilters } from "./lib/vision.js";
|
|
21
|
+
import { AxisControl, FlagControl, JsonControl, Label, NumberControl, StringControl, TextControl, visible } from "./playground.js";
|
|
22
|
+
import { PropsTable } from "./props-table.js";
|
|
23
|
+
const VIEW = { bg: "canvas", viewport: "fill", zoom: 100, rtl: false, vision: "typical", outline: false, measure: false };
|
|
24
|
+
const ZOOMS = [50, 75, 100, 125, 150, 200];
|
|
25
|
+
const VIEWPORTS = [
|
|
26
|
+
{ value: "fill", label: "Fill", note: "The width of the stage" },
|
|
27
|
+
{ value: "375", label: "Phone", note: "375 px" },
|
|
28
|
+
{ value: "768", label: "Tablet", note: "768 px" },
|
|
29
|
+
{ value: "1280", label: "Desktop", note: "1280 px" },
|
|
30
|
+
];
|
|
31
|
+
/** Props the panel never offers: they are the component's plumbing, not its settings. */
|
|
32
|
+
const PLUMBING = new Set(["children", "className", "style", "render", "ref", "key", "id", "asChild"]);
|
|
33
|
+
const isFunction = (name, p) => /=>|^\(|Function|Handler/.test(p.type) || /^on[A-Z]/.test(name);
|
|
34
|
+
const isJson = (p) => /\[\]|^\{|Array<|Record<|\bobject\b/.test(p.type);
|
|
35
|
+
/** An event or value, in a line someone can read: `click on <button>`, `"ines"`, `{ value: 3 }`. */
|
|
36
|
+
function describe(args) {
|
|
37
|
+
return args
|
|
38
|
+
.map((a) => {
|
|
39
|
+
if (a && typeof a === "object" && "nativeEvent" in a && "type" in a) {
|
|
40
|
+
const t = a.target;
|
|
41
|
+
return `${String(a.type)} on <${t?.tagName?.toLowerCase() ?? "element"}>`;
|
|
42
|
+
}
|
|
43
|
+
if (a instanceof Event)
|
|
44
|
+
return `${a.type} event`;
|
|
45
|
+
try {
|
|
46
|
+
const json = JSON.stringify(a);
|
|
47
|
+
return json === undefined ? String(a) : json.length > 80 ? `${json.slice(0, 77)}…` : json;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return String(a);
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
.join(", ");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A component's one main area. On the stage: the component live, at any viewport
|
|
57
|
+
* and zoom, on any background, in either mode or direction, as people with
|
|
58
|
+
* different colour vision see it, with its boxes outlined or measured, and pinned
|
|
59
|
+
* at rest, hovered, focused, pressed or disabled. Beside it: a property for each
|
|
60
|
+
* variant and each prop, generated from their types. Under it: the code for
|
|
61
|
+
* exactly what is showing, the props, a log of every handler that fires, what an
|
|
62
|
+
* agent reads, and a full accessibility report that re-runs as anything changes.
|
|
63
|
+
* Every setting is in the URL, so a configuration can be shared as a link.
|
|
64
|
+
*
|
|
65
|
+
* Pass `props` for what it needs to render at all (its starting values), `content`
|
|
66
|
+
* for children richer than text (a Card's parts), and `wrap` for what it must sit
|
|
67
|
+
* inside to work (a provider, the root a part belongs to): an element, which the
|
|
68
|
+
* component is rendered into.
|
|
69
|
+
*/
|
|
70
|
+
export function Workbench({ entry, props: base = {}, children: initialText = entry.name, content, wrap, backgrounds = [], previewHref, }) {
|
|
71
|
+
const lookup = useLookup();
|
|
72
|
+
const Component = lookup.componentFor(entry);
|
|
73
|
+
const axes = entry.variants?.axes ?? [];
|
|
74
|
+
const axisNames = new Set(axes.map((a) => a.name));
|
|
75
|
+
const own = Object.entries(entry.props).filter(([name, p]) => !axisNames.has(name) && !PLUMBING.has(name) && p.deprecated === undefined);
|
|
76
|
+
// disabled is a pinned state ("Off"), not a prop control
|
|
77
|
+
const settable = own.filter(([name, p]) => !isFunction(name, p) && !p.from && name !== "disabled");
|
|
78
|
+
const handlers = own.filter(([name, p]) => isFunction(name, p)).map(([name]) => name);
|
|
79
|
+
const canDisable = "disabled" in entry.props;
|
|
80
|
+
const [variants, setVariants] = useState({});
|
|
81
|
+
const [values, setValues] = useState({});
|
|
82
|
+
const [text, setText] = useState(initialText);
|
|
83
|
+
const [state, setState] = useState("rest");
|
|
84
|
+
const [view, setView] = useState(VIEW);
|
|
85
|
+
const [log, setLog] = useState([]);
|
|
86
|
+
const [tab, setTab] = useState("code");
|
|
87
|
+
const [full, setFull] = useState(false);
|
|
88
|
+
const [shared, setShared] = useState(false);
|
|
89
|
+
const set = (key, value) => setView((v) => ({ ...v, [key]: value }));
|
|
90
|
+
// read a shared configuration from the URL once, then keep the URL in step with every change;
|
|
91
|
+
// the ref keeps a second run (React's development double effect) from reading the URL back after it was rewritten
|
|
92
|
+
const read = useRef(false);
|
|
93
|
+
const [ready, setReady] = useState(false);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (read.current)
|
|
96
|
+
return;
|
|
97
|
+
read.current = true;
|
|
98
|
+
const q = new URLSearchParams(window.location.search);
|
|
99
|
+
const v = {};
|
|
100
|
+
const p = {};
|
|
101
|
+
for (const [k, val] of q) {
|
|
102
|
+
if (k.startsWith("v."))
|
|
103
|
+
v[k.slice(2)] = val;
|
|
104
|
+
if (k.startsWith("p.")) {
|
|
105
|
+
try {
|
|
106
|
+
p[k.slice(2)] = JSON.parse(val);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
p[k.slice(2)] = val;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
setVariants(v);
|
|
114
|
+
setValues(p);
|
|
115
|
+
if (q.has("text"))
|
|
116
|
+
setText(q.get("text") ?? "");
|
|
117
|
+
if (q.has("state"))
|
|
118
|
+
setState(q.get("state"));
|
|
119
|
+
setView((cur) => ({
|
|
120
|
+
...cur,
|
|
121
|
+
theme: q.get("theme") ?? undefined,
|
|
122
|
+
bg: q.get("bg") ?? cur.bg,
|
|
123
|
+
viewport: q.get("vp") ?? cur.viewport,
|
|
124
|
+
zoom: Number(q.get("zoom")) || cur.zoom,
|
|
125
|
+
rtl: q.get("dir") === "rtl",
|
|
126
|
+
vision: q.get("vision") ?? cur.vision,
|
|
127
|
+
}));
|
|
128
|
+
setReady(true);
|
|
129
|
+
}, []);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!ready)
|
|
132
|
+
return;
|
|
133
|
+
const q = new URLSearchParams();
|
|
134
|
+
for (const [k, v] of Object.entries(variants))
|
|
135
|
+
q.set(`v.${k}`, v);
|
|
136
|
+
for (const [k, v] of Object.entries(values))
|
|
137
|
+
q.set(`p.${k}`, typeof v === "string" ? v : JSON.stringify(v));
|
|
138
|
+
if (text !== initialText)
|
|
139
|
+
q.set("text", text);
|
|
140
|
+
if (state !== "rest")
|
|
141
|
+
q.set("state", state);
|
|
142
|
+
if (view.theme)
|
|
143
|
+
q.set("theme", view.theme);
|
|
144
|
+
if (view.bg !== VIEW.bg)
|
|
145
|
+
q.set("bg", view.bg);
|
|
146
|
+
if (view.viewport !== VIEW.viewport)
|
|
147
|
+
q.set("vp", view.viewport);
|
|
148
|
+
if (view.zoom !== VIEW.zoom)
|
|
149
|
+
q.set("zoom", String(view.zoom));
|
|
150
|
+
if (view.rtl)
|
|
151
|
+
q.set("dir", "rtl");
|
|
152
|
+
if (view.vision !== VIEW.vision)
|
|
153
|
+
q.set("vision", view.vision);
|
|
154
|
+
const search = q.toString();
|
|
155
|
+
window.history.replaceState(null, "", `${window.location.pathname}${search ? `?${search}` : ""}${window.location.hash}`);
|
|
156
|
+
}, [ready, variants, values, text, state, view, initialText]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (state === "hover" || state === "focus" || state === "active")
|
|
159
|
+
installPseudoStates();
|
|
160
|
+
}, [state]);
|
|
161
|
+
const root = useRef(null);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const on = () => setFull(document.fullscreenElement === root.current);
|
|
164
|
+
document.addEventListener("fullscreenchange", on);
|
|
165
|
+
return () => document.removeEventListener("fullscreenchange", on);
|
|
166
|
+
}, []);
|
|
167
|
+
const toggleFull = () => (document.fullscreenElement ? document.exitFullscreen() : root.current?.requestFullscreen());
|
|
168
|
+
const record = useCallback((name, args) => {
|
|
169
|
+
const now = new Date();
|
|
170
|
+
setLog((l) => [{ id: now.getTime() + Math.random(), name, time: now.toLocaleTimeString(undefined, { hour12: false }), detail: describe(args) }, ...l].slice(0, 50));
|
|
171
|
+
}, []);
|
|
172
|
+
// every handler the component declares, and the DOM's own click and change, reported to the log
|
|
173
|
+
const logged = useMemo(() => Object.fromEntries([...new Set([...handlers, "onClick", "onChange"])].map((name) => [name, (...args) => record(name, args)])),
|
|
174
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: the handler names come from the manifest and do not change
|
|
175
|
+
[record]);
|
|
176
|
+
const chosen = { ...coerce(variants), ...values, ...(state === "disabled" ? { disabled: true } : {}) };
|
|
177
|
+
const jsx = toJSX(entry.export, { ...visible(base), ...visible(values), ...coerce(variants), ...(state === "disabled" ? { disabled: true } : {}) }, content ? undefined : text || undefined);
|
|
178
|
+
const code = `import { ${entry.export} } from "${importOf(entry)}";\n\n${jsx}`;
|
|
179
|
+
const markdown = useMemo(() => componentMarkdown(entry), [entry]);
|
|
180
|
+
const area = useRef(null);
|
|
181
|
+
const target = useRef(null);
|
|
182
|
+
const axe = useAxe(target, { auto: true, watch: JSON.stringify([variants, values, text, state, view.theme, view.bg]) });
|
|
183
|
+
const issues = axe.result?.violations.length;
|
|
184
|
+
const filterId = useId().replace(/:/g, "");
|
|
185
|
+
if (!Component)
|
|
186
|
+
return _jsx(Missing, { name: entry.name });
|
|
187
|
+
const changed = Object.keys(variants).length > 0 || Object.keys(values).length > 0 || text !== initialText || state !== "rest";
|
|
188
|
+
const reset = () => {
|
|
189
|
+
setVariants({});
|
|
190
|
+
setValues({});
|
|
191
|
+
setText(initialText);
|
|
192
|
+
setState("rest");
|
|
193
|
+
setView(VIEW);
|
|
194
|
+
};
|
|
195
|
+
const share = async () => {
|
|
196
|
+
try {
|
|
197
|
+
await navigator.clipboard.writeText(window.location.href);
|
|
198
|
+
setShared(true);
|
|
199
|
+
setTimeout(() => setShared(false), 1500);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// clipboard refused: the URL bar still holds the link
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const bgs = [
|
|
206
|
+
{ value: "canvas", label: "Canvas", note: "The page" },
|
|
207
|
+
{ value: "surface", label: "Surface", note: "Cards and panels" },
|
|
208
|
+
{ value: "muted", label: "Muted", note: "Quiet panels" },
|
|
209
|
+
{ value: "primary", label: "Primary", note: "On the brand colour" },
|
|
210
|
+
...backgrounds.map((b) => ({ value: b, label: b.replace(/^surface-/, "").replace(/^./, (c) => c.toUpperCase()), note: `--${b}` })),
|
|
211
|
+
{ value: "checker", label: "Transparent", note: "To see what it paints" },
|
|
212
|
+
];
|
|
213
|
+
const paint = view.bg === "checker"
|
|
214
|
+
? { background: "repeating-conic-gradient(var(--muted) 0 25%, var(--canvas) 0 50%) 0 0 / 16px 16px", color: "var(--canvas-foreground)" }
|
|
215
|
+
: { background: `var(--${view.bg})`, color: `var(--${view.bg}-foreground, var(--canvas-foreground))` };
|
|
216
|
+
const dotted = view.bg === "canvas";
|
|
217
|
+
const node = (_jsx(Live, { children: _jsx(Component, { ...base, ...logged, ...chosen, children: content ?? (text || undefined) }) }));
|
|
218
|
+
const scope = [
|
|
219
|
+
entry.name,
|
|
220
|
+
...Object.entries(variants).map(([k, v]) => `${k} ${v}`),
|
|
221
|
+
state !== "rest" ? state : null,
|
|
222
|
+
view.theme ? `in ${view.theme} mode` : "in the page's mode",
|
|
223
|
+
view.bg !== "canvas" ? `on ${view.bg}` : null,
|
|
224
|
+
]
|
|
225
|
+
.filter(Boolean)
|
|
226
|
+
.join(", ");
|
|
227
|
+
const tabClass = "relative inline-flex h-11 shrink-0 cursor-pointer items-center gap-2 px-1 text-sm text-muted-foreground transition-colors duration-(--dt-duration-fast) hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring data-active:text-canvas-foreground";
|
|
228
|
+
const toggle = (on) => cx("relative inline-flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 text-xs font-medium text-muted-foreground transition-colors duration-(--dt-duration-fast)", "after:absolute after:inset-x-0 after:-inset-y-[calc((var(--docs-target,2.75rem)-2rem)/2)] after:content-['']", "hover:bg-muted hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring", on && "bg-muted text-canvas-foreground");
|
|
229
|
+
return (_jsxs("div", { "data-slot": "workbench", className: "overflow-hidden rounded-2xl border border-border bg-surface text-surface-foreground shadow-xs", children: [_jsx("style", { children: `[data-wb-outline] * { outline: 1px solid color-mix(in oklch, var(--ring) 55%, transparent); outline-offset: -1px; }` }), _jsx(VisionFilters, { id: filterId }), _jsxs("div", { ref: root, className: cx("flex flex-col bg-surface", full && "h-dvh"), children: [_jsxs(Toolbar, { className: "flex-nowrap overflow-x-auto", children: [_jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx(ToolbarMenu, { icon: Monitor, label: "Viewport", value: view.viewport, fallback: "fill", options: VIEWPORTS, onChange: (v) => set("viewport", v) }), _jsx(ToolbarMenu, { icon: PaintBucket, label: "Background", value: view.bg, fallback: "canvas", options: bgs, onChange: (v) => set("bg", v), swatch: (v) => (v === "checker" ? "repeating-conic-gradient(var(--muted) 0 25%, var(--canvas) 0 50%) 0 0 / 6px 6px" : `var(--${v})`) }), _jsxs("span", { className: "flex items-center rounded-md", role: "group", "aria-label": "Zoom", children: [_jsx(IconButton, { icon: Minus, label: "Zoom out", disabled: view.zoom <= ZOOMS[0], onClick: () => set("zoom", ZOOMS[Math.max(0, ZOOMS.indexOf(view.zoom) - 1)]), className: "disabled:opacity-40" }), _jsxs("button", { type: "button", onClick: () => set("zoom", 100), title: "Reset zoom", className: "h-8 w-11 cursor-pointer rounded-md font-mono text-xs tabular-nums hover:bg-muted", children: [view.zoom, "%"] }), _jsx(IconButton, { icon: Plus, label: "Zoom in", disabled: view.zoom >= ZOOMS[ZOOMS.length - 1], onClick: () => set("zoom", ZOOMS[Math.min(ZOOMS.length - 1, ZOOMS.indexOf(view.zoom) + 1)]), className: "disabled:opacity-40" })] }), _jsx(ToolbarDivider, {}), _jsxs("button", { type: "button", "aria-pressed": view.outline, title: "Outline every box", onClick: () => set("outline", !view.outline), className: toggle(view.outline), children: [_jsx(SquareDashed, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), _jsx("span", { className: "sr-only", children: "Outline every box" })] }), _jsxs("button", { type: "button", "aria-pressed": view.measure, title: "Measure: point at an element", onClick: () => set("measure", !view.measure), className: toggle(view.measure), children: [_jsx(Ruler, { "aria-hidden": true, className: "size-(--dt-size-xs)" }), _jsx("span", { className: "sr-only", children: "Measure" })] }), _jsx(ToolbarMenu, { icon: Eye, label: "Colour vision", value: view.vision, fallback: "typical", options: VISIONS, onChange: (v) => set("vision", v) }), _jsx("button", { type: "button", "aria-pressed": view.rtl, title: "Right to left", onClick: () => set("rtl", !view.rtl), className: toggle(view.rtl), children: _jsx("span", { className: "font-mono text-[0.6875rem] tracking-wide", children: "RTL" }) }), _jsx(IconButton, { icon: view.theme === "dark" ? Sun : Moon, label: view.theme === "dark" ? "Show in light mode" : "Show in dark mode", pressed: view.theme !== undefined, onClick: () => set("theme", view.theme === "dark" ? "light" : "dark") })] }), _jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx(IconButton, { icon: shared ? Check : Link2, label: shared ? "Link copied" : "Copy a link to this configuration", onClick: share }), _jsx(IconButton, { icon: full ? Minimize2 : Maximize2, label: full ? "Leave full screen" : "Full screen", onClick: toggleFull }), previewHref && _jsx(IconLink, { icon: ExternalLink, label: "Open on its own", href: previewHref, target: "_blank", rel: "noreferrer" })] })] }), _jsxs("div", { className: cx("grid min-h-0 flex-1 lg:grid-cols-[minmax(0,1fr)_20rem]", full && "overflow-hidden"), children: [_jsxs("div", { ref: area, "data-slot": "workbench-stage", className: cx("relative min-w-0 overflow-auto", full ? "h-full" : "min-h-96"), children: [_jsx("div", { "data-docs-stage": "", "data-theme": view.theme, className: cx("flex min-h-full min-w-fit items-center justify-center p-10", dotted && "bg-[radial-gradient(color-mix(in_oklch,var(--border)_85%,transparent)_1px,transparent_1px)] [background-size:14px_14px]"), style: paint, children: _jsx("div", { dir: view.rtl ? "rtl" : undefined, "data-pseudo": state === "hover" || state === "focus" || state === "active" ? state : undefined, "data-wb-outline": view.outline || undefined, className: cx("flex items-center justify-center", view.viewport !== "fill" && "rounded-lg border border-dashed border-current/25 p-4"), style: {
|
|
230
|
+
width: view.viewport === "fill" ? undefined : Number(view.viewport),
|
|
231
|
+
zoom: view.zoom === 100 ? undefined : view.zoom / 100,
|
|
232
|
+
filter: filterFor(view.vision, filterId),
|
|
233
|
+
}, children: _jsx("div", { ref: target, "data-slot": "workbench-target", className: "flex max-w-full items-center justify-center", children: _jsx(Boundary, { label: entry.name, children: wrap && isValidElement(wrap) ? cloneElement(wrap, undefined, node) : node }) }) }) }), view.measure && _jsx(Measure, { area: area, target: target })] }), _jsxs("div", { "data-slot": "workbench-controls", className: cx("flex flex-col gap-7 border-t border-border p-5 lg:border-t-0 lg:border-l", full ? "overflow-y-auto" : "lg:max-h-[44rem] lg:overflow-y-auto"), children: [_jsxs("div", { className: "-mt-1.5 -mb-2 flex min-h-9 items-center justify-between", children: [_jsx("p", { className: "text-sm font-medium", children: "Properties" }), _jsx(IconButton, { icon: RotateCcw, label: "Reset everything", onClick: reset, disabled: !changed, className: "disabled:pointer-events-none disabled:opacity-40" })] }), _jsx(StateControl, { value: state, onChange: setState, canDisable: canDisable }), axes.length > 0 && (_jsx(Section, { title: "Variants", children: axes.map((axis) => (_jsx(AxisControl, { axis: axis, value: variants[axis.name] ?? axis.default ?? "", onChange: (v) => setVariants((s) => ({ ...s, [axis.name]: v })) }, axis.name))) })), settable.length > 0 && (_jsx(Section, { title: "Props", children: settable.map(([name, p]) => {
|
|
234
|
+
const current = name in values ? values[name] : base[name];
|
|
235
|
+
const put = (v) => setValues((s) => ({ ...s, [name]: v }));
|
|
236
|
+
const info = _jsx(PropInfo, { prop: p });
|
|
237
|
+
if (p.type === "boolean")
|
|
238
|
+
return _jsx(FlagControl, { name: name, checked: !!current, onChange: put }, name);
|
|
239
|
+
if (p.values?.length) {
|
|
240
|
+
const axis = { name, options: p.values.map((v) => ({ name: v })), default: p.default?.replace(/^["']|["']$/g, "") };
|
|
241
|
+
return _jsx(AxisControl, { axis: axis, value: String(current ?? axis.default ?? ""), onChange: put }, name);
|
|
242
|
+
}
|
|
243
|
+
if (p.type === "number")
|
|
244
|
+
return _jsx(NumberControl, { name: name, value: current, onChange: put, info: info }, name);
|
|
245
|
+
if (isJson(p))
|
|
246
|
+
return _jsx(JsonControl, { name: name, value: current, onChange: put, info: info }, name);
|
|
247
|
+
return _jsx(StringControl, { name: name, value: String(current ?? ""), onChange: put, info: info }, name);
|
|
248
|
+
}) })), !content && initialText !== "" && (_jsx(Section, { title: "Content", children: _jsx(TextControl, { value: text, onChange: setText }) })), handlers.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [handlers.join(", "), " ", handlers.length === 1 ? "is" : "are", " logged in", " ", _jsx("button", { type: "button", onClick: () => setTab("actions"), className: "cursor-pointer underline underline-offset-2", children: "Actions" }), "."] }))] })] })] }), _jsxs(Tabs.Root, { value: tab, onValueChange: (v) => setTab(String(v)), className: "border-t border-border", children: [_jsxs(Tabs.List, { "aria-label": `About this ${entry.name}`, className: "relative flex gap-6 overflow-x-auto border-b border-border px-5", children: [_jsxs(Tabs.Tab, { value: "code", className: tabClass, children: [_jsx(Code, { "aria-hidden": true, className: "size-4" }), " Code"] }), _jsxs(Tabs.Tab, { value: "props", className: tabClass, children: [_jsx(Braces, { "aria-hidden": true, className: "size-4" }), " Props", _jsx("span", { className: "font-mono text-xs text-muted-foreground", children: Object.keys(entry.props).length })] }), _jsxs(Tabs.Tab, { value: "actions", className: tabClass, children: [_jsx(Zap, { "aria-hidden": true, className: "size-4" }), " Actions", log.length > 0 && _jsx("span", { className: "rounded-full bg-primary-subdued px-1.5 font-mono text-xs leading-5 text-primary-subdued-foreground", children: log.length })] }), _jsxs(Tabs.Tab, { value: "agent", className: tabClass, children: [_jsx(Bot, { "aria-hidden": true, className: "size-4" }), " For agents"] }), _jsxs(Tabs.Tab, { value: "a11y", className: tabClass, children: [_jsx(PersonStanding, { "aria-hidden": true, className: "size-4" }), " Accessibility", issues !== undefined && (_jsxs("span", { className: cx("rounded-full px-1.5 font-mono text-xs leading-5", issues ? "bg-destructive-subdued text-destructive-subdued-foreground" : "bg-success-subdued text-success-subdued-foreground"), children: [issues ? issues : "ok", _jsx("span", { className: "sr-only", children: issues ? ` ${issues === 1 ? "violation" : "violations"}` : ", no violations" })] }))] }), _jsx(Tabs.Indicator, { className: "absolute bottom-0 left-(--active-tab-left) h-0.5 w-(--active-tab-width) bg-[var(--docs-accent,var(--primary))] transition-[left,width] duration-(--dt-duration-base) ease-(--dt-ease-out)" })] }), _jsx(Tabs.Panel, { value: "code", className: "outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", children: _jsx(CodeView, { code: code, label: `${entry.name}.tsx`, bare: true }) }), _jsx(Tabs.Panel, { value: "props", className: "p-5 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", children: _jsx(PropsTable, { entry: entry }) }), _jsx(Tabs.Panel, { value: "actions", className: "outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", children: _jsx(ActionLog, { log: log, onClear: () => setLog([]) }) }), _jsxs(Tabs.Panel, { value: "agent", className: "outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", children: [_jsxs("p", { className: "border-b border-border px-5 py-3 text-sm text-muted-foreground", children: ["What an agent reads at this page's address with ", _jsx("code", { className: "font-mono text-xs", children: ".md" }), ": generated from the manifest, never written twice."] }), _jsx(CodeView, { code: markdown, lang: "markdown", label: `${entry.name}.md`, bare: true, wrap: true })] }), _jsx(Tabs.Panel, { value: "a11y", className: "p-5 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", children: _jsx(AxeReport, { ...axe, entry: entry, scope: scope }) })] })] }));
|
|
249
|
+
}
|
|
250
|
+
function Section({ title, children }) {
|
|
251
|
+
return (_jsxs("section", { className: "flex flex-col gap-5", children: [_jsx("h4", { className: "text-xs font-medium tracking-wide text-muted-foreground uppercase", children: title }), children] }));
|
|
252
|
+
}
|
|
253
|
+
function PropInfo({ prop }) {
|
|
254
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-mono text-muted-foreground", children: prop.type }), prop.description && _jsx("span", { className: "text-muted-foreground", children: prop.description }), prop.default !== undefined && _jsxs("span", { className: "text-muted-foreground", children: ["Default: ", prop.default] })] }));
|
|
255
|
+
}
|
|
256
|
+
/** Rest, hover, focus, pressed and disabled, pinned: hover and focus without a pointer or a keyboard. */
|
|
257
|
+
function StateControl({ value, onChange, canDisable }) {
|
|
258
|
+
const states = [
|
|
259
|
+
{ value: "rest", label: "Rest" },
|
|
260
|
+
{ value: "hover", label: "Hover" },
|
|
261
|
+
{ value: "focus", label: "Focus" },
|
|
262
|
+
{ value: "active", label: "Press" },
|
|
263
|
+
...(canDisable ? [{ value: "disabled", label: "Off" }] : []),
|
|
264
|
+
];
|
|
265
|
+
const id = useId();
|
|
266
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { id: id, name: "state", info: _jsx("span", { className: "text-muted-foreground", children: "Pins a state on the preview without a pointer or keyboard. Off is the disabled prop." }) }), _jsx(ToggleGroup, { "aria-labelledby": id, value: [value], onValueChange: (next) => next[0] && onChange(next[0]), className: "grid grid-flow-col auto-cols-fr gap-0.5 rounded-lg bg-muted p-1", children: states.map((s) => (_jsx(Toggle, { value: s.value, className: "relative h-8 min-w-0 cursor-pointer truncate rounded-md px-1 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: s.label }, s.value))) })] }));
|
|
267
|
+
}
|
|
268
|
+
/** Every handler call, newest first: which handler, when, and with what. */
|
|
269
|
+
function ActionLog({ log, onClear }) {
|
|
270
|
+
if (log.length === 0) {
|
|
271
|
+
return (_jsxs("p", { className: "flex items-center gap-3 px-5 py-8 text-sm text-muted-foreground", children: [_jsx(Zap, { "aria-hidden": true, className: "size-4" }), "Use the component on the stage: every handler it calls appears here, with what it was called with."] }));
|
|
272
|
+
}
|
|
273
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border px-5 py-2", children: [_jsxs("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: [log.length, " ", log.length === 1 ? "call" : "calls"] }), _jsxs("button", { type: "button", onClick: onClear, className: "inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-md px-2 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-canvas-foreground", children: [_jsx(Trash2, { "aria-hidden": true, className: "size-3.5" }), " Clear"] })] }), _jsx("ol", { className: "max-h-80 divide-y divide-border overflow-y-auto font-mono text-xs", children: log.map((e) => (_jsxs("li", { className: "grid grid-cols-[5rem_8rem_minmax(0,1fr)] items-baseline gap-4 px-5 py-2.5", children: [_jsx("span", { className: "text-muted-foreground tabular-nums", children: e.time }), _jsx("span", { className: "font-medium text-primary-subdued-foreground", children: e.name }), _jsx("span", { className: "truncate text-muted-foreground", title: e.detail, children: e.detail })] }, e.id))) })] }));
|
|
274
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,110 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designtools/docs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Design system docs as a package: pages, navigation, search, a workbench per component, rules, patterns, brand pages and markdown for agents, generated from the @designtools/manifest and themed by your tokens. One Next adapter serves them inside your app or as a standalone site.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Made by Many",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/madebymany/designtools.git",
|
|
11
|
+
"directory": "packages/docs"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/madebymany/designtools/tree/main/packages/docs#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/madebymany/designtools/issues"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"designtools-docs": "dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./next": {
|
|
26
|
+
"types": "./dist/next/index.d.ts",
|
|
27
|
+
"default": "./dist/next/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./next/proxy": {
|
|
30
|
+
"types": "./dist/next/proxy.d.ts",
|
|
31
|
+
"default": "./dist/next/proxy.js"
|
|
32
|
+
},
|
|
33
|
+
"./registry": {
|
|
34
|
+
"types": "./dist/lib/registry.d.ts",
|
|
35
|
+
"default": "./dist/lib/registry.js"
|
|
36
|
+
},
|
|
37
|
+
"./styles.css": "./dist/styles.css",
|
|
38
|
+
"./*": {
|
|
39
|
+
"types": "./dist/*.d.ts",
|
|
40
|
+
"default": "./dist/*.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"src",
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
],
|
|
51
|
+
"sideEffects": [
|
|
52
|
+
"*.css"
|
|
53
|
+
],
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"design-system",
|
|
59
|
+
"documentation",
|
|
60
|
+
"docs",
|
|
61
|
+
"react",
|
|
62
|
+
"nextjs",
|
|
63
|
+
"base-ui",
|
|
64
|
+
"storybook-alternative",
|
|
65
|
+
"playground",
|
|
66
|
+
"tokens",
|
|
67
|
+
"agents"
|
|
68
|
+
],
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@base-ui/react": "^1.6.0",
|
|
71
|
+
"lucide-react": "^1.23.0",
|
|
72
|
+
"next": ">=16",
|
|
73
|
+
"react": ">=19",
|
|
74
|
+
"react-dom": ">=19",
|
|
75
|
+
"axe-core": "^4.14.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"axe-core": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"fuzzysort": "4.0.2",
|
|
84
|
+
"shiki": "4.5.0"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@base-ui/react": "1.6.0",
|
|
88
|
+
"@tailwindcss/postcss": "4.3.3",
|
|
89
|
+
"@types/node": "^24.19.1",
|
|
90
|
+
"@types/react": "^19.3.0",
|
|
91
|
+
"@types/react-dom": "^19.3.0",
|
|
92
|
+
"axe-core": "^4.14.0",
|
|
93
|
+
"lucide-react": "1.23.0",
|
|
94
|
+
"next": "16.3.0",
|
|
95
|
+
"postcss": "8.5.29",
|
|
96
|
+
"react": "19.2.7",
|
|
97
|
+
"react-dom": "19.2.7",
|
|
98
|
+
"tailwindcss": "4.3.3",
|
|
99
|
+
"tsup": "^8.5.1",
|
|
100
|
+
"tsx": "^4.23.15",
|
|
101
|
+
"typescript": "~6.0.3",
|
|
102
|
+
"vitest": "^4.1.11"
|
|
103
|
+
},
|
|
104
|
+
"scripts": {
|
|
105
|
+
"build": "rm -rf dist && tsup cli/cli.ts --format esm --out-dir dist && tsc -p tsconfig.build.json && node scripts/fix-extensions.mjs && node scripts/build-css.mjs",
|
|
106
|
+
"typecheck": "tsc --noEmit",
|
|
107
|
+
"test": "vitest run",
|
|
108
|
+
"cli": "tsx cli/cli.ts"
|
|
109
|
+
}
|
|
6
110
|
}
|