react-mcu 1.3.1 → 1.3.2
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/dist/cli.js +729 -0
- package/package.json +3 -4
- package/src/cli.ts +0 -116
package/dist/cli.js
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { Command, Option } from "commander";
|
|
7
|
+
|
|
8
|
+
// src/lib/builder.ts
|
|
9
|
+
import {
|
|
10
|
+
argbFromHex,
|
|
11
|
+
Blend,
|
|
12
|
+
blueFromArgb,
|
|
13
|
+
DynamicColor,
|
|
14
|
+
DynamicScheme,
|
|
15
|
+
greenFromArgb,
|
|
16
|
+
Hct,
|
|
17
|
+
hexFromArgb,
|
|
18
|
+
MaterialDynamicColors,
|
|
19
|
+
redFromArgb,
|
|
20
|
+
SchemeContent,
|
|
21
|
+
SchemeExpressive,
|
|
22
|
+
SchemeFidelity,
|
|
23
|
+
SchemeMonochrome,
|
|
24
|
+
SchemeNeutral,
|
|
25
|
+
SchemeTonalSpot,
|
|
26
|
+
SchemeVibrant,
|
|
27
|
+
TonalPalette
|
|
28
|
+
} from "@material/material-color-utilities";
|
|
29
|
+
import { kebabCase, startCase, upperFirst } from "lodash-es";
|
|
30
|
+
function adjustToneForContrast(baseTone, contrastLevel, adjustmentFactor = DEFAULT_CONTRAST_ADJUSTMENT_FACTOR) {
|
|
31
|
+
if (contrastLevel === 0) return baseTone;
|
|
32
|
+
const distanceToCenter = baseTone - 50;
|
|
33
|
+
const delta = distanceToCenter * contrastLevel * adjustmentFactor;
|
|
34
|
+
const adjustedTone = baseTone + delta;
|
|
35
|
+
return Math.max(0, Math.min(100, adjustedTone));
|
|
36
|
+
}
|
|
37
|
+
var schemeNames = [
|
|
38
|
+
"tonalSpot",
|
|
39
|
+
"monochrome",
|
|
40
|
+
"neutral",
|
|
41
|
+
"vibrant",
|
|
42
|
+
"expressive",
|
|
43
|
+
"fidelity",
|
|
44
|
+
"content"
|
|
45
|
+
];
|
|
46
|
+
var schemesMap = {
|
|
47
|
+
tonalSpot: SchemeTonalSpot,
|
|
48
|
+
monochrome: SchemeMonochrome,
|
|
49
|
+
neutral: SchemeNeutral,
|
|
50
|
+
vibrant: SchemeVibrant,
|
|
51
|
+
expressive: SchemeExpressive,
|
|
52
|
+
fidelity: SchemeFidelity,
|
|
53
|
+
content: SchemeContent
|
|
54
|
+
};
|
|
55
|
+
var DEFAULT_SCHEME = "tonalSpot";
|
|
56
|
+
var DEFAULT_CONTRAST = 0;
|
|
57
|
+
var DEFAULT_CUSTOM_COLORS = [];
|
|
58
|
+
var DEFAULT_CONTRAST_ALL_COLORS = false;
|
|
59
|
+
var DEFAULT_ADAPTIVE_SHADES = false;
|
|
60
|
+
var DEFAULT_BLEND = true;
|
|
61
|
+
var DEFAULT_CONTRAST_ADJUSTMENT_FACTOR = 0.2;
|
|
62
|
+
var STANDARD_TONES = [
|
|
63
|
+
0,
|
|
64
|
+
5,
|
|
65
|
+
10,
|
|
66
|
+
15,
|
|
67
|
+
20,
|
|
68
|
+
25,
|
|
69
|
+
30,
|
|
70
|
+
35,
|
|
71
|
+
40,
|
|
72
|
+
50,
|
|
73
|
+
60,
|
|
74
|
+
70,
|
|
75
|
+
80,
|
|
76
|
+
90,
|
|
77
|
+
95,
|
|
78
|
+
98,
|
|
79
|
+
99,
|
|
80
|
+
100
|
|
81
|
+
];
|
|
82
|
+
var Variant = {
|
|
83
|
+
MONOCHROME: 0,
|
|
84
|
+
NEUTRAL: 1,
|
|
85
|
+
TONAL_SPOT: 2,
|
|
86
|
+
VIBRANT: 3,
|
|
87
|
+
EXPRESSIVE: 4,
|
|
88
|
+
FIDELITY: 5,
|
|
89
|
+
CONTENT: 6,
|
|
90
|
+
RAINBOW: 7,
|
|
91
|
+
FRUIT_SALAD: 8
|
|
92
|
+
};
|
|
93
|
+
var schemeToVariant = {
|
|
94
|
+
monochrome: Variant.MONOCHROME,
|
|
95
|
+
neutral: Variant.NEUTRAL,
|
|
96
|
+
tonalSpot: Variant.TONAL_SPOT,
|
|
97
|
+
vibrant: Variant.VIBRANT,
|
|
98
|
+
expressive: Variant.EXPRESSIVE,
|
|
99
|
+
fidelity: Variant.FIDELITY,
|
|
100
|
+
content: Variant.CONTENT
|
|
101
|
+
};
|
|
102
|
+
var tokenNames = [
|
|
103
|
+
"background",
|
|
104
|
+
"error",
|
|
105
|
+
"errorContainer",
|
|
106
|
+
"inverseOnSurface",
|
|
107
|
+
"inversePrimary",
|
|
108
|
+
"inverseSurface",
|
|
109
|
+
"onBackground",
|
|
110
|
+
"onError",
|
|
111
|
+
"onErrorContainer",
|
|
112
|
+
"onPrimary",
|
|
113
|
+
"onPrimaryContainer",
|
|
114
|
+
"onPrimaryFixed",
|
|
115
|
+
"onPrimaryFixedVariant",
|
|
116
|
+
"onSecondary",
|
|
117
|
+
"onSecondaryContainer",
|
|
118
|
+
"onSecondaryFixed",
|
|
119
|
+
"onSecondaryFixedVariant",
|
|
120
|
+
"onSurface",
|
|
121
|
+
"onSurfaceVariant",
|
|
122
|
+
"onTertiary",
|
|
123
|
+
"onTertiaryContainer",
|
|
124
|
+
"onTertiaryFixed",
|
|
125
|
+
"onTertiaryFixedVariant",
|
|
126
|
+
"outline",
|
|
127
|
+
"outlineVariant",
|
|
128
|
+
"primary",
|
|
129
|
+
"primaryContainer",
|
|
130
|
+
"primaryFixed",
|
|
131
|
+
"primaryFixedDim",
|
|
132
|
+
"scrim",
|
|
133
|
+
"secondary",
|
|
134
|
+
"secondaryContainer",
|
|
135
|
+
"secondaryFixed",
|
|
136
|
+
"secondaryFixedDim",
|
|
137
|
+
"shadow",
|
|
138
|
+
"surface",
|
|
139
|
+
"surfaceBright",
|
|
140
|
+
"surfaceContainer",
|
|
141
|
+
"surfaceContainerHigh",
|
|
142
|
+
"surfaceContainerHighest",
|
|
143
|
+
"surfaceContainerLow",
|
|
144
|
+
"surfaceContainerLowest",
|
|
145
|
+
"surfaceDim",
|
|
146
|
+
"surfaceTint",
|
|
147
|
+
"surfaceVariant",
|
|
148
|
+
"tertiary",
|
|
149
|
+
"tertiaryContainer",
|
|
150
|
+
"tertiaryFixed",
|
|
151
|
+
"tertiaryFixedDim"
|
|
152
|
+
];
|
|
153
|
+
function toRecord(arr, getEntry) {
|
|
154
|
+
return Object.fromEntries(arr.map(getEntry));
|
|
155
|
+
}
|
|
156
|
+
function getPalette(palettes, colorName) {
|
|
157
|
+
const palette = palettes[colorName];
|
|
158
|
+
if (!palette) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Custom color palette not found for '${colorName}'. This is likely a bug in the implementation.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return palette;
|
|
164
|
+
}
|
|
165
|
+
function mergeBaseAndCustomColors(scheme, customColors, colorPalettes, contrastAllColors) {
|
|
166
|
+
const baseVars = toRecord(tokenNames, (tokenName) => {
|
|
167
|
+
const dynamicColor = MaterialDynamicColors[tokenName];
|
|
168
|
+
const argb = dynamicColor.getArgb(scheme);
|
|
169
|
+
return [tokenName, argb];
|
|
170
|
+
});
|
|
171
|
+
const customVars = {};
|
|
172
|
+
customColors.forEach((color) => {
|
|
173
|
+
const colorname = color.name;
|
|
174
|
+
const getPaletteForColor = (s) => getPalette(colorPalettes, colorname);
|
|
175
|
+
const getTone = (baseTone) => (s) => {
|
|
176
|
+
if (!contrastAllColors) return baseTone;
|
|
177
|
+
return adjustToneForContrast(baseTone, s.contrastLevel);
|
|
178
|
+
};
|
|
179
|
+
const colorDynamicColor = new DynamicColor(
|
|
180
|
+
colorname,
|
|
181
|
+
getPaletteForColor,
|
|
182
|
+
(s) => getTone(s.isDark ? 80 : 40)(s),
|
|
183
|
+
// Main color: lighter in dark mode, darker in light mode
|
|
184
|
+
true
|
|
185
|
+
// background
|
|
186
|
+
);
|
|
187
|
+
const onColorDynamicColor = new DynamicColor(
|
|
188
|
+
`on${upperFirst(colorname)}`,
|
|
189
|
+
getPaletteForColor,
|
|
190
|
+
(s) => getTone(s.isDark ? 20 : 100)(s),
|
|
191
|
+
// Text on main color: high contrast (dark on light, light on dark)
|
|
192
|
+
false
|
|
193
|
+
);
|
|
194
|
+
const containerDynamicColor = new DynamicColor(
|
|
195
|
+
`${colorname}Container`,
|
|
196
|
+
getPaletteForColor,
|
|
197
|
+
(s) => getTone(s.isDark ? 30 : 90)(s),
|
|
198
|
+
// Container: subtle variant (darker in dark mode, lighter in light mode)
|
|
199
|
+
true
|
|
200
|
+
// background
|
|
201
|
+
);
|
|
202
|
+
const onContainerDynamicColor = new DynamicColor(
|
|
203
|
+
`on${upperFirst(colorname)}Container`,
|
|
204
|
+
getPaletteForColor,
|
|
205
|
+
(s) => getTone(s.isDark ? 90 : 30)(s),
|
|
206
|
+
// Text on container: high contrast against container background
|
|
207
|
+
false
|
|
208
|
+
);
|
|
209
|
+
customVars[colorname] = colorDynamicColor.getArgb(scheme);
|
|
210
|
+
customVars[`on${upperFirst(colorname)}`] = onColorDynamicColor.getArgb(scheme);
|
|
211
|
+
customVars[`${colorname}Container`] = containerDynamicColor.getArgb(scheme);
|
|
212
|
+
customVars[`on${upperFirst(colorname)}Container`] = onContainerDynamicColor.getArgb(scheme);
|
|
213
|
+
});
|
|
214
|
+
return { ...baseVars, ...customVars };
|
|
215
|
+
}
|
|
216
|
+
function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonization) {
|
|
217
|
+
const colorArgb = argbFromHex(colorDef.hex);
|
|
218
|
+
const harmonizedArgb = colorDef.blend ? Blend.harmonize(colorArgb, effectiveSourceForHarmonization) : colorArgb;
|
|
219
|
+
const hct = Hct.fromInt(harmonizedArgb);
|
|
220
|
+
let targetChroma;
|
|
221
|
+
if (colorDef.core && colorDef.chromaSource) {
|
|
222
|
+
if (colorDef.chromaSource === "neutral") {
|
|
223
|
+
targetChroma = baseScheme.neutralPalette.chroma;
|
|
224
|
+
} else if (colorDef.chromaSource === "neutralVariant") {
|
|
225
|
+
targetChroma = baseScheme.neutralVariantPalette.chroma;
|
|
226
|
+
} else {
|
|
227
|
+
targetChroma = baseScheme.primaryPalette.chroma;
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
targetChroma = baseScheme.primaryPalette.chroma;
|
|
231
|
+
}
|
|
232
|
+
return TonalPalette.fromHueAndChroma(hct.hue, targetChroma);
|
|
233
|
+
}
|
|
234
|
+
function builder(hexSource, {
|
|
235
|
+
scheme = DEFAULT_SCHEME,
|
|
236
|
+
contrast = DEFAULT_CONTRAST,
|
|
237
|
+
primary,
|
|
238
|
+
secondary,
|
|
239
|
+
tertiary,
|
|
240
|
+
neutral,
|
|
241
|
+
neutralVariant,
|
|
242
|
+
error,
|
|
243
|
+
customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
|
|
244
|
+
contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS,
|
|
245
|
+
adaptiveShades = DEFAULT_ADAPTIVE_SHADES
|
|
246
|
+
} = {}) {
|
|
247
|
+
const sourceArgb = argbFromHex(hexSource);
|
|
248
|
+
const sourceHct = Hct.fromInt(sourceArgb);
|
|
249
|
+
const effectiveSource = primary || hexSource;
|
|
250
|
+
const effectiveSourceArgb = argbFromHex(effectiveSource);
|
|
251
|
+
const effectiveSourceForHarmonization = primary ? argbFromHex(primary) : sourceArgb;
|
|
252
|
+
const SchemeClass = schemesMap[scheme];
|
|
253
|
+
const primaryHct = Hct.fromInt(effectiveSourceArgb);
|
|
254
|
+
const baseScheme = new SchemeClass(primaryHct, false, contrast);
|
|
255
|
+
const allColors = [
|
|
256
|
+
// Core colors (hex may be undefined)
|
|
257
|
+
{
|
|
258
|
+
name: "primary",
|
|
259
|
+
hex: primary,
|
|
260
|
+
core: true,
|
|
261
|
+
chromaSource: "primary"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "secondary",
|
|
265
|
+
hex: secondary,
|
|
266
|
+
core: true,
|
|
267
|
+
chromaSource: "primary"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "tertiary",
|
|
271
|
+
hex: tertiary,
|
|
272
|
+
core: true,
|
|
273
|
+
chromaSource: "primary"
|
|
274
|
+
},
|
|
275
|
+
{ name: "error", hex: error, core: true, chromaSource: "primary" },
|
|
276
|
+
{
|
|
277
|
+
name: "neutral",
|
|
278
|
+
hex: neutral,
|
|
279
|
+
core: true,
|
|
280
|
+
chromaSource: "neutral"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "neutralVariant",
|
|
284
|
+
hex: neutralVariant,
|
|
285
|
+
core: true,
|
|
286
|
+
chromaSource: "neutralVariant"
|
|
287
|
+
},
|
|
288
|
+
//
|
|
289
|
+
// Custom colors
|
|
290
|
+
//
|
|
291
|
+
...hexCustomColors.map((c) => ({
|
|
292
|
+
name: c.name,
|
|
293
|
+
hex: c.hex,
|
|
294
|
+
blend: c.blend,
|
|
295
|
+
core: false
|
|
296
|
+
}))
|
|
297
|
+
];
|
|
298
|
+
const definedColors = allColors.filter(
|
|
299
|
+
(c) => c.hex !== void 0
|
|
300
|
+
);
|
|
301
|
+
const colorPalettes = Object.fromEntries(
|
|
302
|
+
definedColors.map((colorDef) => [
|
|
303
|
+
colorDef.name,
|
|
304
|
+
createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonization)
|
|
305
|
+
])
|
|
306
|
+
);
|
|
307
|
+
const variant = schemeToVariant[scheme];
|
|
308
|
+
const schemeConfig = {
|
|
309
|
+
sourceColorArgb: effectiveSourceArgb,
|
|
310
|
+
variant,
|
|
311
|
+
contrastLevel: contrast,
|
|
312
|
+
primaryPalette: colorPalettes["primary"] || baseScheme.primaryPalette,
|
|
313
|
+
secondaryPalette: colorPalettes["secondary"] || baseScheme.secondaryPalette,
|
|
314
|
+
tertiaryPalette: colorPalettes["tertiary"] || baseScheme.tertiaryPalette,
|
|
315
|
+
neutralPalette: colorPalettes["neutral"] || baseScheme.neutralPalette,
|
|
316
|
+
neutralVariantPalette: colorPalettes["neutralVariant"] || baseScheme.neutralVariantPalette
|
|
317
|
+
};
|
|
318
|
+
const lightScheme = new DynamicScheme({ ...schemeConfig, isDark: false });
|
|
319
|
+
const darkScheme = new DynamicScheme({ ...schemeConfig, isDark: true });
|
|
320
|
+
const errorPalette = colorPalettes["error"];
|
|
321
|
+
if (errorPalette) {
|
|
322
|
+
lightScheme.errorPalette = errorPalette;
|
|
323
|
+
darkScheme.errorPalette = errorPalette;
|
|
324
|
+
}
|
|
325
|
+
const allPalettes = {
|
|
326
|
+
primary: lightScheme.primaryPalette,
|
|
327
|
+
secondary: lightScheme.secondaryPalette,
|
|
328
|
+
tertiary: lightScheme.tertiaryPalette,
|
|
329
|
+
error: lightScheme.errorPalette,
|
|
330
|
+
neutral: lightScheme.neutralPalette,
|
|
331
|
+
"neutral-variant": lightScheme.neutralVariantPalette,
|
|
332
|
+
// Add custom color palettes
|
|
333
|
+
...Object.fromEntries(
|
|
334
|
+
definedColors.filter((c) => !c.core).map((colorDef) => [colorDef.name, colorPalettes[colorDef.name]])
|
|
335
|
+
)
|
|
336
|
+
};
|
|
337
|
+
const customColors = definedColors.filter((c) => !c.core).map((c) => ({
|
|
338
|
+
name: c.name,
|
|
339
|
+
blend: c.blend ?? DEFAULT_BLEND,
|
|
340
|
+
value: argbFromHex(c.hex)
|
|
341
|
+
}));
|
|
342
|
+
const mergedColorsLight = mergeBaseAndCustomColors(
|
|
343
|
+
lightScheme,
|
|
344
|
+
customColors,
|
|
345
|
+
colorPalettes,
|
|
346
|
+
contrastAllColors
|
|
347
|
+
);
|
|
348
|
+
const mergedColorsDark = mergeBaseAndCustomColors(
|
|
349
|
+
darkScheme,
|
|
350
|
+
customColors,
|
|
351
|
+
colorPalettes,
|
|
352
|
+
contrastAllColors
|
|
353
|
+
);
|
|
354
|
+
return {
|
|
355
|
+
//
|
|
356
|
+
// ██████ ███████ ███████
|
|
357
|
+
// ██ ██ ██
|
|
358
|
+
// ██ ███████ ███████
|
|
359
|
+
// ██ ██ ██
|
|
360
|
+
// ██████ ███████ ███████
|
|
361
|
+
//
|
|
362
|
+
toCss() {
|
|
363
|
+
function cssVar(colorName, colorValue) {
|
|
364
|
+
const name = `--mcu-${kebabCase(colorName)}`;
|
|
365
|
+
const value = hexFromArgb(colorValue);
|
|
366
|
+
return `${name}:${value};`;
|
|
367
|
+
}
|
|
368
|
+
function toCssVars(mergedColors) {
|
|
369
|
+
return Object.entries(mergedColors).map(([name, value]) => cssVar(name, value)).join(" ");
|
|
370
|
+
}
|
|
371
|
+
function generateTonalPaletteVars(paletteName, palette, scheme2, applyContrast, adaptiveShades2) {
|
|
372
|
+
return STANDARD_TONES.map((tone) => {
|
|
373
|
+
let toneToUse = tone;
|
|
374
|
+
if (adaptiveShades2 && scheme2.isDark) {
|
|
375
|
+
toneToUse = 100 - tone;
|
|
376
|
+
}
|
|
377
|
+
if (applyContrast) {
|
|
378
|
+
toneToUse = adjustToneForContrast(toneToUse, scheme2.contrastLevel);
|
|
379
|
+
}
|
|
380
|
+
const color = palette.tone(toneToUse);
|
|
381
|
+
return cssVar(`${paletteName}-${tone}`, color);
|
|
382
|
+
}).join(" ");
|
|
383
|
+
}
|
|
384
|
+
function generateTonalVars(s) {
|
|
385
|
+
return Object.entries(allPalettes).map(
|
|
386
|
+
([name, palette]) => generateTonalPaletteVars(
|
|
387
|
+
kebabCase(name),
|
|
388
|
+
palette,
|
|
389
|
+
s,
|
|
390
|
+
contrastAllColors,
|
|
391
|
+
adaptiveShades
|
|
392
|
+
)
|
|
393
|
+
).join(" ");
|
|
394
|
+
}
|
|
395
|
+
const lightVars = toCssVars(mergedColorsLight);
|
|
396
|
+
const darkVars = toCssVars(mergedColorsDark);
|
|
397
|
+
const lightTonalVars = generateTonalVars(lightScheme);
|
|
398
|
+
const darkTonalVars = generateTonalVars(darkScheme);
|
|
399
|
+
return `
|
|
400
|
+
:root { ${lightVars} ${lightTonalVars} }
|
|
401
|
+
.dark { ${darkVars} ${adaptiveShades ? darkTonalVars : lightTonalVars} }
|
|
402
|
+
`;
|
|
403
|
+
},
|
|
404
|
+
//
|
|
405
|
+
// ██ ███████ ██████ ███ ██
|
|
406
|
+
// ██ ██ ██ ██ ████ ██
|
|
407
|
+
// ██ ███████ ██ ██ ██ ██ ██
|
|
408
|
+
// ██ ██ ██ ██ ██ ██ ██ ██
|
|
409
|
+
// █████ ███████ ██████ ██ ████
|
|
410
|
+
//
|
|
411
|
+
toJson() {
|
|
412
|
+
const fixtureTokenOrder = [
|
|
413
|
+
"primary",
|
|
414
|
+
"surfaceTint",
|
|
415
|
+
"onPrimary",
|
|
416
|
+
"primaryContainer",
|
|
417
|
+
"onPrimaryContainer",
|
|
418
|
+
"secondary",
|
|
419
|
+
"onSecondary",
|
|
420
|
+
"secondaryContainer",
|
|
421
|
+
"onSecondaryContainer",
|
|
422
|
+
"tertiary",
|
|
423
|
+
"onTertiary",
|
|
424
|
+
"tertiaryContainer",
|
|
425
|
+
"onTertiaryContainer",
|
|
426
|
+
"error",
|
|
427
|
+
"onError",
|
|
428
|
+
"errorContainer",
|
|
429
|
+
"onErrorContainer",
|
|
430
|
+
"background",
|
|
431
|
+
"onBackground",
|
|
432
|
+
"surface",
|
|
433
|
+
"onSurface",
|
|
434
|
+
"surfaceVariant",
|
|
435
|
+
"onSurfaceVariant",
|
|
436
|
+
"outline",
|
|
437
|
+
"outlineVariant",
|
|
438
|
+
"shadow",
|
|
439
|
+
"scrim",
|
|
440
|
+
"inverseSurface",
|
|
441
|
+
"inverseOnSurface",
|
|
442
|
+
"inversePrimary",
|
|
443
|
+
"primaryFixed",
|
|
444
|
+
"onPrimaryFixed",
|
|
445
|
+
"primaryFixedDim",
|
|
446
|
+
"onPrimaryFixedVariant",
|
|
447
|
+
"secondaryFixed",
|
|
448
|
+
"onSecondaryFixed",
|
|
449
|
+
"secondaryFixedDim",
|
|
450
|
+
"onSecondaryFixedVariant",
|
|
451
|
+
"tertiaryFixed",
|
|
452
|
+
"onTertiaryFixed",
|
|
453
|
+
"tertiaryFixedDim",
|
|
454
|
+
"onTertiaryFixedVariant",
|
|
455
|
+
"surfaceDim",
|
|
456
|
+
"surfaceBright",
|
|
457
|
+
"surfaceContainerLowest",
|
|
458
|
+
"surfaceContainerLow",
|
|
459
|
+
"surfaceContainer",
|
|
460
|
+
"surfaceContainerHigh",
|
|
461
|
+
"surfaceContainerHighest"
|
|
462
|
+
];
|
|
463
|
+
const neuHct = neutral ? Hct.fromInt(argbFromHex(neutral)) : sourceHct;
|
|
464
|
+
const nvHct = neutralVariant ? Hct.fromInt(argbFromHex(neutralVariant)) : sourceHct;
|
|
465
|
+
const rawPalettes = {
|
|
466
|
+
primary: TonalPalette.fromInt(effectiveSourceArgb),
|
|
467
|
+
secondary: secondary ? TonalPalette.fromInt(argbFromHex(secondary)) : TonalPalette.fromHueAndChroma(sourceHct.hue, sourceHct.chroma / 3),
|
|
468
|
+
tertiary: tertiary ? TonalPalette.fromInt(argbFromHex(tertiary)) : TonalPalette.fromHueAndChroma(
|
|
469
|
+
(sourceHct.hue + 60) % 360,
|
|
470
|
+
sourceHct.chroma / 2
|
|
471
|
+
),
|
|
472
|
+
neutral: TonalPalette.fromHueAndChroma(
|
|
473
|
+
neuHct.hue,
|
|
474
|
+
Math.min(neuHct.chroma / 12, 4)
|
|
475
|
+
),
|
|
476
|
+
"neutral-variant": TonalPalette.fromHueAndChroma(
|
|
477
|
+
nvHct.hue,
|
|
478
|
+
Math.min(nvHct.chroma / 6, 8)
|
|
479
|
+
)
|
|
480
|
+
};
|
|
481
|
+
function buildJsonSchemes() {
|
|
482
|
+
function extractSchemeColors(scheme2, backgroundScheme) {
|
|
483
|
+
const colors = {};
|
|
484
|
+
for (const tokenName of fixtureTokenOrder) {
|
|
485
|
+
const dynamicColor = MaterialDynamicColors[tokenName];
|
|
486
|
+
const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
|
|
487
|
+
colors[tokenName] = hexFromArgb(
|
|
488
|
+
dynamicColor.getArgb(useScheme)
|
|
489
|
+
).toUpperCase();
|
|
490
|
+
}
|
|
491
|
+
return colors;
|
|
492
|
+
}
|
|
493
|
+
function resolveOverridePalette(hex, role) {
|
|
494
|
+
if (!hex) return null;
|
|
495
|
+
return new SchemeClass(Hct.fromInt(argbFromHex(hex)), false, 0)[role];
|
|
496
|
+
}
|
|
497
|
+
const secPalette = resolveOverridePalette(secondary, "primaryPalette");
|
|
498
|
+
const terPalette = resolveOverridePalette(tertiary, "primaryPalette");
|
|
499
|
+
const errPalette = resolveOverridePalette(error, "primaryPalette");
|
|
500
|
+
const neuPalette = resolveOverridePalette(neutral, "neutralPalette");
|
|
501
|
+
const nvPalette = resolveOverridePalette(
|
|
502
|
+
neutralVariant,
|
|
503
|
+
"neutralVariantPalette"
|
|
504
|
+
);
|
|
505
|
+
const jsonSchemes = {};
|
|
506
|
+
const jsonContrastLevels = [
|
|
507
|
+
{ name: "light", isDark: false, contrast: 0 },
|
|
508
|
+
{ name: "light-medium-contrast", isDark: false, contrast: 0.5 },
|
|
509
|
+
{ name: "light-high-contrast", isDark: false, contrast: 1 },
|
|
510
|
+
{ name: "dark", isDark: true, contrast: 0 },
|
|
511
|
+
{ name: "dark-medium-contrast", isDark: true, contrast: 0.5 },
|
|
512
|
+
{ name: "dark-high-contrast", isDark: true, contrast: 1 }
|
|
513
|
+
];
|
|
514
|
+
for (const { name, isDark, contrast: contrast2 } of jsonContrastLevels) {
|
|
515
|
+
const baseScheme2 = new SchemeClass(primaryHct, isDark, contrast2);
|
|
516
|
+
const composedScheme = new DynamicScheme({
|
|
517
|
+
sourceColorArgb: effectiveSourceArgb,
|
|
518
|
+
variant: schemeToVariant[scheme],
|
|
519
|
+
contrastLevel: contrast2,
|
|
520
|
+
isDark,
|
|
521
|
+
primaryPalette: baseScheme2.primaryPalette,
|
|
522
|
+
secondaryPalette: secPalette || baseScheme2.secondaryPalette,
|
|
523
|
+
tertiaryPalette: terPalette || baseScheme2.tertiaryPalette,
|
|
524
|
+
neutralPalette: neuPalette || baseScheme2.neutralPalette,
|
|
525
|
+
neutralVariantPalette: nvPalette || baseScheme2.neutralVariantPalette
|
|
526
|
+
});
|
|
527
|
+
if (errPalette) composedScheme.errorPalette = errPalette;
|
|
528
|
+
jsonSchemes[name] = extractSchemeColors(composedScheme, baseScheme2);
|
|
529
|
+
}
|
|
530
|
+
return jsonSchemes;
|
|
531
|
+
}
|
|
532
|
+
function rawPalettesToJson() {
|
|
533
|
+
const jsonPalettes = {};
|
|
534
|
+
const RAW_PALETTE_NAMES = [
|
|
535
|
+
"primary",
|
|
536
|
+
"secondary",
|
|
537
|
+
"tertiary",
|
|
538
|
+
"neutral",
|
|
539
|
+
"neutral-variant"
|
|
540
|
+
];
|
|
541
|
+
for (const name of RAW_PALETTE_NAMES) {
|
|
542
|
+
const palette = rawPalettes[name];
|
|
543
|
+
const tones = {};
|
|
544
|
+
for (const tone of STANDARD_TONES) {
|
|
545
|
+
tones[tone.toString()] = hexFromArgb(
|
|
546
|
+
palette.tone(tone)
|
|
547
|
+
).toUpperCase();
|
|
548
|
+
}
|
|
549
|
+
jsonPalettes[name] = tones;
|
|
550
|
+
}
|
|
551
|
+
return jsonPalettes;
|
|
552
|
+
}
|
|
553
|
+
function buildCoreColors(opts) {
|
|
554
|
+
const colors = { primary: opts.primary };
|
|
555
|
+
if (opts.secondary) colors.secondary = opts.secondary.toUpperCase();
|
|
556
|
+
if (opts.tertiary) colors.tertiary = opts.tertiary.toUpperCase();
|
|
557
|
+
if (opts.error) colors.error = opts.error.toUpperCase();
|
|
558
|
+
if (opts.neutral) colors.neutral = opts.neutral.toUpperCase();
|
|
559
|
+
if (opts.neutralVariant)
|
|
560
|
+
colors.neutralVariant = opts.neutralVariant.toUpperCase();
|
|
561
|
+
return colors;
|
|
562
|
+
}
|
|
563
|
+
const seed = hexSource.toUpperCase();
|
|
564
|
+
const coreColors = buildCoreColors({
|
|
565
|
+
primary: (primary || hexSource).toUpperCase(),
|
|
566
|
+
secondary,
|
|
567
|
+
tertiary,
|
|
568
|
+
error,
|
|
569
|
+
neutral,
|
|
570
|
+
neutralVariant
|
|
571
|
+
});
|
|
572
|
+
const extendedColors = hexCustomColors.map((c) => ({
|
|
573
|
+
name: c.name,
|
|
574
|
+
color: c.hex.toUpperCase(),
|
|
575
|
+
description: "",
|
|
576
|
+
harmonized: c.blend ?? DEFAULT_BLEND
|
|
577
|
+
}));
|
|
578
|
+
return {
|
|
579
|
+
seed,
|
|
580
|
+
coreColors,
|
|
581
|
+
extendedColors,
|
|
582
|
+
schemes: buildJsonSchemes(),
|
|
583
|
+
palettes: rawPalettesToJson()
|
|
584
|
+
};
|
|
585
|
+
},
|
|
586
|
+
//
|
|
587
|
+
// ███████ ██ ██████ ███ ███ █████
|
|
588
|
+
// ██ ██ ██ ████ ████ ██ ██
|
|
589
|
+
// █████ ██ ██ ███ ██ ████ ██ ███████
|
|
590
|
+
// ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
591
|
+
// ██ ██ ██████ ██ ██ ██ ██
|
|
592
|
+
//
|
|
593
|
+
toFigmaTokens() {
|
|
594
|
+
function argbToFigmaColorValue(argb) {
|
|
595
|
+
return {
|
|
596
|
+
colorSpace: "srgb",
|
|
597
|
+
components: [
|
|
598
|
+
redFromArgb(argb) / 255,
|
|
599
|
+
greenFromArgb(argb) / 255,
|
|
600
|
+
blueFromArgb(argb) / 255
|
|
601
|
+
],
|
|
602
|
+
alpha: 1,
|
|
603
|
+
hex: hexFromArgb(argb).toUpperCase()
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function figmaToken(argb) {
|
|
607
|
+
return {
|
|
608
|
+
$type: "color",
|
|
609
|
+
$value: argbToFigmaColorValue(argb),
|
|
610
|
+
$extensions: {
|
|
611
|
+
"com.figma.scopes": ["ALL_SCOPES"],
|
|
612
|
+
"com.figma.isOverride": true
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function buildFigmaSchemeTokens(mergedColors) {
|
|
617
|
+
const tokens = {};
|
|
618
|
+
for (const [name, argb] of Object.entries(mergedColors)) {
|
|
619
|
+
tokens[startCase(name)] = figmaToken(argb);
|
|
620
|
+
}
|
|
621
|
+
return tokens;
|
|
622
|
+
}
|
|
623
|
+
function buildFigmaPaletteTokens(isDark) {
|
|
624
|
+
const palettes = {};
|
|
625
|
+
for (const [name, palette] of Object.entries(allPalettes)) {
|
|
626
|
+
const tones = {};
|
|
627
|
+
for (const tone of STANDARD_TONES) {
|
|
628
|
+
let toneToUse = tone;
|
|
629
|
+
if (adaptiveShades && isDark) {
|
|
630
|
+
toneToUse = 100 - tone;
|
|
631
|
+
}
|
|
632
|
+
if (contrastAllColors) {
|
|
633
|
+
toneToUse = adjustToneForContrast(toneToUse, contrast);
|
|
634
|
+
}
|
|
635
|
+
const argb = palette.tone(toneToUse);
|
|
636
|
+
tones[tone.toString()] = figmaToken(argb);
|
|
637
|
+
}
|
|
638
|
+
palettes[startCase(name)] = tones;
|
|
639
|
+
}
|
|
640
|
+
return palettes;
|
|
641
|
+
}
|
|
642
|
+
function buildModeFile(modeName, mergedColors, isDark) {
|
|
643
|
+
return {
|
|
644
|
+
Schemes: buildFigmaSchemeTokens(mergedColors),
|
|
645
|
+
Palettes: buildFigmaPaletteTokens(isDark),
|
|
646
|
+
$extensions: {
|
|
647
|
+
"com.figma.modeName": modeName
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
"Light.tokens.json": buildModeFile("Light", mergedColorsLight, false),
|
|
653
|
+
"Dark.tokens.json": buildModeFile("Dark", mergedColorsDark, true)
|
|
654
|
+
};
|
|
655
|
+
},
|
|
656
|
+
//
|
|
657
|
+
// API
|
|
658
|
+
//
|
|
659
|
+
mergedColorsLight,
|
|
660
|
+
mergedColorsDark,
|
|
661
|
+
allPalettes
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/cli.ts
|
|
666
|
+
var program = new Command();
|
|
667
|
+
program.name("react-mcu").description("m3 color system for react");
|
|
668
|
+
program.command("builder").description("Generate a color theme from a source color").argument("<source>", "Source color in hex format (e.g. #6750A4)").addOption(
|
|
669
|
+
new Option("--scheme <name>", "Color scheme variant").choices(schemeNames).default(DEFAULT_SCHEME)
|
|
670
|
+
).option(
|
|
671
|
+
"--contrast <number>",
|
|
672
|
+
"Contrast level from -1.0 to 1.0",
|
|
673
|
+
parseFloat,
|
|
674
|
+
DEFAULT_CONTRAST
|
|
675
|
+
).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(
|
|
676
|
+
"--custom-colors <json>",
|
|
677
|
+
`Custom colors as JSON array (e.g. '[{"name":"brand","hex":"#FF5733","blend":true}]')`
|
|
678
|
+
).option("--format <type>", "Output format: json, css, or figma", "figma").option("--output <dir>", "Output directory (required for figma format)").option(
|
|
679
|
+
"--adaptive-shades",
|
|
680
|
+
"Adapt tonal palette shades for dark mode",
|
|
681
|
+
DEFAULT_ADAPTIVE_SHADES
|
|
682
|
+
).option(
|
|
683
|
+
"--contrast-all-colors",
|
|
684
|
+
"Apply contrast adjustment to tonal palette shades",
|
|
685
|
+
DEFAULT_CONTRAST_ALL_COLORS
|
|
686
|
+
).action((source, opts) => {
|
|
687
|
+
let customColors = [];
|
|
688
|
+
if (opts.customColors) {
|
|
689
|
+
try {
|
|
690
|
+
const parsed = JSON.parse(opts.customColors);
|
|
691
|
+
customColors = parsed.map((c) => ({
|
|
692
|
+
name: c.name,
|
|
693
|
+
hex: c.hex,
|
|
694
|
+
blend: c.blend ?? DEFAULT_BLEND
|
|
695
|
+
}));
|
|
696
|
+
} catch {
|
|
697
|
+
console.error("Error: --custom-colors must be valid JSON");
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const result = builder(source, {
|
|
702
|
+
scheme: opts.scheme,
|
|
703
|
+
contrast: opts.contrast,
|
|
704
|
+
primary: opts.primary,
|
|
705
|
+
secondary: opts.secondary,
|
|
706
|
+
tertiary: opts.tertiary,
|
|
707
|
+
error: opts.error,
|
|
708
|
+
neutral: opts.neutral,
|
|
709
|
+
neutralVariant: opts.neutralVariant,
|
|
710
|
+
customColors,
|
|
711
|
+
adaptiveShades: opts.adaptiveShades,
|
|
712
|
+
contrastAllColors: opts.contrastAllColors
|
|
713
|
+
});
|
|
714
|
+
if (opts.format === "css") {
|
|
715
|
+
process.stdout.write(result.toCss());
|
|
716
|
+
} else if (opts.format === "figma") {
|
|
717
|
+
const outputDir = opts.output ?? "mcu-theme";
|
|
718
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
719
|
+
const files = result.toFigmaTokens();
|
|
720
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
721
|
+
const filePath = path.join(outputDir, filename);
|
|
722
|
+
fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n");
|
|
723
|
+
console.error(`wrote ${filePath}`);
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
process.stdout.write(JSON.stringify(result.toJson(), null, 2) + "\n");
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-mcu",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "A React component library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"src/tailwind.css"
|
|
30
30
|
],
|
|
31
31
|
"bin": {
|
|
32
|
-
"react-mcu": "./
|
|
32
|
+
"react-mcu": "./dist/cli.js"
|
|
33
33
|
},
|
|
34
34
|
"type": "module",
|
|
35
35
|
"devDependencies": {
|
|
@@ -81,8 +81,7 @@
|
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@material/material-color-utilities": "^0.3.0",
|
|
83
83
|
"commander": "^14.0.3",
|
|
84
|
-
"lodash-es": "^4.17.22"
|
|
85
|
-
"tsx": "^4.21.0"
|
|
84
|
+
"lodash-es": "^4.17.22"
|
|
86
85
|
},
|
|
87
86
|
"scripts": {
|
|
88
87
|
"build": "tsup",
|
package/src/cli.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
|
|
3
|
-
// @example
|
|
4
|
-
|
|
5
|
-
// ```sh
|
|
6
|
-
// $ npx tsx src/cli.ts builder '#6750A4'
|
|
7
|
-
// $ npx tsx src/cli.ts builder '#6750A4' --format css
|
|
8
|
-
// $ npx tsx src/cli.ts builder '#6750A4' --adaptive-shades --format figma
|
|
9
|
-
// ```
|
|
10
|
-
|
|
11
|
-
import * as fs from "node:fs";
|
|
12
|
-
import * as path from "node:path";
|
|
13
|
-
|
|
14
|
-
import { Command, Option } from "commander";
|
|
15
|
-
import {
|
|
16
|
-
builder,
|
|
17
|
-
DEFAULT_ADAPTIVE_SHADES,
|
|
18
|
-
DEFAULT_BLEND,
|
|
19
|
-
DEFAULT_CONTRAST,
|
|
20
|
-
DEFAULT_CONTRAST_ALL_COLORS,
|
|
21
|
-
DEFAULT_SCHEME,
|
|
22
|
-
schemeNames,
|
|
23
|
-
} from "./lib/builder";
|
|
24
|
-
|
|
25
|
-
const program = new Command();
|
|
26
|
-
|
|
27
|
-
program.name("react-mcu").description("m3 color system for react");
|
|
28
|
-
|
|
29
|
-
program
|
|
30
|
-
.command("builder")
|
|
31
|
-
.description("Generate a color theme from a source color")
|
|
32
|
-
.argument("<source>", "Source color in hex format (e.g. #6750A4)")
|
|
33
|
-
.addOption(
|
|
34
|
-
new Option("--scheme <name>", "Color scheme variant")
|
|
35
|
-
.choices(schemeNames)
|
|
36
|
-
.default(DEFAULT_SCHEME),
|
|
37
|
-
)
|
|
38
|
-
.option(
|
|
39
|
-
"--contrast <number>",
|
|
40
|
-
"Contrast level from -1.0 to 1.0",
|
|
41
|
-
parseFloat,
|
|
42
|
-
DEFAULT_CONTRAST,
|
|
43
|
-
)
|
|
44
|
-
.option("--primary <hex>", "Primary color override")
|
|
45
|
-
.option("--secondary <hex>", "Secondary color override")
|
|
46
|
-
.option("--tertiary <hex>", "Tertiary color override")
|
|
47
|
-
.option("--error <hex>", "Error color override")
|
|
48
|
-
.option("--neutral <hex>", "Neutral color override")
|
|
49
|
-
.option("--neutral-variant <hex>", "Neutral variant color override")
|
|
50
|
-
.option(
|
|
51
|
-
"--custom-colors <json>",
|
|
52
|
-
'Custom colors as JSON array (e.g. \'[{"name":"brand","hex":"#FF5733","blend":true}]\')',
|
|
53
|
-
)
|
|
54
|
-
.option("--format <type>", "Output format: json, css, or figma", "figma")
|
|
55
|
-
.option("--output <dir>", "Output directory (required for figma format)")
|
|
56
|
-
.option(
|
|
57
|
-
"--adaptive-shades",
|
|
58
|
-
"Adapt tonal palette shades for dark mode",
|
|
59
|
-
DEFAULT_ADAPTIVE_SHADES,
|
|
60
|
-
)
|
|
61
|
-
.option(
|
|
62
|
-
"--contrast-all-colors",
|
|
63
|
-
"Apply contrast adjustment to tonal palette shades",
|
|
64
|
-
DEFAULT_CONTRAST_ALL_COLORS,
|
|
65
|
-
)
|
|
66
|
-
.action((source: string, opts) => {
|
|
67
|
-
let customColors: { name: string; hex: string; blend: boolean }[] = [];
|
|
68
|
-
if (opts.customColors) {
|
|
69
|
-
try {
|
|
70
|
-
const parsed = JSON.parse(opts.customColors) as {
|
|
71
|
-
name: string;
|
|
72
|
-
hex: string;
|
|
73
|
-
blend?: boolean;
|
|
74
|
-
}[];
|
|
75
|
-
customColors = parsed.map((c) => ({
|
|
76
|
-
name: c.name,
|
|
77
|
-
hex: c.hex,
|
|
78
|
-
blend: c.blend ?? DEFAULT_BLEND,
|
|
79
|
-
}));
|
|
80
|
-
} catch {
|
|
81
|
-
console.error("Error: --custom-colors must be valid JSON");
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const result = builder(source, {
|
|
87
|
-
scheme: opts.scheme,
|
|
88
|
-
contrast: opts.contrast,
|
|
89
|
-
primary: opts.primary,
|
|
90
|
-
secondary: opts.secondary,
|
|
91
|
-
tertiary: opts.tertiary,
|
|
92
|
-
error: opts.error,
|
|
93
|
-
neutral: opts.neutral,
|
|
94
|
-
neutralVariant: opts.neutralVariant,
|
|
95
|
-
customColors,
|
|
96
|
-
adaptiveShades: opts.adaptiveShades,
|
|
97
|
-
contrastAllColors: opts.contrastAllColors,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (opts.format === "css") {
|
|
101
|
-
process.stdout.write(result.toCss());
|
|
102
|
-
} else if (opts.format === "figma") {
|
|
103
|
-
const outputDir = opts.output ?? "mcu-theme";
|
|
104
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
105
|
-
const files = result.toFigmaTokens();
|
|
106
|
-
for (const [filename, content] of Object.entries(files)) {
|
|
107
|
-
const filePath = path.join(outputDir, filename);
|
|
108
|
-
fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n");
|
|
109
|
-
console.error(`wrote ${filePath}`);
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
process.stdout.write(JSON.stringify(result.toJson(), null, 2) + "\n");
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
program.parse();
|