react-mcu 1.2.0 → 1.3.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
@@ -35,6 +35,8 @@ Extra:
35
35
 
36
36
  - [x] `contrastAllColors`: contrast also applies to custom-colors and shades
37
37
  (not only the core-colors)
38
+ - [x] `adaptiveShades`: shades adapt to the light/dark mode (instead of being
39
+ fixed)
38
40
 
39
41
  # Usage
40
42
 
@@ -173,6 +175,37 @@ Simply override/remap
173
175
  > Make sure `:root, .dark { ... }` comes AFTER `.root { ... } .dark { ... }` to
174
176
  > take precedence.
175
177
 
178
+ ## Programmatic API
179
+
180
+ ```ts
181
+ import { builder } from "react-mcu";
182
+
183
+ const theme = builder("#6750A4", {
184
+ scheme: "vibrant",
185
+ contrast: 0.5,
186
+ primary: "#FF0000",
187
+ secondary: "#00FF00",
188
+ customColors: [
189
+ { name: "brand", hex: "#FF5733", blend: true },
190
+ { name: "success", hex: "#28A745", blend: false },
191
+ ],
192
+ contrastAllColors: true,
193
+ });
194
+
195
+ theme.toJson();
196
+ theme.toCss();
197
+ ```
198
+
199
+ ## CLI
200
+
201
+ ```sh
202
+ $ npx react-mcu builder "#6750A4"
203
+ ```
204
+
205
+ will generate a `mcu-theme` folder with: `Light.tokens.json` and `Dark.tokens.json` [design-tokens](https://www.designtokens.org/tr/2025.10/) files, you can (both) import into Figma.
206
+
207
+ See `npx react-mcu builder --help` for all available options.
208
+
176
209
  # Dev
177
210
 
178
211
  ## INSTALL
@@ -198,6 +231,12 @@ Pre-requisites:
198
231
  $ pnpm i
199
232
  ```
200
233
 
234
+ ## Validation
235
+
236
+ ```sh
237
+ $ pnpm run lgtm
238
+ ```
239
+
201
240
  ## CONTRIBUTING
202
241
 
203
242
  When submitting a pull request, please include a changeset to document your
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { SchemeTonalSpot, SchemeMonochrome, SchemeNeutral, SchemeVibrant, SchemeExpressive, SchemeFidelity, SchemeContent, CustomColor } from '@material/material-color-utilities';
2
+ import { CustomColor, TonalPalette } from '@material/material-color-utilities';
3
3
 
4
4
  type HexCustomColor = Omit<CustomColor, "value"> & {
5
5
  hex: string;
@@ -29,7 +29,6 @@ type McuConfig = {
29
29
  * When false (default), colors may be adjusted for better harmonization.
30
30
  * Corresponds to "Color match - Stay true to my color inputs" in Material Theme Builder.
31
31
  *
32
- * @default false
33
32
  * @deprecated Not yet implemented. This prop is currently ignored.
34
33
  */
35
34
  colorMatch?: boolean;
@@ -48,33 +47,127 @@ type McuConfig = {
48
47
  /**
49
48
  * When true, applies the contrast level to all colors including custom colors and tonal palette shades.
50
49
  * When false (default), only core colors are affected by the contrast level.
51
- *
52
- * @default false
53
50
  */
54
51
  contrastAllColors?: boolean;
52
+ /**
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.
56
+ */
57
+ adaptiveShades?: boolean;
55
58
  };
56
- declare const schemesMap: {
57
- readonly tonalSpot: typeof SchemeTonalSpot;
58
- readonly monochrome: typeof SchemeMonochrome;
59
- readonly neutral: typeof SchemeNeutral;
60
- readonly vibrant: typeof SchemeVibrant;
61
- readonly expressive: typeof SchemeExpressive;
62
- readonly fidelity: typeof SchemeFidelity;
63
- readonly content: typeof SchemeContent;
64
- };
65
- declare const schemeNames: (keyof typeof schemesMap)[];
59
+ declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
66
60
  type SchemeName = (typeof schemeNames)[number];
67
- declare function Mcu({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, contrastAllColors, children, }: McuConfig & {
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 & {
68
67
  children?: React.ReactNode;
69
68
  }): react_jsx_runtime.JSX.Element;
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"];
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"];
71
70
  type TokenName = (typeof tokenNames)[number];
71
+ declare function builder(hexSource: McuConfig["source"], { scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, customColors: hexCustomColors, contrastAllColors, adaptiveShades, }?: Omit<McuConfig, "source">): {
72
+ toCss(): string;
73
+ toJson(): {
74
+ seed: string;
75
+ coreColors: Record<string, string>;
76
+ extendedColors: {
77
+ name: string;
78
+ color: string;
79
+ description: string;
80
+ harmonized: boolean;
81
+ }[];
82
+ schemes: Record<string, Record<string, string>>;
83
+ palettes: Record<string, Record<string, string>>;
84
+ };
85
+ toFigmaTokens(): {
86
+ "Light.tokens.json": {
87
+ Schemes: Record<string, {
88
+ $type: "color";
89
+ $value: {
90
+ colorSpace: "srgb";
91
+ components: number[];
92
+ alpha: number;
93
+ hex: string;
94
+ };
95
+ $extensions: {
96
+ "com.figma.scopes": string[];
97
+ "com.figma.isOverride": boolean;
98
+ };
99
+ }>;
100
+ Palettes: Record<string, Record<string, {
101
+ $type: "color";
102
+ $value: {
103
+ colorSpace: "srgb";
104
+ components: number[];
105
+ alpha: number;
106
+ hex: string;
107
+ };
108
+ $extensions: {
109
+ "com.figma.scopes": string[];
110
+ "com.figma.isOverride": boolean;
111
+ };
112
+ }>>;
113
+ $extensions: {
114
+ "com.figma.modeName": string;
115
+ };
116
+ };
117
+ "Dark.tokens.json": {
118
+ Schemes: Record<string, {
119
+ $type: "color";
120
+ $value: {
121
+ colorSpace: "srgb";
122
+ components: number[];
123
+ alpha: number;
124
+ hex: string;
125
+ };
126
+ $extensions: {
127
+ "com.figma.scopes": string[];
128
+ "com.figma.isOverride": boolean;
129
+ };
130
+ }>;
131
+ Palettes: Record<string, Record<string, {
132
+ $type: "color";
133
+ $value: {
134
+ colorSpace: "srgb";
135
+ components: number[];
136
+ alpha: number;
137
+ hex: string;
138
+ };
139
+ $extensions: {
140
+ "com.figma.scopes": string[];
141
+ "com.figma.isOverride": boolean;
142
+ };
143
+ }>>;
144
+ $extensions: {
145
+ "com.figma.modeName": string;
146
+ };
147
+ };
148
+ };
149
+ mergedColorsLight: {
150
+ [x: string]: number;
151
+ };
152
+ mergedColorsDark: {
153
+ [x: string]: number;
154
+ };
155
+ allPalettes: {
156
+ primary: TonalPalette;
157
+ secondary: TonalPalette;
158
+ tertiary: TonalPalette;
159
+ error: TonalPalette;
160
+ neutral: TonalPalette;
161
+ "neutral-variant": TonalPalette;
162
+ };
163
+ };
72
164
 
73
165
  type Api = {
74
166
  initials: McuConfig;
75
167
  setMcuConfig: (config: McuConfig) => void;
76
168
  getMcuColor: (colorName: TokenName, theme?: string) => string;
169
+ allPalettes: Record<string, TonalPalette>;
77
170
  };
78
171
  declare const useMcu: () => Api;
79
172
 
80
- export { Mcu, useMcu };
173
+ export { DEFAULT_ADAPTIVE_SHADES, DEFAULT_BLEND, DEFAULT_CONTRAST, DEFAULT_CONTRAST_ALL_COLORS, DEFAULT_SCHEME, Mcu, builder, schemeNames, useMcu };
package/dist/index.js CHANGED
@@ -4,11 +4,14 @@
4
4
  import {
5
5
  argbFromHex,
6
6
  Blend,
7
+ blueFromArgb,
7
8
  DynamicColor,
8
9
  DynamicScheme,
10
+ greenFromArgb,
9
11
  Hct,
10
12
  hexFromArgb as hexFromArgb2,
11
13
  MaterialDynamicColors,
14
+ redFromArgb,
12
15
  SchemeContent,
13
16
  SchemeExpressive,
14
17
  SchemeFidelity,
@@ -18,12 +21,14 @@ import {
18
21
  SchemeVibrant,
19
22
  TonalPalette
20
23
  } from "@material/material-color-utilities";
21
- import { kebabCase, upperFirst } from "lodash-es";
24
+ import { kebabCase, startCase, upperFirst } from "lodash-es";
22
25
  import { useMemo as useMemo2 } from "react";
23
26
 
24
27
  // src/Mcu.context.tsx
25
- import { hexFromArgb } from "@material/material-color-utilities";
26
28
  import {
29
+ hexFromArgb
30
+ } from "@material/material-color-utilities";
31
+ import React, {
27
32
  useCallback,
28
33
  useInsertionEffect,
29
34
  useMemo,
@@ -48,26 +53,20 @@ var createRequiredContext = () => {
48
53
  import { jsx } from "react/jsx-runtime";
49
54
  var [useMcu, Provider, McuContext] = createRequiredContext();
50
55
  var McuProvider = ({
51
- source: initialSource,
52
- scheme: initialScheme,
53
- contrast: initialContrast,
54
- customColors: initialCustomColors,
55
- contrastAllColors: initialContrastAllColors,
56
56
  styleId,
57
- children
57
+ children,
58
+ ...configProps
58
59
  }) => {
59
- const [initials] = useState(() => ({
60
- source: initialSource,
61
- scheme: initialScheme,
62
- contrast: initialContrast,
63
- customColors: initialCustomColors,
64
- contrastAllColors: initialContrastAllColors
65
- }));
60
+ const [initials] = useState(() => configProps);
66
61
  const [mcuConfig, setMcuConfig] = useState(initials);
67
- const { css, mergedColorsLight, mergedColorsDark } = useMemo(
68
- () => generateCss(mcuConfig),
69
- [mcuConfig]
70
- );
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]);
71
70
  useInsertionEffect(() => {
72
71
  let tag = document.getElementById(styleId);
73
72
  if (!tag) {
@@ -79,9 +78,12 @@ var McuProvider = ({
79
78
  }, [css, styleId]);
80
79
  const getMcuColor = useCallback(
81
80
  (colorName, theme) => {
82
- return hexFromArgb(
83
- (theme === "light" ? mergedColorsLight : mergedColorsDark)[colorName]
84
- );
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);
85
87
  },
86
88
  [mergedColorsDark, mergedColorsLight]
87
89
  );
@@ -89,25 +91,32 @@ var McuProvider = ({
89
91
  () => ({
90
92
  initials,
91
93
  setMcuConfig,
92
- getMcuColor
94
+ getMcuColor,
95
+ allPalettes
93
96
  }),
94
- [getMcuColor, initials]
97
+ [getMcuColor, initials, allPalettes]
95
98
  );
96
99
  return /* @__PURE__ */ jsx(Provider, { value, children });
97
100
  };
98
101
 
99
102
  // src/Mcu.tsx
100
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
101
- function adjustToneForContrast(baseTone, contrastLevel, isDark, adjustmentFactor = DEFAULT_CONTRAST_ADJUSTMENT_FACTOR) {
103
+ import { jsx as jsx2 } from "react/jsx-runtime";
104
+ function adjustToneForContrast(baseTone, contrastLevel, adjustmentFactor = DEFAULT_CONTRAST_ADJUSTMENT_FACTOR) {
102
105
  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
- }
106
+ const distanceToCenter = baseTone - 50;
107
+ const delta = distanceToCenter * contrastLevel * adjustmentFactor;
108
+ const adjustedTone = baseTone + delta;
109
109
  return Math.max(0, Math.min(100, adjustedTone));
110
110
  }
111
+ var schemeNames = [
112
+ "tonalSpot",
113
+ "monochrome",
114
+ "neutral",
115
+ "vibrant",
116
+ "expressive",
117
+ "fidelity",
118
+ "content"
119
+ ];
111
120
  var schemesMap = {
112
121
  tonalSpot: SchemeTonalSpot,
113
122
  monochrome: SchemeMonochrome,
@@ -117,14 +126,12 @@ var schemesMap = {
117
126
  fidelity: SchemeFidelity,
118
127
  content: SchemeContent
119
128
  };
120
- var schemeNames = Object.keys(
121
- schemesMap
122
- );
123
129
  var DEFAULT_SCHEME = "tonalSpot";
124
130
  var DEFAULT_CONTRAST = 0;
125
131
  var DEFAULT_COLOR_MATCH = false;
126
132
  var DEFAULT_CUSTOM_COLORS = [];
127
133
  var DEFAULT_CONTRAST_ALL_COLORS = false;
134
+ var DEFAULT_ADAPTIVE_SHADES = false;
128
135
  var DEFAULT_BLEND = true;
129
136
  var DEFAULT_CONTRAST_ADJUSTMENT_FACTOR = 0.2;
130
137
  var STANDARD_TONES = [
@@ -181,6 +188,7 @@ function Mcu({
181
188
  colorMatch = DEFAULT_COLOR_MATCH,
182
189
  customColors = DEFAULT_CUSTOM_COLORS,
183
190
  contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS,
191
+ adaptiveShades = DEFAULT_ADAPTIVE_SHADES,
184
192
  children
185
193
  }) {
186
194
  const config = useMemo2(
@@ -197,7 +205,8 @@ function Mcu({
197
205
  colorMatch,
198
206
  customColors,
199
207
  // extras features
200
- contrastAllColors
208
+ contrastAllColors,
209
+ adaptiveShades
201
210
  }),
202
211
  [
203
212
  contrast,
@@ -211,14 +220,11 @@ function Mcu({
211
220
  neutralVariant,
212
221
  error,
213
222
  colorMatch,
214
- contrastAllColors
223
+ contrastAllColors,
224
+ adaptiveShades
215
225
  ]
216
226
  );
217
- const { css } = useMemo2(() => generateCss(config), [config]);
218
- return /* @__PURE__ */ jsxs(Fragment, { children: [
219
- /* @__PURE__ */ jsx2("style", { id: mcuStyleId, children: css }),
220
- /* @__PURE__ */ jsx2(McuProvider, { ...config, styleId: mcuStyleId, children })
221
- ] });
227
+ return /* @__PURE__ */ jsx2(McuProvider, { ...config, styleId: mcuStyleId, children });
222
228
  }
223
229
  var tokenNames = [
224
230
  "background",
@@ -232,13 +238,14 @@ var tokenNames = [
232
238
  "surfaceContainerHigh",
233
239
  "surfaceContainerHighest",
234
240
  "onSurface",
241
+ "surfaceVariant",
235
242
  "onSurfaceVariant",
236
243
  "outline",
237
244
  "outlineVariant",
238
245
  "inverseSurface",
239
246
  "inverseOnSurface",
240
247
  "primary",
241
- // "primaryDim",
248
+ "surfaceTint",
242
249
  "onPrimary",
243
250
  "primaryContainer",
244
251
  "onPrimaryContainer",
@@ -247,12 +254,7 @@ var tokenNames = [
247
254
  "onPrimaryFixed",
248
255
  "onPrimaryFixedVariant",
249
256
  "inversePrimary",
250
- "primaryFixed",
251
- "primaryFixedDim",
252
- "onPrimaryFixed",
253
- "onPrimaryFixedVariant",
254
257
  "secondary",
255
- // "secondaryDim",
256
258
  "onSecondary",
257
259
  "secondaryContainer",
258
260
  "onSecondaryContainer",
@@ -261,7 +263,6 @@ var tokenNames = [
261
263
  "onSecondaryFixed",
262
264
  "onSecondaryFixedVariant",
263
265
  "tertiary",
264
- // "tertiaryDim",
265
266
  "onTertiary",
266
267
  "tertiaryContainer",
267
268
  "onTertiaryContainer",
@@ -270,24 +271,14 @@ var tokenNames = [
270
271
  "onTertiaryFixed",
271
272
  "onTertiaryFixedVariant",
272
273
  "error",
273
- // "errorDim",
274
274
  "onError",
275
275
  "errorContainer",
276
276
  "onErrorContainer",
277
277
  "scrim",
278
- // added manually, was missing
279
278
  "shadow"
280
- // added manually, was missing
281
279
  ];
282
280
  function toRecord(arr, getEntry) {
283
- return arr.reduce(
284
- (acc, item) => {
285
- const [key, value] = getEntry(item);
286
- acc[key] = value;
287
- return acc;
288
- },
289
- {}
290
- );
281
+ return Object.fromEntries(arr.map(getEntry));
291
282
  }
292
283
  function getPalette(palettes, colorName) {
293
284
  const palette = palettes[colorName];
@@ -310,7 +301,7 @@ function mergeBaseAndCustomColors(scheme, customColors, colorPalettes, contrastA
310
301
  const getPaletteForColor = (s) => getPalette(colorPalettes, colorname);
311
302
  const getTone = (baseTone) => (s) => {
312
303
  if (!contrastAllColors) return baseTone;
313
- return adjustToneForContrast(baseTone, s.contrastLevel, s.isDark);
304
+ return adjustToneForContrast(baseTone, s.contrastLevel);
314
305
  };
315
306
  const colorDynamicColor = new DynamicColor(
316
307
  colorname,
@@ -349,25 +340,6 @@ function mergeBaseAndCustomColors(scheme, customColors, colorPalettes, contrastA
349
340
  });
350
341
  return { ...baseVars, ...customVars };
351
342
  }
352
- var cssVar = (colorName, colorValue) => {
353
- const name = `--mcu-${kebabCase(colorName)}`;
354
- const value = hexFromArgb2(colorValue);
355
- return `${name}:${value};`;
356
- };
357
- var generateTonalPaletteVars = (paletteName, palette, scheme, applyContrast = false) => {
358
- return STANDARD_TONES.map((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);
368
- return cssVar(`${paletteName}-${tone}`, color);
369
- }).join(" ");
370
- };
371
343
  function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonization) {
372
344
  const colorArgb = argbFromHex(colorDef.hex);
373
345
  const harmonizedArgb = colorDef.blend ? Blend.harmonize(colorArgb, effectiveSourceForHarmonization) : colorArgb;
@@ -386,11 +358,7 @@ function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonizatio
386
358
  }
387
359
  return TonalPalette.fromHueAndChroma(hct.hue, targetChroma);
388
360
  }
389
- var toCssVars = (mergedColors) => {
390
- return Object.entries(mergedColors).map(([name, value]) => cssVar(name, value)).join(" ");
391
- };
392
- function generateCss({
393
- source: hexSource,
361
+ function builder(hexSource, {
394
362
  scheme = DEFAULT_SCHEME,
395
363
  contrast = DEFAULT_CONTRAST,
396
364
  primary,
@@ -399,11 +367,12 @@ function generateCss({
399
367
  neutral,
400
368
  neutralVariant,
401
369
  error,
402
- colorMatch = DEFAULT_COLOR_MATCH,
403
370
  customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
404
- contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS
405
- }) {
371
+ contrastAllColors = DEFAULT_CONTRAST_ALL_COLORS,
372
+ adaptiveShades = DEFAULT_ADAPTIVE_SHADES
373
+ } = {}) {
406
374
  const sourceArgb = argbFromHex(hexSource);
375
+ const sourceHct = Hct.fromInt(sourceArgb);
407
376
  const effectiveSource = primary || hexSource;
408
377
  const effectiveSourceArgb = argbFromHex(effectiveSource);
409
378
  const effectiveSourceForHarmonization = primary ? argbFromHex(primary) : sourceArgb;
@@ -462,12 +431,8 @@ function generateCss({
462
431
  createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonization)
463
432
  ])
464
433
  );
465
- const createSchemes = (baseConfig) => [
466
- new DynamicScheme({ ...baseConfig, isDark: false }),
467
- new DynamicScheme({ ...baseConfig, isDark: true })
468
- ];
469
434
  const variant = schemeToVariant[scheme];
470
- const [lightScheme, darkScheme] = createSchemes({
435
+ const schemeConfig = {
471
436
  sourceColorArgb: effectiveSourceArgb,
472
437
  variant,
473
438
  contrastLevel: contrast,
@@ -476,12 +441,26 @@ function generateCss({
476
441
  tertiaryPalette: colorPalettes["tertiary"] || baseScheme.tertiaryPalette,
477
442
  neutralPalette: colorPalettes["neutral"] || baseScheme.neutralPalette,
478
443
  neutralVariantPalette: colorPalettes["neutralVariant"] || baseScheme.neutralVariantPalette
479
- });
444
+ };
445
+ const lightScheme = new DynamicScheme({ ...schemeConfig, isDark: false });
446
+ const darkScheme = new DynamicScheme({ ...schemeConfig, isDark: true });
480
447
  const errorPalette = colorPalettes["error"];
481
448
  if (errorPalette) {
482
449
  lightScheme.errorPalette = errorPalette;
483
450
  darkScheme.errorPalette = errorPalette;
484
451
  }
452
+ const allPalettes = {
453
+ primary: lightScheme.primaryPalette,
454
+ secondary: lightScheme.secondaryPalette,
455
+ tertiary: lightScheme.tertiaryPalette,
456
+ error: lightScheme.errorPalette,
457
+ neutral: lightScheme.neutralPalette,
458
+ "neutral-variant": lightScheme.neutralVariantPalette,
459
+ // Add custom color palettes
460
+ ...Object.fromEntries(
461
+ definedColors.filter((c) => !c.core).map((colorDef) => [colorDef.name, colorPalettes[colorDef.name]])
462
+ )
463
+ };
485
464
  const customColors = definedColors.filter((c) => !c.core).map((c) => ({
486
465
  name: c.name,
487
466
  blend: c.blend ?? DEFAULT_BLEND,
@@ -499,67 +478,324 @@ function generateCss({
499
478
  colorPalettes,
500
479
  contrastAllColors
501
480
  );
502
- const lightVars = toCssVars(mergedColorsLight);
503
- const darkVars = toCssVars(mergedColorsDark);
504
- const allTonalVars = [
505
- // Core colors from the scheme
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
- ),
536
- generateTonalPaletteVars(
537
- "neutral-variant",
538
- lightScheme.neutralVariantPalette,
539
- lightScheme,
540
- contrastAllColors
541
- ),
542
- // Custom colors from our unified palette map
543
- ...customColors.map((customColorObj) => {
544
- const palette = getPalette(colorPalettes, customColorObj.name);
545
- return generateTonalPaletteVars(
546
- kebabCase(customColorObj.name),
547
- palette,
548
- lightScheme,
549
- contrastAllColors
550
- );
551
- })
552
- ].join(" ");
553
481
  return {
554
- css: `
555
- :root { ${lightVars} ${allTonalVars} }
556
- .dark { ${darkVars} }
557
- `,
482
+ //
483
+ // ██████ ███████ ███████
484
+ // ██ ██ ██
485
+ // ██ ███████ ███████
486
+ // ██ ██ ██
487
+ // ██████ ███████ ███████
488
+ //
489
+ toCss() {
490
+ function cssVar(colorName, colorValue) {
491
+ const name = `--mcu-${kebabCase(colorName)}`;
492
+ const value = hexFromArgb2(colorValue);
493
+ return `${name}:${value};`;
494
+ }
495
+ function toCssVars(mergedColors) {
496
+ return Object.entries(mergedColors).map(([name, value]) => cssVar(name, value)).join(" ");
497
+ }
498
+ function generateTonalPaletteVars(paletteName, palette, scheme2, applyContrast, adaptiveShades2) {
499
+ return STANDARD_TONES.map((tone) => {
500
+ let toneToUse = tone;
501
+ if (adaptiveShades2 && scheme2.isDark) {
502
+ toneToUse = 100 - tone;
503
+ }
504
+ if (applyContrast) {
505
+ toneToUse = adjustToneForContrast(toneToUse, scheme2.contrastLevel);
506
+ }
507
+ const color = palette.tone(toneToUse);
508
+ return cssVar(`${paletteName}-${tone}`, color);
509
+ }).join(" ");
510
+ }
511
+ function generateTonalVars(s) {
512
+ return Object.entries(allPalettes).map(
513
+ ([name, palette]) => generateTonalPaletteVars(
514
+ kebabCase(name),
515
+ palette,
516
+ s,
517
+ contrastAllColors,
518
+ adaptiveShades
519
+ )
520
+ ).join(" ");
521
+ }
522
+ const lightVars = toCssVars(mergedColorsLight);
523
+ const darkVars = toCssVars(mergedColorsDark);
524
+ const lightTonalVars = generateTonalVars(lightScheme);
525
+ const darkTonalVars = generateTonalVars(darkScheme);
526
+ return `
527
+ :root { ${lightVars} ${lightTonalVars} }
528
+ .dark { ${darkVars} ${adaptiveShades ? darkTonalVars : lightTonalVars} }
529
+ `;
530
+ },
531
+ //
532
+ // ██ ███████ ██████ ███ ██
533
+ // ██ ██ ██ ██ ████ ██
534
+ // ██ ███████ ██ ██ ██ ██ ██
535
+ // ██ ██ ██ ██ ██ ██ ██ ██
536
+ // █████ ███████ ██████ ██ ████
537
+ //
538
+ toJson() {
539
+ const fixtureTokenOrder = [
540
+ "primary",
541
+ "surfaceTint",
542
+ "onPrimary",
543
+ "primaryContainer",
544
+ "onPrimaryContainer",
545
+ "secondary",
546
+ "onSecondary",
547
+ "secondaryContainer",
548
+ "onSecondaryContainer",
549
+ "tertiary",
550
+ "onTertiary",
551
+ "tertiaryContainer",
552
+ "onTertiaryContainer",
553
+ "error",
554
+ "onError",
555
+ "errorContainer",
556
+ "onErrorContainer",
557
+ "background",
558
+ "onBackground",
559
+ "surface",
560
+ "onSurface",
561
+ "surfaceVariant",
562
+ "onSurfaceVariant",
563
+ "outline",
564
+ "outlineVariant",
565
+ "shadow",
566
+ "scrim",
567
+ "inverseSurface",
568
+ "inverseOnSurface",
569
+ "inversePrimary",
570
+ "primaryFixed",
571
+ "onPrimaryFixed",
572
+ "primaryFixedDim",
573
+ "onPrimaryFixedVariant",
574
+ "secondaryFixed",
575
+ "onSecondaryFixed",
576
+ "secondaryFixedDim",
577
+ "onSecondaryFixedVariant",
578
+ "tertiaryFixed",
579
+ "onTertiaryFixed",
580
+ "tertiaryFixedDim",
581
+ "onTertiaryFixedVariant",
582
+ "surfaceDim",
583
+ "surfaceBright",
584
+ "surfaceContainerLowest",
585
+ "surfaceContainerLow",
586
+ "surfaceContainer",
587
+ "surfaceContainerHigh",
588
+ "surfaceContainerHighest"
589
+ ];
590
+ const neuHct = neutral ? Hct.fromInt(argbFromHex(neutral)) : sourceHct;
591
+ const nvHct = neutralVariant ? Hct.fromInt(argbFromHex(neutralVariant)) : sourceHct;
592
+ const rawPalettes = {
593
+ primary: TonalPalette.fromInt(effectiveSourceArgb),
594
+ secondary: secondary ? TonalPalette.fromInt(argbFromHex(secondary)) : TonalPalette.fromHueAndChroma(sourceHct.hue, sourceHct.chroma / 3),
595
+ tertiary: tertiary ? TonalPalette.fromInt(argbFromHex(tertiary)) : TonalPalette.fromHueAndChroma(
596
+ (sourceHct.hue + 60) % 360,
597
+ sourceHct.chroma / 2
598
+ ),
599
+ neutral: TonalPalette.fromHueAndChroma(
600
+ neuHct.hue,
601
+ Math.min(neuHct.chroma / 12, 4)
602
+ ),
603
+ "neutral-variant": TonalPalette.fromHueAndChroma(
604
+ nvHct.hue,
605
+ Math.min(nvHct.chroma / 6, 8)
606
+ )
607
+ };
608
+ function buildJsonSchemes() {
609
+ function extractSchemeColors(scheme2, backgroundScheme) {
610
+ const colors = {};
611
+ for (const tokenName of fixtureTokenOrder) {
612
+ const dynamicColor = MaterialDynamicColors[tokenName];
613
+ const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
614
+ colors[tokenName] = hexFromArgb2(
615
+ dynamicColor.getArgb(useScheme)
616
+ ).toUpperCase();
617
+ }
618
+ return colors;
619
+ }
620
+ function resolveOverridePalette(hex, role) {
621
+ if (!hex) return null;
622
+ return new SchemeClass(Hct.fromInt(argbFromHex(hex)), false, 0)[role];
623
+ }
624
+ const secPalette = resolveOverridePalette(secondary, "primaryPalette");
625
+ const terPalette = resolveOverridePalette(tertiary, "primaryPalette");
626
+ const errPalette = resolveOverridePalette(error, "primaryPalette");
627
+ const neuPalette = resolveOverridePalette(neutral, "neutralPalette");
628
+ const nvPalette = resolveOverridePalette(
629
+ neutralVariant,
630
+ "neutralVariantPalette"
631
+ );
632
+ const jsonSchemes = {};
633
+ const jsonContrastLevels = [
634
+ { name: "light", isDark: false, contrast: 0 },
635
+ { name: "light-medium-contrast", isDark: false, contrast: 0.5 },
636
+ { name: "light-high-contrast", isDark: false, contrast: 1 },
637
+ { name: "dark", isDark: true, contrast: 0 },
638
+ { name: "dark-medium-contrast", isDark: true, contrast: 0.5 },
639
+ { name: "dark-high-contrast", isDark: true, contrast: 1 }
640
+ ];
641
+ for (const { name, isDark, contrast: contrast2 } of jsonContrastLevels) {
642
+ const baseScheme2 = new SchemeClass(primaryHct, isDark, contrast2);
643
+ const composedScheme = new DynamicScheme({
644
+ sourceColorArgb: effectiveSourceArgb,
645
+ variant: schemeToVariant[scheme],
646
+ contrastLevel: contrast2,
647
+ isDark,
648
+ primaryPalette: baseScheme2.primaryPalette,
649
+ secondaryPalette: secPalette || baseScheme2.secondaryPalette,
650
+ tertiaryPalette: terPalette || baseScheme2.tertiaryPalette,
651
+ neutralPalette: neuPalette || baseScheme2.neutralPalette,
652
+ neutralVariantPalette: nvPalette || baseScheme2.neutralVariantPalette
653
+ });
654
+ if (errPalette) composedScheme.errorPalette = errPalette;
655
+ jsonSchemes[name] = extractSchemeColors(composedScheme, baseScheme2);
656
+ }
657
+ return jsonSchemes;
658
+ }
659
+ function rawPalettesToJson() {
660
+ const jsonPalettes = {};
661
+ const RAW_PALETTE_NAMES = [
662
+ "primary",
663
+ "secondary",
664
+ "tertiary",
665
+ "neutral",
666
+ "neutral-variant"
667
+ ];
668
+ for (const name of RAW_PALETTE_NAMES) {
669
+ const palette = rawPalettes[name];
670
+ const tones = {};
671
+ for (const tone of STANDARD_TONES) {
672
+ tones[tone.toString()] = hexFromArgb2(
673
+ palette.tone(tone)
674
+ ).toUpperCase();
675
+ }
676
+ jsonPalettes[name] = tones;
677
+ }
678
+ return jsonPalettes;
679
+ }
680
+ function buildCoreColors(opts) {
681
+ const colors = { primary: opts.primary };
682
+ if (opts.secondary) colors.secondary = opts.secondary.toUpperCase();
683
+ if (opts.tertiary) colors.tertiary = opts.tertiary.toUpperCase();
684
+ if (opts.error) colors.error = opts.error.toUpperCase();
685
+ if (opts.neutral) colors.neutral = opts.neutral.toUpperCase();
686
+ if (opts.neutralVariant)
687
+ colors.neutralVariant = opts.neutralVariant.toUpperCase();
688
+ return colors;
689
+ }
690
+ const seed = hexSource.toUpperCase();
691
+ const coreColors = buildCoreColors({
692
+ primary: (primary || hexSource).toUpperCase(),
693
+ secondary,
694
+ tertiary,
695
+ error,
696
+ neutral,
697
+ neutralVariant
698
+ });
699
+ const extendedColors = hexCustomColors.map((c) => ({
700
+ name: c.name,
701
+ color: c.hex.toUpperCase(),
702
+ description: "",
703
+ harmonized: c.blend ?? DEFAULT_BLEND
704
+ }));
705
+ return {
706
+ seed,
707
+ coreColors,
708
+ extendedColors,
709
+ schemes: buildJsonSchemes(),
710
+ palettes: rawPalettesToJson()
711
+ };
712
+ },
713
+ //
714
+ // ███████ ██ ██████ ███ ███ █████
715
+ // ██ ██ ██ ████ ████ ██ ██
716
+ // █████ ██ ██ ███ ██ ████ ██ ███████
717
+ // ██ ██ ██ ██ ██ ██ ██ ██ ██
718
+ // ██ ██ ██████ ██ ██ ██ ██
719
+ //
720
+ toFigmaTokens() {
721
+ function argbToFigmaColorValue(argb) {
722
+ return {
723
+ colorSpace: "srgb",
724
+ components: [
725
+ redFromArgb(argb) / 255,
726
+ greenFromArgb(argb) / 255,
727
+ blueFromArgb(argb) / 255
728
+ ],
729
+ alpha: 1,
730
+ hex: hexFromArgb2(argb).toUpperCase()
731
+ };
732
+ }
733
+ function figmaToken(argb) {
734
+ return {
735
+ $type: "color",
736
+ $value: argbToFigmaColorValue(argb),
737
+ $extensions: {
738
+ "com.figma.scopes": ["ALL_SCOPES"],
739
+ "com.figma.isOverride": true
740
+ }
741
+ };
742
+ }
743
+ function buildFigmaSchemeTokens(mergedColors) {
744
+ const tokens = {};
745
+ for (const [name, argb] of Object.entries(mergedColors)) {
746
+ tokens[startCase(name)] = figmaToken(argb);
747
+ }
748
+ return tokens;
749
+ }
750
+ function buildFigmaPaletteTokens(isDark) {
751
+ const palettes = {};
752
+ for (const [name, palette] of Object.entries(allPalettes)) {
753
+ const tones = {};
754
+ for (const tone of STANDARD_TONES) {
755
+ let toneToUse = tone;
756
+ if (adaptiveShades && isDark) {
757
+ toneToUse = 100 - tone;
758
+ }
759
+ if (contrastAllColors) {
760
+ toneToUse = adjustToneForContrast(toneToUse, contrast);
761
+ }
762
+ const argb = palette.tone(toneToUse);
763
+ tones[tone.toString()] = figmaToken(argb);
764
+ }
765
+ palettes[startCase(name)] = tones;
766
+ }
767
+ return palettes;
768
+ }
769
+ function buildModeFile(modeName, mergedColors, isDark) {
770
+ return {
771
+ Schemes: buildFigmaSchemeTokens(mergedColors),
772
+ Palettes: buildFigmaPaletteTokens(isDark),
773
+ $extensions: {
774
+ "com.figma.modeName": modeName
775
+ }
776
+ };
777
+ }
778
+ return {
779
+ "Light.tokens.json": buildModeFile("Light", mergedColorsLight, false),
780
+ "Dark.tokens.json": buildModeFile("Dark", mergedColorsDark, true)
781
+ };
782
+ },
783
+ //
784
+ // API
785
+ //
558
786
  mergedColorsLight,
559
- mergedColorsDark
787
+ mergedColorsDark,
788
+ allPalettes
560
789
  };
561
790
  }
562
791
  export {
792
+ DEFAULT_ADAPTIVE_SHADES,
793
+ DEFAULT_BLEND,
794
+ DEFAULT_CONTRAST,
795
+ DEFAULT_CONTRAST_ALL_COLORS,
796
+ DEFAULT_SCHEME,
563
797
  Mcu,
798
+ builder,
799
+ schemeNames,
564
800
  useMcu
565
801
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mcu",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A React component library",
5
5
  "keywords": [
6
6
  "react",
@@ -28,32 +28,50 @@
28
28
  "dist",
29
29
  "src/tailwind.css"
30
30
  ],
31
+ "bin": {
32
+ "react-mcu": "./src/cli.ts"
33
+ },
31
34
  "type": "module",
32
35
  "devDependencies": {
33
36
  "@arethetypeswrong/cli": "^0.18.2",
34
37
  "@changesets/cli": "^2.27.7",
38
+ "@chromatic-com/storybook": "^5.0.0",
39
+ "@eslint/js": "^10.0.1",
35
40
  "@storybook/addon-docs": "^10.1.11",
36
41
  "@storybook/addon-themes": "^10.1.11",
37
42
  "@storybook/react-vite": "^10.1.11",
38
43
  "@tailwindcss/postcss": "^4.1.18",
39
44
  "@testing-library/dom": "^10.4.1",
40
45
  "@testing-library/react": "^16.3.1",
46
+ "@types/culori": "^4.0.1",
41
47
  "@types/lodash-es": "^4.17.12",
42
48
  "@types/react": "^19.2.7",
43
49
  "@types/react-dom": "^19.2.3",
44
50
  "@vitejs/plugin-react": "^5.1.2",
51
+ "ajv": "^8.18.0",
52
+ "ajv-formats": "^3.0.1",
45
53
  "chromatic": "^13.3.5",
54
+ "class-variance-authority": "^0.7.1",
55
+ "clsx": "^2.1.1",
56
+ "culori": "^4.0.2",
57
+ "eslint": "^10.0.0",
58
+ "eslint-plugin-react-hooks": "^7.0.1",
59
+ "eslint-plugin-sonarjs": "^3.0.7",
60
+ "globals": "^17.3.0",
46
61
  "husky": "^9.1.7",
47
62
  "jsdom": "^27.4.0",
48
63
  "lint-staged": "^16.2.7",
49
64
  "postcss": "^8.5.6",
50
65
  "prettier": "^3.3.3",
66
+ "prettier-plugin-organize-imports": "^4.3.0",
51
67
  "react": "^19.2.3",
52
68
  "react-dom": "^19.2.3",
53
69
  "storybook": "^10.1.11",
70
+ "tailwind-merge": "^3.4.0",
54
71
  "tailwindcss": "^4.1.18",
55
72
  "tsup": "^8.2.4",
56
73
  "typescript": "^5.5.4",
74
+ "typescript-eslint": "^8.55.0",
57
75
  "vitest": "^4.0.16"
58
76
  },
59
77
  "peerDependencies": {
@@ -62,13 +80,17 @@
62
80
  },
63
81
  "dependencies": {
64
82
  "@material/material-color-utilities": "^0.3.0",
65
- "lodash-es": "^4.17.22"
83
+ "commander": "^14.0.3",
84
+ "lodash-es": "^4.17.22",
85
+ "tsx": "^4.21.0"
66
86
  },
67
87
  "scripts": {
68
88
  "build": "tsup",
69
- "lgtm": "pnpm run build && pnpm run check-format && pnpm run check-exports && pnpm run typecheck && pnpm run test",
89
+ "lint": "eslint .",
90
+ "lgtm": "pnpm run build && pnpm run lint && pnpm run check-format && pnpm run check-exports && pnpm run typecheck && pnpm run test",
70
91
  "typecheck": "tsc",
71
92
  "test": "vitest run",
93
+ "pretest": "bash scripts/download-dtcg-schemas.sh",
72
94
  "format": "prettier --write .",
73
95
  "check-format": "prettier --check .",
74
96
  "check-exports": "attw --pack . --ignore-rules cjs-resolves-to-esm no-resolution",
package/src/cli.ts ADDED
@@ -0,0 +1,116 @@
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();