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.
Files changed (3) hide show
  1. package/dist/cli.js +729 -0
  2. package/package.json +3 -4
  3. 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.1",
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": "./src/cli.ts"
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();