@conductor-by-89soone/tokens 0.0.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/LICENSE +21 -0
- package/README.md +42 -0
- package/bin/conductor-build-tokens.mjs +4 -0
- package/bin/conductor-check-contrast.mjs +18 -0
- package/bin/conductor-lint-tokens.mjs +18 -0
- package/dist/breakpoints.d.ts +16 -0
- package/dist/breakpoints.js +8 -0
- package/dist/cli.js +1635 -0
- package/dist/contrast-cli.js +1510 -0
- package/dist/contrast-report.json +2158 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +388 -0
- package/dist/lint-cli.js +348 -0
- package/dist/tokens.css +928 -0
- package/dist/tokens.d.ts +385 -0
- package/dist/tokens.js +377 -0
- package/dist/tokens.json +3081 -0
- package/package.json +54 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1635 @@
|
|
|
1
|
+
import { watch, mkdirSync, writeFileSync, rmSync, renameSync, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { resolve, dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
// src/cli.ts
|
|
6
|
+
|
|
7
|
+
// src/build/errors.ts
|
|
8
|
+
var EXIT_CODES = {
|
|
9
|
+
"TOK-CYCLE": 1,
|
|
10
|
+
"TOK-UNKNOWN-REF": 1,
|
|
11
|
+
"TOK-DEPTH": 1,
|
|
12
|
+
"TOK-TIER": 1,
|
|
13
|
+
"TOK-TIER-MISSING": 1,
|
|
14
|
+
"TOK-DUPLICATE-KEY": 1,
|
|
15
|
+
"TOK-ICON": 1,
|
|
16
|
+
"TOK-GROUP-SIZE": 1,
|
|
17
|
+
"TOK-USAGE": 1,
|
|
18
|
+
"TOK-VALUE": 1,
|
|
19
|
+
"TOK-Z-DUPLICATE": 1,
|
|
20
|
+
"TOK-PREFIX": 2,
|
|
21
|
+
"TOK-NAME-COLLISION": 2,
|
|
22
|
+
"TOK-MEDIA-VAR": 2,
|
|
23
|
+
"TOK-CP-DUPLICATE-ID": 1,
|
|
24
|
+
"TOK-CP-UNKNOWN-KEY": 1,
|
|
25
|
+
"TOK-CP-DECORATIVE": 1,
|
|
26
|
+
"TOK-CP-USAGE": 1,
|
|
27
|
+
"TOK-CP-COLOR": 1,
|
|
28
|
+
"TOK-CP-ALPHA": 1,
|
|
29
|
+
"TOK-LITERAL": 1,
|
|
30
|
+
"TOK-ARG": 3
|
|
31
|
+
};
|
|
32
|
+
var TokenBuildError = class extends Error {
|
|
33
|
+
code;
|
|
34
|
+
exitCode;
|
|
35
|
+
/** Indented lines printed under the summary: offending keys, cycle paths, hints. */
|
|
36
|
+
details;
|
|
37
|
+
constructor(code, summary, details = []) {
|
|
38
|
+
super(`error[${code}]: ${summary}`);
|
|
39
|
+
this.name = "TokenBuildError";
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.exitCode = EXIT_CODES[code];
|
|
42
|
+
this.details = details;
|
|
43
|
+
}
|
|
44
|
+
/** The full stderr rendering, summary line first. */
|
|
45
|
+
format() {
|
|
46
|
+
return [this.message, ...this.details.map((line) => ` ${line}`)].join("\n");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/schema.ts
|
|
51
|
+
var MAX_REFERENCE_DEPTH = 10;
|
|
52
|
+
var DEFAULT_SEGMENT = "DEFAULT";
|
|
53
|
+
var REFERENCE_PATTERN = /\{([^{}]+)\}/g;
|
|
54
|
+
function extractReferences(value2) {
|
|
55
|
+
return [...value2.matchAll(REFERENCE_PATTERN)].map((match) => match[1]);
|
|
56
|
+
}
|
|
57
|
+
function stripBraces(reference) {
|
|
58
|
+
const trimmed = reference.trim();
|
|
59
|
+
return trimmed.startsWith("{") && trimmed.endsWith("}") ? trimmed.slice(1, -1).trim() : trimmed;
|
|
60
|
+
}
|
|
61
|
+
function referencesOf(token) {
|
|
62
|
+
if (token.alias !== void 0) return [stripBraces(token.alias)];
|
|
63
|
+
return typeof token.value === "string" ? extractReferences(token.value) : [];
|
|
64
|
+
}
|
|
65
|
+
var TIER_RANK = {
|
|
66
|
+
primitive: 0,
|
|
67
|
+
semantic: 1,
|
|
68
|
+
component: 2
|
|
69
|
+
};
|
|
70
|
+
function tierRank(tier) {
|
|
71
|
+
return TIER_RANK[tier];
|
|
72
|
+
}
|
|
73
|
+
var FIXED_GROUP_SIZES = {
|
|
74
|
+
status: 7,
|
|
75
|
+
severity: 4,
|
|
76
|
+
meter: 3
|
|
77
|
+
};
|
|
78
|
+
var ICON_REQUIRED_GROUPS = ["status", "severity"];
|
|
79
|
+
function groupKeyOf(key) {
|
|
80
|
+
const [head] = key.split(".");
|
|
81
|
+
return head;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/build/reference.ts
|
|
85
|
+
function buildTokenIndex(tokens) {
|
|
86
|
+
const index = /* @__PURE__ */ new Map();
|
|
87
|
+
const duplicates = [];
|
|
88
|
+
for (const token of tokens) {
|
|
89
|
+
if (index.has(token.key)) duplicates.push(token.key);
|
|
90
|
+
else index.set(token.key, token);
|
|
91
|
+
}
|
|
92
|
+
if (duplicates.length > 0) {
|
|
93
|
+
throw new TokenBuildError(
|
|
94
|
+
"TOK-DUPLICATE-KEY",
|
|
95
|
+
`${duplicates.length} token key(s) declared more than once`,
|
|
96
|
+
[...duplicates, "hint: a token key is unique across primitives, palette and components."]
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return index;
|
|
100
|
+
}
|
|
101
|
+
function resolveReferenceTarget(index, reference) {
|
|
102
|
+
const key = stripBraces(reference);
|
|
103
|
+
if (index.has(key)) return key;
|
|
104
|
+
const defaulted = `${key}.DEFAULT`;
|
|
105
|
+
return index.has(defaulted) ? defaulted : void 0;
|
|
106
|
+
}
|
|
107
|
+
function formatCycle(path) {
|
|
108
|
+
return path.join(" \u2192 ");
|
|
109
|
+
}
|
|
110
|
+
function resolveTokens(index) {
|
|
111
|
+
const values2 = /* @__PURE__ */ new Map();
|
|
112
|
+
const edges = [];
|
|
113
|
+
function resolve2(key, chain) {
|
|
114
|
+
const cached = values2.get(key);
|
|
115
|
+
if (cached !== void 0) return cached;
|
|
116
|
+
const cycleStart = chain.indexOf(key);
|
|
117
|
+
if (cycleStart !== -1) {
|
|
118
|
+
const path = [...chain.slice(cycleStart), key];
|
|
119
|
+
throw new TokenBuildError("TOK-CYCLE", "circular token reference detected", [
|
|
120
|
+
formatCycle(path),
|
|
121
|
+
"hint: break the cycle; a token cannot resolve through itself."
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
if (chain.length > MAX_REFERENCE_DEPTH) {
|
|
125
|
+
throw new TokenBuildError(
|
|
126
|
+
"TOK-DEPTH",
|
|
127
|
+
`token reference chain exceeds the maximum depth of ${MAX_REFERENCE_DEPTH}`,
|
|
128
|
+
[formatCycle([...chain, key]), "hint: flatten the chain or point at a literal."]
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const token = index.get(key);
|
|
132
|
+
const nextChain = [...chain, key];
|
|
133
|
+
const depth = chain.length;
|
|
134
|
+
const resolveEdge = (reference) => {
|
|
135
|
+
const target = resolveReferenceTarget(index, reference);
|
|
136
|
+
if (target === void 0) {
|
|
137
|
+
throw new TokenBuildError("TOK-UNKNOWN-REF", "token reference points at an unknown key", [
|
|
138
|
+
`from: ${key}`,
|
|
139
|
+
`to: ${stripBraces(reference)}`,
|
|
140
|
+
"hint: check the spelling, or define the missing token."
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
const resolvedValue = resolve2(target, nextChain);
|
|
144
|
+
edges.push({ from: key, to: target, depth, resolvedValue });
|
|
145
|
+
return resolvedValue;
|
|
146
|
+
};
|
|
147
|
+
let resolved;
|
|
148
|
+
if (token.alias !== void 0) {
|
|
149
|
+
resolved = resolveEdge(token.alias);
|
|
150
|
+
} else if (typeof token.value === "number") {
|
|
151
|
+
resolved = String(token.value);
|
|
152
|
+
} else if (typeof token.value === "string") {
|
|
153
|
+
resolved = token.value.replace(
|
|
154
|
+
/\{([^{}]+)\}/g,
|
|
155
|
+
(_match, reference) => resolveEdge(reference)
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
throw new TokenBuildError("TOK-VALUE", "token declares neither `value` nor `alias`", [
|
|
159
|
+
`key: ${key}`,
|
|
160
|
+
"hint: a primitive needs a literal `value`; a semantic or component token needs one of the two."
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
values2.set(key, resolved);
|
|
164
|
+
return resolved;
|
|
165
|
+
}
|
|
166
|
+
for (const key of index.keys()) resolve2(key, []);
|
|
167
|
+
return { values: values2, edges };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/scales.ts
|
|
171
|
+
function semantic(key, value2, description) {
|
|
172
|
+
return { key, tier: "semantic", value: value2, usage: "decorative", description };
|
|
173
|
+
}
|
|
174
|
+
function aliased(key, alias, description) {
|
|
175
|
+
return { key, tier: "semantic", alias, usage: "decorative", description };
|
|
176
|
+
}
|
|
177
|
+
var FONT_SIZE_PX = {
|
|
178
|
+
"2xs": 10,
|
|
179
|
+
xs: 11,
|
|
180
|
+
sm: 12,
|
|
181
|
+
base: 13,
|
|
182
|
+
md: 14,
|
|
183
|
+
lg: 16,
|
|
184
|
+
xl: 20
|
|
185
|
+
};
|
|
186
|
+
var LINE_HEIGHT_RATIO = {
|
|
187
|
+
"2xs": 1.4,
|
|
188
|
+
xs: 1.45,
|
|
189
|
+
sm: 1.5,
|
|
190
|
+
base: 1.5,
|
|
191
|
+
md: 1.5,
|
|
192
|
+
lg: 1.5,
|
|
193
|
+
xl: 1.3
|
|
194
|
+
};
|
|
195
|
+
var FONT_STEPS = Object.keys(FONT_SIZE_PX);
|
|
196
|
+
function roundHalfUp(value2) {
|
|
197
|
+
return Math.floor(value2 + 0.5);
|
|
198
|
+
}
|
|
199
|
+
function lineHeightPx(step) {
|
|
200
|
+
return roundHalfUp(FONT_SIZE_PX[step] * LINE_HEIGHT_RATIO[step]);
|
|
201
|
+
}
|
|
202
|
+
var fontSize = FONT_STEPS.map(
|
|
203
|
+
(step) => semantic(`font.size.${step}`, `${FONT_SIZE_PX[step]}px`, `Font size step \`${step}\`.`)
|
|
204
|
+
);
|
|
205
|
+
var fontLineHeight = FONT_STEPS.map(
|
|
206
|
+
(step) => semantic(
|
|
207
|
+
`font.lineHeight.${step}`,
|
|
208
|
+
`${lineHeightPx(step)}px`,
|
|
209
|
+
`Line height paired with \`font.size.${step}\`.`
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
var space = [
|
|
213
|
+
aliased("space.1", "scale.4", "Tightest gap. Badge padding block."),
|
|
214
|
+
aliased("space.2", "scale.8", "Button padding block, button gap."),
|
|
215
|
+
aliased("space.3", "scale.12", "Table cell padding inline, input padding inline."),
|
|
216
|
+
aliased("space.4", "scale.16", "Table cell padding block."),
|
|
217
|
+
aliased("space.5", "scale.24", "Card padding, card grid gap, content stack gap."),
|
|
218
|
+
aliased("space.6", "scale.32", "Page stack gap."),
|
|
219
|
+
aliased("space.7", "scale.40", "Section spacing."),
|
|
220
|
+
aliased("space.8", "scale.48", "Widest gap.")
|
|
221
|
+
];
|
|
222
|
+
var radius = [
|
|
223
|
+
aliased("radius.xs", "curve.6", "Smallest radius."),
|
|
224
|
+
aliased("radius.sm", "curve.9", "Form control radius."),
|
|
225
|
+
aliased("radius.md", "curve.12", "Button radius, compact card radius."),
|
|
226
|
+
aliased("radius.lg", "curve.18", "Card and overlay radius."),
|
|
227
|
+
aliased("radius.xl", "curve.24", "Largest radius."),
|
|
228
|
+
semantic("radius.pill", "9999px", "Fully rounded badge, toggle, marker and progress geometry.")
|
|
229
|
+
];
|
|
230
|
+
var Z_LAYERS = {
|
|
231
|
+
base: 0,
|
|
232
|
+
raised: 10,
|
|
233
|
+
sticky: 20,
|
|
234
|
+
drawer: 30,
|
|
235
|
+
overlay: 40,
|
|
236
|
+
popover: 50
|
|
237
|
+
};
|
|
238
|
+
var zDescriptions = {
|
|
239
|
+
base: "Normal flow.",
|
|
240
|
+
raised: "Lifts a focus ring out of an ancestor's `overflow: hidden`.",
|
|
241
|
+
sticky: "The app shell top bar.",
|
|
242
|
+
drawer: "Side navigation and the `Drawer` panel.",
|
|
243
|
+
overlay: "`Dialog` and `Drawer` scrims.",
|
|
244
|
+
popover: "`Tooltip`, `DropdownMenu`, `Select` content, skip link."
|
|
245
|
+
};
|
|
246
|
+
var z = Object.keys(Z_LAYERS).map(
|
|
247
|
+
(layer) => semantic(`z.${layer}`, Z_LAYERS[layer], zDescriptions[layer])
|
|
248
|
+
);
|
|
249
|
+
var BREAKPOINT_PX = {
|
|
250
|
+
sm: 560,
|
|
251
|
+
md: 800,
|
|
252
|
+
lg: 1080
|
|
253
|
+
};
|
|
254
|
+
var breakpointDescriptions = {
|
|
255
|
+
sm: "Single-column card grid; taller form controls.",
|
|
256
|
+
md: "Single-column split layout; table horizontal scroll; off-canvas navigation.",
|
|
257
|
+
lg: "Narrower navigation; narrower minimum grid column."
|
|
258
|
+
};
|
|
259
|
+
var breakpoint = Object.keys(BREAKPOINT_PX).map(
|
|
260
|
+
(name) => semantic(`breakpoint.${name}`, `${BREAKPOINT_PX[name]}px`, breakpointDescriptions[name])
|
|
261
|
+
);
|
|
262
|
+
var motion = [
|
|
263
|
+
semantic("motion.fast", "140ms {ease.entrance}", "Colour, background and border transitions."),
|
|
264
|
+
semantic("motion.standard", "240ms {ease.entrance}", "Overlay and page entrance."),
|
|
265
|
+
semantic("motion.bounce", "300ms {ease.overshoot}", "Toggle and switch settle.")
|
|
266
|
+
];
|
|
267
|
+
var scaleTokens = [
|
|
268
|
+
...fontSize,
|
|
269
|
+
...fontLineHeight,
|
|
270
|
+
...space,
|
|
271
|
+
...radius,
|
|
272
|
+
...z,
|
|
273
|
+
...breakpoint,
|
|
274
|
+
...motion
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
// src/components.ts
|
|
278
|
+
function component(key, source, description, usage = "decorative") {
|
|
279
|
+
return { key, tier: "component", usage, description, ...source };
|
|
280
|
+
}
|
|
281
|
+
var value = (literal) => ({ value: literal });
|
|
282
|
+
var ref = (key) => ({ alias: key });
|
|
283
|
+
var button = [
|
|
284
|
+
component("button.minHeight", value("40px"), "Minimum target height."),
|
|
285
|
+
component("button.minHeightCompact", value("42px"), "Minimum height below `breakpoint.sm`."),
|
|
286
|
+
component("button.paddingBlock", ref("space.2"), "Vertical padding."),
|
|
287
|
+
component("button.paddingInline", value("14px"), "Horizontal padding."),
|
|
288
|
+
component("button.radius", ref("radius.md"), "Corner radius."),
|
|
289
|
+
component("button.fontSize", ref("font.size.md"), "Label size. 14px for body-level readability."),
|
|
290
|
+
component("button.gap", ref("space.2"), "Gap between icon and label."),
|
|
291
|
+
component("button.transition", ref("motion.fast"), "State transition."),
|
|
292
|
+
component("button.primary.background", ref("accent.DEFAULT"), "Primary fill."),
|
|
293
|
+
component("button.primary.backgroundHover", ref("accent.DEFAULT"), "Primary fill stays flat on hover."),
|
|
294
|
+
component("button.primary.text", ref("text.inverse"), "Primary label. 5.39:1 on the fill.", "body"),
|
|
295
|
+
component("button.primary.border", value("transparent"), "Primary needs no edge."),
|
|
296
|
+
component("button.primary.shadow", ref("elevation.raised"), "Primary resting shadow."),
|
|
297
|
+
component("button.primary.shadowHover", ref("elevation.hover"), "Primary hover shadow."),
|
|
298
|
+
component("button.secondary.background", ref("surface.raised"), "Secondary fill."),
|
|
299
|
+
component("button.secondary.backgroundHover", ref("surface.elevated"), "Secondary hover fill."),
|
|
300
|
+
component("button.secondary.text", ref("text.primary"), "Secondary label.", "body"),
|
|
301
|
+
component(
|
|
302
|
+
"button.secondary.border",
|
|
303
|
+
ref("border.strong"),
|
|
304
|
+
"Secondary edge. Not `border.control`: FR-THM-005 AC-2 scopes that token to the five form controls, and a button is identified by its fill, label and focus ring."
|
|
305
|
+
),
|
|
306
|
+
component("button.secondary.borderHover", ref("accent.DEFAULT"), "Secondary hover edge."),
|
|
307
|
+
component("button.ghost.background", value("transparent"), "Ghost has no fill."),
|
|
308
|
+
component("button.ghost.backgroundHover", ref("state.hover"), "Ghost hover wash."),
|
|
309
|
+
component("button.ghost.text", ref("text.muted"), "Ghost label.", "body"),
|
|
310
|
+
component("button.ghost.textHover", ref("text.primary"), "Ghost hover label.", "body"),
|
|
311
|
+
component("button.ghost.border", value("transparent"), "Ghost has no edge."),
|
|
312
|
+
component("button.disabled.background", ref("state.disabled"), "Disabled fill."),
|
|
313
|
+
component("button.disabled.text", ref("text.muted"), "Disabled label. 5.05:1 on the fill.", "body"),
|
|
314
|
+
component("button.disabled.border", ref("border.default"), "Disabled edge."),
|
|
315
|
+
component("button.policyDisabled.background", ref("state.disabledPolicy"), "Policy-blocked fill."),
|
|
316
|
+
component(
|
|
317
|
+
"button.policyDisabled.text",
|
|
318
|
+
ref("meter.exceeded"),
|
|
319
|
+
"Policy-blocked label. 5.27:1 on the dark amber fill (CP-041); `status.danger` would be 3.87:1.",
|
|
320
|
+
"body"
|
|
321
|
+
),
|
|
322
|
+
component("button.policyDisabled.border", ref("status.danger"), "Policy-blocked edge."),
|
|
323
|
+
component("button.focusRing", ref("focusRing"), "Focus indicator.")
|
|
324
|
+
];
|
|
325
|
+
var card = [
|
|
326
|
+
component("card.background", ref("surface.glass"), "Card fill."),
|
|
327
|
+
component("card.border", ref("border.default"), "Card edge."),
|
|
328
|
+
component("card.radius", ref("radius.lg"), "Card radius."),
|
|
329
|
+
component("card.radiusCompact", ref("radius.md"), "Card radius below `breakpoint.sm`."),
|
|
330
|
+
component("card.padding", ref("space.5"), "Card padding."),
|
|
331
|
+
component("card.paddingCompact", value("18px"), "Card padding below `breakpoint.sm`."),
|
|
332
|
+
component("card.shadow", ref("elevation.raised"), "Card resting shadow."),
|
|
333
|
+
component("card.interactive.borderHover", ref("border.strong"), "Interactive card hover edge."),
|
|
334
|
+
component("card.interactive.shadowHover", ref("elevation.hover"), "Interactive card hover shadow."),
|
|
335
|
+
component("card.interactive.translateHover", value("-2px"), "Interactive card hover lift."),
|
|
336
|
+
component("card.interactive.transition", ref("motion.fast"), "Interactive card transition."),
|
|
337
|
+
component("card.grid.minColumn", value("320px"), "`auto-fill` minimum column width."),
|
|
338
|
+
component("card.grid.minColumnCompact", value("260px"), "Minimum column below `breakpoint.lg`."),
|
|
339
|
+
component("card.grid.gap", ref("space.5"), "Grid gap.")
|
|
340
|
+
];
|
|
341
|
+
var badge = [
|
|
342
|
+
component("badge.radius", value("9999px"), "Pill radius."),
|
|
343
|
+
component("badge.paddingBlock", ref("space.1"), "Vertical padding."),
|
|
344
|
+
component("badge.paddingInline", value("10px"), "Horizontal padding."),
|
|
345
|
+
component("badge.fontSize", ref("font.size.sm"), "Label size."),
|
|
346
|
+
component("badge.lineHeight", ref("font.lineHeight.sm"), "Label line height."),
|
|
347
|
+
component("badge.gap", value("6px"), "Gap between icon and label."),
|
|
348
|
+
component("badge.iconSize", value("12px"), "Icon box."),
|
|
349
|
+
component("badge.fill.text", ref("text.inverse"), "Label on a status fill. 5.03:1 to 9.87:1.", "body"),
|
|
350
|
+
component("badge.marker.background", ref("surface.raised"), "Marker-shape background."),
|
|
351
|
+
component("badge.marker.text", ref("text.primary"), "Marker-shape label. 15.77:1.", "body"),
|
|
352
|
+
component("badge.marker.border", ref("border.strong"), "Marker-shape edge."),
|
|
353
|
+
component("badge.marker.dotSize", value("9px"), "Status dot diameter."),
|
|
354
|
+
component("badge.marker.dotRing", ref("surface.raised"), "Marker dot ring."),
|
|
355
|
+
component("badge.marker.dotRingWidth", value("2px"), "Marker dot ring width."),
|
|
356
|
+
component("badge.severity.text", ref("text.primary"), "Label on a severity fill. 4.67:1 to 9.32:1.", "body")
|
|
357
|
+
];
|
|
358
|
+
var table = [
|
|
359
|
+
component("table.headerText", ref("text.muted"), "Column header text.", "body"),
|
|
360
|
+
component("table.headerFontSize", ref("font.size.base"), "Column header size."),
|
|
361
|
+
component("table.headerBorder", ref("border.default"), "Header rule."),
|
|
362
|
+
component("table.headerBorderWidth", value("2px"), "Header rule width."),
|
|
363
|
+
component(
|
|
364
|
+
"table.cellBorder",
|
|
365
|
+
ref("border.default"),
|
|
366
|
+
"Row rule. A decorative border is correct here: row separators divide content, they do not identify a component, so WCAG 1.4.11 does not apply."
|
|
367
|
+
),
|
|
368
|
+
component("table.cellPaddingBlock", ref("space.4"), "Cell vertical padding."),
|
|
369
|
+
component("table.cellPaddingInline", ref("space.3"), "Cell horizontal padding."),
|
|
370
|
+
component("table.rowBackgroundHover", ref("state.hover"), "Row hover wash."),
|
|
371
|
+
component("table.rowTransition", ref("motion.fast"), "Row hover transition."),
|
|
372
|
+
component("table.linkText", ref("text.secondary"), "In-cell link.", "body"),
|
|
373
|
+
component("table.linkTextHover", ref("text.primary"), "In-cell link hover.", "body"),
|
|
374
|
+
component("table.numFontFamily", ref("font.mono"), "Numeric cell font."),
|
|
375
|
+
component("table.scrollBreakpoint", ref("breakpoint.md"), "Below this, the table scrolls.")
|
|
376
|
+
];
|
|
377
|
+
var timeline = [
|
|
378
|
+
component("timeline.background", ref("surface.timeline"), "Timeline fill."),
|
|
379
|
+
component("timeline.border", ref("border.default"), "Timeline edge."),
|
|
380
|
+
component("timeline.stepBackgroundHover", ref("state.hover"), "Interactive step hover fill."),
|
|
381
|
+
component("timeline.markerBackground", ref("accent.DEFAULT"), "Default marker fill."),
|
|
382
|
+
component("timeline.markerRing", ref("accent.glow"), "Default marker glow."),
|
|
383
|
+
component("timeline.markerBorder", ref("surface.timeline"), "Marker ring edge.")
|
|
384
|
+
];
|
|
385
|
+
var codeBlock = [
|
|
386
|
+
component("codeBlock.background", ref("surface.timeline"), "Code block fill."),
|
|
387
|
+
component("codeBlock.border", ref("border.default"), "Code block edge."),
|
|
388
|
+
component("codeBlock.text", ref("text.monoPayload"), "Code block text.", "body")
|
|
389
|
+
];
|
|
390
|
+
var kbd = [
|
|
391
|
+
component("kbd.background", ref("surface.raised"), "Keycap fill."),
|
|
392
|
+
component("kbd.border", ref("border.default"), "Keycap edge."),
|
|
393
|
+
component("kbd.borderBottom", ref("border.strong"), "Keycap lower edge."),
|
|
394
|
+
component("kbd.text", ref("text.secondary"), "Keycap text.", "body")
|
|
395
|
+
];
|
|
396
|
+
var banner = [
|
|
397
|
+
component("banner.info.background", ref("surface.raised"), "Info banner surface."),
|
|
398
|
+
component("banner.info.border", ref("status.running"), "Info banner edge."),
|
|
399
|
+
component("banner.info.text", ref("text.secondary"), "Info banner body text.", "body"),
|
|
400
|
+
component("banner.warning.background", ref("surface.raised"), "Warning banner surface."),
|
|
401
|
+
component("banner.warning.border", ref("status.waiting"), "Warning banner edge."),
|
|
402
|
+
component("banner.warning.text", ref("text.secondary"), "Warning banner body text.", "body"),
|
|
403
|
+
component("banner.danger.background", ref("surface.raised"), "Danger banner surface."),
|
|
404
|
+
component("banner.danger.border", ref("status.danger"), "Danger banner edge."),
|
|
405
|
+
component("banner.danger.text", ref("text.secondary"), "Danger banner body text.", "body")
|
|
406
|
+
];
|
|
407
|
+
var emptyState = [
|
|
408
|
+
component("emptyState.titleText", ref("text.primary"), "Empty-state title.", "body"),
|
|
409
|
+
component("emptyState.descriptionText", ref("text.muted"), "Empty-state description.", "body"),
|
|
410
|
+
component("emptyState.padding", ref("space.6"), "Empty-state padding.")
|
|
411
|
+
];
|
|
412
|
+
var feedbackMeter = [
|
|
413
|
+
component("feedbackMeter.trackBackground", ref("surface.track"), "Meter track."),
|
|
414
|
+
component("feedbackMeter.fillNormal", ref("meter.normal"), "Normal meter fill."),
|
|
415
|
+
component("feedbackMeter.fillWarning", ref("meter.warning"), "Warning meter fill."),
|
|
416
|
+
component("feedbackMeter.fillExceeded", ref("meter.exceeded"), "Exceeded meter fill."),
|
|
417
|
+
component("feedbackMeter.height", value("8px"), "Meter track height.")
|
|
418
|
+
];
|
|
419
|
+
var progressRing = [
|
|
420
|
+
component("progressRing.trackStroke", ref("surface.track"), "Progress-ring track."),
|
|
421
|
+
component("progressRing.indicatorStroke", ref("accent.DEFAULT"), "Progress-ring indicator."),
|
|
422
|
+
component("progressRing.labelText", ref("text.primary"), "Progress-ring label.", "body"),
|
|
423
|
+
component("progressRing.size", value("64px"), "Medium progress-ring diameter."),
|
|
424
|
+
component("progressRing.sizeCompact", value("40px"), "Small progress-ring diameter.")
|
|
425
|
+
];
|
|
426
|
+
var spinner = [
|
|
427
|
+
component("spinner.trackStroke", ref("surface.track"), "Spinner track."),
|
|
428
|
+
component("spinner.indicatorStroke", ref("accent.DEFAULT"), "Spinner indicator."),
|
|
429
|
+
component("spinner.size", value("24px"), "Medium spinner diameter."),
|
|
430
|
+
component("spinner.sizeCompact", value("16px"), "Small spinner diameter.")
|
|
431
|
+
];
|
|
432
|
+
var input = [
|
|
433
|
+
component("input.minHeight", value("40px"), "Minimum target height."),
|
|
434
|
+
component("input.minHeightCompact", value("42px"), "Minimum height below `breakpoint.sm`."),
|
|
435
|
+
component("input.background", ref("surface.base"), "Field fill."),
|
|
436
|
+
component("input.backgroundFocus", ref("surface.canvas"), "Focused field fill."),
|
|
437
|
+
component("input.text", ref("text.primary"), "Field value.", "body"),
|
|
438
|
+
component(
|
|
439
|
+
"input.placeholder",
|
|
440
|
+
ref("text.faint"),
|
|
441
|
+
"Placeholder. `text.faint` is allowed here and the dark field sits on `surface.base`, so the combination FR-THM-005 AC-3 forbids never renders."
|
|
442
|
+
),
|
|
443
|
+
component("input.border", ref("border.control"), "Field edge. 3.23:1 on `surface.raised`."),
|
|
444
|
+
component("input.borderHover", ref("border.control"), "Hovered field edge."),
|
|
445
|
+
component("input.borderFocus", ref("accent.DEFAULT"), "Focused field edge."),
|
|
446
|
+
component("input.borderInvalid", ref("status.danger"), "Invalid field edge."),
|
|
447
|
+
component("input.radius", ref("radius.sm"), "Field radius."),
|
|
448
|
+
component("input.paddingBlock", value("9px"), "Field vertical padding."),
|
|
449
|
+
component("input.paddingInline", ref("space.3"), "Field horizontal padding."),
|
|
450
|
+
component("input.focusRing", ref("focusRing"), "Focus indicator."),
|
|
451
|
+
component("input.label.text", ref("text.secondary"), "Field label with stronger hierarchy.", "body"),
|
|
452
|
+
component("input.label.fontSize", ref("font.size.base"), "Field label size."),
|
|
453
|
+
component("input.error.text", ref("status.danger"), "Field error message.", "body"),
|
|
454
|
+
component("input.transition", ref("motion.fast"), "Field transition.")
|
|
455
|
+
];
|
|
456
|
+
var overlay = [
|
|
457
|
+
component("overlay.scrim", ref("surface.overlay"), "Backdrop behind Dialog and Drawer."),
|
|
458
|
+
component("overlay.scrimBlur", value("4px"), "Backdrop blur radius."),
|
|
459
|
+
component("overlay.scrimZ", ref("z.overlay"), "Backdrop stacking layer."),
|
|
460
|
+
component("overlay.background", ref("surface.glass"), "Overlay panel fill."),
|
|
461
|
+
component("overlay.backgroundBlur", value("16px"), "Overlay panel backdrop blur."),
|
|
462
|
+
component("overlay.border", ref("border.strong"), "Overlay panel edge."),
|
|
463
|
+
component("overlay.radius", ref("radius.lg"), "Overlay panel radius."),
|
|
464
|
+
component("overlay.shadow", ref("elevation.overlay"), "Overlay panel shadow."),
|
|
465
|
+
component("overlay.contentZ", ref("z.popover"), "Tooltip, menu and select content layer."),
|
|
466
|
+
component("overlay.drawerZ", ref("z.drawer"), "Drawer panel layer."),
|
|
467
|
+
component("overlay.drawerBackground", ref("surface.raised"), "Drawer panel fill."),
|
|
468
|
+
component("overlay.drawerWidth", value("500px"), "Drawer panel width."),
|
|
469
|
+
component("overlay.enter", ref("motion.standard"), "Overlay entrance."),
|
|
470
|
+
component("overlay.exit", ref("motion.fast"), "Overlay exit.")
|
|
471
|
+
];
|
|
472
|
+
var HEADING_MIN_PX = FONT_SIZE_PX.xl * 1.2;
|
|
473
|
+
var HEADING_MAX_PX = FONT_SIZE_PX.xl * 1.6;
|
|
474
|
+
var page = [
|
|
475
|
+
component(
|
|
476
|
+
"page.headingSize",
|
|
477
|
+
value(`clamp(${HEADING_MIN_PX}px, 3vw, ${HEADING_MAX_PX}px)`),
|
|
478
|
+
"Responsive page heading size, derived from `font.size.xl` (\xD7 1.2 and \xD7 1.6)."
|
|
479
|
+
),
|
|
480
|
+
component(
|
|
481
|
+
"page.headingLineHeight",
|
|
482
|
+
value("1.16"),
|
|
483
|
+
"The only unitless line height: `clamp()` makes a px value impossible here."
|
|
484
|
+
),
|
|
485
|
+
component("page.stackGap", ref("space.6"), "Gap between page sections."),
|
|
486
|
+
component("page.stackGapCompact", ref("space.5"), "Section gap below `breakpoint.sm`."),
|
|
487
|
+
component("page.contentStackGap", ref("space.5"), "Gap inside a content stack."),
|
|
488
|
+
component("page.splitBreakpoint", ref("breakpoint.md"), "Below this, a split layout stacks."),
|
|
489
|
+
component("page.splitAsideWidth", value("minmax(280px, 340px)"), "Split layout aside track."),
|
|
490
|
+
component("page.shellNavWidth", value("272px"), "App shell navigation width."),
|
|
491
|
+
component("page.shellNavWidthCompact", value("232px"), "Navigation width below `breakpoint.lg`."),
|
|
492
|
+
component("page.topbarZ", ref("z.sticky"), "Top bar stacking layer."),
|
|
493
|
+
component("page.navZ", ref("z.drawer"), "Navigation stacking layer."),
|
|
494
|
+
component("page.skipLinkZ", ref("z.popover"), "Skip link stacking layer.")
|
|
495
|
+
];
|
|
496
|
+
var shell = [
|
|
497
|
+
component("appShell.navWidth", ref("page.shellNavWidth"), "AppShell desktop navigation width."),
|
|
498
|
+
component("appShell.mainMaxWidth", value("1480px"), "AppShell main content maximum width."),
|
|
499
|
+
component("appShell.overlayBackground", ref("surface.overlay"), "Mobile navigation scrim."),
|
|
500
|
+
component("skipLink.background", ref("accent.DEFAULT"), "Skip-link fill."),
|
|
501
|
+
component("skipLink.text", ref("text.inverse"), "Skip-link label."),
|
|
502
|
+
component("navList.background", ref("surface.raised"), "Navigation surface."),
|
|
503
|
+
component("navList.border", ref("border.subtle"), "Navigation surface edge."),
|
|
504
|
+
component("navItem.text", ref("text.muted"), "Navigation item label."),
|
|
505
|
+
component("navItem.textActive", ref("text.primary"), "Active navigation item label."),
|
|
506
|
+
component("navItem.backgroundHover", ref("state.hover"), "Navigation item hover wash."),
|
|
507
|
+
component("navItem.backgroundActive", ref("state.selected"), "Active navigation item wash."),
|
|
508
|
+
component("navItem.indicator", ref("accent.DEFAULT"), "Active navigation item indicator."),
|
|
509
|
+
component("navSectionLabel.text", ref("text.faint"), "Navigation section label."),
|
|
510
|
+
component("font.weight.sectionLabel", value(700), "Navigation section label weight."),
|
|
511
|
+
component("topBar.background", ref("surface.glass"), "TopBar glass surface."),
|
|
512
|
+
component("topBar.border", ref("border.subtle"), "TopBar lower edge."),
|
|
513
|
+
component("topBar.eyebrowText", ref("text.faint"), "TopBar eyebrow label."),
|
|
514
|
+
component("topBar.titleText", ref("text.secondary"), "TopBar context title."),
|
|
515
|
+
component("topBar.minHeight", value("68px"), "TopBar minimum height.")
|
|
516
|
+
];
|
|
517
|
+
var componentTokens = [
|
|
518
|
+
...button,
|
|
519
|
+
...card,
|
|
520
|
+
...badge,
|
|
521
|
+
...table,
|
|
522
|
+
...timeline,
|
|
523
|
+
...codeBlock,
|
|
524
|
+
...kbd,
|
|
525
|
+
...banner,
|
|
526
|
+
...emptyState,
|
|
527
|
+
...feedbackMeter,
|
|
528
|
+
...progressRing,
|
|
529
|
+
...spinner,
|
|
530
|
+
...input,
|
|
531
|
+
...overlay,
|
|
532
|
+
...page,
|
|
533
|
+
...shell
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
// src/palette.dark.ts
|
|
537
|
+
function darkToken(input2) {
|
|
538
|
+
return { tier: "semantic", ...input2 };
|
|
539
|
+
}
|
|
540
|
+
var surface = [
|
|
541
|
+
darkToken({
|
|
542
|
+
key: "surface.base",
|
|
543
|
+
usage: "decorative",
|
|
544
|
+
value: "#080b12",
|
|
545
|
+
description: "Application background. Contrast-exempt: surfaces are background arguments."
|
|
546
|
+
}),
|
|
547
|
+
darkToken({
|
|
548
|
+
key: "surface.canvas",
|
|
549
|
+
usage: "decorative",
|
|
550
|
+
value: "#0b101a",
|
|
551
|
+
description: "Content canvas. Contrast-exempt: surfaces are background arguments."
|
|
552
|
+
}),
|
|
553
|
+
darkToken({
|
|
554
|
+
key: "surface.subtle",
|
|
555
|
+
usage: "decorative",
|
|
556
|
+
value: "#101722",
|
|
557
|
+
description: "Recessed fill. Contrast-exempt: surfaces are background arguments."
|
|
558
|
+
}),
|
|
559
|
+
darkToken({
|
|
560
|
+
key: "surface.2",
|
|
561
|
+
usage: "decorative",
|
|
562
|
+
alias: "surface.subtle",
|
|
563
|
+
description: "Source alias of `surface.subtle`, kept as a reference (FR-THM-001 AC-2)."
|
|
564
|
+
}),
|
|
565
|
+
darkToken({
|
|
566
|
+
key: "surface.raised",
|
|
567
|
+
usage: "decorative",
|
|
568
|
+
value: "#141d2a",
|
|
569
|
+
description: "Card and panel fill. Contrast-exempt: surfaces are background arguments."
|
|
570
|
+
}),
|
|
571
|
+
darkToken({
|
|
572
|
+
key: "surface.elevated",
|
|
573
|
+
usage: "decorative",
|
|
574
|
+
value: "#192535",
|
|
575
|
+
description: "Highest surface. Contrast-exempt: surfaces are background arguments."
|
|
576
|
+
}),
|
|
577
|
+
darkToken({
|
|
578
|
+
key: "surface.overlay",
|
|
579
|
+
usage: "decorative",
|
|
580
|
+
value: "rgba(4, 7, 12, 0.78)",
|
|
581
|
+
description: "Dialog scrim. Contrast-exempt: nothing is drawn on top of a scrim."
|
|
582
|
+
}),
|
|
583
|
+
darkToken({
|
|
584
|
+
key: "surface.glass",
|
|
585
|
+
usage: "decorative",
|
|
586
|
+
value: "rgba(16, 23, 34, 0.82)",
|
|
587
|
+
description: "Translucent panel behind `backdrop-filter`. Contrast-exempt: translucent layer."
|
|
588
|
+
}),
|
|
589
|
+
darkToken({
|
|
590
|
+
key: "surface.timeline",
|
|
591
|
+
usage: "decorative",
|
|
592
|
+
value: "#0d141f",
|
|
593
|
+
description: "Timeline rail. Contrast-exempt: surfaces are background arguments."
|
|
594
|
+
}),
|
|
595
|
+
darkToken({
|
|
596
|
+
key: "surface.track",
|
|
597
|
+
usage: "decorative",
|
|
598
|
+
value: "rgba(255, 255, 255, 0.05)",
|
|
599
|
+
description: "Progress and meter track, matching the source 1:1."
|
|
600
|
+
})
|
|
601
|
+
];
|
|
602
|
+
var text = [
|
|
603
|
+
darkToken({
|
|
604
|
+
key: "text.primary",
|
|
605
|
+
usage: "body",
|
|
606
|
+
value: "#f4f7fb",
|
|
607
|
+
description: "Top of the text hierarchy. 14.40:1 to 18.32:1 across the five surfaces."
|
|
608
|
+
}),
|
|
609
|
+
darkToken({
|
|
610
|
+
key: "text.secondary",
|
|
611
|
+
usage: "body",
|
|
612
|
+
value: "#c5cfdd",
|
|
613
|
+
description: "Supporting copy. 9.83:1 to 12.51:1 across the five surfaces."
|
|
614
|
+
}),
|
|
615
|
+
darkToken({
|
|
616
|
+
key: "text.muted",
|
|
617
|
+
usage: "body",
|
|
618
|
+
value: "#8290a3",
|
|
619
|
+
description: "De-emphasised copy, table headers, field labels. 4.76:1 to 6.06:1."
|
|
620
|
+
}),
|
|
621
|
+
darkToken({
|
|
622
|
+
key: "text.faint",
|
|
623
|
+
usage: "decorative",
|
|
624
|
+
value: "#5f6d80",
|
|
625
|
+
description: "Meta text, timestamps, uppercase labels and input placeholders only. Contrast-exempt: 2.94:1 to 3.74:1 never reaches body 4.5:1. Forbidden on `surface.elevated` (FR-THM-005 AC-3)."
|
|
626
|
+
}),
|
|
627
|
+
darkToken({
|
|
628
|
+
key: "text.inverse",
|
|
629
|
+
usage: "body",
|
|
630
|
+
value: "#07111f",
|
|
631
|
+
description: "Text on an accent, status or severity fill."
|
|
632
|
+
}),
|
|
633
|
+
darkToken({
|
|
634
|
+
key: "text.monoPayload",
|
|
635
|
+
usage: "body",
|
|
636
|
+
value: "#dce6f3",
|
|
637
|
+
description: "Monospace payload text. 13.44:1 on `surface.raised`."
|
|
638
|
+
})
|
|
639
|
+
];
|
|
640
|
+
var border = [
|
|
641
|
+
darkToken({
|
|
642
|
+
key: "border.subtle",
|
|
643
|
+
usage: "decorative",
|
|
644
|
+
value: "rgba(148, 163, 184, 0.1)",
|
|
645
|
+
description: "Hairline divider. Contrast-exempt (1.13:1), WCAG 1.4.11 exception: card and panel edges are already identified by surface luminance and `elevation.*` (FR-THM-005 AC-4)."
|
|
646
|
+
}),
|
|
647
|
+
darkToken({
|
|
648
|
+
key: "border.default",
|
|
649
|
+
usage: "decorative",
|
|
650
|
+
value: "rgba(148, 163, 184, 0.18)",
|
|
651
|
+
description: "Default edge. Contrast-exempt (1.30:1), same WCAG 1.4.11 exception. Form controls use `border.control` instead (FR-THM-005 AC-4)."
|
|
652
|
+
}),
|
|
653
|
+
darkToken({
|
|
654
|
+
key: "border.strong",
|
|
655
|
+
usage: "decorative",
|
|
656
|
+
value: "rgba(148, 163, 184, 0.3)",
|
|
657
|
+
description: "Pronounced edge for buttons and overlays. Contrast-exempt (1.69:1), same WCAG 1.4.11 exception (FR-THM-005 AC-4)."
|
|
658
|
+
}),
|
|
659
|
+
darkToken({
|
|
660
|
+
key: "border.control",
|
|
661
|
+
usage: "nonText",
|
|
662
|
+
value: "rgba(148, 163, 184, 0.6)",
|
|
663
|
+
description: "Form control edge. Corrected token (FR-THM-005 AC-2): the source reused `border.default` at 1.30:1. Alpha 0.60 yields 3.23:1 on `surface.raised`. Applies to TextField, TextArea, Select, Switch and Checkbox only."
|
|
664
|
+
}),
|
|
665
|
+
darkToken({
|
|
666
|
+
key: "border.DEFAULT",
|
|
667
|
+
usage: "decorative",
|
|
668
|
+
alias: "border.default",
|
|
669
|
+
description: "Source alias of `border.default`, kept as a reference (FR-THM-001 AC-2)."
|
|
670
|
+
})
|
|
671
|
+
];
|
|
672
|
+
var accent = [
|
|
673
|
+
darkToken({
|
|
674
|
+
key: "accent.DEFAULT",
|
|
675
|
+
usage: "body",
|
|
676
|
+
value: "#6d7cff",
|
|
677
|
+
description: "The single accent. 5.60:1 on `surface.base`, 4.82:1 on `surface.raised`. Body use on `surface.elevated` is forbidden (4.40:1); large text and non-text use are allowed."
|
|
678
|
+
}),
|
|
679
|
+
darkToken({
|
|
680
|
+
key: "accent.strong",
|
|
681
|
+
usage: "nonText",
|
|
682
|
+
value: "#5667f5",
|
|
683
|
+
description: "Second accent step. Never a fill beneath body text; 4.36:1 on `surface.base`."
|
|
684
|
+
}),
|
|
685
|
+
darkToken({
|
|
686
|
+
key: "accent.soft",
|
|
687
|
+
usage: "decorative",
|
|
688
|
+
value: "rgba(109, 124, 255, 0.14)",
|
|
689
|
+
description: "Faint accent wash. Contrast-exempt: text above it is always `text.primary`."
|
|
690
|
+
}),
|
|
691
|
+
darkToken({
|
|
692
|
+
key: "accent.glow",
|
|
693
|
+
usage: "decorative",
|
|
694
|
+
value: "rgba(109, 124, 255, 0.28)",
|
|
695
|
+
description: "Dark-only glow. Contrast-exempt: never the sole carrier of information."
|
|
696
|
+
})
|
|
697
|
+
];
|
|
698
|
+
var status = [
|
|
699
|
+
darkToken({
|
|
700
|
+
key: "status.queued",
|
|
701
|
+
usage: "nonText",
|
|
702
|
+
value: "#64748b",
|
|
703
|
+
icon: "circle-dashed",
|
|
704
|
+
description: "Queued. 3.56:1 on `surface.raised`. Dot and marker only, never a text foreground; the component must render an icon and a label alongside it (FR-THM-005 AC-5, AC-7)."
|
|
705
|
+
}),
|
|
706
|
+
darkToken({
|
|
707
|
+
key: "status.running",
|
|
708
|
+
usage: "body",
|
|
709
|
+
alias: "accent.DEFAULT",
|
|
710
|
+
icon: "loader",
|
|
711
|
+
description: "Running. Resolves to the accent, matching the source 1:1 (`#6d7cff`); the reference keeps the accent family single-sourced (design principle P-5)."
|
|
712
|
+
}),
|
|
713
|
+
darkToken({
|
|
714
|
+
key: "status.waiting",
|
|
715
|
+
usage: "body",
|
|
716
|
+
value: "#f59e0b",
|
|
717
|
+
icon: "pause-circle",
|
|
718
|
+
description: "Waiting on input. 7.89:1 on `surface.raised`."
|
|
719
|
+
}),
|
|
720
|
+
darkToken({
|
|
721
|
+
key: "status.success",
|
|
722
|
+
usage: "body",
|
|
723
|
+
value: "#10b981",
|
|
724
|
+
icon: "check-circle-2",
|
|
725
|
+
description: "Completed successfully. 6.68:1 on `surface.raised`."
|
|
726
|
+
}),
|
|
727
|
+
darkToken({
|
|
728
|
+
key: "status.partial",
|
|
729
|
+
usage: "body",
|
|
730
|
+
value: "#eab308",
|
|
731
|
+
icon: "alert-circle",
|
|
732
|
+
description: "Completed with gaps. 8.84:1 on `surface.raised`."
|
|
733
|
+
}),
|
|
734
|
+
darkToken({
|
|
735
|
+
key: "status.danger",
|
|
736
|
+
usage: "body",
|
|
737
|
+
value: "#ef4444",
|
|
738
|
+
icon: "x-circle",
|
|
739
|
+
description: "Failed. 4.50:1 on `surface.raised`."
|
|
740
|
+
}),
|
|
741
|
+
darkToken({
|
|
742
|
+
key: "status.neutralEnd",
|
|
743
|
+
usage: "decorative",
|
|
744
|
+
value: "#475569",
|
|
745
|
+
icon: "circle-slash",
|
|
746
|
+
description: "Cancelled or superseded. Contrast-exempt per CR-006 (`srs_final.md` 12.1): 2.04:1 to 2.60:1 reaches non-text 3:1 on no surface. WCAG 1.4.11 exception \u2014 FR-THM-005 AC-7 forces an icon and a label alongside the colour, and the source's surface-coloured ring supplies the marker's shape boundary, so the dot's fill carries no identification on its own. Known cost: the dot reads faintly against a dark background."
|
|
747
|
+
})
|
|
748
|
+
];
|
|
749
|
+
var meter = [
|
|
750
|
+
darkToken({
|
|
751
|
+
key: "meter.normal",
|
|
752
|
+
usage: "body",
|
|
753
|
+
value: "#34d399",
|
|
754
|
+
description: "Below the warning threshold. 8.82:1 on `surface.raised`."
|
|
755
|
+
}),
|
|
756
|
+
darkToken({
|
|
757
|
+
key: "meter.warning",
|
|
758
|
+
usage: "body",
|
|
759
|
+
value: "#fbbf24",
|
|
760
|
+
description: "Past the warning threshold. 10.15:1 on `surface.raised`."
|
|
761
|
+
}),
|
|
762
|
+
darkToken({
|
|
763
|
+
key: "meter.exceeded",
|
|
764
|
+
usage: "body",
|
|
765
|
+
alias: "red.400",
|
|
766
|
+
description: "Past the hard limit. 6.13:1 on `surface.raised`."
|
|
767
|
+
})
|
|
768
|
+
];
|
|
769
|
+
var severity = [
|
|
770
|
+
darkToken({
|
|
771
|
+
key: "severity.read",
|
|
772
|
+
usage: "body",
|
|
773
|
+
value: "#15803d",
|
|
774
|
+
icon: "eye",
|
|
775
|
+
description: "Read-only side effect. Fill only; 4.67:1 beneath `text.primary`."
|
|
776
|
+
}),
|
|
777
|
+
darkToken({
|
|
778
|
+
key: "severity.write",
|
|
779
|
+
usage: "body",
|
|
780
|
+
value: "#c2410c",
|
|
781
|
+
icon: "pencil",
|
|
782
|
+
description: "Mutating side effect. Fill only; 4.82:1 beneath `text.primary`."
|
|
783
|
+
}),
|
|
784
|
+
darkToken({
|
|
785
|
+
key: "severity.destructive",
|
|
786
|
+
usage: "body",
|
|
787
|
+
value: "#b91c1c",
|
|
788
|
+
icon: "trash-2",
|
|
789
|
+
description: "Destructive side effect. Fill only; 6.02:1 beneath `text.primary`."
|
|
790
|
+
}),
|
|
791
|
+
darkToken({
|
|
792
|
+
key: "severity.blocked",
|
|
793
|
+
usage: "body",
|
|
794
|
+
value: "#7f1d1d",
|
|
795
|
+
icon: "shield-x",
|
|
796
|
+
description: "Blocked by policy. Fill only; 9.32:1 beneath `text.primary`."
|
|
797
|
+
})
|
|
798
|
+
];
|
|
799
|
+
var fontFamily = [
|
|
800
|
+
darkToken({
|
|
801
|
+
key: "font.sans",
|
|
802
|
+
usage: "decorative",
|
|
803
|
+
alias: "stack.sans",
|
|
804
|
+
description: "Interface font stack. Not a colour."
|
|
805
|
+
}),
|
|
806
|
+
darkToken({
|
|
807
|
+
key: "font.mono",
|
|
808
|
+
usage: "decorative",
|
|
809
|
+
alias: "stack.mono",
|
|
810
|
+
description: "Payload and code font stack. Not a colour."
|
|
811
|
+
})
|
|
812
|
+
];
|
|
813
|
+
var elevation = [
|
|
814
|
+
darkToken({
|
|
815
|
+
key: "elevation.raised",
|
|
816
|
+
usage: "decorative",
|
|
817
|
+
value: "0 12px 30px rgba(0, 0, 0, 0.18)",
|
|
818
|
+
description: "Resting card shadow. Contrast-exempt: a shadow, not a colour."
|
|
819
|
+
}),
|
|
820
|
+
darkToken({
|
|
821
|
+
key: "elevation.hover",
|
|
822
|
+
usage: "decorative",
|
|
823
|
+
value: "0 18px 46px rgba(0, 0, 0, 0.26)",
|
|
824
|
+
description: "Hovered card shadow. Contrast-exempt: a shadow, not a colour."
|
|
825
|
+
}),
|
|
826
|
+
darkToken({
|
|
827
|
+
key: "elevation.overlay",
|
|
828
|
+
usage: "decorative",
|
|
829
|
+
value: "0 24px 64px rgba(0, 0, 0, 0.45), 0 0 0 1px {border.strong}",
|
|
830
|
+
description: "Overlay shadow plus a 1px ring. The ring references `border.strong` so each theme resolves its own edge. Contrast-exempt with that border (FR-THM-005 AC-4)."
|
|
831
|
+
})
|
|
832
|
+
];
|
|
833
|
+
var interaction = [
|
|
834
|
+
darkToken({
|
|
835
|
+
key: "focusRing",
|
|
836
|
+
usage: "nonText",
|
|
837
|
+
value: "0 0 0 3px rgba(109, 124, 255, 0.8)",
|
|
838
|
+
description: "Corrected token (FR-THM-005 AC-1). The source used accent alpha 0.30, which composites to 1.50:1 on `surface.base` and fails WCAG 2.4.11; a focus indicator cannot be decorative. Alpha 0.80 gives 3.93:1 on `surface.base` and 3.56:1 on `surface.raised`."
|
|
839
|
+
}),
|
|
840
|
+
darkToken({
|
|
841
|
+
key: "state.hover",
|
|
842
|
+
usage: "decorative",
|
|
843
|
+
value: "rgba(255, 255, 255, 0.055)",
|
|
844
|
+
description: "Hover wash. Contrast-exempt: nudges the surface, carries no meaning."
|
|
845
|
+
}),
|
|
846
|
+
darkToken({
|
|
847
|
+
key: "state.selected",
|
|
848
|
+
usage: "decorative",
|
|
849
|
+
value: "rgba(109, 124, 255, 0.16)",
|
|
850
|
+
description: "Selected wash. Contrast-exempt: selection is also announced via `aria-pressed` and text."
|
|
851
|
+
}),
|
|
852
|
+
darkToken({
|
|
853
|
+
key: "state.disabled",
|
|
854
|
+
usage: "decorative",
|
|
855
|
+
value: "#18202c",
|
|
856
|
+
description: "Disabled fill. Contrast-exempt as a fill; checked beneath `text.muted` (5.05:1)."
|
|
857
|
+
}),
|
|
858
|
+
darkToken({
|
|
859
|
+
key: "state.disabledPolicy",
|
|
860
|
+
usage: "decorative",
|
|
861
|
+
value: "#422006",
|
|
862
|
+
description: "Policy-blocked fill. Contrast-exempt as a fill; checked beneath the label (5.27:1)."
|
|
863
|
+
})
|
|
864
|
+
];
|
|
865
|
+
var darkPalette = [
|
|
866
|
+
...surface,
|
|
867
|
+
...text,
|
|
868
|
+
...border,
|
|
869
|
+
...accent,
|
|
870
|
+
...status,
|
|
871
|
+
...meter,
|
|
872
|
+
...severity,
|
|
873
|
+
...fontFamily,
|
|
874
|
+
...elevation,
|
|
875
|
+
...interaction
|
|
876
|
+
];
|
|
877
|
+
|
|
878
|
+
// src/palette.light.ts
|
|
879
|
+
var values = {
|
|
880
|
+
"surface.base": "#e8ecf2",
|
|
881
|
+
"surface.canvas": "#eef1f6",
|
|
882
|
+
"surface.subtle": "#f3f6f9",
|
|
883
|
+
"surface.raised": "#fbfcfd",
|
|
884
|
+
"surface.elevated": "#ffffff",
|
|
885
|
+
"surface.overlay": "rgba(12, 18, 28, 0.45)",
|
|
886
|
+
"surface.glass": "rgba(251, 252, 253, 0.86)",
|
|
887
|
+
"surface.timeline": "#f1f4f8",
|
|
888
|
+
"surface.track": "rgba(12, 18, 28, 0.08)",
|
|
889
|
+
"text.primary": "#0c121c",
|
|
890
|
+
"text.secondary": "#33415a",
|
|
891
|
+
"text.muted": "#4d5a6e",
|
|
892
|
+
"text.faint": "#6b788c",
|
|
893
|
+
"text.inverse": "#f4f7fb",
|
|
894
|
+
"text.monoPayload": "#1b2537",
|
|
895
|
+
"border.subtle": "#e2e8f0",
|
|
896
|
+
"border.default": "#6b788c",
|
|
897
|
+
"border.strong": "#5b6879",
|
|
898
|
+
"border.control": "#64748b",
|
|
899
|
+
"accent.DEFAULT": "#4f5bd5",
|
|
900
|
+
"accent.strong": "#3f4ac0",
|
|
901
|
+
"accent.soft": "rgba(79, 91, 213, 0.14)",
|
|
902
|
+
"accent.glow": "rgba(79, 91, 213, 0.22)",
|
|
903
|
+
"status.queued": "#52607a",
|
|
904
|
+
"status.waiting": "#b45309",
|
|
905
|
+
"status.success": "#047857",
|
|
906
|
+
"status.partial": "#a16207",
|
|
907
|
+
"status.danger": "#c81e1e",
|
|
908
|
+
"status.neutralEnd": "#3f4b5f",
|
|
909
|
+
"meter.normal": "#047857",
|
|
910
|
+
"meter.warning": "#b45309",
|
|
911
|
+
"meter.exceeded": "#dc2626",
|
|
912
|
+
"elevation.raised": "0 12px 30px rgba(12, 18, 28, 0.08)",
|
|
913
|
+
"elevation.hover": "0 18px 46px rgba(12, 18, 28, 0.12)",
|
|
914
|
+
"elevation.overlay": "0 24px 64px rgba(12, 18, 28, 0.2), 0 0 0 1px {border.strong}",
|
|
915
|
+
"focusRing": "0 0 0 3px rgba(79, 91, 213, 0.8)",
|
|
916
|
+
"state.hover": "rgba(12, 18, 28, 0.05)",
|
|
917
|
+
"state.selected": "rgba(79, 91, 213, 0.12)",
|
|
918
|
+
"state.disabled": "#e2e8f0",
|
|
919
|
+
"state.disabledPolicy": "#fef3c7"
|
|
920
|
+
};
|
|
921
|
+
var lightPalette = darkPalette.map((token) => {
|
|
922
|
+
const value2 = values[token.key];
|
|
923
|
+
return value2 === void 0 ? token : { ...token, value: value2, alias: void 0 };
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// src/primitives.ts
|
|
927
|
+
function primitive(key, value2, description) {
|
|
928
|
+
return { key, tier: "primitive", value: value2, usage: "decorative", description };
|
|
929
|
+
}
|
|
930
|
+
var ink = [
|
|
931
|
+
primitive("ink.0", "#ffffff", "Pure white. `::selection` text, light `surface.elevated`."),
|
|
932
|
+
primitive("ink.25", "#fbfcfd", "Light `surface.raised`."),
|
|
933
|
+
primitive("ink.50", "#f4f7fb", "Dark `text.primary`, light `text.inverse`."),
|
|
934
|
+
primitive("ink.75", "#f3f6f9", "Light `surface.subtle`."),
|
|
935
|
+
primitive("ink.100", "#f1f4f8", "Light `surface.timeline`."),
|
|
936
|
+
primitive("ink.150", "#eef1f6", "Light `surface.canvas`."),
|
|
937
|
+
primitive("ink.200", "#e8ecf2", "Light `surface.base`."),
|
|
938
|
+
primitive("ink.250", "#e2e8f0", "Light `border.subtle` and `state.disabled`."),
|
|
939
|
+
primitive("ink.300", "#dce6f3", "Dark `text.monoPayload`."),
|
|
940
|
+
primitive("ink.350", "#c5cfdd", "Dark `text.secondary`."),
|
|
941
|
+
primitive("ink.400", "#8290a3", "Dark `text.muted`."),
|
|
942
|
+
primitive("ink.500", "#6b788c", "Light `text.faint` and `border.default`."),
|
|
943
|
+
primitive("ink.600", "#5f6d80", "Dark `text.faint`."),
|
|
944
|
+
primitive("ink.650", "#5b6879", "Light `border.strong`."),
|
|
945
|
+
primitive("ink.700", "#4d5a6e", "Light `text.muted`."),
|
|
946
|
+
primitive("ink.750", "#33415a", "Light `text.secondary`."),
|
|
947
|
+
primitive("ink.800", "#1b2537", "Light `text.monoPayload`."),
|
|
948
|
+
primitive("ink.810", "#192535", "Dark `surface.elevated`."),
|
|
949
|
+
primitive("ink.820", "#18202c", "Dark `state.disabled`."),
|
|
950
|
+
primitive("ink.840", "#141d2a", "Dark `surface.raised`."),
|
|
951
|
+
primitive("ink.860", "#101722", "Dark `surface.subtle`."),
|
|
952
|
+
primitive("ink.870", "#0d141f", "Dark `surface.timeline`."),
|
|
953
|
+
primitive("ink.880", "#0c121c", "Light `text.primary`; base colour of the light shadow ramp."),
|
|
954
|
+
primitive("ink.890", "#07111f", "Dark `text.inverse`."),
|
|
955
|
+
primitive("ink.895", "#0b101a", "Dark `surface.canvas`."),
|
|
956
|
+
primitive("ink.900", "#080b12", "Dark `surface.base`."),
|
|
957
|
+
primitive("ink.950", "#04070c", "Base colour of the dark `surface.overlay` scrim.")
|
|
958
|
+
];
|
|
959
|
+
var indigo = [
|
|
960
|
+
primitive("indigo.300", "#aab3ff", "Link hover in the source product."),
|
|
961
|
+
primitive("indigo.400", "#7b89ff", "Upper stop of the source primary-button gradient."),
|
|
962
|
+
primitive("indigo.500", "#6d7cff", "Dark `accent`."),
|
|
963
|
+
primitive("indigo.600", "#5667f5", "Dark `accent.strong`."),
|
|
964
|
+
primitive("indigo.700", "#4f5bd5", "Light `accent`."),
|
|
965
|
+
primitive("indigo.800", "#3f4ac0", "Light `accent.strong`.")
|
|
966
|
+
];
|
|
967
|
+
var slate = [
|
|
968
|
+
primitive("slate.400", "#94a3b8", "Base colour of all four dark border tokens and the scrollbar."),
|
|
969
|
+
primitive("slate.500", "#64748b", "Dark `status.queued`; light `border.control`."),
|
|
970
|
+
primitive("slate.600", "#475569", "Dark `status.neutralEnd`."),
|
|
971
|
+
primitive("slate.700", "#3f4b5f", "Light `status.neutralEnd`."),
|
|
972
|
+
primitive("slate.750", "#52607a", "Light `status.queued`.")
|
|
973
|
+
];
|
|
974
|
+
var statusHues = [
|
|
975
|
+
primitive("emerald.400", "#34d399", "Dark `meter.normal`."),
|
|
976
|
+
primitive("emerald.500", "#10b981", "Dark `status.success`."),
|
|
977
|
+
primitive("emerald.700", "#047857", "Light `status.success` and `meter.normal`."),
|
|
978
|
+
primitive("green.700", "#15803d", "`severity.read`, both themes."),
|
|
979
|
+
primitive("amber.100", "#fef3c7", "Light `state.disabledPolicy`."),
|
|
980
|
+
primitive("amber.400", "#fbbf24", "Dark `meter.warning`."),
|
|
981
|
+
primitive("amber.500", "#f59e0b", "Dark `status.waiting`."),
|
|
982
|
+
primitive("amber.700", "#b45309", "Light `status.waiting` and `meter.warning`."),
|
|
983
|
+
primitive("amber.950", "#422006", "Dark `state.disabledPolicy`."),
|
|
984
|
+
primitive("yellow.500", "#eab308", "Dark `status.partial`."),
|
|
985
|
+
primitive("yellow.700", "#a16207", "Light `status.partial`."),
|
|
986
|
+
primitive("orange.700", "#c2410c", "`severity.write`, both themes."),
|
|
987
|
+
primitive("red.300", "#fca5a5", "Policy-disabled button label in the source product."),
|
|
988
|
+
primitive("red.400", "#f87171", "Dark `meter.exceeded`."),
|
|
989
|
+
primitive("red.500", "#ef4444", "Dark `status.danger`."),
|
|
990
|
+
primitive("red.600", "#dc2626", "Light `meter.exceeded`."),
|
|
991
|
+
primitive("red.650", "#c81e1e", "Light `status.danger`."),
|
|
992
|
+
primitive("red.700", "#b91c1c", "`severity.destructive`, both themes."),
|
|
993
|
+
primitive("red.900", "#7f1d1d", "`severity.blocked`, both themes.")
|
|
994
|
+
];
|
|
995
|
+
var nonColour = [
|
|
996
|
+
primitive(
|
|
997
|
+
"stack.sans",
|
|
998
|
+
'Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
999
|
+
"System sans stack. `Inter` is supplied by the consumer or falls through."
|
|
1000
|
+
),
|
|
1001
|
+
primitive(
|
|
1002
|
+
"stack.mono",
|
|
1003
|
+
`'JetBrains Mono', ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace`,
|
|
1004
|
+
"System mono stack for payloads and code."
|
|
1005
|
+
),
|
|
1006
|
+
primitive("scale.4", "4px", "Spacing step."),
|
|
1007
|
+
primitive("scale.8", "8px", "Spacing step."),
|
|
1008
|
+
primitive("scale.12", "12px", "Spacing step."),
|
|
1009
|
+
primitive("scale.16", "16px", "Spacing step."),
|
|
1010
|
+
primitive("scale.24", "24px", "Spacing step."),
|
|
1011
|
+
primitive("scale.32", "32px", "Spacing step."),
|
|
1012
|
+
primitive("scale.40", "40px", "Spacing step."),
|
|
1013
|
+
primitive("scale.48", "48px", "Spacing step."),
|
|
1014
|
+
primitive("curve.6", "6px", "Corner radius step."),
|
|
1015
|
+
primitive("curve.9", "9px", "Corner radius step."),
|
|
1016
|
+
primitive("curve.12", "12px", "Corner radius step."),
|
|
1017
|
+
primitive("curve.18", "18px", "Corner radius step."),
|
|
1018
|
+
primitive("curve.24", "24px", "Corner radius step."),
|
|
1019
|
+
primitive("ease.entrance", "cubic-bezier(0.2, 0, 0, 1)", "Entrance and state-change easing."),
|
|
1020
|
+
primitive("ease.overshoot", "cubic-bezier(0.34, 1.56, 0.64, 1)", "Toggle settle easing.")
|
|
1021
|
+
];
|
|
1022
|
+
var primitiveTokens = [
|
|
1023
|
+
...ink,
|
|
1024
|
+
...indigo,
|
|
1025
|
+
...slate,
|
|
1026
|
+
...statusHues,
|
|
1027
|
+
...nonColour
|
|
1028
|
+
];
|
|
1029
|
+
|
|
1030
|
+
// src/token-source.ts
|
|
1031
|
+
function componentOverride(key, source) {
|
|
1032
|
+
const token = componentTokens.find((candidate) => candidate.key === key);
|
|
1033
|
+
if (token === void 0) throw new Error(`unknown component token override: ${key}`);
|
|
1034
|
+
return { ...token, ...source };
|
|
1035
|
+
}
|
|
1036
|
+
var THEME_SOURCES = [
|
|
1037
|
+
{
|
|
1038
|
+
theme: "dark",
|
|
1039
|
+
colorScheme: "dark",
|
|
1040
|
+
selectors: [":root", '[data-cdt-theme="dark"]'],
|
|
1041
|
+
palette: darkPalette
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
theme: "light",
|
|
1045
|
+
colorScheme: "light",
|
|
1046
|
+
selectors: [':root[data-cdt-theme="light"]'],
|
|
1047
|
+
palette: lightPalette,
|
|
1048
|
+
componentOverrides: [
|
|
1049
|
+
componentOverride("button.policyDisabled.text", { alias: "severity.destructive" }),
|
|
1050
|
+
componentOverride("card.background", { alias: "surface.elevated" }),
|
|
1051
|
+
componentOverride("card.border", { alias: "border.strong" }),
|
|
1052
|
+
componentOverride("badge.severity.text", { alias: "text.inverse" }),
|
|
1053
|
+
componentOverride("input.background", { alias: "surface.elevated" }),
|
|
1054
|
+
componentOverride("input.backgroundFocus", { alias: "surface.elevated" }),
|
|
1055
|
+
componentOverride("overlay.background", { alias: "surface.elevated" }),
|
|
1056
|
+
componentOverride("overlay.backgroundBlur", { value: "0px" })
|
|
1057
|
+
]
|
|
1058
|
+
}
|
|
1059
|
+
];
|
|
1060
|
+
function tokensForTheme(theme) {
|
|
1061
|
+
const overrides = new Map((theme.componentOverrides ?? []).map((token) => [token.key, token]));
|
|
1062
|
+
return [
|
|
1063
|
+
...primitiveTokens,
|
|
1064
|
+
...theme.palette,
|
|
1065
|
+
...scaleTokens,
|
|
1066
|
+
...componentTokens.map((token) => overrides.get(token.key) ?? token)
|
|
1067
|
+
];
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/contrast/check.ts
|
|
1071
|
+
function exclusionsFor(tokens) {
|
|
1072
|
+
return tokens.filter((token) => token.tier !== "primitive" && token.usage === "decorative").map((token) => ({ key: token.key, usage: "decorative", reason: token.description }));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/constants.ts
|
|
1076
|
+
var CDT_PREFIX = "cdt-";
|
|
1077
|
+
|
|
1078
|
+
// src/build/names.ts
|
|
1079
|
+
var CUSTOM_PROPERTY_PREFIX = `--${CDT_PREFIX}`;
|
|
1080
|
+
function kebab(segment) {
|
|
1081
|
+
return segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1082
|
+
}
|
|
1083
|
+
function cssPropertyName(key) {
|
|
1084
|
+
const segments = key.split(".").filter((segment) => segment !== DEFAULT_SEGMENT).map(kebab);
|
|
1085
|
+
return `${CUSTOM_PROPERTY_PREFIX}${segments.join("-")}`;
|
|
1086
|
+
}
|
|
1087
|
+
function assertNoNameCollisions(tokens) {
|
|
1088
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1089
|
+
for (const token of tokens) {
|
|
1090
|
+
const name = cssPropertyName(token.key);
|
|
1091
|
+
const keys = byName.get(name);
|
|
1092
|
+
if (keys) keys.push(token.key);
|
|
1093
|
+
else byName.set(name, [token.key]);
|
|
1094
|
+
}
|
|
1095
|
+
const collisions = [...byName.entries()].filter(([, keys]) => keys.length > 1);
|
|
1096
|
+
if (collisions.length === 0) return;
|
|
1097
|
+
throw new TokenBuildError(
|
|
1098
|
+
"TOK-NAME-COLLISION",
|
|
1099
|
+
`${collisions.length} CSS custom property name(s) claimed by more than one token key`,
|
|
1100
|
+
[
|
|
1101
|
+
...collisions.map(([name, keys]) => `${name} <- ${keys.join(", ")}`),
|
|
1102
|
+
"hint: rename one of the keys; the dot-to-kebab mapping is not injective."
|
|
1103
|
+
]
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
function assertPrefixed(propertyNames) {
|
|
1107
|
+
const unprefixed = propertyNames.filter((name) => !name.startsWith(CUSTOM_PROPERTY_PREFIX));
|
|
1108
|
+
if (unprefixed.length === 0) return;
|
|
1109
|
+
throw new TokenBuildError(
|
|
1110
|
+
"TOK-PREFIX",
|
|
1111
|
+
`${unprefixed.length} custom property declaration(s) missing the \`${CUSTOM_PROPERTY_PREFIX}\` prefix`,
|
|
1112
|
+
[...unprefixed, `hint: every Conductor custom property is namespaced \`${CUSTOM_PROPERTY_PREFIX}\`.`]
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1116
|
+
function isSafeIdentifier(segment) {
|
|
1117
|
+
return IDENTIFIER_PATTERN.test(segment);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/build/media.ts
|
|
1121
|
+
var BREAKPOINT_NAMES = Object.keys(BREAKPOINT_PX);
|
|
1122
|
+
var MEDIA_PRELUDE = /@media((?:[^{]|\{[^{}]*\})*)\{/g;
|
|
1123
|
+
var BREAKPOINT_VAR = /var\(\s*--cdt-breakpoint-([a-z]+)\s*\)/g;
|
|
1124
|
+
var BREAKPOINT_REF = /\{\s*breakpoint\.([a-z]+)\s*\}/g;
|
|
1125
|
+
function literalFor(name) {
|
|
1126
|
+
return BREAKPOINT_NAMES.includes(name) ? `${BREAKPOINT_PX[name]}px` : void 0;
|
|
1127
|
+
}
|
|
1128
|
+
function substitutePrelude(prelude) {
|
|
1129
|
+
return prelude.replace(BREAKPOINT_VAR, (match, name) => literalFor(name) ?? match).replace(BREAKPOINT_REF, (match, name) => literalFor(name) ?? match);
|
|
1130
|
+
}
|
|
1131
|
+
function substituteBreakpoints(css) {
|
|
1132
|
+
return css.replace(MEDIA_PRELUDE, (_match, prelude) => `@media${substitutePrelude(prelude)}{`);
|
|
1133
|
+
}
|
|
1134
|
+
function findBreakpointVarsInMedia(css) {
|
|
1135
|
+
const offenders = [];
|
|
1136
|
+
for (const [, prelude] of css.matchAll(MEDIA_PRELUDE)) {
|
|
1137
|
+
const condition = prelude;
|
|
1138
|
+
if (/var\(\s*--cdt-breakpoint-/.test(condition)) offenders.push(`@media${condition.trimEnd()}`);
|
|
1139
|
+
}
|
|
1140
|
+
return offenders;
|
|
1141
|
+
}
|
|
1142
|
+
function assertNoBreakpointVarsInMedia(css) {
|
|
1143
|
+
const offenders = findBreakpointVarsInMedia(css);
|
|
1144
|
+
if (offenders.length === 0) return;
|
|
1145
|
+
throw new TokenBuildError(
|
|
1146
|
+
"TOK-MEDIA-VAR",
|
|
1147
|
+
`${offenders.length} \`@media\` condition(s) reference a breakpoint custom property`,
|
|
1148
|
+
[
|
|
1149
|
+
...offenders,
|
|
1150
|
+
`hint: custom properties do not evaluate in media conditions; ${cssPropertyName(
|
|
1151
|
+
"breakpoint.md"
|
|
1152
|
+
)} must be substituted as a literal.`
|
|
1153
|
+
]
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
var breakpointPixels = BREAKPOINT_PX;
|
|
1157
|
+
|
|
1158
|
+
// src/build/emit-css.ts
|
|
1159
|
+
var BANNER = [
|
|
1160
|
+
"/*",
|
|
1161
|
+
" * Generated by @conductor-by-89soone/tokens. Do not edit.",
|
|
1162
|
+
" * Source: packages/tokens/src/{primitives,palette.dark,palette.light,scales,components}.ts",
|
|
1163
|
+
" */"
|
|
1164
|
+
].join("\n");
|
|
1165
|
+
function emittableTokens(tokens) {
|
|
1166
|
+
return tokens.filter((token) => token.tier !== "primitive");
|
|
1167
|
+
}
|
|
1168
|
+
function declarationsFor(tokens, values2) {
|
|
1169
|
+
const lines = [];
|
|
1170
|
+
let previousGroup;
|
|
1171
|
+
for (const token of tokens) {
|
|
1172
|
+
const group = groupKeyOf(token.key);
|
|
1173
|
+
if (previousGroup !== void 0 && group !== previousGroup) lines.push("");
|
|
1174
|
+
previousGroup = group;
|
|
1175
|
+
const resolved = values2.get(token.key);
|
|
1176
|
+
lines.push(` ${cssPropertyName(token.key)}: ${resolved};`);
|
|
1177
|
+
}
|
|
1178
|
+
return lines;
|
|
1179
|
+
}
|
|
1180
|
+
function emitCss(tokens, themes, fallbacks = []) {
|
|
1181
|
+
const emitted = emittableTokens(tokens);
|
|
1182
|
+
assertNoNameCollisions(emitted);
|
|
1183
|
+
assertPrefixed(emitted.map((token) => cssPropertyName(token.key)));
|
|
1184
|
+
const blocks = themes.map((theme) => {
|
|
1185
|
+
const selector = theme.selectors.join(",\n");
|
|
1186
|
+
const body = [` color-scheme: ${theme.colorScheme};`, "", ...declarationsFor(emitted, theme.values)];
|
|
1187
|
+
return `${selector} {
|
|
1188
|
+
${body.join("\n")}
|
|
1189
|
+
}`;
|
|
1190
|
+
});
|
|
1191
|
+
const fallbackBlocks = fallbacks.map((theme) => {
|
|
1192
|
+
const selector = theme.selectors.join(",\n");
|
|
1193
|
+
const body = [` color-scheme: ${theme.colorScheme};`, "", ...declarationsFor(emitted, theme.values)];
|
|
1194
|
+
return `@media (prefers-color-scheme: light) {
|
|
1195
|
+
${selector} {
|
|
1196
|
+
${body.join("\n")}
|
|
1197
|
+
}
|
|
1198
|
+
}`;
|
|
1199
|
+
});
|
|
1200
|
+
const css = `${BANNER}
|
|
1201
|
+
|
|
1202
|
+
${[...blocks, ...fallbackBlocks].join("\n\n")}
|
|
1203
|
+
`;
|
|
1204
|
+
const substituted = substituteBreakpoints(css);
|
|
1205
|
+
assertNoBreakpointVarsInMedia(substituted);
|
|
1206
|
+
return substituted;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/build/emit-json.ts
|
|
1210
|
+
function buildTokenJson(tokens, themes) {
|
|
1211
|
+
const entries = emittableTokens(tokens).map((token) => {
|
|
1212
|
+
const values2 = {};
|
|
1213
|
+
for (const theme of themes) values2[theme.theme] = theme.values.get(token.key);
|
|
1214
|
+
return {
|
|
1215
|
+
key: token.key,
|
|
1216
|
+
cssName: cssPropertyName(token.key),
|
|
1217
|
+
tier: token.tier,
|
|
1218
|
+
// `assertUsageMetadata` has already rejected any token without one.
|
|
1219
|
+
usage: token.usage,
|
|
1220
|
+
description: token.description,
|
|
1221
|
+
...token.alias !== void 0 ? { alias: token.alias } : {},
|
|
1222
|
+
...token.icon !== void 0 ? { icon: token.icon } : {},
|
|
1223
|
+
...token.themeSpecific !== void 0 ? { themeSpecific: token.themeSpecific } : {},
|
|
1224
|
+
values: values2
|
|
1225
|
+
};
|
|
1226
|
+
});
|
|
1227
|
+
return { themes: themes.map((theme) => theme.theme), tokens: entries };
|
|
1228
|
+
}
|
|
1229
|
+
function emitTokenJson(tokens, themes) {
|
|
1230
|
+
return `${JSON.stringify(buildTokenJson(tokens, themes), null, 2)}
|
|
1231
|
+
`;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/build/emit-ts.ts
|
|
1235
|
+
function emptyNode() {
|
|
1236
|
+
return { children: /* @__PURE__ */ new Map() };
|
|
1237
|
+
}
|
|
1238
|
+
function buildTree(tokens) {
|
|
1239
|
+
const root = emptyNode();
|
|
1240
|
+
for (const token of tokens) {
|
|
1241
|
+
let node = root;
|
|
1242
|
+
const segments = token.key.split(".");
|
|
1243
|
+
for (const segment of segments.slice(0, -1)) {
|
|
1244
|
+
let child = node.children.get(segment);
|
|
1245
|
+
if (!child) {
|
|
1246
|
+
child = emptyNode();
|
|
1247
|
+
node.children.set(segment, child);
|
|
1248
|
+
}
|
|
1249
|
+
node = child;
|
|
1250
|
+
}
|
|
1251
|
+
const last = segments[segments.length - 1];
|
|
1252
|
+
let leafNode = node.children.get(last);
|
|
1253
|
+
if (!leafNode) {
|
|
1254
|
+
leafNode = emptyNode();
|
|
1255
|
+
node.children.set(last, leafNode);
|
|
1256
|
+
}
|
|
1257
|
+
leafNode.leaf = `var(${cssPropertyName(token.key)})`;
|
|
1258
|
+
}
|
|
1259
|
+
return root;
|
|
1260
|
+
}
|
|
1261
|
+
function propertyName(segment) {
|
|
1262
|
+
return isSafeIdentifier(segment) ? segment : JSON.stringify(segment);
|
|
1263
|
+
}
|
|
1264
|
+
function serialize(node, depth) {
|
|
1265
|
+
const pad = " ".repeat(depth);
|
|
1266
|
+
const innerPad = " ".repeat(depth + 1);
|
|
1267
|
+
const entries = [...node.children.entries()].map(([segment, child]) => {
|
|
1268
|
+
const name = propertyName(segment);
|
|
1269
|
+
if (child.leaf !== void 0 && child.children.size === 0) {
|
|
1270
|
+
return `${innerPad}${name}: ${JSON.stringify(child.leaf)},`;
|
|
1271
|
+
}
|
|
1272
|
+
return `${innerPad}${name}: ${serialize(child, depth + 1)},`;
|
|
1273
|
+
});
|
|
1274
|
+
return `{
|
|
1275
|
+
${entries.join("\n")}
|
|
1276
|
+
${pad}}`;
|
|
1277
|
+
}
|
|
1278
|
+
var TOKENS_BANNER = [
|
|
1279
|
+
"/**",
|
|
1280
|
+
" * Generated by @conductor-by-89soone/tokens. Do not edit.",
|
|
1281
|
+
" * Run `pnpm --filter @conductor-by-89soone/tokens run build` to regenerate.",
|
|
1282
|
+
" *",
|
|
1283
|
+
" * Values are CSS custom property references, not raw colours: a theme swap changes what the",
|
|
1284
|
+
" * browser resolves them to, never the strings themselves (FR-THM-003 AC-4).",
|
|
1285
|
+
" */"
|
|
1286
|
+
].join("\n");
|
|
1287
|
+
function emitTokensModule(tokens) {
|
|
1288
|
+
const emitted = tokens.filter((token) => token.tier !== "primitive");
|
|
1289
|
+
const tree = buildTree(emitted);
|
|
1290
|
+
return [
|
|
1291
|
+
TOKENS_BANNER,
|
|
1292
|
+
"",
|
|
1293
|
+
`export const tokens = ${serialize(tree, 0)} as const;`,
|
|
1294
|
+
"",
|
|
1295
|
+
"/** Shape of the token object, with every leaf narrowed to its own string literal. */",
|
|
1296
|
+
"export type Tokens = typeof tokens;",
|
|
1297
|
+
""
|
|
1298
|
+
].join("\n");
|
|
1299
|
+
}
|
|
1300
|
+
var BREAKPOINTS_BANNER = [
|
|
1301
|
+
"/**",
|
|
1302
|
+
" * Generated by @conductor-by-89soone/tokens. Do not edit.",
|
|
1303
|
+
" * Run `pnpm --filter @conductor-by-89soone/tokens run build` to regenerate.",
|
|
1304
|
+
" *",
|
|
1305
|
+
" * Literal pixel numbers, unlike `tokens`: a custom property does not evaluate inside a media",
|
|
1306
|
+
" * query condition, so JS that computes a breakpoint needs the number itself (FR-TOK-009 AC-3).",
|
|
1307
|
+
" */"
|
|
1308
|
+
].join("\n");
|
|
1309
|
+
function emitBreakpointsModule() {
|
|
1310
|
+
const entries = Object.entries(breakpointPixels).map(
|
|
1311
|
+
([name, pixels]) => ` ${propertyName(name)}: ${pixels},`
|
|
1312
|
+
);
|
|
1313
|
+
return [
|
|
1314
|
+
BREAKPOINTS_BANNER,
|
|
1315
|
+
"",
|
|
1316
|
+
`export const breakpoints = {
|
|
1317
|
+
${entries.join("\n")}
|
|
1318
|
+
} as const;`,
|
|
1319
|
+
"",
|
|
1320
|
+
"/** Shape of the breakpoint object, with every value narrowed to its own numeric literal. */",
|
|
1321
|
+
"export type Breakpoints = typeof breakpoints;",
|
|
1322
|
+
""
|
|
1323
|
+
].join("\n");
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/build/tiers.ts
|
|
1327
|
+
var VALID_TIERS = ["primitive", "semantic", "component"];
|
|
1328
|
+
function assertEveryTokenHasATier(tokens) {
|
|
1329
|
+
const untiered = tokens.filter((token) => !VALID_TIERS.includes(token.tier));
|
|
1330
|
+
if (untiered.length === 0) return;
|
|
1331
|
+
throw new TokenBuildError(
|
|
1332
|
+
"TOK-TIER-MISSING",
|
|
1333
|
+
`${untiered.length} token(s) declare no valid tier`,
|
|
1334
|
+
[
|
|
1335
|
+
...untiered.map((token) => token.key),
|
|
1336
|
+
`hint: every token declares one of ${VALID_TIERS.join(", ")}.`
|
|
1337
|
+
]
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
function findTierViolations(index) {
|
|
1341
|
+
const violations = [];
|
|
1342
|
+
for (const token of index.values()) {
|
|
1343
|
+
for (const reference of referencesOf(token)) {
|
|
1344
|
+
const targetKey = resolveReferenceTarget(index, reference);
|
|
1345
|
+
if (targetKey === void 0) continue;
|
|
1346
|
+
const target = index.get(targetKey);
|
|
1347
|
+
if (token.tier === "primitive") {
|
|
1348
|
+
violations.push({
|
|
1349
|
+
from: token.key,
|
|
1350
|
+
fromTier: token.tier,
|
|
1351
|
+
to: targetKey,
|
|
1352
|
+
toTier: target.tier,
|
|
1353
|
+
reason: "a primitive token references nothing (AC-1)"
|
|
1354
|
+
});
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (tierRank(target.tier) > tierRank(token.tier)) {
|
|
1358
|
+
violations.push({
|
|
1359
|
+
from: token.key,
|
|
1360
|
+
fromTier: token.tier,
|
|
1361
|
+
to: targetKey,
|
|
1362
|
+
toTier: target.tier,
|
|
1363
|
+
reason: `a ${token.tier} token may not reference a ${target.tier} token`
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return violations;
|
|
1369
|
+
}
|
|
1370
|
+
function assertTierDirection(index) {
|
|
1371
|
+
const violations = findTierViolations(index);
|
|
1372
|
+
if (violations.length === 0) return;
|
|
1373
|
+
throw new TokenBuildError(
|
|
1374
|
+
"TOK-TIER",
|
|
1375
|
+
`${violations.length} reverse token reference(s)`,
|
|
1376
|
+
[
|
|
1377
|
+
...violations.map(
|
|
1378
|
+
(violation) => `${violation.from} (${violation.fromTier}) -> ${violation.to} (${violation.toTier}): ${violation.reason}`
|
|
1379
|
+
),
|
|
1380
|
+
"hint: references run primitive <- semantic <- component; never upward."
|
|
1381
|
+
]
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/build/validate.ts
|
|
1386
|
+
function buildTokenGroups(tokens) {
|
|
1387
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1388
|
+
for (const token of tokens) {
|
|
1389
|
+
const groupKey = groupKeyOf(token.key);
|
|
1390
|
+
const group = groups.get(groupKey);
|
|
1391
|
+
if (group) group.memberKeys.push(token.key);
|
|
1392
|
+
else groups.set(groupKey, { tier: token.tier, memberKeys: [token.key] });
|
|
1393
|
+
}
|
|
1394
|
+
return [...groups.entries()].map(([groupKey, group], order) => ({
|
|
1395
|
+
groupKey,
|
|
1396
|
+
tier: group.tier,
|
|
1397
|
+
memberKeys: group.memberKeys,
|
|
1398
|
+
order
|
|
1399
|
+
}));
|
|
1400
|
+
}
|
|
1401
|
+
function assertGroupSizes(tokens) {
|
|
1402
|
+
const groups = new Map(buildTokenGroups(tokens).map((group) => [group.groupKey, group]));
|
|
1403
|
+
const problems = [];
|
|
1404
|
+
for (const [groupKey, expected] of Object.entries(FIXED_GROUP_SIZES)) {
|
|
1405
|
+
const actual = groups.get(groupKey)?.memberKeys.length ?? 0;
|
|
1406
|
+
if (actual !== expected) problems.push(`${groupKey}: expected ${expected} members, found ${actual}`);
|
|
1407
|
+
}
|
|
1408
|
+
if (problems.length === 0) return;
|
|
1409
|
+
throw new TokenBuildError("TOK-GROUP-SIZE", `${problems.length} token group(s) of the wrong size`, [
|
|
1410
|
+
...problems,
|
|
1411
|
+
"hint: FR-TOK-005 fixes these counts; changing one needs a change request."
|
|
1412
|
+
]);
|
|
1413
|
+
}
|
|
1414
|
+
function assertIconMetadata(tokens) {
|
|
1415
|
+
const missing = tokens.filter((token) => ICON_REQUIRED_GROUPS.includes(groupKeyOf(token.key))).filter((token) => token.icon === void 0 || token.icon.trim() === "").map((token) => token.key);
|
|
1416
|
+
if (missing.length === 0) return;
|
|
1417
|
+
throw new TokenBuildError("TOK-ICON", `${missing.length} token(s) missing \`icon\` metadata`, [
|
|
1418
|
+
...missing,
|
|
1419
|
+
`hint: every ${ICON_REQUIRED_GROUPS.join(" and ")} token names a non-empty icon.`
|
|
1420
|
+
]);
|
|
1421
|
+
}
|
|
1422
|
+
function assertUsageMetadata(tokens) {
|
|
1423
|
+
const missing = tokens.filter((token) => token.usage === void 0).map((token) => token.key);
|
|
1424
|
+
if (missing.length === 0) return;
|
|
1425
|
+
throw new TokenBuildError("TOK-USAGE", `${missing.length} token(s) missing \`usage\` metadata`, [
|
|
1426
|
+
...missing,
|
|
1427
|
+
"hint: classify as body, large, nonText or decorative (`srs_final.md` 12.1)."
|
|
1428
|
+
]);
|
|
1429
|
+
}
|
|
1430
|
+
function assertDistinctZLayers(values2) {
|
|
1431
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
1432
|
+
for (const [key, value2] of values2) {
|
|
1433
|
+
if (groupKeyOf(key) !== "z") continue;
|
|
1434
|
+
const keys = byValue.get(value2);
|
|
1435
|
+
if (keys) keys.push(key);
|
|
1436
|
+
else byValue.set(value2, [key]);
|
|
1437
|
+
}
|
|
1438
|
+
const duplicates = [...byValue.entries()].filter(([, keys]) => keys.length > 1);
|
|
1439
|
+
if (duplicates.length === 0) return;
|
|
1440
|
+
throw new TokenBuildError(
|
|
1441
|
+
"TOK-Z-DUPLICATE",
|
|
1442
|
+
`${duplicates.length} stacking value(s) shared by more than one \`z\` layer`,
|
|
1443
|
+
[
|
|
1444
|
+
...duplicates.map(([layerValue, keys]) => `${layerValue} <- ${keys.join(", ")}`),
|
|
1445
|
+
"hint: two layers with the same z-index have an undefined paint order."
|
|
1446
|
+
]
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
function isUnchanged(path, contents) {
|
|
1450
|
+
return existsSync(path) && readFileSync(path, "utf8") === contents;
|
|
1451
|
+
}
|
|
1452
|
+
function writeArtifacts(outDir, artifacts) {
|
|
1453
|
+
mkdirSync(outDir, { recursive: true });
|
|
1454
|
+
const staged = [];
|
|
1455
|
+
try {
|
|
1456
|
+
for (const [name, contents] of artifacts) {
|
|
1457
|
+
const final = join(outDir, name);
|
|
1458
|
+
if (isUnchanged(final, contents)) continue;
|
|
1459
|
+
const temporary = `${final}.${process.pid}.tmp`;
|
|
1460
|
+
writeFileSync(temporary, contents, "utf8");
|
|
1461
|
+
staged.push({ temporary, final, name });
|
|
1462
|
+
}
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
for (const { temporary } of staged) rmSync(temporary, { force: true });
|
|
1465
|
+
throw error;
|
|
1466
|
+
}
|
|
1467
|
+
for (const { temporary, final } of staged) renameSync(temporary, final);
|
|
1468
|
+
return staged.map(({ name }) => name);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/build/index.ts
|
|
1472
|
+
function validateSource(tokens) {
|
|
1473
|
+
assertEveryTokenHasATier(tokens);
|
|
1474
|
+
assertUsageMetadata(tokens);
|
|
1475
|
+
assertIconMetadata(tokens);
|
|
1476
|
+
assertGroupSizes(tokens);
|
|
1477
|
+
}
|
|
1478
|
+
function countByTier(tokens) {
|
|
1479
|
+
const counts = { primitive: 0, semantic: 0, component: 0 };
|
|
1480
|
+
for (const token of tokens) counts[token.tier] = (counts[token.tier] ?? 0) + 1;
|
|
1481
|
+
return counts;
|
|
1482
|
+
}
|
|
1483
|
+
function contrastExclusions(tokens) {
|
|
1484
|
+
return exclusionsFor(tokens).map((exclusion) => ({ ...exclusion }));
|
|
1485
|
+
}
|
|
1486
|
+
function resolveTheme(theme) {
|
|
1487
|
+
const tokens = tokensForTheme(theme);
|
|
1488
|
+
validateSource(tokens);
|
|
1489
|
+
const index = buildTokenIndex(tokens);
|
|
1490
|
+
assertTierDirection(index);
|
|
1491
|
+
const { values: values2 } = resolveTokens(index);
|
|
1492
|
+
assertDistinctZLayers(values2);
|
|
1493
|
+
return {
|
|
1494
|
+
emit: {
|
|
1495
|
+
theme: theme.theme,
|
|
1496
|
+
selectors: theme.selectors,
|
|
1497
|
+
colorScheme: theme.colorScheme,
|
|
1498
|
+
values: values2
|
|
1499
|
+
},
|
|
1500
|
+
tokens
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function buildTokens(options) {
|
|
1504
|
+
const resolved = THEME_SOURCES.map(resolveTheme);
|
|
1505
|
+
const canonical = resolved[0].tokens;
|
|
1506
|
+
const themes = resolved.map(({ emit }) => emit);
|
|
1507
|
+
const lightFallback = resolved.find(({ emit }) => emit.theme === "light")?.emit;
|
|
1508
|
+
const css = emitCss(
|
|
1509
|
+
canonical,
|
|
1510
|
+
themes,
|
|
1511
|
+
lightFallback === void 0 ? [] : [{ selectors: [":root:not([data-cdt-theme])"], colorScheme: "light", values: lightFallback.values }]
|
|
1512
|
+
);
|
|
1513
|
+
const json = emitTokenJson(canonical, themes);
|
|
1514
|
+
const tokensModule = emitTokensModule(canonical);
|
|
1515
|
+
const breakpointsModule = emitBreakpointsModule();
|
|
1516
|
+
const counts = countByTier(canonical);
|
|
1517
|
+
const emitted = emittableTokens(canonical);
|
|
1518
|
+
const stdout = [
|
|
1519
|
+
`[tokens] resolved ${canonical.length} tokens (${counts.primitive} primitive, ${counts.semantic} semantic, ${counts.component} component)`
|
|
1520
|
+
];
|
|
1521
|
+
if (options.report === true) {
|
|
1522
|
+
const exclusions = contrastExclusions(canonical);
|
|
1523
|
+
stdout.push(`[tokens] ${exclusions.length} token(s) excluded from contrast checks (decorative)`);
|
|
1524
|
+
for (const exclusion of exclusions) stdout.push(` ${exclusion.key}`);
|
|
1525
|
+
stdout.push("[tokens] --report: no files written");
|
|
1526
|
+
return { stdout, written: [], css, json, tokensModule, breakpointsModule };
|
|
1527
|
+
}
|
|
1528
|
+
const distArtifacts = /* @__PURE__ */ new Map([
|
|
1529
|
+
["tokens.css", css],
|
|
1530
|
+
["tokens.json", json]
|
|
1531
|
+
]);
|
|
1532
|
+
const written = writeArtifacts(options.outDir, distArtifacts).map((name) => `${options.outDir}/${name}`);
|
|
1533
|
+
stdout.push(`[tokens] wrote ${options.outDir}/tokens.css (${emitted.length} declarations)`);
|
|
1534
|
+
stdout.push(`[tokens] wrote ${options.outDir}/tokens.json`);
|
|
1535
|
+
if (options.srcDir !== void 0) {
|
|
1536
|
+
const sourceArtifacts = /* @__PURE__ */ new Map([
|
|
1537
|
+
["tokens.ts", tokensModule],
|
|
1538
|
+
["breakpoints.ts", breakpointsModule]
|
|
1539
|
+
]);
|
|
1540
|
+
for (const name of writeArtifacts(options.srcDir, sourceArtifacts)) {
|
|
1541
|
+
written.push(`${options.srcDir}/${name}`);
|
|
1542
|
+
}
|
|
1543
|
+
stdout.push(`[tokens] wrote ${options.srcDir}/tokens.ts, ${options.srcDir}/breakpoints.ts`);
|
|
1544
|
+
}
|
|
1545
|
+
return { stdout, written, css, json, tokensModule, breakpointsModule };
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// src/build/args.ts
|
|
1549
|
+
var USAGE = "usage: conductor-build-tokens [--out <dir>] [--report] [--watch]";
|
|
1550
|
+
function parseArgs(argv) {
|
|
1551
|
+
let outDir = "dist";
|
|
1552
|
+
let report = false;
|
|
1553
|
+
let watch2 = false;
|
|
1554
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1555
|
+
const arg = argv[index];
|
|
1556
|
+
switch (arg) {
|
|
1557
|
+
case "--report":
|
|
1558
|
+
report = true;
|
|
1559
|
+
break;
|
|
1560
|
+
case "--watch":
|
|
1561
|
+
watch2 = true;
|
|
1562
|
+
break;
|
|
1563
|
+
case "--out": {
|
|
1564
|
+
const next = argv[index + 1];
|
|
1565
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
1566
|
+
throw new TokenBuildError("TOK-ARG", "`--out` needs a directory", [USAGE]);
|
|
1567
|
+
}
|
|
1568
|
+
outDir = next;
|
|
1569
|
+
index += 1;
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
default:
|
|
1573
|
+
throw new TokenBuildError("TOK-ARG", `unrecognised argument \`${arg}\``, [USAGE]);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (report && watch2) {
|
|
1577
|
+
throw new TokenBuildError("TOK-ARG", "`--report` and `--watch` are mutually exclusive", [USAGE]);
|
|
1578
|
+
}
|
|
1579
|
+
return { outDir, report, watch: watch2 };
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// src/cli.ts
|
|
1583
|
+
var PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
1584
|
+
var SOURCE_DIR = join(PACKAGE_ROOT, "src");
|
|
1585
|
+
function runOnce(options) {
|
|
1586
|
+
const started = Date.now();
|
|
1587
|
+
const result = buildTokens({
|
|
1588
|
+
outDir: resolve(PACKAGE_ROOT, options.outDir),
|
|
1589
|
+
srcDir: options.report ? void 0 : SOURCE_DIR,
|
|
1590
|
+
report: options.report
|
|
1591
|
+
});
|
|
1592
|
+
for (const line of result.stdout) console.log(line);
|
|
1593
|
+
if (!options.report) console.log(`done in ${Date.now() - started}ms`);
|
|
1594
|
+
}
|
|
1595
|
+
function reportFailure(error) {
|
|
1596
|
+
if (error instanceof TokenBuildError) {
|
|
1597
|
+
console.error(error.format());
|
|
1598
|
+
if (error.exitCode === 1) console.error("no output written");
|
|
1599
|
+
return error.exitCode;
|
|
1600
|
+
}
|
|
1601
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
1602
|
+
return 1;
|
|
1603
|
+
}
|
|
1604
|
+
function main(argv) {
|
|
1605
|
+
let options;
|
|
1606
|
+
try {
|
|
1607
|
+
options = parseArgs(argv);
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
return reportFailure(error);
|
|
1610
|
+
}
|
|
1611
|
+
if (!options.watch) {
|
|
1612
|
+
try {
|
|
1613
|
+
runOnce(options);
|
|
1614
|
+
return 0;
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
return reportFailure(error);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
const rebuild = () => {
|
|
1620
|
+
try {
|
|
1621
|
+
runOnce(options);
|
|
1622
|
+
} catch (error) {
|
|
1623
|
+
reportFailure(error);
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
rebuild();
|
|
1627
|
+
let pending;
|
|
1628
|
+
watch(SOURCE_DIR, { recursive: true }, () => {
|
|
1629
|
+
if (pending !== void 0) clearTimeout(pending);
|
|
1630
|
+
pending = setTimeout(rebuild, 50);
|
|
1631
|
+
});
|
|
1632
|
+
console.log(`[tokens] watching ${SOURCE_DIR}`);
|
|
1633
|
+
return 0;
|
|
1634
|
+
}
|
|
1635
|
+
process.exitCode = main(process.argv.slice(2));
|