@designtools/blocks 0.0.0-stage → 0.1.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 +9 -0
- package/LICENSE +21 -0
- package/README.md +117 -2
- package/dist/cli.js +381 -0
- package/package.json +59 -3
- package/registry/a11y-panel.tsx +99 -0
- package/registry/adherence-summary.tsx +110 -0
- package/registry/agent-view.tsx +57 -0
- package/registry/anatomy.tsx +88 -0
- package/registry/ask-claude.tsx +71 -0
- package/registry/code-view.tsx +74 -0
- package/registry/examples.tsx +96 -0
- package/registry/glossary.tsx +58 -0
- package/registry/imagery.tsx +39 -0
- package/registry/lib/adherence-types.ts +179 -0
- package/registry/lib/boundary.tsx +21 -0
- package/registry/lib/contrast.ts +44 -0
- package/registry/lib/cx.ts +4 -0
- package/registry/lib/jsx.ts +29 -0
- package/registry/lib/lookup.tsx +46 -0
- package/registry/lib/manifest-types.ts +273 -0
- package/registry/lib/manifest.ts +182 -0
- package/registry/lib/markdown.ts +283 -0
- package/registry/lib/status.tsx +30 -0
- package/registry/lib/text.tsx +27 -0
- package/registry/logo-usage.tsx +87 -0
- package/registry/pattern.tsx +68 -0
- package/registry/playground.tsx +180 -0
- package/registry/preview-frame.tsx +106 -0
- package/registry/props-table.tsx +110 -0
- package/registry/registry.generated.tsx +9 -0
- package/registry/rule.tsx +98 -0
- package/registry/scale.tsx +115 -0
- package/registry/shell.tsx +184 -0
- package/registry/standards.tsx +45 -0
- package/registry/swatches.tsx +146 -0
- package/registry/tsconfig.json +13 -0
- package/registry/type-ramp.tsx +105 -0
- package/registry/usage.tsx +72 -0
- package/registry/variant-matrix.tsx +105 -0
- package/registry/voice-terms.tsx +45 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { assetUrl } from "./lib/lookup";
|
|
4
|
+
import type { AssetEntry } from "./lib/manifest-types";
|
|
5
|
+
import { inline } from "./lib/text";
|
|
6
|
+
|
|
7
|
+
/** The approved icons and imagery, each with how to use it. */
|
|
8
|
+
export function Imagery({ assets, kinds = ["icon", "image", "other"] }: { assets: AssetEntry[]; kinds?: AssetEntry["kind"][] }) {
|
|
9
|
+
const shown = assets.filter((a) => kinds.includes(a.kind));
|
|
10
|
+
if (shown.length === 0) return <p className="text-sm text-muted-foreground">No icons or imagery in the brand folder yet.</p>;
|
|
11
|
+
return (
|
|
12
|
+
<ul data-slot="imagery" className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
13
|
+
{shown.map((a) => {
|
|
14
|
+
const src = assetUrl(a);
|
|
15
|
+
return (
|
|
16
|
+
<li key={a.file} className="min-w-0">
|
|
17
|
+
<figure data-slot="asset" className="flex h-full flex-col overflow-hidden rounded-lg border border-border">
|
|
18
|
+
<div className="flex h-36 items-center justify-center bg-surface p-4 text-surface-foreground">
|
|
19
|
+
{src ? (
|
|
20
|
+
<img src={src} alt={a.alt ?? ""} className={a.kind === "icon" ? "size-12" : "max-h-full max-w-full object-contain"} />
|
|
21
|
+
) : (
|
|
22
|
+
<span className="font-mono text-xs text-muted-foreground">{a.file}</span>
|
|
23
|
+
)}
|
|
24
|
+
</div>
|
|
25
|
+
<figcaption className="flex flex-col gap-1 border-t border-border px-4 py-3 text-sm">
|
|
26
|
+
<span className="font-medium">{a.name}</span>
|
|
27
|
+
{a.usage ? <span className="text-muted-foreground">{inline(a.usage)}</span> : <span className="text-muted-foreground">No usage notes yet.</span>}
|
|
28
|
+
<span className="font-mono text-xs text-muted-foreground">
|
|
29
|
+
{a.kind}
|
|
30
|
+
{a.width && a.height ? ` · ${a.width} × ${a.height}` : ""}
|
|
31
|
+
</span>
|
|
32
|
+
</figcaption>
|
|
33
|
+
</figure>
|
|
34
|
+
</li>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</ul>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The adherence report: what `designtools-adherence` writes with `--json`, and
|
|
3
|
+
* what the "Adherence summary" docs block renders.
|
|
4
|
+
*
|
|
5
|
+
* This file has no imports on purpose, so @designtools/blocks can carry a copy
|
|
6
|
+
* and read the report through the same types the tool wrote it with.
|
|
7
|
+
*
|
|
8
|
+
* Every path is relative to the project root, with forward slashes. Lines and
|
|
9
|
+
* columns start at 1. Every array is sorted (files and routes by path, findings
|
|
10
|
+
* by line then column, components and elements by name), and there are no
|
|
11
|
+
* timestamps, so the same source gives the same bytes.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const ADHERENCE_SCHEMA = "designtools.adherence/1";
|
|
15
|
+
|
|
16
|
+
export interface AdherenceReport {
|
|
17
|
+
/** `@designtools/adherence@<version>`. */
|
|
18
|
+
generator: string;
|
|
19
|
+
schema: string;
|
|
20
|
+
/** The components.json the system was read from. */
|
|
21
|
+
manifest: string;
|
|
22
|
+
/** The system folder, which is never scanned as product. */
|
|
23
|
+
system: string;
|
|
24
|
+
/** The globs that chose the product files, as given or defaulted. */
|
|
25
|
+
include: string[];
|
|
26
|
+
exclude: string[];
|
|
27
|
+
summary: Summary;
|
|
28
|
+
/** One per Next app router page (`app/**\/page.tsx`), sorted by route. */
|
|
29
|
+
routes: RouteEntry[];
|
|
30
|
+
/** Every scanned file with at least one use, raw element or finding, sorted by path. */
|
|
31
|
+
files: FileEntry[];
|
|
32
|
+
/** Every system component, used or not, sorted by name. */
|
|
33
|
+
components: ComponentUsage[];
|
|
34
|
+
/** Every raw element some component `replaces`, sorted by selector. */
|
|
35
|
+
elements: ElementUsage[];
|
|
36
|
+
/** Files a scanner could not parse, sorted by path. Their numbers are missing from the report. */
|
|
37
|
+
skipped: SkippedFile[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The numbers every level of the report shares: the whole product, a route, a file. */
|
|
41
|
+
export interface Counts {
|
|
42
|
+
/** JSX uses of system components, sub-components included. */
|
|
43
|
+
systemUses: number;
|
|
44
|
+
/** Raw elements that some system component `replaces`, such as `<button>` when Button replaces it. */
|
|
45
|
+
rawElements: number;
|
|
46
|
+
/**
|
|
47
|
+
* systemUses ÷ (systemUses + rawElements), from 0 to 1, to four decimal places.
|
|
48
|
+
* Null when both are 0: there was nothing to measure.
|
|
49
|
+
*/
|
|
50
|
+
share: number | null;
|
|
51
|
+
/** Classes that bypass the tokens: palette steps and arbitrary values. */
|
|
52
|
+
offSystemValues: number;
|
|
53
|
+
/** System components given a className that changes colour, spacing or radius. */
|
|
54
|
+
overrides: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Summary extends Counts {
|
|
58
|
+
/** Product files scanned. */
|
|
59
|
+
files: number;
|
|
60
|
+
/** Files with at least one raw element, off-system value or override. */
|
|
61
|
+
filesWithFindings: number;
|
|
62
|
+
offSystem: Record<OffSystemKind, number>;
|
|
63
|
+
overrideKinds: Record<OverrideKind, number>;
|
|
64
|
+
/** System components used at least once. */
|
|
65
|
+
componentsUsed: number;
|
|
66
|
+
/** System components never used in the product, by name. */
|
|
67
|
+
componentsUnused: string[];
|
|
68
|
+
/**
|
|
69
|
+
* The raw elements counted, from every component's `replaces`. Empty means no
|
|
70
|
+
* component declares any, so `share` measures nothing yet.
|
|
71
|
+
*/
|
|
72
|
+
replaceable: string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RouteEntry {
|
|
76
|
+
/** The URL path: route groups and parallel-route slots removed, dynamic segments kept. `/companies/[key]`. */
|
|
77
|
+
route: string;
|
|
78
|
+
/** The page file. */
|
|
79
|
+
page: string;
|
|
80
|
+
/**
|
|
81
|
+
* The files counted for the route: the page, files beside it, and files in
|
|
82
|
+
* folders under it that lead to no other page. Layouts shared by several
|
|
83
|
+
* routes are counted per file only.
|
|
84
|
+
*/
|
|
85
|
+
files: string[];
|
|
86
|
+
counts: Counts;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface FileEntry {
|
|
90
|
+
file: string;
|
|
91
|
+
/** The route the file is counted under, when it has one. */
|
|
92
|
+
route?: string;
|
|
93
|
+
counts: Counts;
|
|
94
|
+
uses: ComponentUse[];
|
|
95
|
+
raw: RawElement[];
|
|
96
|
+
offSystem: OffSystemValue[];
|
|
97
|
+
overrides: Override[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A literal prop value, or the kind of expression it was, e.g. `(Identifier)`. */
|
|
101
|
+
export type PropValue = string | number | boolean | null;
|
|
102
|
+
|
|
103
|
+
export interface ComponentUse {
|
|
104
|
+
/** The component's name in the manifest. */
|
|
105
|
+
component: string;
|
|
106
|
+
/** The element as written, when it differs: `Select.Trigger`, `DS.Button`. */
|
|
107
|
+
as?: string;
|
|
108
|
+
line: number;
|
|
109
|
+
column: number;
|
|
110
|
+
/** Props as written, sorted. A className built from expressions keeps its literal classes only. */
|
|
111
|
+
props: Record<string, PropValue>;
|
|
112
|
+
/** True when it also takes `{...spread}` props, whose names are unknown. */
|
|
113
|
+
spread: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface RawElement {
|
|
117
|
+
/** The tag as written: `button`. */
|
|
118
|
+
element: string;
|
|
119
|
+
/** The `replaces` selector it matched: `button`, `input[type=checkbox]`. */
|
|
120
|
+
selector: string;
|
|
121
|
+
/** The components that replace it, most specific first. */
|
|
122
|
+
replacedBy: string[];
|
|
123
|
+
line: number;
|
|
124
|
+
column: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* `palette`: a primitive ramp step used directly, `bg-blue-500` or `text-primary-500`.
|
|
129
|
+
* `arbitrary`: a value in brackets, `p-[13px]` or `bg-[#fff]`.
|
|
130
|
+
*/
|
|
131
|
+
export type OffSystemKind = "palette" | "arbitrary";
|
|
132
|
+
|
|
133
|
+
export interface OffSystemValue {
|
|
134
|
+
/** The class as written, variants included: `hover:bg-blue-500`. */
|
|
135
|
+
class: string;
|
|
136
|
+
kind: OffSystemKind;
|
|
137
|
+
line: number;
|
|
138
|
+
column: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type OverrideKind = "colour" | "spacing" | "radius";
|
|
142
|
+
|
|
143
|
+
export interface Override {
|
|
144
|
+
component: string;
|
|
145
|
+
/** Each class in the className that overrides the component, with what it changes. */
|
|
146
|
+
classes: { class: string; kind: OverrideKind }[];
|
|
147
|
+
line: number;
|
|
148
|
+
column: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ComponentUsage {
|
|
152
|
+
name: string;
|
|
153
|
+
source: string;
|
|
154
|
+
uses: number;
|
|
155
|
+
/** Files it is used in. */
|
|
156
|
+
files: number;
|
|
157
|
+
/** How many uses pass each prop, by prop name. */
|
|
158
|
+
props: Record<string, number>;
|
|
159
|
+
/**
|
|
160
|
+
* Per variant axis, how many uses pick each option. A use that leaves the prop
|
|
161
|
+
* out counts under the axis's default, or `(unset)`; one that computes it
|
|
162
|
+
* counts under `(dynamic)`.
|
|
163
|
+
*/
|
|
164
|
+
variants?: Record<string, Record<string, number>>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface ElementUsage {
|
|
168
|
+
/** The `replaces` selector. */
|
|
169
|
+
selector: string;
|
|
170
|
+
/** Raw uses in the product. */
|
|
171
|
+
uses: number;
|
|
172
|
+
files: number;
|
|
173
|
+
replacedBy: string[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface SkippedFile {
|
|
177
|
+
file: string;
|
|
178
|
+
reason: string;
|
|
179
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Component, type ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
/** Keeps one broken example from blanking the whole page: the error shows where the example would. */
|
|
6
|
+
export class Boundary extends Component<{ label: string; children: ReactNode }, { error: Error | null }> {
|
|
7
|
+
state = { error: null as Error | null };
|
|
8
|
+
|
|
9
|
+
static getDerivedStateFromError(error: Error) {
|
|
10
|
+
return { error };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
if (!this.state.error) return this.props.children;
|
|
15
|
+
return (
|
|
16
|
+
<p data-slot="block-error" role="alert" className="rounded-md border border-dashed border-destructive p-3 text-xs">
|
|
17
|
+
<span className="font-medium">{this.props.label} did not render:</span> {this.state.error.message}
|
|
18
|
+
</p>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrast measured on what the browser paints, not on the values written in
|
|
3
|
+
* the stylesheet: a 1px canvas turns any computed CSS colour (oklch, color-mix,
|
|
4
|
+
* a var chain) into sRGB bytes. Browser only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
let context: CanvasRenderingContext2D | null = null;
|
|
8
|
+
|
|
9
|
+
function rgb(css: string): [number, number, number] | null {
|
|
10
|
+
if (typeof document === "undefined") return null;
|
|
11
|
+
if (!context) {
|
|
12
|
+
const canvas = document.createElement("canvas");
|
|
13
|
+
canvas.width = canvas.height = 1;
|
|
14
|
+
context = canvas.getContext("2d", { willReadFrequently: true });
|
|
15
|
+
}
|
|
16
|
+
if (!context) return null;
|
|
17
|
+
context.clearRect(0, 0, 1, 1);
|
|
18
|
+
context.fillStyle = "#000";
|
|
19
|
+
context.fillStyle = css;
|
|
20
|
+
context.fillRect(0, 0, 1, 1);
|
|
21
|
+
const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
|
|
22
|
+
return [r, g, b];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function luminance([r, g, b]: [number, number, number]): number {
|
|
26
|
+
const channel = (v: number) => {
|
|
27
|
+
const s = v / 255;
|
|
28
|
+
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
29
|
+
};
|
|
30
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** WCAG 2 contrast ratio between two CSS colours, or null where it cannot be measured. */
|
|
34
|
+
export function contrastRatio(foreground: string, background: string): number | null {
|
|
35
|
+
const a = rgb(foreground);
|
|
36
|
+
const b = rgb(background);
|
|
37
|
+
if (!a || !b) return null;
|
|
38
|
+
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
|
39
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function wcagGrade(ratio: number): "AAA" | "AA" | "AA large" | "Fail" {
|
|
43
|
+
return ratio >= 7 ? "AAA" : ratio >= 4.5 ? "AA" : ratio >= 3 ? "AA large" : "Fail";
|
|
44
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props back to JSX, so the code shown beside a preview is the code that drew it.
|
|
3
|
+
* Defaults and undefined values are left out: the snippet is what a person would write.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type JsxProps = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
export function toJSX(name: string, props: JsxProps, children?: string): string {
|
|
9
|
+
const attrs = Object.entries(props)
|
|
10
|
+
.filter(([key, value]) => value !== undefined && key !== "children")
|
|
11
|
+
.map(([key, value]) => attribute(key, value));
|
|
12
|
+
|
|
13
|
+
// text with braces or angle brackets is not valid JSX as written; quote it as an expression
|
|
14
|
+
if (children && /[{}<>]/.test(children)) children = `{${JSON.stringify(children)}}`;
|
|
15
|
+
const open = attrs.length ? `<${name} ${attrs.join(" ")}` : `<${name}`;
|
|
16
|
+
const oneLine = children ? `${open}>${children}</${name}>` : `${open} />`;
|
|
17
|
+
if (oneLine.length <= 80) return oneLine;
|
|
18
|
+
|
|
19
|
+
const lines = [`<${name}`, ...attrs.map((a) => ` ${a}`)];
|
|
20
|
+
return children ? `${lines.join("\n")}\n>\n ${children}\n</${name}>` : `${lines.join("\n")}\n/>`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function attribute(key: string, value: unknown): string {
|
|
24
|
+
if (value === true) return key;
|
|
25
|
+
if (typeof value === "string") return /["\n{}]/.test(value) ? `${key}={${JSON.stringify(value)}}` : `${key}="${value}"`;
|
|
26
|
+
if (typeof value === "number" || typeof value === "boolean") return `${key}={${String(value)}}`;
|
|
27
|
+
if (typeof value === "function") return `${key}={() => {}}`;
|
|
28
|
+
return `${key}={${JSON.stringify(value)}}`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createElement, isValidElement, type ComponentType, type ReactNode } from "react";
|
|
2
|
+
import { componentKey } from "./manifest";
|
|
3
|
+
import type { AssetEntry, ComponentEntry, PatternEntry } from "./manifest-types";
|
|
4
|
+
import { assets, components, examples, patterns } from "../registry.generated";
|
|
5
|
+
|
|
6
|
+
/** The real component a manifest entry describes, from the registry `designtools-blocks map` generates. */
|
|
7
|
+
export function componentFor(entry: ComponentEntry): ComponentType<any> | undefined {
|
|
8
|
+
return components[componentKey(entry)];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The examples module for a component: each named export is an example. */
|
|
12
|
+
export function examplesFor(entry: ComponentEntry): Record<string, unknown> | undefined {
|
|
13
|
+
return entry.examples ? examples[entry.examples.source] : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The pattern module: each named export is a composition. */
|
|
17
|
+
export function compositionsFor(pattern: PatternEntry): Record<string, unknown> | undefined {
|
|
18
|
+
return patterns[pattern.source];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A brand asset's URL, from its static import in the registry. */
|
|
22
|
+
export function assetUrl(asset: AssetEntry): string | undefined {
|
|
23
|
+
const imported = assets[asset.file];
|
|
24
|
+
return typeof imported === "string" ? imported : imported?.src;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Variant values are text in the manifest; boolean variants want booleans. */
|
|
28
|
+
export function coerce(values: Record<string, string>): Record<string, unknown> {
|
|
29
|
+
return Object.fromEntries(Object.entries(values).map(([k, v]) => [k, v === "true" ? true : v === "false" ? false : v]));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Missing({ name, what = "component" }: { name: string; what?: string }) {
|
|
33
|
+
return (
|
|
34
|
+
<p data-slot="block-missing" className="rounded-md border border-dashed border-border p-4 text-sm text-muted-foreground">
|
|
35
|
+
{name}'s {what} is not in the generated registry. Run <code className="font-mono">designtools-blocks map</code>{" "}
|
|
36
|
+
after rebuilding the manifest.
|
|
37
|
+
</p>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** An example export is a component that takes no props, or an element. */
|
|
42
|
+
export function renderExample(value: unknown, name: string): ReactNode {
|
|
43
|
+
if (isValidElement(value)) return value;
|
|
44
|
+
if (typeof value === "function") return createElement(value as ComponentType);
|
|
45
|
+
return <span className="text-xs text-muted-foreground">{name} is not a component or an element.</span>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The manifest schema: what `designtools-manifest build` writes to
|
|
3
|
+
* `<system>/manifest/`: components.json, tokens.json, rules.json,
|
|
4
|
+
* patterns.json, taxonomy.json and assets.json.
|
|
5
|
+
*
|
|
6
|
+
* This file has no imports on purpose. @designtools/blocks carries a byte-identical
|
|
7
|
+
* copy, so a project's docs read the manifest through the same types the tool
|
|
8
|
+
* wrote it with, and a test in this repo fails if the two drift.
|
|
9
|
+
*
|
|
10
|
+
* Every path is relative to the project root, with forward slashes. Arrays keep
|
|
11
|
+
* source order where order means something (variant axes, fixture states, token
|
|
12
|
+
* ramps); everything else is sorted, so a diff of the files shows only real changes.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const MANIFEST_SCHEMA = "designtools.manifest/1";
|
|
16
|
+
|
|
17
|
+
/** What every manifest file opens with. */
|
|
18
|
+
interface ManifestFile {
|
|
19
|
+
/** `@designtools/manifest@<version>`, so `check` can tell a tool upgrade from an edit. */
|
|
20
|
+
generator: string;
|
|
21
|
+
schema: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** components.json */
|
|
25
|
+
export interface ComponentsManifest extends ManifestFile {
|
|
26
|
+
/** The system folder the components were read from. */
|
|
27
|
+
system: string;
|
|
28
|
+
/** Sorted by name, then source. */
|
|
29
|
+
components: ComponentEntry[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ComponentEntry {
|
|
33
|
+
/** The export name, or `@name` when the JSDoc gives one. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** The exported identifier to import. Equals `name` unless `@name` renamed it. */
|
|
36
|
+
export: string;
|
|
37
|
+
/** The file that exports it. */
|
|
38
|
+
source: string;
|
|
39
|
+
/** The component's JSDoc, or its variant config's when the component has none. */
|
|
40
|
+
description?: string;
|
|
41
|
+
/** `@category`. Blocks group components without one under "Components". */
|
|
42
|
+
category?: string;
|
|
43
|
+
/** `@status`, or `deprecated` from `@deprecated`. Absent until someone decides it. */
|
|
44
|
+
status?: Status;
|
|
45
|
+
/** Why it has that status: the `@deprecated` reason, or text after `@status`. */
|
|
46
|
+
statusNote?: string;
|
|
47
|
+
/** `@use`, `@avoid`, `@instead`: when to reach for it, when not, and what instead. */
|
|
48
|
+
usage?: Usage;
|
|
49
|
+
/** `@example` snippets in the JSDoc, each one JSX. */
|
|
50
|
+
snippets?: string[];
|
|
51
|
+
/** `@see`, each one a link or a component name. */
|
|
52
|
+
see?: string[];
|
|
53
|
+
/** `@primitive`: the primitive it wraps, as a link or a package path. */
|
|
54
|
+
primitive?: string;
|
|
55
|
+
/** `@aria`: the ARIA pattern it follows, usually a link. */
|
|
56
|
+
aria?: string;
|
|
57
|
+
/** `@role` and `@keyboard`: what assistive technology meets, and the keys it answers. */
|
|
58
|
+
a11y?: { role?: string; keyboard?: string[] };
|
|
59
|
+
/** `@replaces`: raw elements this component stands in for, which the adherence report counts. */
|
|
60
|
+
replaces?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* What it also accepts beyond its own props: an intrinsic element (`"span"`)
|
|
63
|
+
* or a wrapped primitive (`"@base-ui/react/select:Root"`).
|
|
64
|
+
*/
|
|
65
|
+
inherits?: string[];
|
|
66
|
+
/** Its own props, and those of a primitive it wraps. Inherited DOM props are left out. */
|
|
67
|
+
props: Record<string, PropEntry>;
|
|
68
|
+
/** Absent when it has no variants by any route. */
|
|
69
|
+
variants?: VariantsEntry;
|
|
70
|
+
/** Every `data-slot` it renders, sorted. */
|
|
71
|
+
slots: string[];
|
|
72
|
+
/** Absent when the component has no examples file. */
|
|
73
|
+
examples?: ExamplesEntry;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type Status = "stable" | "emerging" | "deprecated";
|
|
77
|
+
|
|
78
|
+
export interface Usage {
|
|
79
|
+
use?: string;
|
|
80
|
+
avoid?: string;
|
|
81
|
+
/** Components, or other things, to use instead. */
|
|
82
|
+
instead?: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface PropEntry {
|
|
86
|
+
/** The type as written or resolved, e.g. `string`, `"sm" | "md"`, `(v: string) => void`. */
|
|
87
|
+
type: string;
|
|
88
|
+
/** Present when the type is a union of literals: each value, unquoted. */
|
|
89
|
+
values?: string[];
|
|
90
|
+
required: boolean;
|
|
91
|
+
/** The default, as source text, when the component destructures one. */
|
|
92
|
+
default?: string;
|
|
93
|
+
description?: string;
|
|
94
|
+
deprecated?: string;
|
|
95
|
+
/** The package that declares it, when it comes from a wrapped primitive rather than the project. */
|
|
96
|
+
from?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface VariantsEntry {
|
|
100
|
+
/** Where the axes came from: a `tv()` or `cva()` config, or the props' literal union types. */
|
|
101
|
+
from: "tv" | "cva" | "props";
|
|
102
|
+
/** The config's variable name. Absent when `from` is `"props"`. */
|
|
103
|
+
config?: string;
|
|
104
|
+
/** Source order. */
|
|
105
|
+
axes: VariantAxis[];
|
|
106
|
+
/** Conditions only, without their classes, in source order. */
|
|
107
|
+
compound?: Record<string, string | string[]>[];
|
|
108
|
+
/** A `tv()` config's named parts, in source order. */
|
|
109
|
+
parts?: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface VariantAxis {
|
|
113
|
+
name: string;
|
|
114
|
+
/** Source order. */
|
|
115
|
+
options: VariantOption[];
|
|
116
|
+
default?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface VariantOption {
|
|
120
|
+
name: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A co-located `*.examples.tsx`: `canonical` is the one to copy, `do*` and
|
|
126
|
+
* `dont*` render as pairs in order, anything else is a further example.
|
|
127
|
+
*/
|
|
128
|
+
export interface ExamplesEntry {
|
|
129
|
+
source: string;
|
|
130
|
+
canonical?: ExampleRef;
|
|
131
|
+
do: ExampleRef[];
|
|
132
|
+
dont: ExampleRef[];
|
|
133
|
+
other: ExampleRef[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface ExampleRef {
|
|
137
|
+
/** The export name. */
|
|
138
|
+
name: string;
|
|
139
|
+
/** Its JSDoc: why to do it, or why not. */
|
|
140
|
+
description?: string;
|
|
141
|
+
/** The JSX it returns, as written, for agents and code views. */
|
|
142
|
+
code?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** tokens.json */
|
|
146
|
+
export interface TokensManifest extends ManifestFile {
|
|
147
|
+
/** The stylesheets read, in the order given. */
|
|
148
|
+
sources: string[];
|
|
149
|
+
/** Source order: file by file, declaration by declaration. */
|
|
150
|
+
tokens: TokenEntry[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type TokenTier =
|
|
154
|
+
| "color"
|
|
155
|
+
| "font"
|
|
156
|
+
| "font-weight"
|
|
157
|
+
| "text"
|
|
158
|
+
| "leading"
|
|
159
|
+
| "tracking"
|
|
160
|
+
| "spacing"
|
|
161
|
+
| "size"
|
|
162
|
+
| "radius"
|
|
163
|
+
| "shadow"
|
|
164
|
+
| "ease"
|
|
165
|
+
| "duration"
|
|
166
|
+
| "motion"
|
|
167
|
+
| "breakpoint"
|
|
168
|
+
| "container"
|
|
169
|
+
| "other";
|
|
170
|
+
|
|
171
|
+
export interface TokenEntry {
|
|
172
|
+
/** The custom property, with its dashes: `--color-primary-500`. */
|
|
173
|
+
name: string;
|
|
174
|
+
tier: TokenTier;
|
|
175
|
+
/**
|
|
176
|
+
* The value in each context it is declared in. `default` is `@theme` or `:root`;
|
|
177
|
+
* `light` and `dark` are the mode blocks; a `p3:` prefix marks the
|
|
178
|
+
* `@media (color-gamut: p3)` layer. Any other selector is kept as written.
|
|
179
|
+
*/
|
|
180
|
+
values: Record<string, string>;
|
|
181
|
+
/** The comment above it, or above the run of declarations it belongs to. */
|
|
182
|
+
description?: string;
|
|
183
|
+
/** The stylesheet that first declares it. */
|
|
184
|
+
source: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** rules.json: brand and interface rules in one format. */
|
|
188
|
+
export interface RulesManifest extends ManifestFile {
|
|
189
|
+
/** Sorted by id. */
|
|
190
|
+
rules: RuleEntry[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface RuleEntry {
|
|
194
|
+
/** The file name without `.rule.json`, used as its anchor: `touch-target`. */
|
|
195
|
+
id: string;
|
|
196
|
+
kind: "brand" | "interface";
|
|
197
|
+
title: string;
|
|
198
|
+
/** The rule, in one or two sentences, for people first. */
|
|
199
|
+
statement: string;
|
|
200
|
+
/** Why it exists. */
|
|
201
|
+
rationale?: string;
|
|
202
|
+
/** The measurable line, e.g. `44 × 44 CSS px`. */
|
|
203
|
+
threshold?: string;
|
|
204
|
+
/** Components, elements, assets or pages it governs. */
|
|
205
|
+
appliesTo?: string[];
|
|
206
|
+
exceptions?: string[];
|
|
207
|
+
/** Tokens it rests on, e.g. `--size-2xl`. */
|
|
208
|
+
tokens?: string[];
|
|
209
|
+
/** Confirmed with the client, or an assumption still to confirm. */
|
|
210
|
+
status: "confirmed" | "assumption";
|
|
211
|
+
/** How it is proved: a test, a lint rule, an audit, or a person. */
|
|
212
|
+
check?: { kind: "test" | "lint" | "audit" | "manual"; ref?: string; description?: string };
|
|
213
|
+
source: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** patterns.json: the product's recurring screen compositions. */
|
|
217
|
+
export interface PatternsManifest extends ManifestFile {
|
|
218
|
+
/** Sorted by name. */
|
|
219
|
+
patterns: PatternEntry[];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface PatternEntry {
|
|
223
|
+
/** From the file name: `checkout` for `checkout.pattern.tsx`. */
|
|
224
|
+
id: string;
|
|
225
|
+
name: string;
|
|
226
|
+
description?: string;
|
|
227
|
+
status?: Status;
|
|
228
|
+
/** The system components it imports, by name, sorted. */
|
|
229
|
+
components: string[];
|
|
230
|
+
/** Each named export is a composition, in source order. */
|
|
231
|
+
examples: ExampleRef[];
|
|
232
|
+
source: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** taxonomy.json: one vocabulary, with the wrong forms to avoid. */
|
|
236
|
+
export interface TaxonomyManifest extends ManifestFile {
|
|
237
|
+
/** Sorted by name. */
|
|
238
|
+
terms: TermEntry[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface TermEntry {
|
|
242
|
+
/** The canonical form. */
|
|
243
|
+
name: string;
|
|
244
|
+
kind: "component" | "variant" | "token" | "pattern" | "rule" | "product";
|
|
245
|
+
/** Known wrong forms: `error` for `destructive`, `Modal` for `Dialog`, "log in" for "sign in". */
|
|
246
|
+
wrong?: string[];
|
|
247
|
+
note?: string;
|
|
248
|
+
/** Where it is defined: a component's source, a token's name, a rule's id. */
|
|
249
|
+
ref?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** assets.json: logos, icons and imagery, with how to use them. */
|
|
253
|
+
export interface AssetsManifest extends ManifestFile {
|
|
254
|
+
/** Sorted by file. */
|
|
255
|
+
assets: AssetEntry[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface AssetEntry {
|
|
259
|
+
file: string;
|
|
260
|
+
kind: "logo" | "icon" | "image" | "other";
|
|
261
|
+
name: string;
|
|
262
|
+
usage?: string;
|
|
263
|
+
/** e.g. `24px tall` */
|
|
264
|
+
minSize?: string;
|
|
265
|
+
/** e.g. `the height of the wordmark's x` or a length */
|
|
266
|
+
clearSpace?: string;
|
|
267
|
+
/** Surfaces it may sit on, as semantic colour names: `canvas`, `primary`. */
|
|
268
|
+
surfaces?: string[];
|
|
269
|
+
alt?: string;
|
|
270
|
+
/** Intrinsic size, read from an SVG's viewBox or a PNG's header. */
|
|
271
|
+
width?: number;
|
|
272
|
+
height?: number;
|
|
273
|
+
}
|