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 +39 -0
- package/dist/index.d.ts +110 -17
- package/dist/index.js +386 -150
- package/package.json +25 -3
- package/src/cli.ts +116 -0
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 {
|
|
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
|
|
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
|
|
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", "
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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 {
|
|
101
|
-
function adjustToneForContrast(baseTone, contrastLevel,
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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();
|