@designtools/docs 0.1.0 → 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 +10 -0
- package/README.md +24 -2
- package/dist/anatomy.js +2 -1
- package/dist/cli.js +1 -1
- package/dist/glossary.d.ts +1 -1
- package/dist/glossary.js +13 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/live.d.ts +10 -0
- package/dist/lib/live.js +10 -0
- package/dist/lib/lookup.js +3 -2
- package/dist/lib/status.d.ts +2 -3
- package/dist/lib/terms.d.ts +15 -0
- package/dist/lib/terms.js +20 -0
- package/dist/next/config.d.ts +24 -4
- package/dist/next/index.d.ts +3 -0
- package/dist/next/index.js +3 -0
- package/dist/next/markdown.js +1 -1
- package/dist/next/pages.js +82 -16
- package/dist/next/prose.d.ts +2 -2
- package/dist/next/prose.js +6 -4
- package/dist/pattern.d.ts +4 -1
- package/dist/pattern.js +11 -8
- package/dist/playground.js +1 -1
- package/dist/rule.d.ts +9 -5
- package/dist/rule.js +14 -9
- package/dist/shell.d.ts +4 -1
- package/dist/shell.js +13 -11
- package/dist/styles.css +932 -743
- package/dist/swatches.js +10 -4
- package/dist/variant-matrix.js +11 -6
- package/dist/voice-terms.js +6 -5
- package/dist/workbench.js +2 -1
- package/package.json +1 -1
- package/src/anatomy.tsx +6 -1
- package/src/glossary.tsx +39 -40
- package/src/index.ts +2 -1
- package/src/lib/live.tsx +15 -0
- package/src/lib/lookup.tsx +3 -2
- package/src/lib/status.tsx +3 -3
- package/src/lib/terms.tsx +53 -0
- package/src/next/config.ts +25 -4
- package/src/next/index.tsx +3 -0
- package/src/next/markdown.ts +1 -1
- package/src/next/pages.tsx +172 -59
- package/src/next/prose.tsx +16 -6
- package/src/pattern.tsx +42 -37
- package/src/playground.tsx +7 -2
- package/src/rule.tsx +108 -59
- package/src/shell.tsx +30 -16
- package/src/styles.css +4 -2
- package/src/swatches.tsx +68 -57
- package/src/variant-matrix.tsx +63 -80
- package/src/voice-terms.tsx +20 -29
- package/src/workbench.tsx +6 -3
package/src/next/pages.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
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";
|
|
3
5
|
import { AdherenceSummary } from "../adherence-summary";
|
|
4
6
|
import { Anatomy } from "../anatomy";
|
|
5
7
|
import { Examples } from "../examples";
|
|
@@ -12,7 +14,7 @@ import { PageActions } from "../page-actions";
|
|
|
12
14
|
import { Pattern } from "../pattern";
|
|
13
15
|
import { PreviewFrame } from "../preview-frame";
|
|
14
16
|
import { PropsTable } from "../props-table";
|
|
15
|
-
import { Rule } from "../rule";
|
|
17
|
+
import { Rule, RuleIndex } from "../rule";
|
|
16
18
|
import { Scale } from "../scale";
|
|
17
19
|
import { DocsFooter, DocsHeader, DocsSection } from "../shell";
|
|
18
20
|
import { Standards } from "../standards";
|
|
@@ -39,6 +41,7 @@ const DEFAULT_BLOCKS = {
|
|
|
39
41
|
PropsTable,
|
|
40
42
|
RampSwatches,
|
|
41
43
|
Rule,
|
|
44
|
+
RuleIndex,
|
|
42
45
|
Scale,
|
|
43
46
|
SemanticSwatches,
|
|
44
47
|
Standards,
|
|
@@ -73,7 +76,7 @@ export function DocsPageView({ site, page }: { site: Site; page: PageRef }) {
|
|
|
73
76
|
return (
|
|
74
77
|
<>
|
|
75
78
|
{header(page.page.title, page.page.lead)}
|
|
76
|
-
<Markdown text={page.page.body} />
|
|
79
|
+
{typeof page.page.body === "string" ? <Markdown text={page.page.body} /> : page.page.body}
|
|
77
80
|
<DocsFooter editorial={page.page.by} />
|
|
78
81
|
</>
|
|
79
82
|
);
|
|
@@ -147,6 +150,9 @@ export function DocsPageView({ site, page }: { site: Site; page: PageRef }) {
|
|
|
147
150
|
return (
|
|
148
151
|
<>
|
|
149
152
|
{header(COPY.rules.title, COPY.rules.lead)}
|
|
153
|
+
<DocsSection title="Every rule" lead="A line each: what it covers, how settled it is, and how it is checked.">
|
|
154
|
+
<B.RuleIndex rules={[...site.rules.rules].sort((a, b) => a.kind.localeCompare(b.kind))} />
|
|
155
|
+
</DocsSection>
|
|
150
156
|
{(["brand", "interface"] as const).map((kind) => {
|
|
151
157
|
const rules = site.rules.rules.filter((r) => r.kind === kind);
|
|
152
158
|
return rules.length ? (
|
|
@@ -193,7 +199,9 @@ export function DocsPageView({ site, page }: { site: Site; page: PageRef }) {
|
|
|
193
199
|
case "pattern":
|
|
194
200
|
return (
|
|
195
201
|
<>
|
|
196
|
-
{
|
|
202
|
+
<DocsHeader title={page.pattern.name} lead={page.pattern.description} status={page.pattern.status}>
|
|
203
|
+
<PageActions basePath={base} />
|
|
204
|
+
</DocsHeader>
|
|
197
205
|
<B.Pattern pattern={page.pattern} manifest={site.components} basePath={base} />
|
|
198
206
|
<DocsFooter source={page.pattern.source} provenance={config.provenance} />
|
|
199
207
|
</>
|
|
@@ -292,22 +300,128 @@ function ComponentPage({ site, blocks: B, entry }: { site: Site; blocks: Blocks;
|
|
|
292
300
|
);
|
|
293
301
|
}
|
|
294
302
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
/** The overview: where to start, then every component by category. Below the hero when there is one. */
|
|
303
|
+
/** The overview, below the hero when there is one: each part of the system and why it exists, then why it is agentic. */
|
|
298
304
|
function Overview({ site }: { site: Site }) {
|
|
299
305
|
const { base, config } = site;
|
|
300
306
|
const families = groupFamilies(site.components);
|
|
301
307
|
const count = families.reduce((n, g) => n + g.families.length, 0);
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
site.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
308
|
+
const brandPages = site.editorial.filter((p) => (p.section ?? "brand") === "brand");
|
|
309
|
+
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
|
+
const share = config.adherence?.summary.share;
|
|
316
|
+
|
|
317
|
+
// each section of the system, why it exists, how much is in it, and a glimpse of it, live from the manifest
|
|
318
|
+
const sections: { title: string; href: string; why: string; count: string; preview: ReactNode }[] = [];
|
|
319
|
+
if (brandFirst || site.shows("logo"))
|
|
320
|
+
sections.push({
|
|
321
|
+
title: "Brand",
|
|
322
|
+
href: `${base}/${brandFirst?.slug ?? "brand/logo"}`,
|
|
323
|
+
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,
|
|
326
|
+
});
|
|
327
|
+
sections.push({
|
|
328
|
+
title: "Foundations",
|
|
329
|
+
href: `${base}/colour`,
|
|
330
|
+
why: "Colour, type and the scale, generated from the tokens and measured in both modes, so nothing is a guess.",
|
|
331
|
+
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
|
+
});
|
|
345
|
+
if (site.rules.rules.length)
|
|
346
|
+
sections.push({
|
|
347
|
+
title: "Rules",
|
|
348
|
+
href: `${base}/rules`,
|
|
349
|
+
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
|
+
),
|
|
364
|
+
});
|
|
365
|
+
if (site.shows("components") && count)
|
|
366
|
+
sections.push({
|
|
367
|
+
title: "Components",
|
|
368
|
+
href: `${base}/components/${slugOf(families[0].families[0].head)}`,
|
|
369
|
+
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
|
+
),
|
|
380
|
+
});
|
|
381
|
+
if (site.patterns.patterns.length)
|
|
382
|
+
sections.push({
|
|
383
|
+
title: "Patterns",
|
|
384
|
+
href: `${base}/patterns/${site.patterns.patterns[0].id}`,
|
|
385
|
+
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>,
|
|
388
|
+
});
|
|
389
|
+
if (site.shows("standards") && (config.baselines || config.adherence))
|
|
390
|
+
sections.push({
|
|
391
|
+
title: "Standards",
|
|
392
|
+
href: `${base}/standards`,
|
|
393
|
+
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
|
+
),
|
|
404
|
+
});
|
|
405
|
+
if (site.shows("vocabulary") && site.taxonomy.terms.length)
|
|
406
|
+
sections.push({
|
|
407
|
+
title: "Vocabulary",
|
|
408
|
+
href: `${base}/vocabulary`,
|
|
409
|
+
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,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const loop = [
|
|
419
|
+
["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."],
|
|
420
|
+
["Generated, never drifting", "This site is built from that code on every change, so it cannot fall out of date."],
|
|
421
|
+
["Read by people and agents", "Every page is also plain markdown, and coding agents are pointed at the same source, so they build with the real components and rules."],
|
|
422
|
+
["Checked", "Each rule names the check that proves it. Stale docs stop a commit; everything else is reported."],
|
|
423
|
+
["Measured", "A report on every change shows how closely the product follows the system. It informs the conversation; it never blocks the work."],
|
|
424
|
+
];
|
|
311
425
|
|
|
312
426
|
return (
|
|
313
427
|
<>
|
|
@@ -316,51 +430,50 @@ function Overview({ site }: { site: Site }) {
|
|
|
316
430
|
<PageActions basePath={base} />
|
|
317
431
|
</DocsHeader>
|
|
318
432
|
)}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
className="group
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
<span className="text-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
{c.description && <span className="line-clamp-2 text-sm text-muted-foreground">{c.description}</span>}
|
|
356
|
-
{parts.length > 0 && <span className="mt-auto font-mono text-xs text-muted-foreground">with {parts.map((p) => p.name).join(", ")}</span>}
|
|
357
|
-
</Link>
|
|
358
|
-
</li>
|
|
359
|
-
))}
|
|
360
|
-
</ul>
|
|
361
|
-
</div>
|
|
433
|
+
|
|
434
|
+
<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.">
|
|
435
|
+
<ul data-slot="overview-sections" className="grid gap-x-12 md:grid-cols-2">
|
|
436
|
+
{sections.map((sec) => (
|
|
437
|
+
<li key={sec.title} className="border-t border-border">
|
|
438
|
+
<Link href={sec.href} className="group flex h-full flex-col gap-4 py-7 focus-visible:outline-2 focus-visible:outline-ring">
|
|
439
|
+
<span className="flex items-baseline justify-between gap-4">
|
|
440
|
+
<span className="text-2xl leading-tight tracking-tight underline-offset-[6px] group-hover:underline">{sec.title}</span>
|
|
441
|
+
<span className="shrink-0 text-xs text-muted-foreground tabular-nums">{sec.count}</span>
|
|
442
|
+
</span>
|
|
443
|
+
<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
|
+
</Link>
|
|
446
|
+
</li>
|
|
447
|
+
))}
|
|
448
|
+
</ul>
|
|
449
|
+
</DocsSection>
|
|
450
|
+
|
|
451
|
+
{config.overview?.agentic !== false && (
|
|
452
|
+
<DocsSection
|
|
453
|
+
title="Why this is agentic"
|
|
454
|
+
lead={
|
|
455
|
+
config.overview?.agenticLead ??
|
|
456
|
+
"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."
|
|
457
|
+
}
|
|
458
|
+
>
|
|
459
|
+
<ol data-slot="overview-loop" className="grid gap-x-6 gap-y-8 sm:grid-cols-2 xl:grid-cols-5">
|
|
460
|
+
{loop.map(([title, body], i) => (
|
|
461
|
+
<li key={title} className="relative flex flex-col gap-3 xl:pr-2">
|
|
462
|
+
<span className="flex items-center gap-3">
|
|
463
|
+
<span className="grid size-8 shrink-0 place-items-center rounded-full bg-canvas-foreground text-sm font-medium text-canvas tabular-nums">{i + 1}</span>
|
|
464
|
+
{i < loop.length - 1 && <span aria-hidden className="hidden h-px flex-1 bg-border xl:block" />}
|
|
465
|
+
</span>
|
|
466
|
+
<span className="text-lg leading-snug tracking-tight">{title}</span>
|
|
467
|
+
<span className="text-sm leading-relaxed text-muted-foreground">{body}</span>
|
|
468
|
+
</li>
|
|
362
469
|
))}
|
|
363
|
-
</
|
|
470
|
+
</ol>
|
|
471
|
+
<p className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground">
|
|
472
|
+
See it the way an agent does:
|
|
473
|
+
<a href={`${base}.md`} className="inline-flex items-center gap-1 font-medium text-canvas-foreground underline underline-offset-4 hover:decoration-2">
|
|
474
|
+
this page as markdown <ArrowUpRight aria-hidden className="size-3.5" />
|
|
475
|
+
</a>
|
|
476
|
+
</p>
|
|
364
477
|
</DocsSection>
|
|
365
478
|
)}
|
|
366
479
|
<DocsFooter provenance={config.provenance} />
|
package/src/next/prose.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DocsSection } from "../shell";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The few markdown shapes editorial pages use: `##` sections, paragraphs and bullet
|
|
5
|
-
* and
|
|
4
|
+
* The few markdown shapes editorial pages use: `##` sections, `###` headings, paragraphs and bullet
|
|
5
|
+
* lists, with bold, code and links. Each `##` is a docs section, so it reads at the same size as a generated page and is
|
|
6
6
|
* listed in "On this page". A list whose every item opens in bold ("**Say it plainly.** …") is a set
|
|
7
7
|
* of principles, numbered, each a heading over its sentence.
|
|
8
8
|
*/
|
|
@@ -31,6 +31,12 @@ function Blocks({ blocks }: { blocks: string[] }) {
|
|
|
31
31
|
return (
|
|
32
32
|
<div className="flex flex-col gap-5">
|
|
33
33
|
{blocks.map((b, i) => {
|
|
34
|
+
if (b.startsWith("### "))
|
|
35
|
+
return (
|
|
36
|
+
<h3 key={i} className="mt-4 max-w-3xl text-2xl leading-snug tracking-tight first:mt-0">
|
|
37
|
+
{inline(b.slice(4))}
|
|
38
|
+
</h3>
|
|
39
|
+
);
|
|
34
40
|
const lines = b.split("\n");
|
|
35
41
|
if (lines.every((l) => l.startsWith("- "))) {
|
|
36
42
|
const items = lines.map((l) => l.slice(2));
|
|
@@ -41,8 +47,8 @@ function Blocks({ blocks }: { blocks: string[] }) {
|
|
|
41
47
|
{items.map((l, n) => {
|
|
42
48
|
const [, head, rest] = /^\*\*([^*]+)\*\*\s*(.*)$/.exec(l) ?? [];
|
|
43
49
|
return (
|
|
44
|
-
<li key={l} className="grid grid-cols-[3rem_minmax(0,1fr)] gap-x-4 gap-y-3 sm:grid-cols-[4rem_minmax(0,1fr)]">
|
|
45
|
-
<span className="
|
|
50
|
+
<li key={l} className="grid grid-cols-[3rem_minmax(0,1fr)] items-baseline gap-x-4 gap-y-3 sm:grid-cols-[4rem_minmax(0,1fr)]">
|
|
51
|
+
<span className="text-lg text-muted-foreground tabular-nums">{String(n + 1).padStart(2, "0")}</span>
|
|
46
52
|
<span className="flex flex-col gap-3">
|
|
47
53
|
<span className="text-2xl leading-snug tracking-tight md:text-3xl">{head.replace(/\.$/, "")}</span>
|
|
48
54
|
<span className="text-lg leading-relaxed text-canvas-foreground/75">{inline(rest)}</span>
|
|
@@ -71,8 +77,12 @@ function Blocks({ blocks }: { blocks: string[] }) {
|
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
function inline(text: string) {
|
|
74
|
-
return text.split(/(\*\*[^*]+\*\*|`[^`]
|
|
75
|
-
part.startsWith("
|
|
80
|
+
return text.split(/(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)\s]+\))/).map((part, i) =>
|
|
81
|
+
part.startsWith("[") ? (
|
|
82
|
+
<a key={i} href={/\(([^)]+)\)$/.exec(part)?.[1]} className="underline decoration-from-font underline-offset-4 hover:decoration-2">
|
|
83
|
+
{/^\[([^\]]+)\]/.exec(part)?.[1]}
|
|
84
|
+
</a>
|
|
85
|
+
) : part.startsWith("**") ? (
|
|
76
86
|
<strong key={i} className="font-semibold">
|
|
77
87
|
{part.slice(2, -2)}
|
|
78
88
|
</strong>
|
package/src/pattern.tsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { ArrowUpRight } from "lucide-react";
|
|
4
3
|
import Link from "next/link";
|
|
5
4
|
import { ExampleFrame } from "./examples";
|
|
6
5
|
import { Missing, renderExample, useLookup } from "./lib/lookup";
|
|
7
6
|
import { slugOf } from "./lib/manifest";
|
|
8
7
|
import type { ComponentsManifest, PatternEntry } from "./lib/manifest-types";
|
|
9
8
|
import { StatusBadge } from "./lib/status";
|
|
9
|
+
import { DocsSection } from "./shell";
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* A recurring screen composition: each composition live in its frame, with its code, then the
|
|
13
|
+
* components it is made of, each linking to its page. Its status sits in the page header.
|
|
14
|
+
*/
|
|
12
15
|
export function Pattern({
|
|
13
16
|
pattern,
|
|
14
17
|
manifest,
|
|
@@ -23,45 +26,47 @@ export function Pattern({
|
|
|
23
26
|
const module = lookup.compositionsFor(pattern);
|
|
24
27
|
const known = new Map((manifest?.components ?? []).map((c) => [c.name, c]));
|
|
25
28
|
return (
|
|
26
|
-
|
|
27
|
-
<div className="flex flex-col gap-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
<>
|
|
30
|
+
<div data-slot="pattern" className="flex flex-col gap-10">
|
|
31
|
+
{!module ? (
|
|
32
|
+
<Missing name={pattern.name} what="compositions" />
|
|
33
|
+
) : (
|
|
34
|
+
pattern.examples.map((ex, i) => (
|
|
35
|
+
<ExampleFrame key={ex.name} name={ex.name} kind={i === 0 ? "canonical" : "other"} description={ex.description} code={ex.code}>
|
|
36
|
+
{renderExample(module[ex.name], ex.name)}
|
|
37
|
+
</ExampleFrame>
|
|
38
|
+
))
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
{pattern.components.length > 0 && (
|
|
42
|
+
<DocsSection title="Made of" lead="The system's own components, so the pattern changes when they do.">
|
|
43
|
+
<ul data-slot="pattern-parts" className="grid gap-x-10 sm:grid-cols-2">
|
|
35
44
|
{pattern.components.map((name) => {
|
|
36
45
|
const c = known.get(name);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const body = (
|
|
47
|
+
<>
|
|
48
|
+
<span className="flex items-center justify-between gap-3">
|
|
49
|
+
<span className="text-lg tracking-tight underline-offset-4 group-hover:underline">{name}</span>
|
|
50
|
+
{c && <StatusBadge status={c.status} />}
|
|
51
|
+
</span>
|
|
52
|
+
{c?.description && <span className="line-clamp-2 text-sm text-muted-foreground">{c.description}</span>}
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
return (
|
|
56
|
+
<li key={name} className="border-t border-border">
|
|
57
|
+
{c ? (
|
|
58
|
+
<Link href={`${basePath}/components/${slugOf(c)}`} className="group flex flex-col gap-1.5 py-4 focus-visible:outline-2 focus-visible:outline-ring">
|
|
59
|
+
{body}
|
|
60
|
+
</Link>
|
|
61
|
+
) : (
|
|
62
|
+
<span className="flex flex-col gap-1.5 py-4">{body}</span>
|
|
63
|
+
)}
|
|
64
|
+
</li>
|
|
51
65
|
);
|
|
52
66
|
})}
|
|
53
|
-
</
|
|
54
|
-
|
|
55
|
-
</div>
|
|
56
|
-
{!module ? (
|
|
57
|
-
<Missing name={pattern.name} what="compositions" />
|
|
58
|
-
) : (
|
|
59
|
-
pattern.examples.map((ex, i) => (
|
|
60
|
-
<ExampleFrame key={ex.name} name={ex.name} kind={i === 0 ? "canonical" : "other"} description={ex.description} code={ex.code}>
|
|
61
|
-
{renderExample(module[ex.name], ex.name)}
|
|
62
|
-
</ExampleFrame>
|
|
63
|
-
))
|
|
67
|
+
</ul>
|
|
68
|
+
</DocsSection>
|
|
64
69
|
)}
|
|
65
|
-
|
|
70
|
+
</>
|
|
66
71
|
);
|
|
67
72
|
}
|
package/src/playground.tsx
CHANGED
|
@@ -137,6 +137,8 @@ export function Label({ id, name, info, htmlFor }: { id?: string; name: string;
|
|
|
137
137
|
)}
|
|
138
138
|
<Tooltip.Root>
|
|
139
139
|
<Tooltip.Trigger
|
|
140
|
+
delay={0}
|
|
141
|
+
closeDelay={0}
|
|
140
142
|
aria-label={`About ${title(name)}`}
|
|
141
143
|
className="relative inline-flex size-6 cursor-pointer items-center justify-center rounded-full text-muted-foreground hover:text-canvas-foreground focus-visible:outline-2 focus-visible:outline-ring after:absolute after:-inset-2.5 after:content-['']"
|
|
142
144
|
>
|
|
@@ -144,8 +146,11 @@ export function Label({ id, name, info, htmlFor }: { id?: string; name: string;
|
|
|
144
146
|
</Tooltip.Trigger>
|
|
145
147
|
<Tooltip.Portal>
|
|
146
148
|
<Tooltip.Positioner data-designtools="" side="left" sideOffset={8} className="z-50">
|
|
147
|
-
<Tooltip.Popup
|
|
148
|
-
|
|
149
|
+
<Tooltip.Popup
|
|
150
|
+
data-slot="docs-tooltip"
|
|
151
|
+
className="flex max-w-64 flex-col gap-1 rounded-md bg-canvas-foreground px-2.5 py-2 text-xs leading-snug text-canvas shadow-md transition-opacity duration-(--dt-duration-fast) data-ending-style:opacity-0 data-starting-style:opacity-0 [&_*]:text-canvas/80"
|
|
152
|
+
>
|
|
153
|
+
<code className="font-mono font-medium text-canvas!">{name}</code>
|
|
149
154
|
{info}
|
|
150
155
|
</Tooltip.Popup>
|
|
151
156
|
</Tooltip.Positioner>
|