material-theme-builder 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -4
- package/dist/cli.js +784 -446
- package/dist/index.d.ts +78 -104
- package/dist/index.js +763 -566
- package/dist/react.d.ts +195 -0
- package/dist/react.js +4604 -0
- package/dist/tailwind.css +0 -2
- package/package.json +22 -4
- package/src/tailwind.css +0 -2
package/dist/cli.js
CHANGED
|
@@ -4,19 +4,16 @@
|
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
|
+
import { z } from "zod";
|
|
7
8
|
|
|
8
9
|
// src/lib/builder.ts
|
|
9
10
|
import {
|
|
10
|
-
argbFromHex,
|
|
11
|
+
argbFromHex as argbFromHex2,
|
|
11
12
|
Blend,
|
|
12
|
-
blueFromArgb,
|
|
13
13
|
DynamicColor,
|
|
14
|
-
DynamicScheme,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
hexFromArgb,
|
|
18
|
-
MaterialDynamicColors,
|
|
19
|
-
redFromArgb,
|
|
14
|
+
DynamicScheme as DynamicScheme2,
|
|
15
|
+
Hct as Hct2,
|
|
16
|
+
MaterialDynamicColors as MaterialDynamicColors2,
|
|
20
17
|
SchemeContent,
|
|
21
18
|
SchemeExpressive,
|
|
22
19
|
SchemeFidelity,
|
|
@@ -24,9 +21,682 @@ import {
|
|
|
24
21
|
SchemeNeutral,
|
|
25
22
|
SchemeTonalSpot,
|
|
26
23
|
SchemeVibrant,
|
|
24
|
+
TonalPalette as TonalPalette2
|
|
25
|
+
} from "@material/material-color-utilities";
|
|
26
|
+
import { kebabCase as kebabCase4, upperFirst as upperFirst2 } from "lodash-es";
|
|
27
|
+
|
|
28
|
+
// src/lib/builder.css.ts
|
|
29
|
+
import {
|
|
30
|
+
hexFromArgb
|
|
31
|
+
} from "@material/material-color-utilities";
|
|
32
|
+
import { kebabCase } from "lodash-es";
|
|
33
|
+
function buildCss(ctx) {
|
|
34
|
+
const {
|
|
35
|
+
prefix,
|
|
36
|
+
allPalettes,
|
|
37
|
+
mergedColorsLight,
|
|
38
|
+
mergedColorsDark,
|
|
39
|
+
tokenToPalette,
|
|
40
|
+
allPaletteNamesKebab
|
|
41
|
+
} = ctx;
|
|
42
|
+
function buildRefPaletteLookup() {
|
|
43
|
+
const lookup = {};
|
|
44
|
+
for (const [name, palette] of Object.entries(allPalettes)) {
|
|
45
|
+
const paletteName = kebabCase(name);
|
|
46
|
+
for (const tone of STANDARD_TONES) {
|
|
47
|
+
const hex = hexFromArgb(palette.tone(tone));
|
|
48
|
+
if (!lookup[hex]) lookup[hex] = [];
|
|
49
|
+
lookup[hex].push({ paletteName, tone });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return lookup;
|
|
53
|
+
}
|
|
54
|
+
function sysColorVar(colorName, colorValue, lookup) {
|
|
55
|
+
const name = `--${prefix}-sys-color-${kebabCase(colorName)}`;
|
|
56
|
+
const hex = hexFromArgb(colorValue);
|
|
57
|
+
const matches = lookup[hex];
|
|
58
|
+
if (matches && matches.length > 0) {
|
|
59
|
+
const preferred = tokenToPalette[colorName] ?? deriveCustomPaletteName(colorName, allPaletteNamesKebab);
|
|
60
|
+
const match = (preferred ? matches.find((m) => m.paletteName === preferred) : void 0) ?? matches[0];
|
|
61
|
+
if (match) {
|
|
62
|
+
return `${name}:var(--${prefix}-ref-palette-${match.paletteName}-${match.tone});`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return `${name}:${hex};`;
|
|
66
|
+
}
|
|
67
|
+
function toCssVars(mergedColors, lookup) {
|
|
68
|
+
return Object.entries(mergedColors).map(([name, value]) => sysColorVar(name, value, lookup)).join(" ");
|
|
69
|
+
}
|
|
70
|
+
function refPaletteVar(paletteName, tone, colorValue) {
|
|
71
|
+
const name = `--${prefix}-ref-palette-${paletteName}-${tone}`;
|
|
72
|
+
const value = hexFromArgb(colorValue);
|
|
73
|
+
return `${name}:${value};`;
|
|
74
|
+
}
|
|
75
|
+
function generateTonalPaletteVars(paletteName, palette) {
|
|
76
|
+
return STANDARD_TONES.map((tone) => {
|
|
77
|
+
const color = palette.tone(tone);
|
|
78
|
+
return refPaletteVar(paletteName, tone, color);
|
|
79
|
+
}).join(" ");
|
|
80
|
+
}
|
|
81
|
+
function generateTonalVars() {
|
|
82
|
+
return Object.entries(allPalettes).map(
|
|
83
|
+
([name, palette]) => generateTonalPaletteVars(kebabCase(name), palette)
|
|
84
|
+
).join(" ");
|
|
85
|
+
}
|
|
86
|
+
const refPaletteLookup = buildRefPaletteLookup();
|
|
87
|
+
const lightVars = toCssVars(mergedColorsLight, refPaletteLookup);
|
|
88
|
+
const darkVars = toCssVars(mergedColorsDark, refPaletteLookup);
|
|
89
|
+
const tonalVars = generateTonalVars();
|
|
90
|
+
return `
|
|
91
|
+
:root { ${lightVars} ${tonalVars} }
|
|
92
|
+
.dark { ${darkVars} ${tonalVars} }
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/lib/builder.figma.ts
|
|
97
|
+
import {
|
|
98
|
+
blueFromArgb,
|
|
99
|
+
greenFromArgb,
|
|
100
|
+
hexFromArgb as hexFromArgb2,
|
|
101
|
+
redFromArgb
|
|
102
|
+
} from "@material/material-color-utilities";
|
|
103
|
+
import { kebabCase as kebabCase2, startCase } from "lodash-es";
|
|
104
|
+
function buildFigmaVariables(ctx) {
|
|
105
|
+
const {
|
|
106
|
+
allPalettes,
|
|
107
|
+
mergedColorsLight,
|
|
108
|
+
mergedColorsDark,
|
|
109
|
+
tokenToPalette,
|
|
110
|
+
allPaletteNamesKebab
|
|
111
|
+
} = ctx;
|
|
112
|
+
const variables = [];
|
|
113
|
+
const paletteHexMap = {};
|
|
114
|
+
for (const [name, palette] of Object.entries(allPalettes)) {
|
|
115
|
+
const paletteName = startCase(name);
|
|
116
|
+
paletteHexMap[paletteName] = {};
|
|
117
|
+
for (const tone of STANDARD_TONES) {
|
|
118
|
+
const argb = palette.tone(tone);
|
|
119
|
+
const path2 = `ref/palette/${paletteName}/${tone}`;
|
|
120
|
+
const hex = hexFromArgb2(argb).toUpperCase();
|
|
121
|
+
paletteHexMap[paletteName][hex] = path2;
|
|
122
|
+
const color = {
|
|
123
|
+
r: redFromArgb(argb) / 255,
|
|
124
|
+
g: greenFromArgb(argb) / 255,
|
|
125
|
+
b: blueFromArgb(argb) / 255,
|
|
126
|
+
a: 1
|
|
127
|
+
};
|
|
128
|
+
variables.push({
|
|
129
|
+
path: path2,
|
|
130
|
+
scopes: ["ALL_SCOPES"],
|
|
131
|
+
values: { Light: color, Dark: color }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function findAlias(hex, tokenName) {
|
|
136
|
+
const preferredKebab = tokenToPalette[tokenName] ?? deriveCustomPaletteName(tokenName, allPaletteNamesKebab);
|
|
137
|
+
const preferred = preferredKebab ? startCase(preferredKebab) : void 0;
|
|
138
|
+
if (preferred && paletteHexMap[preferred]?.[hex]) {
|
|
139
|
+
return paletteHexMap[preferred][hex];
|
|
140
|
+
}
|
|
141
|
+
for (const palHexes of Object.values(paletteHexMap)) {
|
|
142
|
+
if (palHexes[hex]) return palHexes[hex];
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function resolveValue(argb, tokenName) {
|
|
147
|
+
const hex = hexFromArgb2(argb).toUpperCase();
|
|
148
|
+
const aliasPath = findAlias(hex, tokenName);
|
|
149
|
+
if (aliasPath) return { alias: aliasPath };
|
|
150
|
+
return {
|
|
151
|
+
r: redFromArgb(argb) / 255,
|
|
152
|
+
g: greenFromArgb(argb) / 255,
|
|
153
|
+
b: blueFromArgb(argb) / 255,
|
|
154
|
+
a: 1
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
for (const [name, lightArgb] of Object.entries(mergedColorsLight)) {
|
|
158
|
+
const darkArgb = mergedColorsDark[name] ?? lightArgb;
|
|
159
|
+
const description = isTokenName(name) ? tokenDescriptions[name] : void 0;
|
|
160
|
+
variables.push({
|
|
161
|
+
path: `sys/color/${startCase(name)}`,
|
|
162
|
+
...description ? { description } : {},
|
|
163
|
+
scopes: ["ALL_SCOPES"],
|
|
164
|
+
values: {
|
|
165
|
+
Light: resolveValue(lightArgb, name),
|
|
166
|
+
Dark: resolveValue(darkArgb, name)
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return variables;
|
|
171
|
+
}
|
|
172
|
+
function buildFigmaTokens(ctx) {
|
|
173
|
+
const { prefix } = ctx;
|
|
174
|
+
const variables = buildFigmaVariables(ctx);
|
|
175
|
+
function rgbToHex(r, g, b) {
|
|
176
|
+
const toHex = (c) => Math.round(c * 255).toString(16).padStart(2, "0");
|
|
177
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
|
|
178
|
+
}
|
|
179
|
+
function toDtcgValue(modeValue) {
|
|
180
|
+
if ("alias" in modeValue) {
|
|
181
|
+
return `{${modeValue.alias.replaceAll("/", ".")}}`;
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
colorSpace: "srgb",
|
|
185
|
+
components: [modeValue.r, modeValue.g, modeValue.b],
|
|
186
|
+
alpha: modeValue.a,
|
|
187
|
+
hex: rgbToHex(modeValue.r, modeValue.g, modeValue.b)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function toDtcgExtensions(v) {
|
|
191
|
+
if (v.path.startsWith("ref/palette/")) {
|
|
192
|
+
return {
|
|
193
|
+
"com.figma.scopes": v.scopes ?? ["ALL_SCOPES"],
|
|
194
|
+
"com.figma.isOverride": true
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const lastSegment = v.path.split("/").at(-1);
|
|
198
|
+
if (!lastSegment) return { "com.figma.scopes": v.scopes ?? ["ALL_SCOPES"] };
|
|
199
|
+
const tokenName = kebabCase2(lastSegment);
|
|
200
|
+
return {
|
|
201
|
+
"com.figma.scopes": v.scopes ?? ["ALL_SCOPES"],
|
|
202
|
+
"css.variable": `--${prefix}-sys-color-${tokenName}`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function buildToken(v, modeValue) {
|
|
206
|
+
return {
|
|
207
|
+
$type: "color",
|
|
208
|
+
$value: toDtcgValue(modeValue),
|
|
209
|
+
...v.description ? { $description: v.description } : {},
|
|
210
|
+
$extensions: toDtcgExtensions(v)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function parsePath(path2) {
|
|
214
|
+
const parts = path2.split("/");
|
|
215
|
+
if (parts[0] === "ref" && parts[1] === "palette" && parts[2] && parts[3]) {
|
|
216
|
+
return {
|
|
217
|
+
kind: "palette",
|
|
218
|
+
paletteName: parts[2],
|
|
219
|
+
tone: parts[3]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (parts[0] === "sys" && parts[1] === "color" && parts[2]) {
|
|
223
|
+
return { kind: "color", tokenName: parts[2] };
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
function buildModeFile(modeName) {
|
|
228
|
+
const palette = {};
|
|
229
|
+
const color = {};
|
|
230
|
+
for (const v of variables) {
|
|
231
|
+
const modeValue = v.values[modeName];
|
|
232
|
+
if (!modeValue) continue;
|
|
233
|
+
const parsed = parsePath(v.path);
|
|
234
|
+
if (!parsed) continue;
|
|
235
|
+
if (parsed.kind === "palette") {
|
|
236
|
+
const group = palette[parsed.paletteName] ??= {};
|
|
237
|
+
group[parsed.tone] = buildToken(v, modeValue);
|
|
238
|
+
} else {
|
|
239
|
+
color[parsed.tokenName] = buildToken(v, modeValue);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
$extensions: { "com.figma.modeName": modeName },
|
|
244
|
+
ref: { palette },
|
|
245
|
+
sys: { color }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
"Light.tokens.json": buildModeFile("Light"),
|
|
250
|
+
"Dark.tokens.json": buildModeFile("Dark")
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/lib/builder.flutter.ts
|
|
255
|
+
import { hexFromArgb as hexFromArgb3 } from "@material/material-color-utilities";
|
|
256
|
+
import { upperFirst } from "lodash-es";
|
|
257
|
+
var FLUTTER_PROPERTIES = [
|
|
258
|
+
"primary",
|
|
259
|
+
"surfaceTint",
|
|
260
|
+
"onPrimary",
|
|
261
|
+
"primaryContainer",
|
|
262
|
+
"onPrimaryContainer",
|
|
263
|
+
"secondary",
|
|
264
|
+
"onSecondary",
|
|
265
|
+
"secondaryContainer",
|
|
266
|
+
"onSecondaryContainer",
|
|
267
|
+
"tertiary",
|
|
268
|
+
"onTertiary",
|
|
269
|
+
"tertiaryContainer",
|
|
270
|
+
"onTertiaryContainer",
|
|
271
|
+
"error",
|
|
272
|
+
"onError",
|
|
273
|
+
"errorContainer",
|
|
274
|
+
"onErrorContainer",
|
|
275
|
+
"background",
|
|
276
|
+
"onBackground",
|
|
277
|
+
"surface",
|
|
278
|
+
"onSurface",
|
|
279
|
+
"surfaceVariant",
|
|
280
|
+
"onSurfaceVariant",
|
|
281
|
+
"outline",
|
|
282
|
+
"outlineVariant",
|
|
283
|
+
"shadow",
|
|
284
|
+
"scrim",
|
|
285
|
+
"inverseSurface",
|
|
286
|
+
"inverseOnSurface",
|
|
287
|
+
"inversePrimary",
|
|
288
|
+
"primaryFixed",
|
|
289
|
+
"onPrimaryFixed",
|
|
290
|
+
"primaryFixedDim",
|
|
291
|
+
"onPrimaryFixedVariant",
|
|
292
|
+
"secondaryFixed",
|
|
293
|
+
"onSecondaryFixed",
|
|
294
|
+
"secondaryFixedDim",
|
|
295
|
+
"onSecondaryFixedVariant",
|
|
296
|
+
"tertiaryFixed",
|
|
297
|
+
"onTertiaryFixed",
|
|
298
|
+
"tertiaryFixedDim",
|
|
299
|
+
"onTertiaryFixedVariant",
|
|
300
|
+
"surfaceDim",
|
|
301
|
+
"surfaceBright",
|
|
302
|
+
"surfaceContainerLowest",
|
|
303
|
+
"surfaceContainerLow",
|
|
304
|
+
"surfaceContainer",
|
|
305
|
+
"surfaceContainerHigh",
|
|
306
|
+
"surfaceContainerHighest"
|
|
307
|
+
];
|
|
308
|
+
var TOKEN_TO_FLUTTER = {
|
|
309
|
+
inverseOnSurface: "onInverseSurface"
|
|
310
|
+
};
|
|
311
|
+
function toFlutterColor(argb) {
|
|
312
|
+
const hex = hexFromArgb3(argb).replace("#", "").toUpperCase();
|
|
313
|
+
return `Color(0xFF${hex})`;
|
|
314
|
+
}
|
|
315
|
+
function flutterPropName(m3Token) {
|
|
316
|
+
return TOKEN_TO_FLUTTER[m3Token] ?? m3Token;
|
|
317
|
+
}
|
|
318
|
+
function buildFlutter(ctx) {
|
|
319
|
+
const { mergedColorsLight, mergedColorsDark, hexCustomColors } = ctx;
|
|
320
|
+
function buildColorScheme(name, brightness, colors) {
|
|
321
|
+
const lines = [];
|
|
322
|
+
lines.push(`const ${name} = ColorScheme(`);
|
|
323
|
+
lines.push(` brightness: Brightness.${brightness},`);
|
|
324
|
+
for (const token of FLUTTER_PROPERTIES) {
|
|
325
|
+
const argb = colors[token];
|
|
326
|
+
if (argb === void 0) continue;
|
|
327
|
+
lines.push(` ${flutterPropName(token)}: ${toFlutterColor(argb)},`);
|
|
328
|
+
}
|
|
329
|
+
lines.push(");");
|
|
330
|
+
return lines.join("\n");
|
|
331
|
+
}
|
|
332
|
+
function buildCustomColors() {
|
|
333
|
+
if (hexCustomColors.length === 0) return "";
|
|
334
|
+
const sections = [];
|
|
335
|
+
for (const cc of hexCustomColors) {
|
|
336
|
+
const ccName = cc.name;
|
|
337
|
+
const tokens = [
|
|
338
|
+
ccName,
|
|
339
|
+
`on${upperFirst(ccName)}`,
|
|
340
|
+
`${ccName}Container`,
|
|
341
|
+
`on${upperFirst(ccName)}Container`
|
|
342
|
+
];
|
|
343
|
+
const lightLines = [];
|
|
344
|
+
const darkLines = [];
|
|
345
|
+
for (const token of tokens) {
|
|
346
|
+
const lightArgb = mergedColorsLight[token];
|
|
347
|
+
const darkArgb = mergedColorsDark[token];
|
|
348
|
+
if (lightArgb !== void 0) {
|
|
349
|
+
lightLines.push(
|
|
350
|
+
`const light${upperFirst(token)} = ${toFlutterColor(lightArgb)};`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (darkArgb !== void 0) {
|
|
354
|
+
darkLines.push(
|
|
355
|
+
`const dark${upperFirst(token)} = ${toFlutterColor(darkArgb)};`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
sections.push([...lightLines, ...darkLines].join("\n"));
|
|
360
|
+
}
|
|
361
|
+
return "\n\n" + sections.join("\n\n");
|
|
362
|
+
}
|
|
363
|
+
const header = "import 'package:flutter/material.dart';";
|
|
364
|
+
const light = buildColorScheme(
|
|
365
|
+
"lightColorScheme",
|
|
366
|
+
"light",
|
|
367
|
+
mergedColorsLight
|
|
368
|
+
);
|
|
369
|
+
const dark = buildColorScheme("darkColorScheme", "dark", mergedColorsDark);
|
|
370
|
+
const custom = buildCustomColors();
|
|
371
|
+
return `${header}
|
|
372
|
+
|
|
373
|
+
${light}
|
|
374
|
+
|
|
375
|
+
${dark}${custom}
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/lib/builder.json.ts
|
|
380
|
+
import {
|
|
381
|
+
argbFromHex,
|
|
382
|
+
DynamicScheme,
|
|
383
|
+
Hct,
|
|
384
|
+
hexFromArgb as hexFromArgb4,
|
|
385
|
+
MaterialDynamicColors,
|
|
27
386
|
TonalPalette
|
|
28
387
|
} from "@material/material-color-utilities";
|
|
29
|
-
|
|
388
|
+
var MTB_TONES = [
|
|
389
|
+
0,
|
|
390
|
+
5,
|
|
391
|
+
10,
|
|
392
|
+
15,
|
|
393
|
+
20,
|
|
394
|
+
25,
|
|
395
|
+
30,
|
|
396
|
+
35,
|
|
397
|
+
40,
|
|
398
|
+
50,
|
|
399
|
+
60,
|
|
400
|
+
70,
|
|
401
|
+
80,
|
|
402
|
+
90,
|
|
403
|
+
95,
|
|
404
|
+
98,
|
|
405
|
+
99,
|
|
406
|
+
100
|
|
407
|
+
];
|
|
408
|
+
var FIXTURE_TOKEN_ORDER = [
|
|
409
|
+
"primary",
|
|
410
|
+
"surfaceTint",
|
|
411
|
+
"onPrimary",
|
|
412
|
+
"primaryContainer",
|
|
413
|
+
"onPrimaryContainer",
|
|
414
|
+
"secondary",
|
|
415
|
+
"onSecondary",
|
|
416
|
+
"secondaryContainer",
|
|
417
|
+
"onSecondaryContainer",
|
|
418
|
+
"tertiary",
|
|
419
|
+
"onTertiary",
|
|
420
|
+
"tertiaryContainer",
|
|
421
|
+
"onTertiaryContainer",
|
|
422
|
+
"error",
|
|
423
|
+
"onError",
|
|
424
|
+
"errorContainer",
|
|
425
|
+
"onErrorContainer",
|
|
426
|
+
"background",
|
|
427
|
+
"onBackground",
|
|
428
|
+
"surface",
|
|
429
|
+
"onSurface",
|
|
430
|
+
"surfaceVariant",
|
|
431
|
+
"onSurfaceVariant",
|
|
432
|
+
"outline",
|
|
433
|
+
"outlineVariant",
|
|
434
|
+
"shadow",
|
|
435
|
+
"scrim",
|
|
436
|
+
"inverseSurface",
|
|
437
|
+
"inverseOnSurface",
|
|
438
|
+
"inversePrimary",
|
|
439
|
+
"primaryFixed",
|
|
440
|
+
"onPrimaryFixed",
|
|
441
|
+
"primaryFixedDim",
|
|
442
|
+
"onPrimaryFixedVariant",
|
|
443
|
+
"secondaryFixed",
|
|
444
|
+
"onSecondaryFixed",
|
|
445
|
+
"secondaryFixedDim",
|
|
446
|
+
"onSecondaryFixedVariant",
|
|
447
|
+
"tertiaryFixed",
|
|
448
|
+
"onTertiaryFixed",
|
|
449
|
+
"tertiaryFixedDim",
|
|
450
|
+
"onTertiaryFixedVariant",
|
|
451
|
+
"surfaceDim",
|
|
452
|
+
"surfaceBright",
|
|
453
|
+
"surfaceContainerLowest",
|
|
454
|
+
"surfaceContainerLow",
|
|
455
|
+
"surfaceContainer",
|
|
456
|
+
"surfaceContainerHigh",
|
|
457
|
+
"surfaceContainerHighest"
|
|
458
|
+
];
|
|
459
|
+
function buildJson(ctx) {
|
|
460
|
+
const {
|
|
461
|
+
hexSource,
|
|
462
|
+
sourceHct,
|
|
463
|
+
effectiveSourceArgb,
|
|
464
|
+
primaryHct,
|
|
465
|
+
SchemeClass,
|
|
466
|
+
scheme,
|
|
467
|
+
primary,
|
|
468
|
+
secondary,
|
|
469
|
+
tertiary,
|
|
470
|
+
error,
|
|
471
|
+
neutral,
|
|
472
|
+
neutralVariant,
|
|
473
|
+
hexCustomColors
|
|
474
|
+
} = ctx;
|
|
475
|
+
const neuHct = neutral ? Hct.fromInt(argbFromHex(neutral)) : sourceHct;
|
|
476
|
+
const nvHct = neutralVariant ? Hct.fromInt(argbFromHex(neutralVariant)) : sourceHct;
|
|
477
|
+
const rawPalettes = {
|
|
478
|
+
primary: TonalPalette.fromInt(effectiveSourceArgb),
|
|
479
|
+
secondary: secondary ? TonalPalette.fromInt(argbFromHex(secondary)) : TonalPalette.fromHueAndChroma(sourceHct.hue, sourceHct.chroma / 3),
|
|
480
|
+
tertiary: tertiary ? TonalPalette.fromInt(argbFromHex(tertiary)) : TonalPalette.fromHueAndChroma(
|
|
481
|
+
(sourceHct.hue + 60) % 360,
|
|
482
|
+
sourceHct.chroma / 2
|
|
483
|
+
),
|
|
484
|
+
neutral: TonalPalette.fromHueAndChroma(
|
|
485
|
+
neuHct.hue,
|
|
486
|
+
Math.min(neuHct.chroma / 12, 4)
|
|
487
|
+
),
|
|
488
|
+
"neutral-variant": TonalPalette.fromHueAndChroma(
|
|
489
|
+
nvHct.hue,
|
|
490
|
+
Math.min(nvHct.chroma / 6, 8)
|
|
491
|
+
)
|
|
492
|
+
};
|
|
493
|
+
function buildJsonSchemes() {
|
|
494
|
+
function extractSchemeColors(scheme2, backgroundScheme) {
|
|
495
|
+
const colors = {};
|
|
496
|
+
for (const tokenName of FIXTURE_TOKEN_ORDER) {
|
|
497
|
+
const dynamicColor = MaterialDynamicColors[tokenName];
|
|
498
|
+
const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
|
|
499
|
+
colors[tokenName] = hexFromArgb4(
|
|
500
|
+
dynamicColor.getArgb(useScheme)
|
|
501
|
+
).toUpperCase();
|
|
502
|
+
}
|
|
503
|
+
return colors;
|
|
504
|
+
}
|
|
505
|
+
function resolveOverridePalette(hex, role) {
|
|
506
|
+
if (!hex) return null;
|
|
507
|
+
return new SchemeClass(Hct.fromInt(argbFromHex(hex)), false, 0)[role];
|
|
508
|
+
}
|
|
509
|
+
const secPalette = resolveOverridePalette(secondary, "primaryPalette");
|
|
510
|
+
const terPalette = resolveOverridePalette(tertiary, "primaryPalette");
|
|
511
|
+
const errPalette = resolveOverridePalette(error, "primaryPalette");
|
|
512
|
+
const neuPalette = resolveOverridePalette(neutral, "neutralPalette");
|
|
513
|
+
const nvPalette = resolveOverridePalette(
|
|
514
|
+
neutralVariant,
|
|
515
|
+
"neutralVariantPalette"
|
|
516
|
+
);
|
|
517
|
+
const jsonSchemes = {};
|
|
518
|
+
const jsonContrastLevels = [
|
|
519
|
+
{ name: "light", isDark: false, contrast: 0 },
|
|
520
|
+
{ name: "light-medium-contrast", isDark: false, contrast: 0.5 },
|
|
521
|
+
{ name: "light-high-contrast", isDark: false, contrast: 1 },
|
|
522
|
+
{ name: "dark", isDark: true, contrast: 0 },
|
|
523
|
+
{ name: "dark-medium-contrast", isDark: true, contrast: 0.5 },
|
|
524
|
+
{ name: "dark-high-contrast", isDark: true, contrast: 1 }
|
|
525
|
+
];
|
|
526
|
+
for (const { name, isDark, contrast } of jsonContrastLevels) {
|
|
527
|
+
const baseScheme = new SchemeClass(primaryHct, isDark, contrast);
|
|
528
|
+
const composedScheme = new DynamicScheme({
|
|
529
|
+
sourceColorArgb: effectiveSourceArgb,
|
|
530
|
+
variant: schemeToVariant[scheme],
|
|
531
|
+
contrastLevel: contrast,
|
|
532
|
+
isDark,
|
|
533
|
+
primaryPalette: baseScheme.primaryPalette,
|
|
534
|
+
secondaryPalette: secPalette || baseScheme.secondaryPalette,
|
|
535
|
+
tertiaryPalette: terPalette || baseScheme.tertiaryPalette,
|
|
536
|
+
neutralPalette: neuPalette || baseScheme.neutralPalette,
|
|
537
|
+
neutralVariantPalette: nvPalette || baseScheme.neutralVariantPalette
|
|
538
|
+
});
|
|
539
|
+
if (errPalette) composedScheme.errorPalette = errPalette;
|
|
540
|
+
jsonSchemes[name] = extractSchemeColors(composedScheme, baseScheme);
|
|
541
|
+
}
|
|
542
|
+
return jsonSchemes;
|
|
543
|
+
}
|
|
544
|
+
function rawPalettesToJson() {
|
|
545
|
+
const jsonPalettes = {};
|
|
546
|
+
const RAW_PALETTE_NAMES = [
|
|
547
|
+
"primary",
|
|
548
|
+
"secondary",
|
|
549
|
+
"tertiary",
|
|
550
|
+
"neutral",
|
|
551
|
+
"neutral-variant"
|
|
552
|
+
];
|
|
553
|
+
for (const name of RAW_PALETTE_NAMES) {
|
|
554
|
+
const palette = rawPalettes[name];
|
|
555
|
+
const tones = {};
|
|
556
|
+
for (const tone of MTB_TONES) {
|
|
557
|
+
tones[tone.toString()] = hexFromArgb4(palette.tone(tone)).toUpperCase();
|
|
558
|
+
}
|
|
559
|
+
jsonPalettes[name] = tones;
|
|
560
|
+
}
|
|
561
|
+
return jsonPalettes;
|
|
562
|
+
}
|
|
563
|
+
function buildCoreColors(opts) {
|
|
564
|
+
const colors = { primary: opts.primary };
|
|
565
|
+
if (opts.secondary) colors.secondary = opts.secondary.toUpperCase();
|
|
566
|
+
if (opts.tertiary) colors.tertiary = opts.tertiary.toUpperCase();
|
|
567
|
+
if (opts.error) colors.error = opts.error.toUpperCase();
|
|
568
|
+
if (opts.neutral) colors.neutral = opts.neutral.toUpperCase();
|
|
569
|
+
if (opts.neutralVariant)
|
|
570
|
+
colors.neutralVariant = opts.neutralVariant.toUpperCase();
|
|
571
|
+
return colors;
|
|
572
|
+
}
|
|
573
|
+
const seed = hexSource.toUpperCase();
|
|
574
|
+
const coreColors = buildCoreColors({
|
|
575
|
+
primary: (primary || hexSource).toUpperCase(),
|
|
576
|
+
secondary,
|
|
577
|
+
tertiary,
|
|
578
|
+
error,
|
|
579
|
+
neutral,
|
|
580
|
+
neutralVariant
|
|
581
|
+
});
|
|
582
|
+
const extendedColors = hexCustomColors.map((c) => ({
|
|
583
|
+
name: c.name,
|
|
584
|
+
color: c.hex.toUpperCase(),
|
|
585
|
+
description: "",
|
|
586
|
+
harmonized: c.blend ?? DEFAULT_BLEND
|
|
587
|
+
}));
|
|
588
|
+
return {
|
|
589
|
+
seed,
|
|
590
|
+
coreColors,
|
|
591
|
+
extendedColors,
|
|
592
|
+
schemes: buildJsonSchemes(),
|
|
593
|
+
palettes: rawPalettesToJson()
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/lib/builder.tailwind.ts
|
|
598
|
+
import { kebabCase as kebabCase3 } from "lodash-es";
|
|
599
|
+
var SHADE_TO_TONE = [
|
|
600
|
+
[50, 95],
|
|
601
|
+
[100, 90],
|
|
602
|
+
[200, 80],
|
|
603
|
+
[300, 70],
|
|
604
|
+
[400, 60],
|
|
605
|
+
[500, 50],
|
|
606
|
+
[600, 40],
|
|
607
|
+
[700, 30],
|
|
608
|
+
[800, 20],
|
|
609
|
+
[900, 10],
|
|
610
|
+
[950, 5]
|
|
611
|
+
];
|
|
612
|
+
var CORE_PALETTES = [
|
|
613
|
+
"primary",
|
|
614
|
+
"secondary",
|
|
615
|
+
"tertiary",
|
|
616
|
+
"error",
|
|
617
|
+
"neutral",
|
|
618
|
+
"neutral-variant"
|
|
619
|
+
];
|
|
620
|
+
var SHADCN_MAPPING = [
|
|
621
|
+
["--background", "surface"],
|
|
622
|
+
["--foreground", "on-surface"],
|
|
623
|
+
["--card", "surface-container-low"],
|
|
624
|
+
["--card-foreground", "on-surface"],
|
|
625
|
+
["--popover", "surface-container-high"],
|
|
626
|
+
["--popover-foreground", "on-surface"],
|
|
627
|
+
["--primary", "primary"],
|
|
628
|
+
["--primary-foreground", "on-primary"],
|
|
629
|
+
["--secondary", "secondary-container"],
|
|
630
|
+
["--secondary-foreground", "on-secondary-container"],
|
|
631
|
+
["--muted", "surface-container-highest"],
|
|
632
|
+
["--muted-foreground", "on-surface-variant"],
|
|
633
|
+
["--accent", "secondary-container"],
|
|
634
|
+
["--accent-foreground", "on-secondary-container"],
|
|
635
|
+
["--destructive", "error"],
|
|
636
|
+
["--border", "outline-variant"],
|
|
637
|
+
["--input", "outline"],
|
|
638
|
+
["--ring", "primary"],
|
|
639
|
+
["--chart-1", "primary-fixed"],
|
|
640
|
+
["--chart-2", "secondary-fixed"],
|
|
641
|
+
["--chart-3", "tertiary-fixed"],
|
|
642
|
+
["--chart-4", "primary-fixed-dim"],
|
|
643
|
+
["--chart-5", "secondary-fixed-dim"],
|
|
644
|
+
["--sidebar", "surface-container-low"],
|
|
645
|
+
["--sidebar-foreground", "on-surface"],
|
|
646
|
+
["--sidebar-primary", "primary"],
|
|
647
|
+
["--sidebar-primary-foreground", "on-primary"],
|
|
648
|
+
["--sidebar-accent", "secondary-container"],
|
|
649
|
+
["--sidebar-accent-foreground", "on-secondary-container"],
|
|
650
|
+
["--sidebar-border", "outline-variant"],
|
|
651
|
+
["--sidebar-ring", "primary"]
|
|
652
|
+
];
|
|
653
|
+
function buildTailwind(ctx, options) {
|
|
654
|
+
const { prefix, mergedColorsLight, hexCustomColors } = ctx;
|
|
655
|
+
const lines = [];
|
|
656
|
+
for (const name of Object.keys(mergedColorsLight)) {
|
|
657
|
+
const kebab = kebabCase3(name);
|
|
658
|
+
lines.push(`--color-${kebab}: var(--${prefix}-sys-color-${kebab});`);
|
|
659
|
+
}
|
|
660
|
+
lines.push("");
|
|
661
|
+
lines.push("/* Shades */");
|
|
662
|
+
for (const palette of CORE_PALETTES) {
|
|
663
|
+
lines.push("");
|
|
664
|
+
for (const [shade, tone] of SHADE_TO_TONE) {
|
|
665
|
+
lines.push(
|
|
666
|
+
`--color-${palette}-${shade}: var(--${prefix}-ref-palette-${palette}-${tone});`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (hexCustomColors.length > 0) {
|
|
671
|
+
for (const customColor of hexCustomColors) {
|
|
672
|
+
const kebab = kebabCase3(customColor.name);
|
|
673
|
+
lines.push("");
|
|
674
|
+
for (const [shade, tone] of SHADE_TO_TONE) {
|
|
675
|
+
lines.push(
|
|
676
|
+
`--color-${kebab}-${shade}: var(--${prefix}-ref-palette-${kebab}-${tone});`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
let output = `@theme inline {
|
|
682
|
+
${lines.join("\n ")}
|
|
683
|
+
}
|
|
684
|
+
`;
|
|
685
|
+
if (options?.shadcn) {
|
|
686
|
+
const shadcnLines = SHADCN_MAPPING.map(
|
|
687
|
+
([shadcnVar, m3Token]) => `${shadcnVar}: var(--${prefix}-sys-color-${m3Token});`
|
|
688
|
+
);
|
|
689
|
+
output += `
|
|
690
|
+
:root,
|
|
691
|
+
.dark {
|
|
692
|
+
${shadcnLines.join("\n ")}
|
|
693
|
+
}
|
|
694
|
+
`;
|
|
695
|
+
}
|
|
696
|
+
return output;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/lib/builder.ts
|
|
30
700
|
var schemeNames = [
|
|
31
701
|
"tonalSpot",
|
|
32
702
|
"monochrome",
|
|
@@ -80,26 +750,6 @@ var STANDARD_TONES = [
|
|
|
80
750
|
99,
|
|
81
751
|
100
|
|
82
752
|
];
|
|
83
|
-
var MTB_TONES = [
|
|
84
|
-
0,
|
|
85
|
-
5,
|
|
86
|
-
10,
|
|
87
|
-
15,
|
|
88
|
-
20,
|
|
89
|
-
25,
|
|
90
|
-
30,
|
|
91
|
-
35,
|
|
92
|
-
40,
|
|
93
|
-
50,
|
|
94
|
-
60,
|
|
95
|
-
70,
|
|
96
|
-
80,
|
|
97
|
-
90,
|
|
98
|
-
95,
|
|
99
|
-
98,
|
|
100
|
-
99,
|
|
101
|
-
100
|
|
102
|
-
];
|
|
103
753
|
var Variant = {
|
|
104
754
|
MONOCHROME: 0,
|
|
105
755
|
NEUTRAL: 1,
|
|
@@ -171,9 +821,21 @@ var tokenDescriptions = {
|
|
|
171
821
|
tertiaryFixed: "Fixed tertiary color that stays the same in light and dark themes.",
|
|
172
822
|
tertiaryFixedDim: "Dimmed variant of the fixed tertiary color for lower emphasis."
|
|
173
823
|
};
|
|
174
|
-
|
|
175
|
-
tokenDescriptions
|
|
176
|
-
|
|
824
|
+
function isTokenName(key) {
|
|
825
|
+
return key in tokenDescriptions;
|
|
826
|
+
}
|
|
827
|
+
var tokenNames = Object.keys(tokenDescriptions).filter(isTokenName);
|
|
828
|
+
function deriveCustomPaletteName(tokenName, allPaletteNamesKebab) {
|
|
829
|
+
let baseName = tokenName;
|
|
830
|
+
if (/^on[A-Z]/.test(baseName) && baseName.length > 2) {
|
|
831
|
+
baseName = baseName.charAt(2).toLowerCase() + baseName.slice(3);
|
|
832
|
+
}
|
|
833
|
+
if (baseName.endsWith("Container")) {
|
|
834
|
+
baseName = baseName.slice(0, -"Container".length);
|
|
835
|
+
}
|
|
836
|
+
const kebab = kebabCase4(baseName);
|
|
837
|
+
return allPaletteNamesKebab.has(kebab) ? kebab : void 0;
|
|
838
|
+
}
|
|
177
839
|
function toRecord(arr, getEntry) {
|
|
178
840
|
return Object.fromEntries(arr.map(getEntry));
|
|
179
841
|
}
|
|
@@ -188,58 +850,55 @@ function getPalette(palettes, colorName) {
|
|
|
188
850
|
}
|
|
189
851
|
function mergeBaseAndCustomColors(scheme, customColors, colorPalettes) {
|
|
190
852
|
const baseVars = toRecord(tokenNames, (tokenName) => {
|
|
191
|
-
const dynamicColor =
|
|
853
|
+
const dynamicColor = MaterialDynamicColors2[tokenName];
|
|
192
854
|
const argb = dynamicColor.getArgb(scheme);
|
|
193
855
|
return [tokenName, argb];
|
|
194
856
|
});
|
|
195
857
|
const customVars = {};
|
|
196
858
|
customColors.forEach((color) => {
|
|
197
859
|
const colorname = color.name;
|
|
198
|
-
const getPaletteForColor = (
|
|
199
|
-
const getTone = (baseTone) => (_s) => {
|
|
200
|
-
return baseTone;
|
|
201
|
-
};
|
|
860
|
+
const getPaletteForColor = () => getPalette(colorPalettes, colorname);
|
|
202
861
|
const colorDynamicColor = new DynamicColor(
|
|
203
862
|
colorname,
|
|
204
863
|
getPaletteForColor,
|
|
205
|
-
(s) =>
|
|
864
|
+
(s) => s.isDark ? 80 : 40,
|
|
206
865
|
// Main color: lighter in dark mode, darker in light mode
|
|
207
866
|
true
|
|
208
867
|
// background
|
|
209
868
|
);
|
|
210
869
|
const onColorDynamicColor = new DynamicColor(
|
|
211
|
-
`on${
|
|
870
|
+
`on${upperFirst2(colorname)}`,
|
|
212
871
|
getPaletteForColor,
|
|
213
|
-
(s) =>
|
|
872
|
+
(s) => s.isDark ? 20 : 100,
|
|
214
873
|
// Text on main color: high contrast (dark on light, light on dark)
|
|
215
874
|
false
|
|
216
875
|
);
|
|
217
876
|
const containerDynamicColor = new DynamicColor(
|
|
218
877
|
`${colorname}Container`,
|
|
219
878
|
getPaletteForColor,
|
|
220
|
-
(s) =>
|
|
879
|
+
(s) => s.isDark ? 30 : 90,
|
|
221
880
|
// Container: subtle variant (darker in dark mode, lighter in light mode)
|
|
222
881
|
true
|
|
223
882
|
// background
|
|
224
883
|
);
|
|
225
884
|
const onContainerDynamicColor = new DynamicColor(
|
|
226
|
-
`on${
|
|
885
|
+
`on${upperFirst2(colorname)}Container`,
|
|
227
886
|
getPaletteForColor,
|
|
228
|
-
(s) =>
|
|
887
|
+
(s) => s.isDark ? 90 : 30,
|
|
229
888
|
// Text on container: high contrast against container background
|
|
230
889
|
false
|
|
231
890
|
);
|
|
232
891
|
customVars[colorname] = colorDynamicColor.getArgb(scheme);
|
|
233
|
-
customVars[`on${
|
|
892
|
+
customVars[`on${upperFirst2(colorname)}`] = onColorDynamicColor.getArgb(scheme);
|
|
234
893
|
customVars[`${colorname}Container`] = containerDynamicColor.getArgb(scheme);
|
|
235
|
-
customVars[`on${
|
|
894
|
+
customVars[`on${upperFirst2(colorname)}Container`] = onContainerDynamicColor.getArgb(scheme);
|
|
236
895
|
});
|
|
237
896
|
return { ...baseVars, ...customVars };
|
|
238
897
|
}
|
|
239
898
|
function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonization) {
|
|
240
|
-
const colorArgb =
|
|
899
|
+
const colorArgb = argbFromHex2(colorDef.hex);
|
|
241
900
|
const harmonizedArgb = colorDef.blend ? Blend.harmonize(colorArgb, effectiveSourceForHarmonization) : colorArgb;
|
|
242
|
-
const hct =
|
|
901
|
+
const hct = Hct2.fromInt(harmonizedArgb);
|
|
243
902
|
let targetChroma;
|
|
244
903
|
if (colorDef.core && colorDef.chromaSource) {
|
|
245
904
|
if (colorDef.chromaSource === "neutral") {
|
|
@@ -252,7 +911,25 @@ function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonizatio
|
|
|
252
911
|
} else {
|
|
253
912
|
targetChroma = baseScheme.primaryPalette.chroma;
|
|
254
913
|
}
|
|
255
|
-
return
|
|
914
|
+
return TonalPalette2.fromHueAndChroma(hct.hue, targetChroma);
|
|
915
|
+
}
|
|
916
|
+
function buildTokenToPaletteMap(schemePalettes, scheme) {
|
|
917
|
+
const result = {};
|
|
918
|
+
for (const propName of Object.getOwnPropertyNames(MaterialDynamicColors2)) {
|
|
919
|
+
const dc = Object.getOwnPropertyDescriptor(
|
|
920
|
+
MaterialDynamicColors2,
|
|
921
|
+
propName
|
|
922
|
+
)?.value;
|
|
923
|
+
if (!(dc instanceof DynamicColor)) continue;
|
|
924
|
+
const palette = dc.palette(scheme);
|
|
925
|
+
for (const [palName, pal] of schemePalettes) {
|
|
926
|
+
if (palette === pal) {
|
|
927
|
+
result[propName] = palName;
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return result;
|
|
256
933
|
}
|
|
257
934
|
function builder(hexSource, {
|
|
258
935
|
scheme = DEFAULT_SCHEME,
|
|
@@ -266,13 +943,13 @@ function builder(hexSource, {
|
|
|
266
943
|
customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
|
|
267
944
|
prefix = DEFAULT_PREFIX
|
|
268
945
|
} = {}) {
|
|
269
|
-
const sourceArgb =
|
|
270
|
-
const sourceHct =
|
|
946
|
+
const sourceArgb = argbFromHex2(hexSource);
|
|
947
|
+
const sourceHct = Hct2.fromInt(sourceArgb);
|
|
271
948
|
const effectiveSource = primary || hexSource;
|
|
272
|
-
const effectiveSourceArgb =
|
|
273
|
-
const effectiveSourceForHarmonization = primary ?
|
|
949
|
+
const effectiveSourceArgb = argbFromHex2(effectiveSource);
|
|
950
|
+
const effectiveSourceForHarmonization = primary ? argbFromHex2(primary) : sourceArgb;
|
|
274
951
|
const SchemeClass = schemesMap[scheme];
|
|
275
|
-
const primaryHct =
|
|
952
|
+
const primaryHct = Hct2.fromInt(effectiveSourceArgb);
|
|
276
953
|
const baseScheme = new SchemeClass(primaryHct, false, contrast);
|
|
277
954
|
const allColors = [
|
|
278
955
|
// Core colors (hex may be undefined)
|
|
@@ -337,8 +1014,8 @@ function builder(hexSource, {
|
|
|
337
1014
|
neutralPalette: colorPalettes["neutral"] || baseScheme.neutralPalette,
|
|
338
1015
|
neutralVariantPalette: colorPalettes["neutralVariant"] || baseScheme.neutralVariantPalette
|
|
339
1016
|
};
|
|
340
|
-
const lightScheme = new
|
|
341
|
-
const darkScheme = new
|
|
1017
|
+
const lightScheme = new DynamicScheme2({ ...schemeConfig, isDark: false });
|
|
1018
|
+
const darkScheme = new DynamicScheme2({ ...schemeConfig, isDark: true });
|
|
342
1019
|
const errorPalette = colorPalettes["error"];
|
|
343
1020
|
if (errorPalette) {
|
|
344
1021
|
lightScheme.errorPalette = errorPalette;
|
|
@@ -359,7 +1036,7 @@ function builder(hexSource, {
|
|
|
359
1036
|
const customColors = definedColors.filter((c) => !c.core).map((c) => ({
|
|
360
1037
|
name: c.name,
|
|
361
1038
|
blend: c.blend ?? DEFAULT_BLEND,
|
|
362
|
-
value:
|
|
1039
|
+
value: argbFromHex2(c.hex)
|
|
363
1040
|
}));
|
|
364
1041
|
const mergedColorsLight = mergeBaseAndCustomColors(
|
|
365
1042
|
lightScheme,
|
|
@@ -379,387 +1056,36 @@ function builder(hexSource, {
|
|
|
379
1056
|
["neutral", lightScheme.neutralPalette],
|
|
380
1057
|
["neutral-variant", lightScheme.neutralVariantPalette]
|
|
381
1058
|
];
|
|
382
|
-
function buildTokenToPaletteMap(schemePalettes2, scheme2) {
|
|
383
|
-
const result = {};
|
|
384
|
-
for (const propName of Object.getOwnPropertyNames(MaterialDynamicColors)) {
|
|
385
|
-
const dc = MaterialDynamicColors[propName];
|
|
386
|
-
if (!(dc instanceof DynamicColor)) continue;
|
|
387
|
-
const palette = dc.palette(scheme2);
|
|
388
|
-
for (const [palName, pal] of schemePalettes2) {
|
|
389
|
-
if (palette === pal) {
|
|
390
|
-
result[propName] = palName;
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return result;
|
|
396
|
-
}
|
|
397
1059
|
const tokenToPalette = buildTokenToPaletteMap(schemePalettes, lightScheme);
|
|
398
|
-
const allPaletteNamesKebab = new Set(Object.keys(allPalettes).map(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1060
|
+
const allPaletteNamesKebab = new Set(Object.keys(allPalettes).map(kebabCase4));
|
|
1061
|
+
const ctx = {
|
|
1062
|
+
hexSource,
|
|
1063
|
+
prefix,
|
|
1064
|
+
scheme,
|
|
1065
|
+
primary,
|
|
1066
|
+
secondary,
|
|
1067
|
+
tertiary,
|
|
1068
|
+
neutral,
|
|
1069
|
+
neutralVariant,
|
|
1070
|
+
error,
|
|
1071
|
+
hexCustomColors,
|
|
1072
|
+
sourceHct,
|
|
1073
|
+
effectiveSourceArgb,
|
|
1074
|
+
primaryHct,
|
|
1075
|
+
SchemeClass,
|
|
1076
|
+
allPalettes,
|
|
1077
|
+
mergedColorsLight,
|
|
1078
|
+
mergedColorsDark,
|
|
1079
|
+
tokenToPalette,
|
|
1080
|
+
allPaletteNamesKebab
|
|
1081
|
+
};
|
|
410
1082
|
return {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
//
|
|
418
|
-
toCss() {
|
|
419
|
-
function buildRefPaletteLookup() {
|
|
420
|
-
const lookup = {};
|
|
421
|
-
for (const [name, palette] of Object.entries(allPalettes)) {
|
|
422
|
-
const paletteName = kebabCase(name);
|
|
423
|
-
for (const tone of STANDARD_TONES) {
|
|
424
|
-
const hex = hexFromArgb(palette.tone(tone));
|
|
425
|
-
if (!lookup[hex]) lookup[hex] = [];
|
|
426
|
-
lookup[hex].push({ paletteName, tone });
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return lookup;
|
|
430
|
-
}
|
|
431
|
-
function sysColorVar(colorName, colorValue, lookup) {
|
|
432
|
-
const name = `--${prefix}-sys-color-${kebabCase(colorName)}`;
|
|
433
|
-
const hex = hexFromArgb(colorValue);
|
|
434
|
-
const matches = lookup[hex];
|
|
435
|
-
if (matches && matches.length > 0) {
|
|
436
|
-
const preferred = tokenToPalette[colorName] ?? deriveCustomPaletteName(colorName);
|
|
437
|
-
const match = (preferred ? matches.find((m) => m.paletteName === preferred) : void 0) ?? matches[0];
|
|
438
|
-
return `${name}:var(--${prefix}-ref-palette-${match.paletteName}-${match.tone});`;
|
|
439
|
-
}
|
|
440
|
-
return `${name}:${hex};`;
|
|
441
|
-
}
|
|
442
|
-
function toCssVars(mergedColors, lookup) {
|
|
443
|
-
return Object.entries(mergedColors).map(([name, value]) => sysColorVar(name, value, lookup)).join(" ");
|
|
444
|
-
}
|
|
445
|
-
function refPaletteVar(paletteName, tone, colorValue) {
|
|
446
|
-
const name = `--${prefix}-ref-palette-${paletteName}-${tone}`;
|
|
447
|
-
const value = hexFromArgb(colorValue);
|
|
448
|
-
return `${name}:${value};`;
|
|
449
|
-
}
|
|
450
|
-
function generateTonalPaletteVars(paletteName, palette) {
|
|
451
|
-
return STANDARD_TONES.map((tone) => {
|
|
452
|
-
const color = palette.tone(tone);
|
|
453
|
-
return refPaletteVar(paletteName, tone, color);
|
|
454
|
-
}).join(" ");
|
|
455
|
-
}
|
|
456
|
-
function generateTonalVars() {
|
|
457
|
-
return Object.entries(allPalettes).map(
|
|
458
|
-
([name, palette]) => generateTonalPaletteVars(kebabCase(name), palette)
|
|
459
|
-
).join(" ");
|
|
460
|
-
}
|
|
461
|
-
const refPaletteLookup = buildRefPaletteLookup();
|
|
462
|
-
const lightVars = toCssVars(mergedColorsLight, refPaletteLookup);
|
|
463
|
-
const darkVars = toCssVars(mergedColorsDark, refPaletteLookup);
|
|
464
|
-
const tonalVars = generateTonalVars();
|
|
465
|
-
return `
|
|
466
|
-
:root { ${lightVars} ${tonalVars} }
|
|
467
|
-
.dark { ${darkVars} ${tonalVars} }
|
|
468
|
-
`;
|
|
469
|
-
},
|
|
470
|
-
//
|
|
471
|
-
// ██ ███████ ██████ ███ ██
|
|
472
|
-
// ██ ██ ██ ██ ████ ██
|
|
473
|
-
// ██ ███████ ██ ██ ██ ██ ██
|
|
474
|
-
// ██ ██ ██ ██ ██ ██ ██ ██
|
|
475
|
-
// █████ ███████ ██████ ██ ████
|
|
476
|
-
//
|
|
477
|
-
toJson() {
|
|
478
|
-
const fixtureTokenOrder = [
|
|
479
|
-
"primary",
|
|
480
|
-
"surfaceTint",
|
|
481
|
-
"onPrimary",
|
|
482
|
-
"primaryContainer",
|
|
483
|
-
"onPrimaryContainer",
|
|
484
|
-
"secondary",
|
|
485
|
-
"onSecondary",
|
|
486
|
-
"secondaryContainer",
|
|
487
|
-
"onSecondaryContainer",
|
|
488
|
-
"tertiary",
|
|
489
|
-
"onTertiary",
|
|
490
|
-
"tertiaryContainer",
|
|
491
|
-
"onTertiaryContainer",
|
|
492
|
-
"error",
|
|
493
|
-
"onError",
|
|
494
|
-
"errorContainer",
|
|
495
|
-
"onErrorContainer",
|
|
496
|
-
"background",
|
|
497
|
-
"onBackground",
|
|
498
|
-
"surface",
|
|
499
|
-
"onSurface",
|
|
500
|
-
"surfaceVariant",
|
|
501
|
-
"onSurfaceVariant",
|
|
502
|
-
"outline",
|
|
503
|
-
"outlineVariant",
|
|
504
|
-
"shadow",
|
|
505
|
-
"scrim",
|
|
506
|
-
"inverseSurface",
|
|
507
|
-
"inverseOnSurface",
|
|
508
|
-
"inversePrimary",
|
|
509
|
-
"primaryFixed",
|
|
510
|
-
"onPrimaryFixed",
|
|
511
|
-
"primaryFixedDim",
|
|
512
|
-
"onPrimaryFixedVariant",
|
|
513
|
-
"secondaryFixed",
|
|
514
|
-
"onSecondaryFixed",
|
|
515
|
-
"secondaryFixedDim",
|
|
516
|
-
"onSecondaryFixedVariant",
|
|
517
|
-
"tertiaryFixed",
|
|
518
|
-
"onTertiaryFixed",
|
|
519
|
-
"tertiaryFixedDim",
|
|
520
|
-
"onTertiaryFixedVariant",
|
|
521
|
-
"surfaceDim",
|
|
522
|
-
"surfaceBright",
|
|
523
|
-
"surfaceContainerLowest",
|
|
524
|
-
"surfaceContainerLow",
|
|
525
|
-
"surfaceContainer",
|
|
526
|
-
"surfaceContainerHigh",
|
|
527
|
-
"surfaceContainerHighest"
|
|
528
|
-
];
|
|
529
|
-
const neuHct = neutral ? Hct.fromInt(argbFromHex(neutral)) : sourceHct;
|
|
530
|
-
const nvHct = neutralVariant ? Hct.fromInt(argbFromHex(neutralVariant)) : sourceHct;
|
|
531
|
-
const rawPalettes = {
|
|
532
|
-
primary: TonalPalette.fromInt(effectiveSourceArgb),
|
|
533
|
-
secondary: secondary ? TonalPalette.fromInt(argbFromHex(secondary)) : TonalPalette.fromHueAndChroma(sourceHct.hue, sourceHct.chroma / 3),
|
|
534
|
-
tertiary: tertiary ? TonalPalette.fromInt(argbFromHex(tertiary)) : TonalPalette.fromHueAndChroma(
|
|
535
|
-
(sourceHct.hue + 60) % 360,
|
|
536
|
-
sourceHct.chroma / 2
|
|
537
|
-
),
|
|
538
|
-
neutral: TonalPalette.fromHueAndChroma(
|
|
539
|
-
neuHct.hue,
|
|
540
|
-
Math.min(neuHct.chroma / 12, 4)
|
|
541
|
-
),
|
|
542
|
-
"neutral-variant": TonalPalette.fromHueAndChroma(
|
|
543
|
-
nvHct.hue,
|
|
544
|
-
Math.min(nvHct.chroma / 6, 8)
|
|
545
|
-
)
|
|
546
|
-
};
|
|
547
|
-
function buildJsonSchemes() {
|
|
548
|
-
function extractSchemeColors(scheme2, backgroundScheme) {
|
|
549
|
-
const colors = {};
|
|
550
|
-
for (const tokenName of fixtureTokenOrder) {
|
|
551
|
-
const dynamicColor = MaterialDynamicColors[tokenName];
|
|
552
|
-
const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
|
|
553
|
-
colors[tokenName] = hexFromArgb(
|
|
554
|
-
dynamicColor.getArgb(useScheme)
|
|
555
|
-
).toUpperCase();
|
|
556
|
-
}
|
|
557
|
-
return colors;
|
|
558
|
-
}
|
|
559
|
-
function resolveOverridePalette(hex, role) {
|
|
560
|
-
if (!hex) return null;
|
|
561
|
-
return new SchemeClass(Hct.fromInt(argbFromHex(hex)), false, 0)[role];
|
|
562
|
-
}
|
|
563
|
-
const secPalette = resolveOverridePalette(secondary, "primaryPalette");
|
|
564
|
-
const terPalette = resolveOverridePalette(tertiary, "primaryPalette");
|
|
565
|
-
const errPalette = resolveOverridePalette(error, "primaryPalette");
|
|
566
|
-
const neuPalette = resolveOverridePalette(neutral, "neutralPalette");
|
|
567
|
-
const nvPalette = resolveOverridePalette(
|
|
568
|
-
neutralVariant,
|
|
569
|
-
"neutralVariantPalette"
|
|
570
|
-
);
|
|
571
|
-
const jsonSchemes = {};
|
|
572
|
-
const jsonContrastLevels = [
|
|
573
|
-
{ name: "light", isDark: false, contrast: 0 },
|
|
574
|
-
{ name: "light-medium-contrast", isDark: false, contrast: 0.5 },
|
|
575
|
-
{ name: "light-high-contrast", isDark: false, contrast: 1 },
|
|
576
|
-
{ name: "dark", isDark: true, contrast: 0 },
|
|
577
|
-
{ name: "dark-medium-contrast", isDark: true, contrast: 0.5 },
|
|
578
|
-
{ name: "dark-high-contrast", isDark: true, contrast: 1 }
|
|
579
|
-
];
|
|
580
|
-
for (const { name, isDark, contrast: contrast2 } of jsonContrastLevels) {
|
|
581
|
-
const baseScheme2 = new SchemeClass(primaryHct, isDark, contrast2);
|
|
582
|
-
const composedScheme = new DynamicScheme({
|
|
583
|
-
sourceColorArgb: effectiveSourceArgb,
|
|
584
|
-
variant: schemeToVariant[scheme],
|
|
585
|
-
contrastLevel: contrast2,
|
|
586
|
-
isDark,
|
|
587
|
-
primaryPalette: baseScheme2.primaryPalette,
|
|
588
|
-
secondaryPalette: secPalette || baseScheme2.secondaryPalette,
|
|
589
|
-
tertiaryPalette: terPalette || baseScheme2.tertiaryPalette,
|
|
590
|
-
neutralPalette: neuPalette || baseScheme2.neutralPalette,
|
|
591
|
-
neutralVariantPalette: nvPalette || baseScheme2.neutralVariantPalette
|
|
592
|
-
});
|
|
593
|
-
if (errPalette) composedScheme.errorPalette = errPalette;
|
|
594
|
-
jsonSchemes[name] = extractSchemeColors(composedScheme, baseScheme2);
|
|
595
|
-
}
|
|
596
|
-
return jsonSchemes;
|
|
597
|
-
}
|
|
598
|
-
function rawPalettesToJson() {
|
|
599
|
-
const jsonPalettes = {};
|
|
600
|
-
const RAW_PALETTE_NAMES = [
|
|
601
|
-
"primary",
|
|
602
|
-
"secondary",
|
|
603
|
-
"tertiary",
|
|
604
|
-
"neutral",
|
|
605
|
-
"neutral-variant"
|
|
606
|
-
];
|
|
607
|
-
for (const name of RAW_PALETTE_NAMES) {
|
|
608
|
-
const palette = rawPalettes[name];
|
|
609
|
-
const tones = {};
|
|
610
|
-
for (const tone of MTB_TONES) {
|
|
611
|
-
tones[tone.toString()] = hexFromArgb(
|
|
612
|
-
palette.tone(tone)
|
|
613
|
-
).toUpperCase();
|
|
614
|
-
}
|
|
615
|
-
jsonPalettes[name] = tones;
|
|
616
|
-
}
|
|
617
|
-
return jsonPalettes;
|
|
618
|
-
}
|
|
619
|
-
function buildCoreColors(opts) {
|
|
620
|
-
const colors = { primary: opts.primary };
|
|
621
|
-
if (opts.secondary) colors.secondary = opts.secondary.toUpperCase();
|
|
622
|
-
if (opts.tertiary) colors.tertiary = opts.tertiary.toUpperCase();
|
|
623
|
-
if (opts.error) colors.error = opts.error.toUpperCase();
|
|
624
|
-
if (opts.neutral) colors.neutral = opts.neutral.toUpperCase();
|
|
625
|
-
if (opts.neutralVariant)
|
|
626
|
-
colors.neutralVariant = opts.neutralVariant.toUpperCase();
|
|
627
|
-
return colors;
|
|
628
|
-
}
|
|
629
|
-
const seed = hexSource.toUpperCase();
|
|
630
|
-
const coreColors = buildCoreColors({
|
|
631
|
-
primary: (primary || hexSource).toUpperCase(),
|
|
632
|
-
secondary,
|
|
633
|
-
tertiary,
|
|
634
|
-
error,
|
|
635
|
-
neutral,
|
|
636
|
-
neutralVariant
|
|
637
|
-
});
|
|
638
|
-
const extendedColors = hexCustomColors.map((c) => ({
|
|
639
|
-
name: c.name,
|
|
640
|
-
color: c.hex.toUpperCase(),
|
|
641
|
-
description: "",
|
|
642
|
-
harmonized: c.blend ?? DEFAULT_BLEND
|
|
643
|
-
}));
|
|
644
|
-
return {
|
|
645
|
-
seed,
|
|
646
|
-
coreColors,
|
|
647
|
-
extendedColors,
|
|
648
|
-
schemes: buildJsonSchemes(),
|
|
649
|
-
palettes: rawPalettesToJson()
|
|
650
|
-
};
|
|
651
|
-
},
|
|
652
|
-
//
|
|
653
|
-
// ███████ ██ ██████ ███ ███ █████
|
|
654
|
-
// ██ ██ ██ ████ ████ ██ ██
|
|
655
|
-
// █████ ██ ██ ███ ██ ████ ██ ███████
|
|
656
|
-
// ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
657
|
-
// ██ ██ ██████ ██ ██ ██ ██
|
|
658
|
-
//
|
|
659
|
-
toFigmaTokens() {
|
|
660
|
-
function argbToFigmaColorValue(argb) {
|
|
661
|
-
return {
|
|
662
|
-
colorSpace: "srgb",
|
|
663
|
-
components: [
|
|
664
|
-
redFromArgb(argb) / 255,
|
|
665
|
-
greenFromArgb(argb) / 255,
|
|
666
|
-
blueFromArgb(argb) / 255
|
|
667
|
-
],
|
|
668
|
-
alpha: 1,
|
|
669
|
-
hex: hexFromArgb(argb).toUpperCase()
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
function figmaToken(argb) {
|
|
673
|
-
return {
|
|
674
|
-
$type: "color",
|
|
675
|
-
$value: argbToFigmaColorValue(argb),
|
|
676
|
-
$extensions: {
|
|
677
|
-
"com.figma.scopes": ["ALL_SCOPES"],
|
|
678
|
-
"com.figma.isOverride": true
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
function buildRefPaletteTokens() {
|
|
683
|
-
const palettes = {};
|
|
684
|
-
for (const [name, palette] of Object.entries(allPalettes)) {
|
|
685
|
-
const tones = {};
|
|
686
|
-
for (const tone of STANDARD_TONES) {
|
|
687
|
-
const argb = palette.tone(tone);
|
|
688
|
-
tones[tone.toString()] = figmaToken(argb);
|
|
689
|
-
}
|
|
690
|
-
palettes[startCase(name)] = tones;
|
|
691
|
-
}
|
|
692
|
-
return palettes;
|
|
693
|
-
}
|
|
694
|
-
function findToneInPalette(hex, paletteName, tones) {
|
|
695
|
-
for (const [tone, token] of Object.entries(tones)) {
|
|
696
|
-
if (token.$value.hex === hex) {
|
|
697
|
-
return `{ref.palette.${paletteName}.${tone}}`;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
return null;
|
|
701
|
-
}
|
|
702
|
-
function findAlias(hex, tokenName, refPalettes2) {
|
|
703
|
-
const preferredPaletteKebab = tokenToPalette[tokenName] ?? deriveCustomPaletteName(tokenName);
|
|
704
|
-
const preferredPalette = preferredPaletteKebab ? startCase(preferredPaletteKebab) : void 0;
|
|
705
|
-
if (preferredPalette && refPalettes2[preferredPalette]) {
|
|
706
|
-
const match = findToneInPalette(
|
|
707
|
-
hex,
|
|
708
|
-
preferredPalette,
|
|
709
|
-
refPalettes2[preferredPalette]
|
|
710
|
-
);
|
|
711
|
-
if (match) return match;
|
|
712
|
-
}
|
|
713
|
-
for (const [palName, tones] of Object.entries(refPalettes2)) {
|
|
714
|
-
const match = findToneInPalette(hex, palName, tones);
|
|
715
|
-
if (match) return match;
|
|
716
|
-
}
|
|
717
|
-
return null;
|
|
718
|
-
}
|
|
719
|
-
function resolveModeValue(argb, tokenName, refPalettes2) {
|
|
720
|
-
const hex = hexFromArgb(argb).toUpperCase();
|
|
721
|
-
return findAlias(hex, tokenName, refPalettes2) ?? argbToFigmaColorValue(argb);
|
|
722
|
-
}
|
|
723
|
-
function buildSysColorTokens(mergedColors, refPalettes2) {
|
|
724
|
-
const tokens = {};
|
|
725
|
-
for (const [name, argb] of Object.entries(mergedColors)) {
|
|
726
|
-
const description = tokenDescriptions[name];
|
|
727
|
-
const cssVar = `--${prefix}-sys-color-${kebabCase(name)}`;
|
|
728
|
-
const value = resolveModeValue(argb, name, refPalettes2);
|
|
729
|
-
tokens[startCase(name)] = {
|
|
730
|
-
$type: "color",
|
|
731
|
-
$value: value,
|
|
732
|
-
...description ? { $description: description } : {},
|
|
733
|
-
$extensions: {
|
|
734
|
-
"com.figma.scopes": ["ALL_SCOPES"],
|
|
735
|
-
"css.variable": cssVar
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
return tokens;
|
|
740
|
-
}
|
|
741
|
-
const refPalettes = buildRefPaletteTokens();
|
|
742
|
-
function buildModeFile(modeName, mergedColors) {
|
|
743
|
-
return {
|
|
744
|
-
ref: {
|
|
745
|
-
palette: refPalettes
|
|
746
|
-
},
|
|
747
|
-
sys: {
|
|
748
|
-
color: buildSysColorTokens(mergedColors, refPalettes)
|
|
749
|
-
},
|
|
750
|
-
$extensions: {
|
|
751
|
-
"com.figma.modeName": modeName
|
|
752
|
-
}
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
return {
|
|
756
|
-
"Light.tokens.json": buildModeFile("Light", mergedColorsLight),
|
|
757
|
-
"Dark.tokens.json": buildModeFile("Dark", mergedColorsDark)
|
|
758
|
-
};
|
|
759
|
-
},
|
|
760
|
-
//
|
|
761
|
-
// API
|
|
762
|
-
//
|
|
1083
|
+
toCss: () => buildCss(ctx),
|
|
1084
|
+
toJson: () => buildJson(ctx),
|
|
1085
|
+
toFigmaVariables: () => buildFigmaVariables(ctx),
|
|
1086
|
+
toFigmaTokens: () => buildFigmaTokens(ctx),
|
|
1087
|
+
toTailwind: (options) => buildTailwind(ctx, options),
|
|
1088
|
+
toFlutter: () => buildFlutter(ctx),
|
|
763
1089
|
mergedColorsLight,
|
|
764
1090
|
mergedColorsDark,
|
|
765
1091
|
allPalettes
|
|
@@ -767,9 +1093,15 @@ function builder(hexSource, {
|
|
|
767
1093
|
}
|
|
768
1094
|
|
|
769
1095
|
// src/cli.ts
|
|
1096
|
+
var customColorSchema = z.array(
|
|
1097
|
+
z.object({
|
|
1098
|
+
name: z.string(),
|
|
1099
|
+
hex: z.string(),
|
|
1100
|
+
blend: z.boolean().default(DEFAULT_BLEND)
|
|
1101
|
+
})
|
|
1102
|
+
);
|
|
770
1103
|
var program = new Command();
|
|
771
|
-
program.name("material-theme-builder").description("
|
|
772
|
-
program.command("builder").description("Generate a color theme from a source color").argument("<source>", "Source color in hex format (e.g. #6750A4)").addOption(
|
|
1104
|
+
program.name("material-theme-builder").description("Generate a color theme from a source color").argument("<source>", "Source color in hex format (e.g. #6750A4)").addOption(
|
|
773
1105
|
new Option("--scheme <name>", "Color scheme variant").choices(schemeNames).default(DEFAULT_SCHEME)
|
|
774
1106
|
).option(
|
|
775
1107
|
"--contrast <number>",
|
|
@@ -779,24 +1111,26 @@ program.command("builder").description("Generate a color theme from a source col
|
|
|
779
1111
|
).option("--primary <hex>", "Primary color override").option("--secondary <hex>", "Secondary color override").option("--tertiary <hex>", "Tertiary color override").option("--error <hex>", "Error color override").option("--neutral <hex>", "Neutral color override").option("--neutral-variant <hex>", "Neutral variant color override").option(
|
|
780
1112
|
"--custom-colors <json>",
|
|
781
1113
|
`Custom colors as JSON array (e.g. '[{"name":"brand","hex":"#FF5733","blend":true}]')`
|
|
782
|
-
).option(
|
|
1114
|
+
).option(
|
|
1115
|
+
"--format <type>",
|
|
1116
|
+
"Output format: json, css, figma, tailwind, or flutter",
|
|
1117
|
+
"figma"
|
|
1118
|
+
).option("--output <dir>", "Output directory (required for figma format)").option(
|
|
1119
|
+
"--shadcn",
|
|
1120
|
+
"Append shadcn CSS variable remapping (with --format tailwind)"
|
|
1121
|
+
).option(
|
|
783
1122
|
"--prefix <string>",
|
|
784
1123
|
"CSS variable prefix (e.g. md \u2192 --md-sys-color-*, --md-ref-palette-*)",
|
|
785
1124
|
DEFAULT_PREFIX
|
|
786
1125
|
).action((source, opts) => {
|
|
787
1126
|
let customColors = [];
|
|
788
1127
|
if (opts.customColors) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
customColors = parsed.map((c) => ({
|
|
792
|
-
name: c.name,
|
|
793
|
-
hex: c.hex,
|
|
794
|
-
blend: c.blend ?? DEFAULT_BLEND
|
|
795
|
-
}));
|
|
796
|
-
} catch {
|
|
1128
|
+
const result2 = customColorSchema.safeParse(JSON.parse(opts.customColors));
|
|
1129
|
+
if (!result2.success) {
|
|
797
1130
|
console.error("Error: --custom-colors must be valid JSON");
|
|
798
1131
|
process.exit(1);
|
|
799
1132
|
}
|
|
1133
|
+
customColors = result2.data;
|
|
800
1134
|
}
|
|
801
1135
|
const result = builder(source, {
|
|
802
1136
|
scheme: opts.scheme,
|
|
@@ -812,6 +1146,10 @@ program.command("builder").description("Generate a color theme from a source col
|
|
|
812
1146
|
});
|
|
813
1147
|
if (opts.format === "css") {
|
|
814
1148
|
process.stdout.write(result.toCss());
|
|
1149
|
+
} else if (opts.format === "tailwind") {
|
|
1150
|
+
process.stdout.write(result.toTailwind({ shadcn: opts.shadcn }));
|
|
1151
|
+
} else if (opts.format === "flutter") {
|
|
1152
|
+
process.stdout.write(result.toFlutter());
|
|
815
1153
|
} else if (opts.format === "figma") {
|
|
816
1154
|
const outputDir = opts.output ?? "material-theme";
|
|
817
1155
|
fs.mkdirSync(outputDir, { recursive: true });
|