react-mcu 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,27 @@ m3 references:
15
15
  | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
16
  | [<img width="2836" height="2266" alt="CleanShot 2026-01-14 at 08 58 40@2x" src="https://github.com/user-attachments/assets/e4b47c00-716f-4b08-b393-de306d5ce302" />](https://material-foundation.github.io/material-theme-builder/) | [<img width="2836" height="2266" alt="CleanShot 2026-01-14 at 09 01 23@2x" src="https://github.com/user-attachments/assets/826e502d-e173-43c4-807a-53d0ba075a88" />](https://m3.material.io/styles/color/roles) |
17
17
 
18
+ Support for:
19
+
20
+ Base (like in the Builder):
21
+
22
+ - [x] light/dark mode
23
+ - [x] source color
24
+ - [x] scheme
25
+ - [x] contrast
26
+ - [x] core-colors overrides: primary, secondary, tertiary, error, neutral,
27
+ neutralVariant
28
+ - [x] custom-colors (aka. "Extended colors")
29
+ - [x] Harmonization (aka. `blend`) -- with effective color: `source` or
30
+ `primary` if defined
31
+ - [x] Shades (aka. "tonals")
32
+ - [ ] colorMatch
33
+
34
+ Extra:
35
+
36
+ - [x] `contrastAllColors`: contrast also applies to custom-colors and shades
37
+ (not only the core-colors)
38
+
18
39
  # Usage
19
40
 
20
41
  ```tsx
package/dist/index.d.ts CHANGED
@@ -45,6 +45,13 @@ type McuConfig = {
45
45
  * ```
46
46
  */
47
47
  customColors?: HexCustomColor[];
48
+ /**
49
+ * When true, applies the contrast level to all colors including custom colors and tonal palette shades.
50
+ * When false (default), only core colors are affected by the contrast level.
51
+ *
52
+ * @default false
53
+ */
54
+ contrastAllColors?: boolean;
48
55
  };
