@designtools/docs 0.2.2 → 0.3.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 +20 -0
- package/README.md +27 -3
- package/dist/adherence-summary.js +2 -2
- package/dist/anatomy.d.ts +6 -2
- package/dist/anatomy.js +3 -2
- package/dist/glossary.d.ts +1 -1
- package/dist/glossary.js +3 -8
- package/dist/lib/adherence-types.d.ts +2 -0
- package/dist/lib/colour.d.ts +16 -0
- package/dist/lib/colour.js +99 -0
- package/dist/lib/contrast.js +1 -7
- package/dist/lib/design-md.d.ts +27 -0
- package/dist/lib/design-md.js +176 -0
- package/dist/lib/markdown.js +1 -0
- package/dist/lib/terms.d.ts +1 -1
- package/dist/lib/terms.js +1 -1
- package/dist/lib/wrap.d.ts +5 -0
- package/dist/lib/wrap.js +5 -0
- package/dist/logo-usage.js +3 -3
- package/dist/next/config.d.ts +9 -0
- package/dist/next/markdown.d.ts +1 -1
- package/dist/next/markdown.js +15 -1
- package/dist/next/pages.js +5 -12
- package/dist/next/proxy.js +3 -2
- package/dist/page-actions.d.ts +2 -1
- package/dist/page-actions.js +4 -3
- package/dist/playground.js +1 -1
- package/dist/shell.js +6 -4
- package/dist/standards.js +1 -1
- package/dist/styles.css +65 -43
- package/dist/type-ramp.js +1 -1
- package/dist/variant-matrix.d.ts +6 -2
- package/dist/variant-matrix.js +3 -2
- package/dist/workbench.js +3 -2
- package/package.json +1 -1
- package/src/adherence-summary.tsx +7 -2
- package/src/anatomy.tsx +6 -4
- package/src/glossary.tsx +10 -22
- package/src/lib/adherence-types.ts +2 -0
- package/src/lib/colour.ts +104 -0
- package/src/lib/contrast.ts +2 -8
- package/src/lib/design-md.ts +193 -0
- package/src/lib/markdown.ts +1 -0
- package/src/lib/terms.tsx +1 -1
- package/src/lib/wrap.tsx +6 -0
- package/src/logo-usage.tsx +20 -12
- package/src/next/config.ts +7 -0
- package/src/next/markdown.ts +16 -1
- package/src/next/pages.tsx +21 -20
- package/src/next/proxy.ts +3 -2
- package/src/page-actions.tsx +10 -2
- package/src/playground.tsx +2 -2
- package/src/shell.tsx +6 -4
- package/src/standards.tsx +2 -2
- package/src/styles.css +9 -8
- package/src/type-ramp.tsx +3 -6
- package/src/variant-matrix.tsx +11 -4
- package/src/workbench.tsx +2 -1
package/src/logo-usage.tsx
CHANGED
|
@@ -24,8 +24,7 @@ export function LogoUsage({ assets, rules = [], rulesHref }: { assets: AssetEntr
|
|
|
24
24
|
const brand = rules.filter((r) => r.kind === "brand");
|
|
25
25
|
const general = brand.filter((r) => (r.appliesTo ?? []).some((x) => ["logo", "logos"].includes(x.toLowerCase())));
|
|
26
26
|
return (
|
|
27
|
-
<div data-slot="logo-usage" className="flex flex-col gap-16">
|
|
28
|
-
{general.length > 0 && <RuleList rules={general} rulesHref={rulesHref} />}
|
|
27
|
+
<div data-slot="logo-usage" className="flex flex-col gap-16 lg:gap-24">
|
|
29
28
|
{logos.map((logo) => {
|
|
30
29
|
const src = lookup.assetUrl(logo);
|
|
31
30
|
const names = new Set([logo.name.toLowerCase(), logo.file.toLowerCase()]);
|
|
@@ -111,27 +110,36 @@ export function LogoUsage({ assets, rules = [], rulesHref }: { assets: AssetEntr
|
|
|
111
110
|
</section>
|
|
112
111
|
);
|
|
113
112
|
})}
|
|
113
|
+
{general.length > 0 && (
|
|
114
|
+
<section data-slot="logo-rules" aria-labelledby="logo-rules" className="flex flex-col gap-5">
|
|
115
|
+
<header className="flex flex-col gap-1.5">
|
|
116
|
+
<h3 id="logo-rules" className="text-3xl tracking-tight">
|
|
117
|
+
Rules for the logo
|
|
118
|
+
</h3>
|
|
119
|
+
<p className="max-w-2xl text-base text-muted-foreground">What holds for every logo above, each with how it is checked.</p>
|
|
120
|
+
</header>
|
|
121
|
+
<RuleList rules={general} rulesHref={rulesHref} />
|
|
122
|
+
</section>
|
|
123
|
+
)}
|
|
114
124
|
</div>
|
|
115
125
|
);
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
function RuleList({ rules, rulesHref }: { rules: RuleEntry[]; rulesHref?: string }) {
|
|
119
129
|
return (
|
|
120
|
-
<ul data-slot="logo-rules" className="
|
|
130
|
+
<ul data-slot="logo-rules" className="flex flex-col divide-y divide-border border-y border-border">
|
|
121
131
|
{rules.map((r) => (
|
|
122
|
-
<li key={r.id}
|
|
132
|
+
<li key={r.id}>
|
|
123
133
|
<a
|
|
124
134
|
href={rulesHref ? `${rulesHref}#${r.id}` : `#${r.id}`}
|
|
125
|
-
className="group
|
|
135
|
+
className="group grid items-baseline gap-x-8 gap-y-1.5 py-5 focus-visible:outline-2 focus-visible:outline-ring md:grid-cols-[14rem_minmax(0,1fr)_auto]"
|
|
126
136
|
>
|
|
127
|
-
<span className="flex items-center
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
{r.title}
|
|
131
|
-
</span>
|
|
132
|
-
<StatusBadge status={r.status} />
|
|
137
|
+
<span className="flex items-center gap-2 text-base font-medium underline-offset-4 group-hover:underline">
|
|
138
|
+
<Scale aria-hidden className="size-(--dt-size-xs) shrink-0 text-muted-foreground" />
|
|
139
|
+
{r.title}
|
|
133
140
|
</span>
|
|
134
|
-
<span className="text-muted-foreground">{inline(r.statement)}</span>
|
|
141
|
+
<span className="text-sm leading-relaxed text-muted-foreground">{inline(r.statement)}</span>
|
|
142
|
+
<StatusBadge status={r.status} />
|
|
135
143
|
</a>
|
|
136
144
|
</li>
|
|
137
145
|
))}
|
package/src/next/config.ts
CHANGED
|
@@ -85,6 +85,13 @@ export interface DocsConfig {
|
|
|
85
85
|
provenance?: string;
|
|
86
86
|
/** `designtools-adherence --json`, for the standards page. */
|
|
87
87
|
adherence?: AdherenceReport;
|
|
88
|
+
/**
|
|
89
|
+
* design.md, served at `<base>/design.md`: the brand in one file for anything made outside the repo's
|
|
90
|
+
* components (a page built elsewhere, a slide, a document). `body` is its hand-written half, in markdown
|
|
91
|
+
* `##` sections: what comes first when guidance conflicts, how to work, the reflexes to avoid, a review
|
|
92
|
+
* list. The values after it are generated. Without a body the file still serves, with the values alone.
|
|
93
|
+
*/
|
|
94
|
+
designMd?: { body: string };
|
|
88
95
|
/** Pages people write, in the order they appear. */
|
|
89
96
|
editorial?: EditorialPage[];
|
|
90
97
|
/** Built-in pages to leave out. */
|
package/src/next/markdown.ts
CHANGED
|
@@ -15,12 +15,13 @@ import {
|
|
|
15
15
|
systemMarkdown,
|
|
16
16
|
typeMarkdown,
|
|
17
17
|
} from "../lib/markdown";
|
|
18
|
+
import { designMarkdown } from "../lib/design-md";
|
|
18
19
|
import { navOf, resolve, type Site } from "./site";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* The markdown form of a docs page, generated from the manifest rather than
|
|
22
23
|
* converted from HTML, or nothing when there is no such page. `context` is the
|
|
23
|
-
* whole system in one file.
|
|
24
|
+
* whole system in one file; `design` is design.md, the brand for work outside the repo.
|
|
24
25
|
*/
|
|
25
26
|
export function markdownFor(site: Site, path: string): string | undefined {
|
|
26
27
|
const { base, title, components, tokens, rules, patterns, taxonomy, assets, config } = site;
|
|
@@ -28,6 +29,19 @@ export function markdownFor(site: Site, path: string): string | undefined {
|
|
|
28
29
|
if (path === "context")
|
|
29
30
|
return systemMarkdown({ components, tokens, rules: rules.rules, patterns: patterns.patterns, terms: taxonomy.terms }, title, config.provenance ?? "");
|
|
30
31
|
|
|
32
|
+
if (path === "design")
|
|
33
|
+
return designMarkdown({
|
|
34
|
+
title,
|
|
35
|
+
base,
|
|
36
|
+
provenance: config.provenance,
|
|
37
|
+
components,
|
|
38
|
+
tokens,
|
|
39
|
+
rules: rules.rules,
|
|
40
|
+
assets: assets.assets,
|
|
41
|
+
terms: taxonomy.terms,
|
|
42
|
+
body: config.designMd?.body,
|
|
43
|
+
});
|
|
44
|
+
|
|
31
45
|
const page = resolve(site, path ? path.split("/") : []);
|
|
32
46
|
if (!page) return undefined;
|
|
33
47
|
switch (page.kind) {
|
|
@@ -51,6 +65,7 @@ export function markdownFor(site: Site, path: string): string | undefined {
|
|
|
51
65
|
),
|
|
52
66
|
...(nav.standards.length ? [`- Standards: ${links(nav.standards)}`] : []),
|
|
53
67
|
`- Everything in [one file](${base}/context.md)`,
|
|
68
|
+
`- The brand for work outside the repo, a page, a slide or a document: [design.md](${base}/design.md)`,
|
|
54
69
|
"",
|
|
55
70
|
manifestLinks(`${base}/manifest`),
|
|
56
71
|
].join("\n"),
|
package/src/next/pages.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArrowRight, ArrowUpRight } from "lucide-react";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
+
import { cx, display } from "../lib/cx";
|
|
3
4
|
import { AdherenceSummary } from "../adherence-summary";
|
|
4
5
|
import { Anatomy } from "../anatomy";
|
|
5
6
|
import { Examples } from "../examples";
|
|
@@ -269,7 +270,7 @@ function ComponentPage({ site, blocks: B, entry }: { site: Site; blocks: Blocks;
|
|
|
269
270
|
</B.Workbench>
|
|
270
271
|
|
|
271
272
|
<DocsSection title="Anatomy">
|
|
272
|
-
<B.Anatomy entry={entry} />
|
|
273
|
+
<B.Anatomy entry={entry} wrap={setup.wrap} />
|
|
273
274
|
</DocsSection>
|
|
274
275
|
|
|
275
276
|
<DocsSection title="Examples" lead="The usual use first, then each do beside its don't. Every one is the real component.">
|
|
@@ -281,7 +282,7 @@ function ComponentPage({ site, blocks: B, entry }: { site: Site; blocks: Blocks;
|
|
|
281
282
|
title="Variants"
|
|
282
283
|
lead={`Every combination, from the ${entry.variants.from === "props" ? "props' types" : `${entry.variants.from}() config`}, then each state.`}
|
|
283
284
|
>
|
|
284
|
-
<B.VariantMatrix entry={entry} props={setup.props}>
|
|
285
|
+
<B.VariantMatrix entry={entry} props={setup.props} wrap={setup.wrap}>
|
|
285
286
|
{setup.children === "" ? undefined : (setup.content ?? entry.name)}
|
|
286
287
|
</B.VariantMatrix>
|
|
287
288
|
</DocsSection>
|
|
@@ -305,57 +306,49 @@ function Overview({ site }: { site: Site }) {
|
|
|
305
306
|
const count = families.reduce((n, g) => n + g.families.length, 0);
|
|
306
307
|
const brandPages = site.editorial.filter((p) => (p.section ?? "brand") === "brand");
|
|
307
308
|
const brandFirst = brandPages[0];
|
|
308
|
-
const share = config.adherence?.summary.share;
|
|
309
309
|
|
|
310
|
-
// each section of the system, why it exists
|
|
311
|
-
const sections: { title: string; href: string; why: string
|
|
310
|
+
// each section of the system, and why it exists
|
|
311
|
+
const sections: { title: string; href: string; why: string }[] = [];
|
|
312
312
|
if (brandFirst || site.shows("logo"))
|
|
313
313
|
sections.push({
|
|
314
314
|
title: "Brand",
|
|
315
315
|
href: `${base}/${brandFirst?.slug ?? "brand/logo"}`,
|
|
316
316
|
why: "What the brand believes, how it sounds and how it looks, so every screen and sentence comes from the same place.",
|
|
317
|
-
count: `${brandPages.length + (site.assets.assets.some((a) => a.kind === "logo") ? 1 : 0)} pages`
|
|
318
317
|
});
|
|
319
318
|
sections.push({
|
|
320
319
|
title: "Foundations",
|
|
321
320
|
href: `${base}/colour`,
|
|
322
321
|
why: "Colour, type and the scale, generated from the tokens and measured in both modes, so nothing is a guess.",
|
|
323
|
-
count: `${site.tokens.tokens.length} tokens`,
|
|
324
322
|
});
|
|
325
323
|
if (site.rules.rules.length)
|
|
326
324
|
sections.push({
|
|
327
325
|
title: "Rules",
|
|
328
326
|
href: `${base}/rules`,
|
|
329
327
|
why: "The lines the brand and the interface hold, each written for people, with the check that proves it.",
|
|
330
|
-
count: `${site.rules.rules.length} rules, ${site.rules.rules.filter((r) => r.status === "confirmed").length} confirmed`
|
|
331
328
|
});
|
|
332
329
|
if (site.shows("components") && count)
|
|
333
330
|
sections.push({
|
|
334
331
|
title: "Components",
|
|
335
332
|
href: `${base}/components/${slugOf(families[0].families[0].head)}`,
|
|
336
333
|
why: "The real components the product is built from, each with when to use it, a live workbench and an accessibility report.",
|
|
337
|
-
count: `${count} components`
|
|
338
334
|
});
|
|
339
335
|
if (site.patterns.patterns.length)
|
|
340
336
|
sections.push({
|
|
341
337
|
title: "Patterns",
|
|
342
338
|
href: `${base}/patterns/${site.patterns.patterns[0].id}`,
|
|
343
339
|
why: "The screens the product keeps making, composed of the components, so a recurring problem is solved once.",
|
|
344
|
-
count: `${site.patterns.patterns.length} patterns`
|
|
345
340
|
});
|
|
346
341
|
if (site.shows("standards") && (config.baselines || config.adherence))
|
|
347
342
|
sections.push({
|
|
348
343
|
title: "Standards",
|
|
349
344
|
href: `${base}/standards`,
|
|
350
345
|
why: "The bar the product is held to, and how closely it follows the system today, reported on every change.",
|
|
351
|
-
count: share == null ? "Baselines agreed" : `${Math.round(share * 100)}% of the product on the system`
|
|
352
346
|
});
|
|
353
347
|
if (site.shows("vocabulary") && site.taxonomy.terms.length)
|
|
354
348
|
sections.push({
|
|
355
349
|
title: "Vocabulary",
|
|
356
350
|
href: `${base}/vocabulary`,
|
|
357
351
|
why: "One name for each thing, and the names people reach for instead, so people and agents mean the same thing.",
|
|
358
|
-
count: `${site.taxonomy.terms.length} terms`
|
|
359
352
|
});
|
|
360
353
|
|
|
361
354
|
const loop = [
|
|
@@ -375,15 +368,19 @@ function Overview({ site }: { site: Site }) {
|
|
|
375
368
|
)}
|
|
376
369
|
|
|
377
370
|
<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.">
|
|
378
|
-
<ul data-slot="overview-sections" className="
|
|
371
|
+
<ul data-slot="overview-sections" className="flex flex-col border-b border-border">
|
|
379
372
|
{sections.map((sec) => (
|
|
380
373
|
<li key={sec.title} className="border-t border-border">
|
|
381
|
-
<Link
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
</span>
|
|
386
|
-
<span className="max-w-
|
|
374
|
+
<Link
|
|
375
|
+
href={sec.href}
|
|
376
|
+
className="group grid items-baseline gap-x-10 gap-y-2 py-6 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring md:grid-cols-[13rem_minmax(0,1fr)_auto]"
|
|
377
|
+
>
|
|
378
|
+
<span className={cx(display, "text-2xl leading-snug tracking-tight underline-offset-4 group-hover:underline")}>{sec.title}</span>
|
|
379
|
+
<span className="max-w-2xl text-base leading-relaxed text-muted-foreground">{sec.why}</span>
|
|
380
|
+
<ArrowUpRight
|
|
381
|
+
aria-hidden
|
|
382
|
+
className="hidden size-(--dt-size-sm) translate-y-0.5 self-start text-muted-foreground transition duration-(--dt-duration-base) ease-(--dt-ease-out) group-hover:translate-x-1 group-hover:-translate-y-0.5 group-hover:text-canvas-foreground md:mt-1.5 md:block"
|
|
383
|
+
/>
|
|
387
384
|
</Link>
|
|
388
385
|
</li>
|
|
389
386
|
))}
|
|
@@ -412,9 +409,13 @@ function Overview({ site }: { site: Site }) {
|
|
|
412
409
|
</ol>
|
|
413
410
|
<p className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground">
|
|
414
411
|
See it the way an agent does:
|
|
415
|
-
<a href={`${base}.md`} className="inline-flex items-center gap-1 font-medium text-canvas-foreground underline underline-offset-4 hover:decoration-2">
|
|
412
|
+
<a href={base ? `${base}.md` : "/.md"} className="inline-flex items-center gap-1 font-medium text-canvas-foreground underline underline-offset-4 hover:decoration-2">
|
|
416
413
|
this page as markdown <ArrowUpRight aria-hidden className="size-3.5" />
|
|
417
414
|
</a>
|
|
415
|
+
or, for a page, slide or document made outside the repo,
|
|
416
|
+
<a href={`${base}/design.md`} className="inline-flex items-center gap-1 font-medium text-canvas-foreground underline underline-offset-4 hover:decoration-2">
|
|
417
|
+
design.md <ArrowUpRight aria-hidden className="size-3.5" />
|
|
418
|
+
</a>
|
|
418
419
|
</p>
|
|
419
420
|
</DocsSection>
|
|
420
421
|
)}
|
package/src/next/proxy.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function docsProxy({ basePath = "/design/system", production }: { basePat
|
|
|
27
27
|
// the markdown responses keep theirs.
|
|
28
28
|
const res = NextResponse.next();
|
|
29
29
|
res.headers.set("vary", "Accept");
|
|
30
|
-
res.headers.set("link", `<${page}.md>; rel="alternate"; type="text/markdown"`);
|
|
30
|
+
res.headers.set("link", `<${page ? `${page}.md` : "/.md"}>; rel="alternate"; type="text/markdown"`);
|
|
31
31
|
res.headers.set("x-robots-tag", "noindex");
|
|
32
32
|
return res;
|
|
33
33
|
};
|
|
@@ -36,7 +36,8 @@ export function docsProxy({ basePath = "/design/system", production }: { basePat
|
|
|
36
36
|
/** The proxy's matcher for a base path. Next reads `config.matcher` statically, so write the result out where it must be a literal. */
|
|
37
37
|
export function docsMatcher(basePath = "/design/system"): string[] {
|
|
38
38
|
const base = basePath.replace(/\/$/, "");
|
|
39
|
-
|
|
39
|
+
// served at the site root, every path is a docs path
|
|
40
|
+
return base ? [base, `${base}.md`, `${base}/:path*`] : ["/", "/:path*"];
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/** text/markdown ranked above text/html in the Accept header. */
|
package/src/page-actions.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Menu } from "@base-ui/react/menu";
|
|
4
|
-
import { Braces, Check, ChevronDown, Copy, FileText, Files } from "lucide-react";
|
|
4
|
+
import { Braces, Check, ChevronDown, Copy, FileText, Files, Palette } from "lucide-react";
|
|
5
5
|
import { usePathname } from "next/navigation";
|
|
6
6
|
import { useState } from "react";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* What a reader can take from a page, as one split button: copy the page as
|
|
10
10
|
* markdown, the same markdown any agent gets at `.md`, with a menu beside it to
|
|
11
|
-
* view that markdown, copy the whole system as one file,
|
|
11
|
+
* view that markdown, copy the whole system as one file, copy design.md (the
|
|
12
|
+
* brand for work outside the repo), or open the manifest.
|
|
12
13
|
* Put it in a `DocsHeader`.
|
|
13
14
|
*/
|
|
14
15
|
export function PageActions({ basePath = "/design/system" }: { basePath?: string }) {
|
|
@@ -73,6 +74,13 @@ export function PageActions({ basePath = "/design/system" }: { basePath?: string
|
|
|
73
74
|
<span className="text-xs text-muted-foreground">Every page in one markdown file</span>
|
|
74
75
|
</span>
|
|
75
76
|
</Menu.Item>
|
|
77
|
+
<Menu.Item className={item} onClick={() => copyFrom(`${basePath}/design.md`, "design.md copied")}>
|
|
78
|
+
<Palette aria-hidden className={icon} />
|
|
79
|
+
<span className="flex flex-col">
|
|
80
|
+
Copy design.md
|
|
81
|
+
<span className="text-xs text-muted-foreground">The brand for a page, slide or document made elsewhere</span>
|
|
82
|
+
</span>
|
|
83
|
+
</Menu.Item>
|
|
76
84
|
<Menu.LinkItem className={item} href={`${basePath}/manifest/components.json`} target="_blank" rel="noreferrer">
|
|
77
85
|
<Braces aria-hidden className={icon} />
|
|
78
86
|
<span className="flex flex-col">
|
package/src/playground.tsx
CHANGED
|
@@ -250,9 +250,9 @@ export function FlagControl({ name, checked, onChange }: { name: string; checked
|
|
|
250
250
|
checked={checked}
|
|
251
251
|
onCheckedChange={onChange}
|
|
252
252
|
// drawn small, pressed large: the hit area reaches the docs' target without the switch growing
|
|
253
|
-
className="relative h-6 w-10 shrink-0 cursor-pointer rounded-full bg-input p-0.5 transition-colors duration-(--dt-duration-fast) after:absolute after:-inset-2.5 after:content-[''] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[checked]:bg-primary"
|
|
253
|
+
className="relative h-6 w-10 shrink-0 cursor-pointer rounded-full bg-input p-0.5 transition-colors duration-(--dt-duration-fast) after:absolute after:-inset-2.5 after:content-[''] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[checked]:bg-[var(--docs-accent,var(--primary))]"
|
|
254
254
|
>
|
|
255
|
-
<Switch.Thumb className="block size-5 rounded-full bg-
|
|
255
|
+
<Switch.Thumb className="block size-5 rounded-full bg-canvas shadow-xs ring-1 ring-border transition-transform duration-(--dt-duration-fast) data-[checked]:translate-x-4" />
|
|
256
256
|
</Switch.Root>
|
|
257
257
|
</div>
|
|
258
258
|
);
|
package/src/shell.tsx
CHANGED
|
@@ -93,6 +93,8 @@ export function DocsShell({
|
|
|
93
93
|
children: ReactNode;
|
|
94
94
|
}) {
|
|
95
95
|
const current = usePathname();
|
|
96
|
+
// the overview's own address: the base path, or the site root when the docs are served there (basePath "")
|
|
97
|
+
const home = basePath || "/";
|
|
96
98
|
const searchItems = useMemo(
|
|
97
99
|
() =>
|
|
98
100
|
searchIndex({
|
|
@@ -109,7 +111,7 @@ export function DocsShell({
|
|
|
109
111
|
const sections = useMemo<Section[]>(() => {
|
|
110
112
|
const families = groupFamilies(manifest);
|
|
111
113
|
const all: Section[] = [
|
|
112
|
-
{ id: "overview", label: "Overview", icon: House, href:
|
|
114
|
+
{ id: "overview", label: "Overview", icon: House, href: home, groups: [] },
|
|
113
115
|
{ id: "brand", label: "Brand", icon: BookOpen, groups: [{ links: brand }] },
|
|
114
116
|
{ id: "foundations", label: "Foundations", icon: Palette, groups: [{ links: foundations }] },
|
|
115
117
|
...(rules?.rules.length ? [{ id: "rules", label: "Rules", icon: Scale, href: `${basePath}/rules`, groups: [] }] : []),
|
|
@@ -135,7 +137,7 @@ export function DocsShell({
|
|
|
135
137
|
{ id: "standards", label: "Standards", icon: Gauge, groups: [{ links: standards }] },
|
|
136
138
|
];
|
|
137
139
|
return all.filter((s) => s.href || s.groups.some((g) => g.links.length));
|
|
138
|
-
}, [manifest, rules, patterns, brand, foundations, standards, basePath]);
|
|
140
|
+
}, [manifest, rules, patterns, brand, foundations, standards, basePath, home]);
|
|
139
141
|
|
|
140
142
|
// every page in navigation order, for the section a page sits in and its neighbours
|
|
141
143
|
const page = useMemo(() => {
|
|
@@ -156,7 +158,7 @@ export function DocsShell({
|
|
|
156
158
|
useEffect(() => setMenu(false), [current]);
|
|
157
159
|
|
|
158
160
|
const brandLink = (
|
|
159
|
-
<Link href={
|
|
161
|
+
<Link href={home} data-slot="docs-title" className={cx(target, "flex min-w-0 items-center gap-3 rounded-md focus-visible:outline-2 focus-visible:outline-ring")}>
|
|
160
162
|
{logo && (
|
|
161
163
|
<span data-slot="docs-logo" className="shrink-0">
|
|
162
164
|
<Live>{logo}</Live>
|
|
@@ -224,7 +226,7 @@ export function DocsShell({
|
|
|
224
226
|
{search && <DocsSearch items={searchItems} shortcuts={false} variant="icon" />}
|
|
225
227
|
</header>
|
|
226
228
|
|
|
227
|
-
{hero && current ===
|
|
229
|
+
{hero && current === home && (
|
|
228
230
|
<div data-slot="docs-hero" className="w-full">
|
|
229
231
|
<Live>{hero}</Live>
|
|
230
232
|
</div>
|
package/src/standards.tsx
CHANGED
|
@@ -26,13 +26,13 @@ export function Standards({ baselines, children }: { baselines: Record<string, u
|
|
|
26
26
|
{section.title}
|
|
27
27
|
</h3>
|
|
28
28
|
{single ? (
|
|
29
|
-
<p className="text-
|
|
29
|
+
<p className="text-xl leading-snug tracking-tight text-pretty">{section.rows[0].value}</p>
|
|
30
30
|
) : section.rows.length ? (
|
|
31
31
|
<dl className="flex flex-col gap-3">
|
|
32
32
|
{section.rows.map((row) => (
|
|
33
33
|
<div key={row.label} className="flex flex-col gap-0.5">
|
|
34
34
|
<dt className="text-xs text-muted-foreground">{row.label}</dt>
|
|
35
|
-
<dd className="text-
|
|
35
|
+
<dd className="text-lg leading-snug tracking-tight tabular-nums">{row.value}</dd>
|
|
36
36
|
</div>
|
|
37
37
|
))}
|
|
38
38
|
</dl>
|
package/src/styles.css
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* semantic names (--canvas, --surface, --primary, …), so a project on our tokens
|
|
10
10
|
* needs nothing; any other project sets those names on the docs. Fonts take
|
|
11
11
|
* --docs-font-sans / --docs-font-mono, else the project's --font-sans and
|
|
12
|
-
* --font-mono; headings --docs-display, else --font-display. Sizes,
|
|
13
|
-
*
|
|
12
|
+
* --font-mono; headings --docs-display, else --font-display. Sizes, shadows,
|
|
13
|
+
* leading and tracking follow the project's scale where it has one; corners
|
|
14
|
+
* are the docs' own (--docs-radius-*), never the project's.
|
|
14
15
|
* Every value has a fallback, so the docs render on a project with none of it.
|
|
15
16
|
*/
|
|
16
17
|
@layer theme, base, components, utilities;
|
|
@@ -109,12 +110,12 @@
|
|
|
109
110
|
--tracking-wide: var(--tracking-wide, 0.04em);
|
|
110
111
|
--tracking-widest: var(--tracking-widest, 0.16em);
|
|
111
112
|
|
|
112
|
-
--radius-xs: var(--docs-radius-xs,
|
|
113
|
-
--radius-sm: var(--docs-radius-sm,
|
|
114
|
-
--radius-md: var(--docs-radius-md,
|
|
115
|
-
--radius-lg: var(--docs-radius-lg,
|
|
116
|
-
--radius-xl: var(--docs-radius-xl,
|
|
117
|
-
--radius-2xl: var(--docs-radius-2xl,
|
|
113
|
+
--radius-xs: var(--docs-radius-xs, 0.125rem);
|
|
114
|
+
--radius-sm: var(--docs-radius-sm, 0.25rem);
|
|
115
|
+
--radius-md: var(--docs-radius-md, 0.375rem);
|
|
116
|
+
--radius-lg: var(--docs-radius-lg, 0.5rem);
|
|
117
|
+
--radius-xl: var(--docs-radius-xl, 0.75rem);
|
|
118
|
+
--radius-2xl: var(--docs-radius-2xl, 1rem);
|
|
118
119
|
|
|
119
120
|
--shadow-xs: var(--shadow-xs, 0 1px 2px 0 color-mix(in oklch, var(--shadow, #0b0d12) 6%, transparent));
|
|
120
121
|
--shadow-sm: var(--shadow-sm, 0 1px 3px 0 color-mix(in oklch, var(--shadow, #0b0d12) 10%, transparent));
|
package/src/type-ramp.tsx
CHANGED
|
@@ -34,12 +34,9 @@ export function TypeRamp({
|
|
|
34
34
|
<div className="grid gap-x-12 gap-y-10 md:grid-cols-2">
|
|
35
35
|
{families.map((f) => (
|
|
36
36
|
<figure key={f.name} data-slot="type-family" className="flex flex-col gap-5 border-t border-border pt-6">
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 &?!
|
|
41
|
-
</span>
|
|
42
|
-
</div>
|
|
37
|
+
<span className="text-8xl leading-none font-light tracking-tighter" style={{ fontFamily: `var(${f.name})` }}>
|
|
38
|
+
Aa
|
|
39
|
+
</span>
|
|
43
40
|
<figcaption className="flex flex-col gap-0.5">
|
|
44
41
|
<code className="font-mono text-xs font-medium">{shortName(f, "font")}</code>
|
|
45
42
|
<span className="truncate font-mono text-xs text-muted-foreground" title={f.values.default}>
|
package/src/variant-matrix.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Live } from "./lib/live";
|
|
4
|
+
import { wrapIn } from "./lib/wrap";
|
|
4
5
|
import { Grid3x3, Moon, Sun } from "lucide-react";
|
|
5
|
-
import { type ReactNode, useEffect, useState } from "react";
|
|
6
|
+
import { type ReactElement, type ReactNode, useEffect, useState } from "react";
|
|
6
7
|
import { cx } from "./lib/cx";
|
|
7
8
|
import { coerce, Missing, useLookup } from "./lib/lookup";
|
|
8
9
|
import type { ComponentEntry, VariantAxis } from "./lib/manifest-types";
|
|
@@ -19,9 +20,12 @@ export function VariantMatrix({
|
|
|
19
20
|
props = {},
|
|
20
21
|
children,
|
|
21
22
|
axes: only,
|
|
23
|
+
wrap,
|
|
22
24
|
}: {
|
|
23
25
|
entry: ComponentEntry;
|
|
24
26
|
props?: Record<string, unknown>;
|
|
27
|
+
/** What each instance must sit in to work: a provider, its root. */
|
|
28
|
+
wrap?: ReactElement<{ children?: ReactNode }>;
|
|
25
29
|
/** What to render inside each instance. Defaults to the component's name. */
|
|
26
30
|
children?: ReactNode;
|
|
27
31
|
/** Limit and order the axes shown. */
|
|
@@ -38,9 +42,12 @@ export function VariantMatrix({
|
|
|
38
42
|
const content = children ?? entry.name;
|
|
39
43
|
const render = (extra: Record<string, string>) => (
|
|
40
44
|
<Live>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
{wrapIn(
|
|
46
|
+
wrap,
|
|
47
|
+
<Component {...props} {...coerce(extra)}>
|
|
48
|
+
{content}
|
|
49
|
+
</Component>,
|
|
50
|
+
)}
|
|
44
51
|
</Live>
|
|
45
52
|
);
|
|
46
53
|
const [rows, cols, ...rest] = axes;
|
package/src/workbench.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { handlerNames } from "./lib/handlers";
|
|
4
4
|
import { Live } from "./lib/live";
|
|
5
|
+
import { wrapIn } from "./lib/wrap";
|
|
5
6
|
import { Tabs } from "@base-ui/react/tabs";
|
|
6
7
|
import { Toggle } from "@base-ui/react/toggle";
|
|
7
8
|
import { ToggleGroup } from "@base-ui/react/toggle-group";
|
|
@@ -381,7 +382,7 @@ export function Workbench({
|
|
|
381
382
|
}}
|
|
382
383
|
>
|
|
383
384
|
<div ref={target} data-slot="workbench-target" className="flex max-w-full items-center justify-center">
|
|
384
|
-
<Boundary label={entry.name}>{
|
|
385
|
+
<Boundary label={entry.name}>{wrapIn(wrap, node)}</Boundary>
|
|
385
386
|
</div>
|
|
386
387
|
</div>
|
|
387
388
|
</div>
|