@designtools/docs 0.2.1 → 0.2.2
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 +6 -0
- package/dist/lib/handlers.d.ts +7 -0
- package/dist/lib/handlers.js +11 -0
- package/dist/next/pages.js +8 -23
- package/dist/styles.css +0 -46
- package/dist/workbench.js +2 -1
- package/package.json +1 -1
- package/src/lib/handlers.ts +14 -0
- package/src/next/pages.tsx +8 -66
- package/src/workbench.tsx +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @designtools/docs
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c4b61fb: The workbench no longer freezes on a component with a function prop it calls while rendering (Base UI's Meter and Progress, through `getAriaValueText`): only event handlers (`on…`) are logged to Actions now, so logging can never re-render the component into calling it again. The overview's section list loses its glimpses (colour dots, the logo, sample rules): each part is its name, its count and why it exists.
|
|
8
|
+
|
|
3
9
|
## 0.2.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PropEntry } from "./manifest-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The props the workbench logs to its Actions tab: event handlers only. A function prop that is not a
|
|
4
|
+
* handler (getAriaValueText, a formatter, a render function) is called while the component renders, and
|
|
5
|
+
* logging it re-renders the component, which calls it again, for ever.
|
|
6
|
+
*/
|
|
7
|
+
export declare function handlerNames(props: Record<string, PropEntry>): string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const isFunction = (name, p) => /=>|^\(|Function|Handler/.test(p.type) || /^on[A-Z]/.test(name);
|
|
2
|
+
/**
|
|
3
|
+
* The props the workbench logs to its Actions tab: event handlers only. A function prop that is not a
|
|
4
|
+
* handler (getAriaValueText, a formatter, a render function) is called while the component renders, and
|
|
5
|
+
* logging it re-renders the component, which calls it again, for ever.
|
|
6
|
+
*/
|
|
7
|
+
export function handlerNames(props) {
|
|
8
|
+
return Object.entries(props)
|
|
9
|
+
.filter(([name, p]) => isFunction(name, p) && /^on[A-Z]/.test(name))
|
|
10
|
+
.map(([name]) => name);
|
|
11
|
+
}
|
package/dist/next/pages.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowRight, ArrowUpRight } from "lucide-react";
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import { cx, display } from "../lib/cx.js";
|
|
5
4
|
import { AdherenceSummary } from "../adherence-summary.js";
|
|
6
5
|
import { Anatomy } from "../anatomy.js";
|
|
7
6
|
import { Examples } from "../examples.js";
|
|
8
7
|
import { Glossary } from "../glossary.js";
|
|
9
8
|
import { Imagery } from "../imagery.js";
|
|
10
9
|
import { groupFamilies, slugOf } from "../lib/manifest.js";
|
|
11
|
-
import { StatusBadge } from "../lib/status.js";
|
|
12
10
|
import { LogoUsage } from "../logo-usage.js";
|
|
13
11
|
import { PageActions } from "../page-actions.js";
|
|
14
12
|
import { Pattern } from "../pattern.js";
|
|
@@ -112,69 +110,56 @@ function Overview({ site }) {
|
|
|
112
110
|
const count = families.reduce((n, g) => n + g.families.length, 0);
|
|
113
111
|
const brandPages = site.editorial.filter((p) => (p.section ?? "brand") === "brand");
|
|
114
112
|
const brandFirst = brandPages[0];
|
|
115
|
-
const products = site.taxonomy.terms.filter((t) => t.kind === "product" && t.wrong?.length);
|
|
116
|
-
const example = products[0] ?? site.taxonomy.terms.find((t) => t.wrong?.length);
|
|
117
|
-
const fills = ["--primary", "--neutral", "--success", "--warning", "--destructive"].filter((n) => site.tokens.tokens.some((t) => t.name === n));
|
|
118
113
|
const share = config.adherence?.summary.share;
|
|
119
|
-
// each section of the system, why it exists
|
|
114
|
+
// each section of the system, why it exists and how much is in it
|
|
120
115
|
const sections = [];
|
|
121
116
|
if (brandFirst || site.shows("logo"))
|
|
122
117
|
sections.push({
|
|
123
118
|
title: "Brand",
|
|
124
119
|
href: `${base}/${brandFirst?.slug ?? "brand/logo"}`,
|
|
125
120
|
why: "What the brand believes, how it sounds and how it looks, so every screen and sentence comes from the same place.",
|
|
126
|
-
count: `${brandPages.length + (site.assets.assets.some((a) => a.kind === "logo") ? 1 : 0)} pages
|
|
127
|
-
preview: config.logo ? _jsx("span", { className: "flex h-10 items-center [&_img]:h-8 [&_svg]:h-8", children: config.logo }) : null,
|
|
121
|
+
count: `${brandPages.length + (site.assets.assets.some((a) => a.kind === "logo") ? 1 : 0)} pages`
|
|
128
122
|
});
|
|
129
123
|
sections.push({
|
|
130
124
|
title: "Foundations",
|
|
131
125
|
href: `${base}/colour`,
|
|
132
126
|
why: "Colour, type and the scale, generated from the tokens and measured in both modes, so nothing is a guess.",
|
|
133
127
|
count: `${site.tokens.tokens.length} tokens`,
|
|
134
|
-
preview: (_jsxs("span", { className: "flex items-center gap-4", children: [_jsx("span", { className: "flex -space-x-1.5", children: fills.map((n) => (_jsx("span", { "aria-hidden": true, className: "size-8 rounded-full ring-2 ring-canvas", style: { background: `var(${n})` } }, n))) }), _jsx("span", { "aria-hidden": true, className: cx(display, "text-3xl leading-none tracking-tight"), children: "Aa" })] })),
|
|
135
128
|
});
|
|
136
129
|
if (site.rules.rules.length)
|
|
137
130
|
sections.push({
|
|
138
131
|
title: "Rules",
|
|
139
132
|
href: `${base}/rules`,
|
|
140
133
|
why: "The lines the brand and the interface hold, each written for people, with the check that proves it.",
|
|
141
|
-
count: `${site.rules.rules.length} rules, ${site.rules.rules.filter((r) => r.status === "confirmed").length} confirmed
|
|
142
|
-
preview: (_jsx("span", { className: "flex flex-col gap-1.5 text-sm", children: site.rules.rules
|
|
143
|
-
.filter((r) => r.kind === "brand")
|
|
144
|
-
.slice(0, 2)
|
|
145
|
-
.map((r) => (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(StatusBadge, { status: r.status }), _jsx("span", { className: "truncate", children: r.title })] }, r.id))) })),
|
|
134
|
+
count: `${site.rules.rules.length} rules, ${site.rules.rules.filter((r) => r.status === "confirmed").length} confirmed`
|
|
146
135
|
});
|
|
147
136
|
if (site.shows("components") && count)
|
|
148
137
|
sections.push({
|
|
149
138
|
title: "Components",
|
|
150
139
|
href: `${base}/components/${slugOf(families[0].families[0].head)}`,
|
|
151
140
|
why: "The real components the product is built from, each with when to use it, a live workbench and an accessibility report.",
|
|
152
|
-
count: `${count} components
|
|
153
|
-
preview: (_jsx("span", { className: "flex flex-wrap gap-1.5", children: families.slice(0, 6).map((g) => (_jsxs("span", { className: "inline-flex min-h-7 items-center rounded-md bg-muted px-2 text-xs", children: [g.category, " ", _jsx("span", { className: "ml-1.5 text-muted-foreground tabular-nums", children: g.families.length })] }, g.category))) })),
|
|
141
|
+
count: `${count} components`
|
|
154
142
|
});
|
|
155
143
|
if (site.patterns.patterns.length)
|
|
156
144
|
sections.push({
|
|
157
145
|
title: "Patterns",
|
|
158
146
|
href: `${base}/patterns/${site.patterns.patterns[0].id}`,
|
|
159
147
|
why: "The screens the product keeps making, composed of the components, so a recurring problem is solved once.",
|
|
160
|
-
count: `${site.patterns.patterns.length} patterns
|
|
161
|
-
preview: _jsx("span", { className: "text-sm text-muted-foreground", children: site.patterns.patterns.slice(0, 3).map((p) => p.name).join(" · ") }),
|
|
148
|
+
count: `${site.patterns.patterns.length} patterns`
|
|
162
149
|
});
|
|
163
150
|
if (site.shows("standards") && (config.baselines || config.adherence))
|
|
164
151
|
sections.push({
|
|
165
152
|
title: "Standards",
|
|
166
153
|
href: `${base}/standards`,
|
|
167
154
|
why: "The bar the product is held to, and how closely it follows the system today, reported on every change.",
|
|
168
|
-
count: share == null ? "Baselines agreed" : `${Math.round(share * 100)}% of the product on the system
|
|
169
|
-
preview: share == null ? null : (_jsxs("span", { className: "flex w-full max-w-56 items-center gap-3", children: [_jsx("span", { className: "h-1.5 flex-1 overflow-hidden rounded-full bg-muted", children: _jsx("span", { className: "block h-full rounded-full bg-canvas-foreground", style: { width: `${Math.round(share * 100)}%` } }) }), _jsxs("span", { className: "text-sm tabular-nums", children: [Math.round(share * 100), "%"] })] })),
|
|
155
|
+
count: share == null ? "Baselines agreed" : `${Math.round(share * 100)}% of the product on the system`
|
|
170
156
|
});
|
|
171
157
|
if (site.shows("vocabulary") && site.taxonomy.terms.length)
|
|
172
158
|
sections.push({
|
|
173
159
|
title: "Vocabulary",
|
|
174
160
|
href: `${base}/vocabulary`,
|
|
175
161
|
why: "One name for each thing, and the names people reach for instead, so people and agents mean the same thing.",
|
|
176
|
-
count: `${site.taxonomy.terms.length} terms
|
|
177
|
-
preview: example ? (_jsxs("span", { className: "text-sm", children: ["Say ", _jsx("strong", { className: "font-semibold", children: example.name }), ", ", _jsx("span", { className: "text-muted-foreground line-through decoration-current/40", children: example.wrong?.[0] })] })) : null,
|
|
162
|
+
count: `${site.taxonomy.terms.length} terms`
|
|
178
163
|
});
|
|
179
164
|
const loop = [
|
|
180
165
|
["Written once", "The components, tokens and rules live in the product's own code. Nobody keeps a second copy in a slide deck or a wiki."],
|
|
@@ -183,6 +168,6 @@ function Overview({ site }) {
|
|
|
183
168
|
["Checked", "Each rule names the check that proves it. Stale docs stop a commit; everything else is reported."],
|
|
184
169
|
["Measured", "A report on every change shows how closely the product follows the system. It informs the conversation; it never blocks the work."],
|
|
185
170
|
];
|
|
186
|
-
return (_jsxs(_Fragment, { children: [!config.hero && (_jsx(DocsHeader, { title: site.title, lead: config.lead ?? "The brand and the design system in one place, for the people who make the product and the agents that build it with them.", children: _jsx(PageActions, { basePath: base }) })), _jsx(DocsSection, { title: "What's here, and why", lead: "Each part of the system has one job. Together they mean the product, its people and its agents work from the same source.", children: _jsx("ul", { "data-slot": "overview-sections", className: "grid gap-x-12 md:grid-cols-2", children: sections.map((sec) => (_jsx("li", { className: "border-t border-border", children: _jsxs(Link, { href: sec.href, className: "group flex h-full flex-col gap-4 py-7 focus-visible:outline-2 focus-visible:outline-ring", children: [_jsxs("span", { className: "flex items-baseline justify-between gap-4", children: [_jsx("span", { className: "text-2xl leading-tight tracking-tight underline-offset-[6px] group-hover:underline", children: sec.title }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground tabular-nums", children: sec.count })] }), _jsx("span", { className: "max-w-md text-base leading-relaxed text-muted-foreground", children: sec.why })
|
|
171
|
+
return (_jsxs(_Fragment, { children: [!config.hero && (_jsx(DocsHeader, { title: site.title, lead: config.lead ?? "The brand and the design system in one place, for the people who make the product and the agents that build it with them.", children: _jsx(PageActions, { basePath: base }) })), _jsx(DocsSection, { title: "What's here, and why", lead: "Each part of the system has one job. Together they mean the product, its people and its agents work from the same source.", children: _jsx("ul", { "data-slot": "overview-sections", className: "grid gap-x-12 md:grid-cols-2", children: sections.map((sec) => (_jsx("li", { className: "border-t border-border", children: _jsxs(Link, { href: sec.href, className: "group flex h-full flex-col gap-4 py-7 focus-visible:outline-2 focus-visible:outline-ring", children: [_jsxs("span", { className: "flex items-baseline justify-between gap-4", children: [_jsx("span", { className: "text-2xl leading-tight tracking-tight underline-offset-[6px] group-hover:underline", children: sec.title }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground tabular-nums", children: sec.count })] }), _jsx("span", { className: "max-w-md text-base leading-relaxed text-muted-foreground", children: sec.why })] }) }, sec.title))) }) }), config.overview?.agentic !== false && (_jsxs(DocsSection, { title: "Why this is agentic", lead: config.overview?.agenticLead ??
|
|
187
172
|
"A design system used to be a document people were asked to follow. This one is the source the product, its people and its AI agents all work from, and it checks itself.", children: [_jsx("ol", { "data-slot": "overview-loop", className: "grid gap-x-6 gap-y-8 sm:grid-cols-2 xl:grid-cols-5", children: loop.map(([title, body], i) => (_jsxs("li", { className: "relative flex flex-col gap-3 xl:pr-2", children: [_jsxs("span", { className: "flex items-center gap-3", children: [_jsx("span", { className: "grid size-8 shrink-0 place-items-center rounded-full bg-canvas-foreground text-sm font-medium text-canvas tabular-nums", children: i + 1 }), i < loop.length - 1 && _jsx("span", { "aria-hidden": true, className: "hidden h-px flex-1 bg-border xl:block" })] }), _jsx("span", { className: "text-lg leading-snug tracking-tight", children: title }), _jsx("span", { className: "text-sm leading-relaxed text-muted-foreground", children: body })] }, title))) }), _jsxs("p", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground", children: ["See it the way an agent does:", _jsxs("a", { href: `${base}.md`, className: "inline-flex items-center gap-1 font-medium text-canvas-foreground underline underline-offset-4 hover:decoration-2", children: ["this page as markdown ", _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5" })] })] })] })), _jsx(DocsFooter, { provenance: config.provenance })] }));
|
|
188
173
|
}
|
package/dist/styles.css
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
--tw-rotate-z: initial;
|
|
11
11
|
--tw-skew-x: initial;
|
|
12
12
|
--tw-skew-y: initial;
|
|
13
|
-
--tw-space-x-reverse: 0;
|
|
14
13
|
--tw-divide-y-reverse: 0;
|
|
15
14
|
--tw-border-style: solid;
|
|
16
15
|
--tw-gradient-position: initial;
|
|
@@ -630,10 +629,6 @@
|
|
|
630
629
|
margin-left: .25rem;
|
|
631
630
|
}
|
|
632
631
|
|
|
633
|
-
:where([data-designtools]) .ml-1\.5:not([data-docs-content] *), .ml-1\.5:where([data-designtools]):not([data-docs-content] *) {
|
|
634
|
-
margin-left: .375rem;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
632
|
:where([data-designtools]) .ml-3:not([data-docs-content] *), .ml-3:where([data-designtools]):not([data-docs-content] *) {
|
|
638
633
|
margin-left: .75rem;
|
|
639
634
|
}
|
|
@@ -783,10 +778,6 @@
|
|
|
783
778
|
height: .125rem;
|
|
784
779
|
}
|
|
785
780
|
|
|
786
|
-
:where([data-designtools]) .h-1\.5:not([data-docs-content] *), .h-1\.5:where([data-designtools]):not([data-docs-content] *) {
|
|
787
|
-
height: .375rem;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
781
|
:where([data-designtools]) .h-2:not([data-docs-content] *), .h-2:where([data-designtools]):not([data-docs-content] *) {
|
|
791
782
|
height: .5rem;
|
|
792
783
|
}
|
|
@@ -903,10 +894,6 @@
|
|
|
903
894
|
min-height: 2.25rem;
|
|
904
895
|
}
|
|
905
896
|
|
|
906
|
-
:where([data-designtools]) .min-h-10:not([data-docs-content] *), .min-h-10:where([data-designtools]):not([data-docs-content] *) {
|
|
907
|
-
min-height: 2.5rem;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
897
|
:where([data-designtools]) .min-h-11:not([data-docs-content] *), .min-h-11:where([data-designtools]):not([data-docs-content] *) {
|
|
911
898
|
min-height: 2.75rem;
|
|
912
899
|
}
|
|
@@ -1027,10 +1014,6 @@
|
|
|
1027
1014
|
max-width: 7rem;
|
|
1028
1015
|
}
|
|
1029
1016
|
|
|
1030
|
-
:where([data-designtools]) .max-w-56:not([data-docs-content] *), .max-w-56:where([data-designtools]):not([data-docs-content] *) {
|
|
1031
|
-
max-width: 14rem;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
1017
|
:where([data-designtools]) .max-w-64:not([data-docs-content] *), .max-w-64:where([data-designtools]):not([data-docs-content] *) {
|
|
1035
1018
|
max-width: 16rem;
|
|
1036
1019
|
}
|
|
@@ -1345,12 +1328,6 @@
|
|
|
1345
1328
|
column-gap: 3rem;
|
|
1346
1329
|
}
|
|
1347
1330
|
|
|
1348
|
-
:where([data-designtools]) :where(.-space-x-1\.5 > :not(:last-child)):not([data-docs-content] *), :where(.-space-x-1\.5 > :not(:last-child)):where([data-designtools]):not([data-docs-content] *) {
|
|
1349
|
-
--tw-space-x-reverse: 0;
|
|
1350
|
-
margin-inline-start: calc(calc(.25rem * -1.5) * var(--tw-space-x-reverse));
|
|
1351
|
-
margin-inline-end: calc(calc(.25rem * -1.5) * calc(1 - var(--tw-space-x-reverse)));
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
1331
|
:where([data-designtools]) .gap-y-1:not([data-docs-content] *), .gap-y-1:where([data-designtools]):not([data-docs-content] *) {
|
|
1355
1332
|
row-gap: .25rem;
|
|
1356
1333
|
}
|
|
@@ -2423,11 +2400,6 @@
|
|
|
2423
2400
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2424
2401
|
}
|
|
2425
2402
|
|
|
2426
|
-
:where([data-designtools]) .ring-2:not([data-docs-content] *), .ring-2:where([data-designtools]):not([data-docs-content] *) {
|
|
2427
|
-
--tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
2428
|
-
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
2403
|
:where([data-designtools]) .ring-\(--border\):not([data-docs-content] *), .ring-\(--border\):where([data-designtools]):not([data-docs-content] *) {
|
|
2432
2404
|
--tw-ring-color: var(--border);
|
|
2433
2405
|
}
|
|
@@ -2446,10 +2418,6 @@
|
|
|
2446
2418
|
--tw-ring-color: var(--border, #e2e4e8);
|
|
2447
2419
|
}
|
|
2448
2420
|
|
|
2449
|
-
:where([data-designtools]) .ring-canvas:not([data-docs-content] *), .ring-canvas:where([data-designtools]):not([data-docs-content] *) {
|
|
2450
|
-
--tw-ring-color: var(--canvas, #fff);
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
2421
|
:where([data-designtools]) .ring-current\/15:not([data-docs-content] *), .ring-current\/15:where([data-designtools]):not([data-docs-content] *) {
|
|
2454
2422
|
--tw-ring-color: currentcolor;
|
|
2455
2423
|
}
|
|
@@ -3312,10 +3280,6 @@
|
|
|
3312
3280
|
font-size: .875em;
|
|
3313
3281
|
}
|
|
3314
3282
|
|
|
3315
|
-
:where([data-designtools]) .\[\&_img\]\:h-8 img:not([data-docs-content] *), .\[\&_img\]\:h-8 img:where([data-designtools]):not([data-docs-content] *) {
|
|
3316
|
-
height: 2rem;
|
|
3317
|
-
}
|
|
3318
|
-
|
|
3319
3283
|
:where([data-designtools]) .\[\&_pre\]\:break-words pre:not([data-docs-content] *), .\[\&_pre\]\:break-words pre:where([data-designtools]):not([data-docs-content] *) {
|
|
3320
3284
|
overflow-wrap: break-word;
|
|
3321
3285
|
}
|
|
@@ -3324,10 +3288,6 @@
|
|
|
3324
3288
|
white-space: pre-wrap;
|
|
3325
3289
|
}
|
|
3326
3290
|
|
|
3327
|
-
:where([data-designtools]) .\[\&_svg\]\:h-8 svg:not([data-docs-content] *), .\[\&_svg\]\:h-8 svg:where([data-designtools]):not([data-docs-content] *) {
|
|
3328
|
-
height: 2rem;
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
3291
|
:where([data-designtools]) .\[\&\:\:-webkit-details-marker\]\:hidden:not([data-docs-content] *)::-webkit-details-marker, .\[\&\:\:-webkit-details-marker\]\:hidden:where([data-designtools]):not([data-docs-content] *)::-webkit-details-marker {
|
|
3332
3292
|
display: none;
|
|
3333
3293
|
}
|
|
@@ -3380,12 +3340,6 @@
|
|
|
3380
3340
|
inherits: false
|
|
3381
3341
|
}
|
|
3382
3342
|
|
|
3383
|
-
@property --tw-space-x-reverse {
|
|
3384
|
-
syntax: "*";
|
|
3385
|
-
inherits: false;
|
|
3386
|
-
initial-value: 0;
|
|
3387
|
-
}
|
|
3388
|
-
|
|
3389
3343
|
@property --tw-divide-y-reverse {
|
|
3390
3344
|
syntax: "*";
|
|
3391
3345
|
inherits: false;
|
package/dist/workbench.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { handlerNames } from "./lib/handlers.js";
|
|
3
4
|
import { Live } from "./lib/live.js";
|
|
4
5
|
import { Tabs } from "@base-ui/react/tabs";
|
|
5
6
|
import { Toggle } from "@base-ui/react/toggle";
|
|
@@ -75,7 +76,7 @@ export function Workbench({ entry, props: base = {}, children: initialText = ent
|
|
|
75
76
|
const own = Object.entries(entry.props).filter(([name, p]) => !axisNames.has(name) && !PLUMBING.has(name) && p.deprecated === undefined);
|
|
76
77
|
// disabled is a pinned state ("Off"), not a prop control
|
|
77
78
|
const settable = own.filter(([name, p]) => !isFunction(name, p) && !p.from && name !== "disabled");
|
|
78
|
-
const handlers =
|
|
79
|
+
const handlers = handlerNames(Object.fromEntries(own));
|
|
79
80
|
const canDisable = "disabled" in entry.props;
|
|
80
81
|
const [variants, setVariants] = useState({});
|
|
81
82
|
const [values, setValues] = useState({});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designtools/docs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PropEntry } from "./manifest-types";
|
|
2
|
+
|
|
3
|
+
const isFunction = (name: string, p: PropEntry) => /=>|^\(|Function|Handler/.test(p.type) || /^on[A-Z]/.test(name);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The props the workbench logs to its Actions tab: event handlers only. A function prop that is not a
|
|
7
|
+
* handler (getAriaValueText, a formatter, a render function) is called while the component renders, and
|
|
8
|
+
* logging it re-renders the component, which calls it again, for ever.
|
|
9
|
+
*/
|
|
10
|
+
export function handlerNames(props: Record<string, PropEntry>): string[] {
|
|
11
|
+
return Object.entries(props)
|
|
12
|
+
.filter(([name, p]) => isFunction(name, p) && /^on[A-Z]/.test(name))
|
|
13
|
+
.map(([name]) => name);
|
|
14
|
+
}
|
package/src/next/pages.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { ArrowRight, ArrowUpRight } from "lucide-react";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { cx, display } from "../lib/cx";
|
|
5
3
|
import { AdherenceSummary } from "../adherence-summary";
|
|
6
4
|
import { Anatomy } from "../anatomy";
|
|
7
5
|
import { Examples } from "../examples";
|
|
@@ -307,112 +305,57 @@ function Overview({ site }: { site: Site }) {
|
|
|
307
305
|
const count = families.reduce((n, g) => n + g.families.length, 0);
|
|
308
306
|
const brandPages = site.editorial.filter((p) => (p.section ?? "brand") === "brand");
|
|
309
307
|
const brandFirst = brandPages[0];
|
|
310
|
-
const products = site.taxonomy.terms.filter((t) => t.kind === "product" && t.wrong?.length);
|
|
311
|
-
const example = products[0] ?? site.taxonomy.terms.find((t) => t.wrong?.length);
|
|
312
|
-
const fills = ["--primary", "--neutral", "--success", "--warning", "--destructive"].filter((n) =>
|
|
313
|
-
site.tokens.tokens.some((t) => t.name === n),
|
|
314
|
-
);
|
|
315
308
|
const share = config.adherence?.summary.share;
|
|
316
309
|
|
|
317
|
-
// each section of the system, why it exists
|
|
318
|
-
const sections: { title: string; href: string; why: string; count: string
|
|
310
|
+
// each section of the system, why it exists and how much is in it
|
|
311
|
+
const sections: { title: string; href: string; why: string; count: string }[] = [];
|
|
319
312
|
if (brandFirst || site.shows("logo"))
|
|
320
313
|
sections.push({
|
|
321
314
|
title: "Brand",
|
|
322
315
|
href: `${base}/${brandFirst?.slug ?? "brand/logo"}`,
|
|
323
316
|
why: "What the brand believes, how it sounds and how it looks, so every screen and sentence comes from the same place.",
|
|
324
|
-
count: `${brandPages.length + (site.assets.assets.some((a) => a.kind === "logo") ? 1 : 0)} pages
|
|
325
|
-
preview: config.logo ? <span className="flex h-10 items-center [&_img]:h-8 [&_svg]:h-8">{config.logo}</span> : null,
|
|
317
|
+
count: `${brandPages.length + (site.assets.assets.some((a) => a.kind === "logo") ? 1 : 0)} pages`
|
|
326
318
|
});
|
|
327
319
|
sections.push({
|
|
328
320
|
title: "Foundations",
|
|
329
321
|
href: `${base}/colour`,
|
|
330
322
|
why: "Colour, type and the scale, generated from the tokens and measured in both modes, so nothing is a guess.",
|
|
331
323
|
count: `${site.tokens.tokens.length} tokens`,
|
|
332
|
-
preview: (
|
|
333
|
-
<span className="flex items-center gap-4">
|
|
334
|
-
<span className="flex -space-x-1.5">
|
|
335
|
-
{fills.map((n) => (
|
|
336
|
-
<span key={n} aria-hidden className="size-8 rounded-full ring-2 ring-canvas" style={{ background: `var(${n})` }} />
|
|
337
|
-
))}
|
|
338
|
-
</span>
|
|
339
|
-
<span aria-hidden className={cx(display, "text-3xl leading-none tracking-tight")}>
|
|
340
|
-
Aa
|
|
341
|
-
</span>
|
|
342
|
-
</span>
|
|
343
|
-
),
|
|
344
324
|
});
|
|
345
325
|
if (site.rules.rules.length)
|
|
346
326
|
sections.push({
|
|
347
327
|
title: "Rules",
|
|
348
328
|
href: `${base}/rules`,
|
|
349
329
|
why: "The lines the brand and the interface hold, each written for people, with the check that proves it.",
|
|
350
|
-
count: `${site.rules.rules.length} rules, ${site.rules.rules.filter((r) => r.status === "confirmed").length} confirmed
|
|
351
|
-
preview: (
|
|
352
|
-
<span className="flex flex-col gap-1.5 text-sm">
|
|
353
|
-
{site.rules.rules
|
|
354
|
-
.filter((r) => r.kind === "brand")
|
|
355
|
-
.slice(0, 2)
|
|
356
|
-
.map((r) => (
|
|
357
|
-
<span key={r.id} className="flex items-center gap-2">
|
|
358
|
-
<StatusBadge status={r.status} />
|
|
359
|
-
<span className="truncate">{r.title}</span>
|
|
360
|
-
</span>
|
|
361
|
-
))}
|
|
362
|
-
</span>
|
|
363
|
-
),
|
|
330
|
+
count: `${site.rules.rules.length} rules, ${site.rules.rules.filter((r) => r.status === "confirmed").length} confirmed`
|
|
364
331
|
});
|
|
365
332
|
if (site.shows("components") && count)
|
|
366
333
|
sections.push({
|
|
367
334
|
title: "Components",
|
|
368
335
|
href: `${base}/components/${slugOf(families[0].families[0].head)}`,
|
|
369
336
|
why: "The real components the product is built from, each with when to use it, a live workbench and an accessibility report.",
|
|
370
|
-
count: `${count} components
|
|
371
|
-
preview: (
|
|
372
|
-
<span className="flex flex-wrap gap-1.5">
|
|
373
|
-
{families.slice(0, 6).map((g) => (
|
|
374
|
-
<span key={g.category} className="inline-flex min-h-7 items-center rounded-md bg-muted px-2 text-xs">
|
|
375
|
-
{g.category} <span className="ml-1.5 text-muted-foreground tabular-nums">{g.families.length}</span>
|
|
376
|
-
</span>
|
|
377
|
-
))}
|
|
378
|
-
</span>
|
|
379
|
-
),
|
|
337
|
+
count: `${count} components`
|
|
380
338
|
});
|
|
381
339
|
if (site.patterns.patterns.length)
|
|
382
340
|
sections.push({
|
|
383
341
|
title: "Patterns",
|
|
384
342
|
href: `${base}/patterns/${site.patterns.patterns[0].id}`,
|
|
385
343
|
why: "The screens the product keeps making, composed of the components, so a recurring problem is solved once.",
|
|
386
|
-
count: `${site.patterns.patterns.length} patterns
|
|
387
|
-
preview: <span className="text-sm text-muted-foreground">{site.patterns.patterns.slice(0, 3).map((p) => p.name).join(" · ")}</span>,
|
|
344
|
+
count: `${site.patterns.patterns.length} patterns`
|
|
388
345
|
});
|
|
389
346
|
if (site.shows("standards") && (config.baselines || config.adherence))
|
|
390
347
|
sections.push({
|
|
391
348
|
title: "Standards",
|
|
392
349
|
href: `${base}/standards`,
|
|
393
350
|
why: "The bar the product is held to, and how closely it follows the system today, reported on every change.",
|
|
394
|
-
count: share == null ? "Baselines agreed" : `${Math.round(share * 100)}% of the product on the system
|
|
395
|
-
preview:
|
|
396
|
-
share == null ? null : (
|
|
397
|
-
<span className="flex w-full max-w-56 items-center gap-3">
|
|
398
|
-
<span className="h-1.5 flex-1 overflow-hidden rounded-full bg-muted">
|
|
399
|
-
<span className="block h-full rounded-full bg-canvas-foreground" style={{ width: `${Math.round(share * 100)}%` }} />
|
|
400
|
-
</span>
|
|
401
|
-
<span className="text-sm tabular-nums">{Math.round(share * 100)}%</span>
|
|
402
|
-
</span>
|
|
403
|
-
),
|
|
351
|
+
count: share == null ? "Baselines agreed" : `${Math.round(share * 100)}% of the product on the system`
|
|
404
352
|
});
|
|
405
353
|
if (site.shows("vocabulary") && site.taxonomy.terms.length)
|
|
406
354
|
sections.push({
|
|
407
355
|
title: "Vocabulary",
|
|
408
356
|
href: `${base}/vocabulary`,
|
|
409
357
|
why: "One name for each thing, and the names people reach for instead, so people and agents mean the same thing.",
|
|
410
|
-
count: `${site.taxonomy.terms.length} terms
|
|
411
|
-
preview: example ? (
|
|
412
|
-
<span className="text-sm">
|
|
413
|
-
Say <strong className="font-semibold">{example.name}</strong>, <span className="text-muted-foreground line-through decoration-current/40">{example.wrong?.[0]}</span>
|
|
414
|
-
</span>
|
|
415
|
-
) : null,
|
|
358
|
+
count: `${site.taxonomy.terms.length} terms`
|
|
416
359
|
});
|
|
417
360
|
|
|
418
361
|
const loop = [
|
|
@@ -441,7 +384,6 @@ function Overview({ site }: { site: Site }) {
|
|
|
441
384
|
<span className="shrink-0 text-xs text-muted-foreground tabular-nums">{sec.count}</span>
|
|
442
385
|
</span>
|
|
443
386
|
<span className="max-w-md text-base leading-relaxed text-muted-foreground">{sec.why}</span>
|
|
444
|
-
{sec.preview && <span className="mt-auto flex min-h-10 items-center pt-2">{sec.preview}</span>}
|
|
445
387
|
</Link>
|
|
446
388
|
</li>
|
|
447
389
|
))}
|
package/src/workbench.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { handlerNames } from "./lib/handlers";
|
|
3
4
|
import { Live } from "./lib/live";
|
|
4
5
|
import { Tabs } from "@base-ui/react/tabs";
|
|
5
6
|
import { Toggle } from "@base-ui/react/toggle";
|
|
@@ -133,7 +134,7 @@ export function Workbench({
|
|
|
133
134
|
const own = Object.entries(entry.props).filter(([name, p]) => !axisNames.has(name) && !PLUMBING.has(name) && p.deprecated === undefined);
|
|
134
135
|
// disabled is a pinned state ("Off"), not a prop control
|
|
135
136
|
const settable = own.filter(([name, p]) => !isFunction(name, p) && !p.from && name !== "disabled");
|
|
136
|
-
const handlers =
|
|
137
|
+
const handlers = handlerNames(Object.fromEntries(own));
|
|
137
138
|
const canDisable = "disabled" in entry.props;
|
|
138
139
|
|
|
139
140
|
const [variants, setVariants] = useState<Record<string, string>>({});
|