49
56
  declare const schemesMap: {
50
57
  readonly tonalSpot: typeof SchemeTonalSpot;
@@ -57,7 +64,7 @@ declare const schemesMap: {
57
64
  };
58
65
  declare const schemeNames: (keyof typeof schemesMap)[];
59
66
  type SchemeName = (typeof schemeNames)[number];
60
- declare function Mcu({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, children, }: McuConfig & {
67
+ declare function Mcu({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, contrastAllColors, children, }: McuConfig & {
61
68
  children?: React.ReactNode;
62
69
  }): react_jsx_runtime.JSX.Element;
63
70
  declare const tokenNames: readonly ["background", "onBackground", "surface", "surfaceDim", "surfaceBright", "surfaceContainerLowest", "surfaceContainerLow", "surfaceContainer", "surfaceContainerHigh", "surfaceContainerHighest", "onSurface", "onSurfaceVariant", "outline", "outlineVariant", "inverseSurface", "inverseOnSurface", "primary", "onPrimary", "primaryContainer", "onPrimaryContainer", "primaryFixed", "primaryFixedDim", "onPrimaryFixed", "onPrimaryFixedVariant", "inversePrimary", "primaryFixed", "primaryFixedDim", "onPrimaryFixed", "onPrimaryFixedVariant", "secondary", "onSecondary", "secondaryContainer", "onSecondaryContainer", "secondaryFixed", "secondaryFixedDim", "onSecondaryFixed", "onSecondaryFixedVariant", "tertiary", "onTertiary", "tertiaryContainer", "onTertiaryContainer", "tertiaryFixed", "tertiaryFixedDim", "onTertiaryFixed", "onTertiaryFixedVariant", "error", "onError", "errorContainer", "onErrorContainer", "scrim", "shadow"];
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ var McuProvider = ({
52
52
  scheme: initialScheme,
53
53
  contrast: initialContrast,
54
54
  customColors: initialCustomColors,
55
+ contrastAllColors: initialContrastAllColors,
55
56
  styleId,
56
57
  children
57
58
  }) => {
@@ -59,7 +60,8 @@ var McuProvider = ({
59
60
  source: initialSource,
60
61
  scheme: initialScheme,
61
62
  contrast: initialContrast,
62
- customColors: initialCustomColors
63
+ customColors: initialCustomColors,
64
+ contrastAllColors: initialContrastAllColors
63
65
  }));
64
66
  const [mcuConfig, setMcuConfig] = useState(initials);
65
67
  const { css, mergedColorsLight, mergedColorsDark } = useMemo(
@@ -96,6 +98,16 @@ var McuProvider = ({
96
98
 
97
99
  // src/Mcu.tsx
98
100
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
101
+ function adjustToneForContrast(baseTone, contrastLevel, isDark, adjustmentFactor = DEFAULT_CONTRAST_ADJUSTMENT_FACTOR) {
102
+ if (contrastLevel === 0) return baseTone;
103
+ let adjustedTone;
104
+ if (isDark) {
105
+ adjustedTone = baseTone + contrastLevel * (100 - baseTone) * adjustmentFactor;
106
+ } else {
107
+ adjustedTone = baseTone - contrastLevel * baseTone * adjustmentFactor;
108
+ }
109
+ return Math.max(0, Math.min(100, adjustedTone));
110
+ }
99
111
  var schemesMap = {
100
112
  tonalSpot: SchemeTonalSpot,
101
113
  monochrome: SchemeMonochrome,
@@ -112,6 +124,9 @@ var DEFAULT_SCHEME = "tonalSpot";
112
124
  var DEFAULT_CONTRAST = 0;
113
125
  var DEFAULT_COLOR_MATCH = false;
114
126
  var DEFAULT_CUSTOM_COLORS = [];
127
+ var DEFAULT_CONTRAST_ALL_COLORS = false;
128
+ var DEFAULT_BLEND = true;
129
+ var DEFAULT_CONTRAST_ADJUSTMENT_FACTOR = 0.2;
115
130
  var STANDARD_TONES = [
116
131
  0,
117
132
  5,
@@ -165,6 +180,7 @@ function Mcu({
165
180
  error,
166
181
  colorMatch = DEFAULT_COLOR_MATCH,
167
182
  customColors = DEFAULT_CUSTOM_COLORS,
183
+ contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS,
168
184
  children
169
185
  }) {
170
186
  const config = useMemo2(
@@ -179,7 +195,9 @@ function Mcu({
179
195
  neutralVariant,
180
196
  error,
181
197
  colorMatch,
182
- customColors
198
+ customColors,
199
+ // extras features
200
+ contrastAllColors
183
201
  }),
184
202
  [
185
203
  contrast,
@@ -192,7 +210,8 @@ function Mcu({
192
210
  neutral,
193
211
  neutralVariant,
194
212
  error,
195
- colorMatch
213
+ colorMatch,
214
+ contrastAllColors
196
215
  ]
197
216
  );
198
217
  const { css } = useMemo2(() => generateCss(config), [config]);
@@ -279,7 +298,7 @@ function getPalette(palettes, colorName) {
279
298
  }
280
299
  return palette;
281
300
  }
282
- function mergeBaseAndCustomColors(scheme, customColors, colorPalettes) {
301
+ function mergeBaseAndCustomColors(scheme, customColors, colorPalettes, contrastAllColors) {
283
302
  const baseVars = toRecord(tokenNames, (tokenName) => {
284
303
  const dynamicColor = MaterialDynamicColors[tokenName];
285
304
  const argb = dynamicColor.getArgb(scheme);
@@ -288,32 +307,39 @@ function mergeBaseAndCustomColors(scheme, customColors, colorPalettes) {
288
307
  const customVars = {};
289
308
  customColors.forEach((color) => {
290
309
  const colorname = color.name;
310
+ const getPaletteForColor = (s) => getPalette(colorPalettes, colorname);
311
+ const getTone = (baseTone) => (s) => {
312
+ if (!contrastAllColors) return baseTone;
313
+ return adjustToneForContrast(baseTone, s.contrastLevel, s.isDark);
314
+ };
291
315
  const colorDynamicColor = new DynamicColor(
292
316
  colorname,
293
- (s) => getPalette(colorPalettes, colorname),
294
- (s) => s.isDark ? 80 : 40,
295
- // Same as primary
296
- false
317
+ getPaletteForColor,
318
+ (s) => getTone(s.isDark ? 80 : 40)(s),
319
+ // Main color: lighter in dark mode, darker in light mode
320
+ true
321
+ // background
297
322
  );
298
323
  const onColorDynamicColor = new DynamicColor(
299
324
  `on${upperFirst(colorname)}`,
300
- (s) => getPalette(colorPalettes, colorname),
301
- (s) => s.isDark ? 20 : 100,
302
- // Same as onPrimary
325
+ getPaletteForColor,
326
+ (s) => getTone(s.isDark ? 20 : 100)(s),
327
+ // Text on main color: high contrast (dark on light, light on dark)
303
328
  false
304
329
  );
305
330
  const containerDynamicColor = new DynamicColor(
306
331
  `${colorname}Container`,
307
- (s) => getPalette(colorPalettes, colorname),
308
- (s) => s.isDark ? 30 : 90,
309
- // Same as primaryContainer
310
- false
332
+ getPaletteForColor,
333
+ (s) => getTone(s.isDark ? 30 : 90)(s),
334
+ // Container: subtle variant (darker in dark mode, lighter in light mode)
335
+ true
336
+ // background
311
337
  );
312
338
  const onContainerDynamicColor = new DynamicColor(
313
339
  `on${upperFirst(colorname)}Container`,
314
- (s) => getPalette(colorPalettes, colorname),
315
- (s) => s.isDark ? 90 : 30,
316
- // Same as onPrimaryContainer
340
+ getPaletteForColor,
341
+ (s) => getTone(s.isDark ? 90 : 30)(s),
342
+ // Text on container: high contrast against container background
317
343
  false
318
344
  );
319
345
  customVars[colorname] = colorDynamicColor.getArgb(scheme);
@@ -328,9 +354,17 @@ var cssVar = (colorName, colorValue) => {
328
354
  const value = hexFromArgb2(colorValue);
329
355
  return `${name}:${value};`;
330
356
  };
331
- var generateTonalPaletteVars = (paletteName, palette) => {
357
+ var generateTonalPaletteVars = (paletteName, palette, scheme, applyContrast = false) => {
332
358
  return STANDARD_TONES.map((tone) => {
333
- const color = palette.tone(tone);
359
+ let toneToUse = tone;
360
+ if (applyContrast && scheme) {
361
+ toneToUse = adjustToneForContrast(
362
+ tone,
363
+ scheme.contrastLevel,
364
+ scheme.isDark
365
+ );
366
+ }
367
+ const color = palette.tone(toneToUse);
334
368
  return cssVar(`${paletteName}-${tone}`, color);
335
369
  }).join(" ");
336
370
  };
@@ -366,7 +400,8 @@ function generateCss({
366
400
  neutralVariant,
367
401
  error,
368
402
  colorMatch = DEFAULT_COLOR_MATCH,
369
- customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS
403
+ customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
404
+ contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS
370
405
  }) {
371
406
  const sourceArgb = argbFromHex(hexSource);
372
407
  const effectiveSource = primary || hexSource;
@@ -449,36 +484,70 @@ function generateCss({
449
484
  }
450
485
  const customColors = definedColors.filter((c) => !c.core).map((c) => ({
451
486
  name: c.name,
452
- blend: c.blend ?? false,
487
+ blend: c.blend ?? DEFAULT_BLEND,
453
488
  value: argbFromHex(c.hex)
454
489
  }));
455
490
  const mergedColorsLight = mergeBaseAndCustomColors(
456
491
  lightScheme,
457
492
  customColors,
458
- colorPalettes
493
+ colorPalettes,
494
+ contrastAllColors
459
495
  );
460
496
  const mergedColorsDark = mergeBaseAndCustomColors(
461
497
  darkScheme,
462
498
  customColors,
463
- colorPalettes
499
+ colorPalettes,
500
+ contrastAllColors
464
501
  );
465
502
  const lightVars = toCssVars(mergedColorsLight);
466
503
  const darkVars = toCssVars(mergedColorsDark);
467
504
  const allTonalVars = [
468
505
  // Core colors from the scheme
469
- generateTonalPaletteVars("primary", lightScheme.primaryPalette),
470
- generateTonalPaletteVars("secondary", lightScheme.secondaryPalette),
471
- generateTonalPaletteVars("tertiary", lightScheme.tertiaryPalette),
472
- generateTonalPaletteVars("error", lightScheme.errorPalette),
473
- generateTonalPaletteVars("neutral", lightScheme.neutralPalette),
506
+ generateTonalPaletteVars(
507
+ "primary",
508
+ lightScheme.primaryPalette,
509
+ lightScheme,
510
+ contrastAllColors
511
+ ),
512
+ generateTonalPaletteVars(
513
+ "secondary",
514
+ lightScheme.secondaryPalette,
515
+ lightScheme,
516
+ contrastAllColors
517
+ ),
518
+ generateTonalPaletteVars(
519
+ "tertiary",
520
+ lightScheme.tertiaryPalette,
521
+ lightScheme,
522
+ contrastAllColors
523
+ ),
524
+ generateTonalPaletteVars(
525
+ "error",
526
+ lightScheme.errorPalette,
527
+ lightScheme,
528
+ contrastAllColors
529
+ ),
530
+ generateTonalPaletteVars(
531
+ "neutral",
532
+ lightScheme.neutralPalette,
533
+ lightScheme,
534
+ contrastAllColors
535
+ ),
474
536
  generateTonalPaletteVars(
475
537
  "neutral-variant",
476
- lightScheme.neutralVariantPalette
538
+ lightScheme.neutralVariantPalette,
539
+ lightScheme,
540
+ contrastAllColors
477
541
  ),
478
542
  // Custom colors from our unified palette map
479
543
  ...customColors.map((customColorObj) => {
480
544
  const palette = getPalette(colorPalettes, customColorObj.name);
481
- return generateTonalPaletteVars(kebabCase(customColorObj.name), palette);
545
+ return generateTonalPaletteVars(
546
+ kebabCase(customColorObj.name),
547
+ palette,
548
+ lightScheme,
549
+ contrastAllColors
550
+ );
482
551
  })
483
552
  ].join(" ");
484
553
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mcu",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "A React component library",
5
5
  "keywords": [
6
6
  "react",