react-mcu 1.3.0 → 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 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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import { CustomColor, TonalPalette } from '@material/material-color-utilities';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
 
4
4
  type HexCustomColor = Omit<CustomColor, "value"> & {
5
5
  hex: string;
@@ -34,39 +34,26 @@ type McuConfig = {
34
34
  colorMatch?: boolean;
35
35
  /**
36
36
  * Array of custom colors to include in the generated palette.
37
- *
38
- * @example
39
- * ```ts
40
- * customColors={[
41
- * { name: "brand", hex: "#FF5733", blend: true },
42
- * { name: "success", hex: "#28A745", blend: false }
43
- * ]}
44
- * ```
37
+ * Each custom color can be blended with the source color for harmonization.
45
38
  */
46
39
  customColors?: HexCustomColor[];
47
40
  /**
48
- * When true, applies the contrast level to all colors including custom colors and tonal palette shades.
49
- * When false (default), only core colors are affected by the contrast level.
41
+ * Apply contrast adjustment to tonal palette shades when true.
42
+ * When true, all tonal palette shades are adjusted based on the contrast level.
43
+ * When false (default), contrast adjustments only apply to core Material Design tokens.
50
44
  */
51
45
  contrastAllColors?: boolean;
52
46
  /**
53
- * When true (default), tonal palette shades adapt to the theme (light/dark) with inverted tone values.
54
- * In dark mode, high tones (light colors) map to low tones (dark colors) and vice versa.
55
- * When false, shades remain the same across themes.
47
+ * Adapt tonal palette shades for dark mode.
48
+ * When true, tonal palette shades automatically invert for dark mode
49
+ * (high values become dark, low values become light).
50
+ * When false (default), tonal palette values remain constant across light/dark mode.
56
51
  */
57
52
  adaptiveShades?: boolean;
58
53
  };
59
54
  declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
60
55
  type SchemeName = (typeof schemeNames)[number];
61
- declare const DEFAULT_SCHEME: SchemeName;
62
- declare const DEFAULT_CONTRAST = 0;
63
- declare const DEFAULT_CONTRAST_ALL_COLORS = false;
64
- declare const DEFAULT_ADAPTIVE_SHADES = false;
65
- declare const DEFAULT_BLEND = true;
66
- declare function Mcu({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, contrastAllColors, adaptiveShades, children, }: McuConfig & {
67
- children?: React.ReactNode;
68
- }): react_jsx_runtime.JSX.Element;
69
- declare const tokenNames: readonly ["background", "onBackground", "surface", "surfaceDim", "surfaceBright", "surfaceContainerLowest", "surfaceContainerLow", "surfaceContainer", "surfaceContainerHigh", "surfaceContainerHighest", "onSurface", "surfaceVariant", "onSurfaceVariant", "outline", "outlineVariant", "inverseSurface", "inverseOnSurface", "primary", "surfaceTint", "onPrimary", "primaryContainer", "onPrimaryContainer", "primaryFixed", "primaryFixedDim", "onPrimaryFixed", "onPrimaryFixedVariant", "inversePrimary", "secondary", "onSecondary", "secondaryContainer", "onSecondaryContainer", "secondaryFixed", "secondaryFixedDim", "onSecondaryFixed", "onSecondaryFixedVariant", "tertiary", "onTertiary", "tertiaryContainer", "onTertiaryContainer", "tertiaryFixed", "tertiaryFixedDim", "onTertiaryFixed", "onTertiaryFixedVariant", "error", "onError", "errorContainer", "onErrorContainer", "scrim", "shadow"];
56
+ declare const tokenNames: readonly ["background", "error", "errorContainer", "inverseOnSurface", "inversePrimary", "inverseSurface", "onBackground", "onError", "onErrorContainer", "onPrimary", "onPrimaryContainer", "onPrimaryFixed", "onPrimaryFixedVariant", "onSecondary", "onSecondaryContainer", "onSecondaryFixed", "onSecondaryFixedVariant", "onSurface", "onSurfaceVariant", "onTertiary", "onTertiaryContainer", "onTertiaryFixed", "onTertiaryFixedVariant", "outline", "outlineVariant", "primary", "primaryContainer", "primaryFixed", "primaryFixedDim", "scrim", "secondary", "secondaryContainer", "secondaryFixed", "secondaryFixedDim", "shadow", "surface", "surfaceBright", "surfaceContainer", "surfaceContainerHigh", "surfaceContainerHighest", "surfaceContainerLow", "surfaceContainerLowest", "surfaceDim", "surfaceTint", "surfaceVariant", "tertiary", "tertiaryContainer", "tertiaryFixed", "tertiaryFixedDim"];
70
57
  type TokenName = (typeof tokenNames)[number];
71
58
  declare function builder(hexSource: McuConfig["source"], { scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, customColors: hexCustomColors, contrastAllColors, adaptiveShades, }?: Omit<McuConfig, "source">): {
72
59
  toCss(): string;
@@ -162,6 +149,10 @@ declare function builder(hexSource: McuConfig["source"], { scheme, contrast, pri
162
149
  };
163
150
  };
164
151
 
152
+ declare function Mcu({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, contrastAllColors, adaptiveShades, children, }: McuConfig & {
153
+ children?: React.ReactNode;
154
+ }): react_jsx_runtime.JSX.Element;
155
+
165
156
  type Api = {
166
157
  initials: McuConfig;
167
158
  setMcuConfig: (config: McuConfig) => void;
@@ -170,4 +161,4 @@ type Api = {
170
161
  };
171
162
  declare const useMcu: () => Api;
172
163
 
173
- export { DEFAULT_ADAPTIVE_SHADES, DEFAULT_BLEND, DEFAULT_CONTRAST, DEFAULT_CONTRAST_ALL_COLORS, DEFAULT_SCHEME, Mcu, builder, schemeNames, useMcu };
164
+ export { Mcu, builder, useMcu };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- // src/Mcu.tsx
3
+ // src/lib/builder.ts
4
4
  import {
5
5
  argbFromHex,
6
6
  Blend,
@@ -9,7 +9,7 @@ import {
9
9
  DynamicScheme,
10
10
  greenFromArgb,
11
11
  Hct,
12
- hexFromArgb as hexFromArgb2,
12
+ hexFromArgb,
13
13
  MaterialDynamicColors,
14
14
  redFromArgb,
15
15
  SchemeContent,
@@ -22,85 +22,6 @@ import {
22
22
  TonalPalette
23
23
  } from "@material/material-color-utilities";
24
24
  import { kebabCase, startCase, upperFirst } from "lodash-es";
25
- import { useMemo as useMemo2 } from "react";
26
-
27
- // src/Mcu.context.tsx
28
- import {
29
- hexFromArgb
30
- } from "@material/material-color-utilities";
31
- import React, {
32
- useCallback,
33
- useInsertionEffect,
34
- useMemo,
35
- useState
36
- } from "react";
37
-
38
- // src/lib/createRequiredContext.ts
39
- import { createContext, useContext } from "react";
40
- var createRequiredContext = () => {
41
- const Ctx = createContext(null);
42
- const useCtx = () => {
43
- const contextValue = useContext(Ctx);
44
- if (contextValue === null) {
45
- throw new Error("Context value is null");
46
- }
47
- return contextValue;
48
- };
49
- return [useCtx, Ctx.Provider, Ctx];
50
- };
51
-
52
- // src/Mcu.context.tsx
53
- import { jsx } from "react/jsx-runtime";
54
- var [useMcu, Provider, McuContext] = createRequiredContext();
55
- var McuProvider = ({
56
- styleId,
57
- children,
58
- ...configProps
59
- }) => {
60
- const [initials] = useState(() => configProps);
61
- const [mcuConfig, setMcuConfig] = useState(initials);
62
- const configKey = JSON.stringify(configProps);
63
- React.useEffect(() => {
64
- setMcuConfig(configProps);
65
- }, [configKey]);
66
- const { css, mergedColorsLight, mergedColorsDark, allPalettes } = useMemo(() => {
67
- const { toCss, ...rest } = builder(mcuConfig.source, mcuConfig);
68
- return { css: toCss(), ...rest };
69
- }, [mcuConfig]);
70
- useInsertionEffect(() => {
71
- let tag = document.getElementById(styleId);
72
- if (!tag) {
73
- tag = document.createElement("style");
74
- tag.id = styleId;
75
- document.head.appendChild(tag);
76
- }
77
- tag.textContent = css;
78
- }, [css, styleId]);
79
- const getMcuColor = useCallback(
80
- (colorName, theme) => {
81
- const mergedColors = theme === "light" ? mergedColorsLight : mergedColorsDark;
82
- const colorValue = mergedColors[colorName];
83
- if (colorValue === void 0) {
84
- throw new Error(`Unknown MCU token '${colorName}'`);
85
- }
86
- return hexFromArgb(colorValue);
87
- },
88
- [mergedColorsDark, mergedColorsLight]
89
- );
90
- const value = useMemo(
91
- () => ({
92
- initials,
93
- setMcuConfig,
94
- getMcuColor,
95
- allPalettes
96
- }),
97
- [getMcuColor, initials, allPalettes]
98
- );
99
- return /* @__PURE__ */ jsx(Provider, { value, children });
100
- };
101
-
102
- // src/Mcu.tsx
103
- import { jsx as jsx2 } from "react/jsx-runtime";
104
25
  function adjustToneForContrast(baseTone, contrastLevel, adjustmentFactor = DEFAULT_CONTRAST_ADJUSTMENT_FACTOR) {
105
26
  if (contrastLevel === 0) return baseTone;
106
27
  const distanceToCenter = baseTone - 50;
@@ -108,15 +29,6 @@ function adjustToneForContrast(baseTone, contrastLevel, adjustmentFactor = DEFAU
108
29
  const adjustedTone = baseTone + delta;
109
30
  return Math.max(0, Math.min(100, adjustedTone));
110
31
  }
111
- var schemeNames = [
112
- "tonalSpot",
113
- "monochrome",
114
- "neutral",
115
- "vibrant",
116
- "expressive",
117
- "fidelity",
118
- "content"
119
- ];
120
32
  var schemesMap = {
121
33
  tonalSpot: SchemeTonalSpot,
122
34
  monochrome: SchemeMonochrome,
@@ -128,7 +40,6 @@ var schemesMap = {
128
40
  };
129
41
  var DEFAULT_SCHEME = "tonalSpot";
130
42
  var DEFAULT_CONTRAST = 0;
131
- var DEFAULT_COLOR_MATCH = false;
132
43
  var DEFAULT_CUSTOM_COLORS = [];
133
44
  var DEFAULT_CONTRAST_ALL_COLORS = false;
134
45
  var DEFAULT_ADAPTIVE_SHADES = false;
@@ -166,116 +77,64 @@ var Variant = {
166
77
  FRUIT_SALAD: 8
167
78
  };
168
79
  var schemeToVariant = {
169
- tonalSpot: Variant.TONAL_SPOT,
170
80
  monochrome: Variant.MONOCHROME,
171
81
  neutral: Variant.NEUTRAL,
82
+ tonalSpot: Variant.TONAL_SPOT,
172
83
  vibrant: Variant.VIBRANT,
173
84
  expressive: Variant.EXPRESSIVE,
174
85
  fidelity: Variant.FIDELITY,
175
86
  content: Variant.CONTENT
176
87
  };
177
- var mcuStyleId = "mcu-styles";
178
- function Mcu({
179
- source,
180
- scheme = DEFAULT_SCHEME,
181
- contrast = DEFAULT_CONTRAST,
182
- primary,
183
- secondary,
184
- tertiary,
185
- neutral,
186
- neutralVariant,
187
- error,
188
- colorMatch = DEFAULT_COLOR_MATCH,
189
- customColors = DEFAULT_CUSTOM_COLORS,
190
- contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS,
191
- adaptiveShades = DEFAULT_ADAPTIVE_SHADES,
192
- children
193
- }) {
194
- const config = useMemo2(
195
- () => ({
196
- source,
197
- scheme,
198
- contrast,
199
- primary,
200
- secondary,
201
- tertiary,
202
- neutral,
203
- neutralVariant,
204
- error,
205
- colorMatch,
206
- customColors,
207
- // extras features
208
- contrastAllColors,
209
- adaptiveShades
210
- }),
211
- [
212
- contrast,
213
- customColors,
214
- scheme,
215
- source,
216
- primary,
217
- secondary,
218
- tertiary,
219
- neutral,
220
- neutralVariant,
221
- error,
222
- colorMatch,
223
- contrastAllColors,
224
- adaptiveShades
225
- ]
226
- );
227
- return /* @__PURE__ */ jsx2(McuProvider, { ...config, styleId: mcuStyleId, children });
228
- }
229
88
  var tokenNames = [
230
89
  "background",
90
+ "error",
91
+ "errorContainer",
92
+ "inverseOnSurface",
93
+ "inversePrimary",
94
+ "inverseSurface",
231
95
  "onBackground",
232
- "surface",
233
- "surfaceDim",
234
- "surfaceBright",
235
- "surfaceContainerLowest",
236
- "surfaceContainerLow",
237
- "surfaceContainer",
238
- "surfaceContainerHigh",
239
- "surfaceContainerHighest",
96
+ "onError",
97
+ "onErrorContainer",
98
+ "onPrimary",
99
+ "onPrimaryContainer",
100
+ "onPrimaryFixed",
101
+ "onPrimaryFixedVariant",
102
+ "onSecondary",
103
+ "onSecondaryContainer",
104
+ "onSecondaryFixed",
105
+ "onSecondaryFixedVariant",
240
106
  "onSurface",
241
- "surfaceVariant",
242
107
  "onSurfaceVariant",
108
+ "onTertiary",
109
+ "onTertiaryContainer",
110
+ "onTertiaryFixed",
111
+ "onTertiaryFixedVariant",
243
112
  "outline",
244
113
  "outlineVariant",
245
- "inverseSurface",
246
- "inverseOnSurface",
247
114
  "primary",
248
- "surfaceTint",
249
- "onPrimary",
250
115
  "primaryContainer",
251
- "onPrimaryContainer",
252
116
  "primaryFixed",
253
117
  "primaryFixedDim",
254
- "onPrimaryFixed",
255
- "onPrimaryFixedVariant",
256
- "inversePrimary",
118
+ "scrim",
257
119
  "secondary",
258
- "onSecondary",
259
120
  "secondaryContainer",
260
- "onSecondaryContainer",
261
121
  "secondaryFixed",
262
122
  "secondaryFixedDim",
263
- "onSecondaryFixed",
264
- "onSecondaryFixedVariant",
123
+ "shadow",
124
+ "surface",
125
+ "surfaceBright",
126
+ "surfaceContainer",
127
+ "surfaceContainerHigh",
128
+ "surfaceContainerHighest",
129
+ "surfaceContainerLow",
130
+ "surfaceContainerLowest",
131
+ "surfaceDim",
132
+ "surfaceTint",
133
+ "surfaceVariant",
265
134
  "tertiary",
266
- "onTertiary",
267
135
  "tertiaryContainer",
268
- "onTertiaryContainer",
269
136
  "tertiaryFixed",
270
- "tertiaryFixedDim",
271
- "onTertiaryFixed",
272
- "onTertiaryFixedVariant",
273
- "error",
274
- "onError",
275
- "errorContainer",
276
- "onErrorContainer",
277
- "scrim",
278
- "shadow"
137
+ "tertiaryFixedDim"
279
138
  ];
280
139
  function toRecord(arr, getEntry) {
281
140
  return Object.fromEntries(arr.map(getEntry));
@@ -489,7 +348,7 @@ function builder(hexSource, {
489
348
  toCss() {
490
349
  function cssVar(colorName, colorValue) {
491
350
  const name = `--mcu-${kebabCase(colorName)}`;
492
- const value = hexFromArgb2(colorValue);
351
+ const value = hexFromArgb(colorValue);
493
352
  return `${name}:${value};`;
494
353
  }
495
354
  function toCssVars(mergedColors) {
@@ -611,7 +470,7 @@ function builder(hexSource, {
611
470
  for (const tokenName of fixtureTokenOrder) {
612
471
  const dynamicColor = MaterialDynamicColors[tokenName];
613
472
  const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
614
- colors[tokenName] = hexFromArgb2(
473
+ colors[tokenName] = hexFromArgb(
615
474
  dynamicColor.getArgb(useScheme)
616
475
  ).toUpperCase();
617
476
  }
@@ -669,7 +528,7 @@ function builder(hexSource, {
669
528
  const palette = rawPalettes[name];
670
529
  const tones = {};
671
530
  for (const tone of STANDARD_TONES) {
672
- tones[tone.toString()] = hexFromArgb2(
531
+ tones[tone.toString()] = hexFromArgb(
673
532
  palette.tone(tone)
674
533
  ).toUpperCase();
675
534
  }
@@ -727,7 +586,7 @@ function builder(hexSource, {
727
586
  blueFromArgb(argb) / 255
728
587
  ],
729
588
  alpha: 1,
730
- hex: hexFromArgb2(argb).toUpperCase()
589
+ hex: hexFromArgb(argb).toUpperCase()
731
590
  };
732
591
  }
733
592
  function figmaToken(argb) {
@@ -788,14 +647,142 @@ function builder(hexSource, {
788
647
  allPalettes
789
648
  };
790
649
  }
650
+
651
+ // src/Mcu.tsx
652
+ import { useMemo as useMemo2 } from "react";
653
+
654
+ // src/Mcu.context.tsx
655
+ import {
656
+ hexFromArgb as hexFromArgb2
657
+ } from "@material/material-color-utilities";
658
+ import React, {
659
+ useCallback,
660
+ useInsertionEffect,
661
+ useMemo,
662
+ useState
663
+ } from "react";
664
+
665
+ // src/lib/createRequiredContext.ts
666
+ import { createContext, useContext } from "react";
667
+ var createRequiredContext = () => {
668
+ const Ctx = createContext(null);
669
+ const useCtx = () => {
670
+ const contextValue = useContext(Ctx);
671
+ if (contextValue === null) {
672
+ throw new Error("Context value is null");
673
+ }
674
+ return contextValue;
675
+ };
676
+ return [useCtx, Ctx.Provider, Ctx];
677
+ };
678
+
679
+ // src/Mcu.context.tsx
680
+ import { jsx } from "react/jsx-runtime";
681
+ var [useMcu, Provider, McuContext] = createRequiredContext();
682
+ var McuProvider = ({
683
+ styleId,
684
+ children,
685
+ ...configProps
686
+ }) => {
687
+ const [initials] = useState(() => configProps);
688
+ const [mcuConfig, setMcuConfig] = useState(initials);
689
+ const configKey = JSON.stringify(configProps);
690
+ React.useEffect(() => {
691
+ setMcuConfig(configProps);
692
+ }, [configKey]);
693
+ const { css, mergedColorsLight, mergedColorsDark, allPalettes } = useMemo(() => {
694
+ const { toCss, ...rest } = builder(mcuConfig.source, mcuConfig);
695
+ return { css: toCss(), ...rest };
696
+ }, [mcuConfig]);
697
+ useInsertionEffect(() => {
698
+ let tag = document.getElementById(styleId);
699
+ if (!tag) {
700
+ tag = document.createElement("style");
701
+ tag.id = styleId;
702
+ document.head.appendChild(tag);
703
+ }
704
+ tag.textContent = css;
705
+ }, [css, styleId]);
706
+ const getMcuColor = useCallback(
707
+ (colorName, theme) => {
708
+ const mergedColors = theme === "light" ? mergedColorsLight : mergedColorsDark;
709
+ const colorValue = mergedColors[colorName];
710
+ if (colorValue === void 0) {
711
+ throw new Error(`Unknown MCU token '${colorName}'`);
712
+ }
713
+ return hexFromArgb2(colorValue);
714
+ },
715
+ [mergedColorsDark, mergedColorsLight]
716
+ );
717
+ const value = useMemo(
718
+ () => ({
719
+ initials,
720
+ setMcuConfig,
721
+ getMcuColor,
722
+ allPalettes
723
+ }),
724
+ [getMcuColor, initials, allPalettes]
725
+ );
726
+ return /* @__PURE__ */ jsx(Provider, { value, children });
727
+ };
728
+
729
+ // src/Mcu.tsx
730
+ import { jsx as jsx2 } from "react/jsx-runtime";
731
+ var mcuStyleId = "mcu-styles";
732
+ var DEFAULT_COLOR_MATCH = false;
733
+ function Mcu({
734
+ source,
735
+ scheme = DEFAULT_SCHEME,
736
+ contrast = DEFAULT_CONTRAST,
737
+ primary,
738
+ secondary,
739
+ tertiary,
740
+ neutral,
741
+ neutralVariant,
742
+ error,
743
+ colorMatch = DEFAULT_COLOR_MATCH,
744
+ customColors = DEFAULT_CUSTOM_COLORS,
745
+ contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS,
746
+ adaptiveShades = DEFAULT_ADAPTIVE_SHADES,
747
+ children
748
+ }) {
749
+ const config = useMemo2(
750
+ () => ({
751
+ source,
752
+ scheme,
753
+ contrast,
754
+ primary,
755
+ secondary,
756
+ tertiary,
757
+ neutral,
758
+ neutralVariant,
759
+ error,
760
+ colorMatch,
761
+ customColors,
762
+ // extras features
763
+ contrastAllColors,
764
+ adaptiveShades
765
+ }),
766
+ [
767
+ contrast,
768
+ customColors,
769
+ scheme,
770
+ source,
771
+ primary,
772
+ secondary,
773
+ tertiary,
774
+ neutral,
775
+ neutralVariant,
776
+ error,
777
+ colorMatch,
778
+ contrastAllColors,
779
+ adaptiveShades
780
+ ]
781
+ );
782
+ return /* @__PURE__ */ jsx2(McuProvider, { ...config, styleId: mcuStyleId, children });
783
+ }
791
784
  export {
792
- DEFAULT_ADAPTIVE_SHADES,
793
- DEFAULT_BLEND,
794
- DEFAULT_CONTRAST,
795
- DEFAULT_CONTRAST_ALL_COLORS,
796
- DEFAULT_SCHEME,
797
785
  Mcu,
798
786
  builder,
799
- schemeNames,
800
787
  useMcu
801
788
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mcu",
3
- "version": "1.3.0",
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 "react-mcu";
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();