material-theme-builder 3.0.0 → 3.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 +16 -17
- package/dist/cli.js +100 -43
- package/dist/index.d.ts +38 -4
- package/dist/index.js +66 -23
- package/dist/react.d.ts +34 -9
- package/dist/react.js +99 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,15 +50,6 @@ theme.toFlutter();
|
|
|
50
50
|
theme.toShadcn();
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
> [!NOTE]
|
|
54
|
-
>
|
|
55
|
-
> The root entry costs a client bundle nothing. It holds `builder` alone — the
|
|
56
|
-
> React bindings live at [`material-theme-builder/react`](#react) and are the
|
|
57
|
-
> only thing carrying `"use client"`. So you can call `builder` from a
|
|
58
|
-
> [React Server Component](https://react.dev/reference/rsc/server-components)
|
|
59
|
-
> and emit `toCss()` into the document yourself, rather than let `<Mcu>` do it
|
|
60
|
-
> on the client, without shipping React components the page never renders.
|
|
61
|
-
|
|
62
53
|
## CLI
|
|
63
54
|
|
|
64
55
|
```sh
|
|
@@ -76,9 +67,9 @@ The React bindings live on their own entry point, `material-theme-builder/react`
|
|
|
76
67
|
CSS variables are injected into the page:
|
|
77
68
|
|
|
78
69
|
```tsx
|
|
79
|
-
import {
|
|
70
|
+
import { Mtb } from "material-theme-builder/react";
|
|
80
71
|
|
|
81
|
-
<
|
|
72
|
+
<Mtb
|
|
82
73
|
source="#0e1216"
|
|
83
74
|
scheme="vibrant"
|
|
84
75
|
contrast={0.5}
|
|
@@ -97,7 +88,7 @@ import { Mcu } from "material-theme-builder/react";
|
|
|
97
88
|
color: "var(--md-sys-color-on-my-custom-color-1)",
|
|
98
89
|
}}>colors<span>!
|
|
99
90
|
</p>
|
|
100
|
-
</
|
|
91
|
+
</Mtb>
|
|
101
92
|
```
|
|
102
93
|
|
|
103
94
|
> [!TIP]
|
|
@@ -110,18 +101,26 @@ import { Mcu } from "material-theme-builder/react";
|
|
|
110
101
|
> CSS varnames are always kebab-cased, e.g. `myCustomColor1` →
|
|
111
102
|
> `--md-sys-color-my-custom-color-1` / `--md-ref-palette-my-custom-color-1-<tone>`
|
|
112
103
|
|
|
113
|
-
|
|
104
|
+
> [!NOTE]
|
|
105
|
+
>
|
|
106
|
+
> `<Mtb>` injects the CSS from the client, and is the only thing here carrying
|
|
107
|
+
> `"use client"`. The root entry holds `builder` alone — so from a
|
|
108
|
+
> [React Server Component](https://react.dev/reference/rsc/server-components)
|
|
109
|
+
> you can call it and emit `toCss()` into the document yourself, without
|
|
110
|
+
> shipping components the page never renders.
|
|
111
|
+
|
|
112
|
+
## `useMtb`
|
|
114
113
|
|
|
115
114
|
A hook is also provided:
|
|
116
115
|
|
|
117
116
|
```tsx
|
|
118
|
-
import {
|
|
117
|
+
import { useMtb } from "material-theme-builder/react";
|
|
119
118
|
|
|
120
|
-
const { initials,
|
|
119
|
+
const { initials, setMtbConfig, getMtbColor } = useMtb();
|
|
121
120
|
|
|
122
121
|
return (
|
|
123
|
-
<button onClick={() =>
|
|
124
|
-
Change to {
|
|
122
|
+
<button onClick={() => setMtbConfig({ ...initials, source: "#FF5722" })}>
|
|
123
|
+
Change to {getMtbColor("primary", "light")}
|
|
125
124
|
</button>
|
|
126
125
|
);
|
|
127
126
|
```
|
package/dist/cli.js
CHANGED
|
@@ -594,29 +594,8 @@ function buildJson(ctx) {
|
|
|
594
594
|
};
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
// src/lib/builder.
|
|
598
|
-
import {
|
|
599
|
-
var SHADE_TO_TONE = [
|
|
600
|
-
[50, 95],
|
|
601
|
-
[100, 90],
|
|
602
|
-
[200, 80],
|
|
603
|
-
[300, 70],
|
|
604
|
-
[400, 60],
|
|
605
|
-
[500, 50],
|
|
606
|
-
[600, 40],
|
|
607
|
-
[700, 30],
|
|
608
|
-
[800, 20],
|
|
609
|
-
[900, 10],
|
|
610
|
-
[950, 5]
|
|
611
|
-
];
|
|
612
|
-
var CORE_PALETTES = [
|
|
613
|
-
"primary",
|
|
614
|
-
"secondary",
|
|
615
|
-
"tertiary",
|
|
616
|
-
"error",
|
|
617
|
-
"neutral",
|
|
618
|
-
"neutral-variant"
|
|
619
|
-
];
|
|
597
|
+
// src/lib/builder.shadcn.ts
|
|
598
|
+
import { camelCase } from "lodash-es";
|
|
620
599
|
var SHADCN_MAPPING = [
|
|
621
600
|
["--background", "surface"],
|
|
622
601
|
["--foreground", "on-surface"],
|
|
@@ -650,6 +629,69 @@ var SHADCN_MAPPING = [
|
|
|
650
629
|
["--sidebar-border", "outline-variant"],
|
|
651
630
|
["--sidebar-ring", "primary"]
|
|
652
631
|
];
|
|
632
|
+
function srgbToLinear(channel) {
|
|
633
|
+
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
634
|
+
}
|
|
635
|
+
function round(n) {
|
|
636
|
+
return String(Number(n.toFixed(3)));
|
|
637
|
+
}
|
|
638
|
+
function argbToOklch(argb) {
|
|
639
|
+
const r = srgbToLinear((argb >> 16 & 255) / 255);
|
|
640
|
+
const g = srgbToLinear((argb >> 8 & 255) / 255);
|
|
641
|
+
const b = srgbToLinear((argb & 255) / 255);
|
|
642
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
643
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
644
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
645
|
+
const lightness = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
646
|
+
const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
647
|
+
const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
648
|
+
const chroma = Math.hypot(a, bb);
|
|
649
|
+
const hue = Number(chroma.toFixed(3)) === 0 ? 0 : (Math.atan2(bb, a) * 180 / Math.PI + 360) % 360;
|
|
650
|
+
return `oklch(${round(lightness)} ${round(chroma)} ${round(hue)})`;
|
|
651
|
+
}
|
|
652
|
+
function toShadcnVars(mergedColors) {
|
|
653
|
+
const entries = SHADCN_MAPPING.map(([cssVar, m3Token]) => {
|
|
654
|
+
const argb = mergedColors[camelCase(m3Token)];
|
|
655
|
+
if (argb === void 0) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`M3 token '${m3Token}' is missing from the scheme, needed by '${cssVar}'. This is likely a bug in the implementation.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return [cssVar.slice(2), argbToOklch(argb)];
|
|
661
|
+
});
|
|
662
|
+
return Object.fromEntries(entries);
|
|
663
|
+
}
|
|
664
|
+
function buildShadcn(ctx) {
|
|
665
|
+
const { mergedColorsLight, mergedColorsDark } = ctx;
|
|
666
|
+
return {
|
|
667
|
+
light: toShadcnVars(mergedColorsLight),
|
|
668
|
+
dark: toShadcnVars(mergedColorsDark)
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/lib/builder.tailwind.ts
|
|
673
|
+
import { kebabCase as kebabCase3 } from "lodash-es";
|
|
674
|
+
var SHADE_TO_TONE = [
|
|
675
|
+
[50, 95],
|
|
676
|
+
[100, 90],
|
|
677
|
+
[200, 80],
|
|
678
|
+
[300, 70],
|
|
679
|
+
[400, 60],
|
|
680
|
+
[500, 50],
|
|
681
|
+
[600, 40],
|
|
682
|
+
[700, 30],
|
|
683
|
+
[800, 20],
|
|
684
|
+
[900, 10],
|
|
685
|
+
[950, 5]
|
|
686
|
+
];
|
|
687
|
+
var CORE_PALETTES = [
|
|
688
|
+
"primary",
|
|
689
|
+
"secondary",
|
|
690
|
+
"tertiary",
|
|
691
|
+
"error",
|
|
692
|
+
"neutral",
|
|
693
|
+
"neutral-variant"
|
|
694
|
+
];
|
|
653
695
|
function buildTailwind(ctx, options) {
|
|
654
696
|
const { prefix, mergedColorsLight, hexCustomColors } = ctx;
|
|
655
697
|
const lines = [];
|
|
@@ -1085,6 +1127,7 @@ function builder(hexSource, {
|
|
|
1085
1127
|
toFigmaVariables: () => buildFigmaVariables(ctx),
|
|
1086
1128
|
toFigmaTokens: () => buildFigmaTokens(ctx),
|
|
1087
1129
|
toTailwind: (options) => buildTailwind(ctx, options),
|
|
1130
|
+
toShadcn: () => buildShadcn(ctx),
|
|
1088
1131
|
toFlutter: () => buildFlutter(ctx),
|
|
1089
1132
|
mergedColorsLight,
|
|
1090
1133
|
mergedColorsDark,
|
|
@@ -1100,6 +1143,31 @@ var customColorSchema = z.array(
|
|
|
1100
1143
|
blend: z.boolean().default(DEFAULT_BLEND)
|
|
1101
1144
|
})
|
|
1102
1145
|
);
|
|
1146
|
+
function writeFigmaTokens(theme, outputDir) {
|
|
1147
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1148
|
+
for (const [filename, content] of Object.entries(theme.toFigmaTokens())) {
|
|
1149
|
+
const filePath = path.join(outputDir, filename);
|
|
1150
|
+
fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n");
|
|
1151
|
+
console.error(`wrote ${filePath}`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
function writeOutput(theme, opts) {
|
|
1155
|
+
const json = (value) => JSON.stringify(value, null, 2) + "\n";
|
|
1156
|
+
switch (opts.format) {
|
|
1157
|
+
case "css":
|
|
1158
|
+
return process.stdout.write(theme.toCss());
|
|
1159
|
+
case "tailwind":
|
|
1160
|
+
return process.stdout.write(theme.toTailwind({ shadcn: opts.shadcn }));
|
|
1161
|
+
case "shadcn":
|
|
1162
|
+
return process.stdout.write(json(theme.toShadcn()));
|
|
1163
|
+
case "flutter":
|
|
1164
|
+
return process.stdout.write(theme.toFlutter());
|
|
1165
|
+
case "figma":
|
|
1166
|
+
return writeFigmaTokens(theme, opts.output ?? "material-theme");
|
|
1167
|
+
default:
|
|
1168
|
+
return process.stdout.write(json(theme.toJson()));
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1103
1171
|
var program = new Command();
|
|
1104
1172
|
program.name("material-theme-builder").description("Generate a color theme from a source color").argument("<source>", "Source color in hex format (e.g. #6750A4)").addOption(
|
|
1105
1173
|
new Option("--scheme <name>", "Color scheme variant").choices(schemeNames).default(DEFAULT_SCHEME)
|
|
@@ -1113,16 +1181,22 @@ program.name("material-theme-builder").description("Generate a color theme from
|
|
|
1113
1181
|
`Custom colors as JSON array (e.g. '[{"name":"brand","hex":"#FF5733","blend":true}]')`
|
|
1114
1182
|
).option(
|
|
1115
1183
|
"--format <type>",
|
|
1116
|
-
"Output format: json, css, figma, tailwind, or flutter",
|
|
1184
|
+
"Output format: json, css, figma, tailwind, shadcn, or flutter",
|
|
1117
1185
|
"figma"
|
|
1118
1186
|
).option("--output <dir>", "Output directory (required for figma format)").option(
|
|
1119
1187
|
"--shadcn",
|
|
1120
|
-
"Append shadcn
|
|
1188
|
+
"Append the shadcn var() alias block to --format tailwind (for concrete values, use --format shadcn)"
|
|
1121
1189
|
).option(
|
|
1122
1190
|
"--prefix <string>",
|
|
1123
1191
|
"CSS variable prefix (e.g. md \u2192 --md-sys-color-*, --md-ref-palette-*)",
|
|
1124
1192
|
DEFAULT_PREFIX
|
|
1125
1193
|
).action((source, opts) => {
|
|
1194
|
+
if (opts.shadcn && opts.format !== "tailwind") {
|
|
1195
|
+
console.error(
|
|
1196
|
+
"Error: --shadcn only applies to --format tailwind. For concrete color values, use --format shadcn."
|
|
1197
|
+
);
|
|
1198
|
+
process.exit(1);
|
|
1199
|
+
}
|
|
1126
1200
|
let customColors = [];
|
|
1127
1201
|
if (opts.customColors) {
|
|
1128
1202
|
const result2 = customColorSchema.safeParse(JSON.parse(opts.customColors));
|
|
@@ -1144,23 +1218,6 @@ program.name("material-theme-builder").description("Generate a color theme from
|
|
|
1144
1218
|
customColors,
|
|
1145
1219
|
prefix: opts.prefix
|
|
1146
1220
|
});
|
|
1147
|
-
|
|
1148
|
-
process.stdout.write(result.toCss());
|
|
1149
|
-
} else if (opts.format === "tailwind") {
|
|
1150
|
-
process.stdout.write(result.toTailwind({ shadcn: opts.shadcn }));
|
|
1151
|
-
} else if (opts.format === "flutter") {
|
|
1152
|
-
process.stdout.write(result.toFlutter());
|
|
1153
|
-
} else if (opts.format === "figma") {
|
|
1154
|
-
const outputDir = opts.output ?? "material-theme";
|
|
1155
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
1156
|
-
const files = result.toFigmaTokens();
|
|
1157
|
-
for (const [filename, content] of Object.entries(files)) {
|
|
1158
|
-
const filePath = path.join(outputDir, filename);
|
|
1159
|
-
fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n");
|
|
1160
|
-
console.error(`wrote ${filePath}`);
|
|
1161
|
-
}
|
|
1162
|
-
} else {
|
|
1163
|
-
process.stdout.write(JSON.stringify(result.toJson(), null, 2) + "\n");
|
|
1164
|
-
}
|
|
1221
|
+
writeOutput(result, opts);
|
|
1165
1222
|
});
|
|
1166
1223
|
program.parse();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
import { CustomColor, TonalPalette } from '@material/material-color-utilities';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* shadcn CSS variable → M3 sys-color token mapping.
|
|
5
|
+
*
|
|
6
|
+
* The single source of truth for which M3 token backs which shadcn variable:
|
|
7
|
+
* `toShadcn()` reads it to emit concrete values, `toTailwind({ shadcn: true })`
|
|
8
|
+
* reads it to emit `var()` aliases. Neither can name a variable the other
|
|
9
|
+
* doesn't.
|
|
10
|
+
*
|
|
11
|
+
* Token names are kebab-case (the CSS spelling); the camelCase spelling the
|
|
12
|
+
* scheme objects use is derived from them here, in one place.
|
|
13
|
+
*
|
|
14
|
+
* @see https://ui.shadcn.com/docs/theming#list-of-variables
|
|
15
|
+
*/
|
|
16
|
+
declare const SHADCN_MAPPING: readonly [readonly ["--background", "surface"], readonly ["--foreground", "on-surface"], readonly ["--card", "surface-container-low"], readonly ["--card-foreground", "on-surface"], readonly ["--popover", "surface-container-high"], readonly ["--popover-foreground", "on-surface"], readonly ["--primary", "primary"], readonly ["--primary-foreground", "on-primary"], readonly ["--secondary", "secondary-container"], readonly ["--secondary-foreground", "on-secondary-container"], readonly ["--muted", "surface-container-highest"], readonly ["--muted-foreground", "on-surface-variant"], readonly ["--accent", "secondary-container"], readonly ["--accent-foreground", "on-secondary-container"], readonly ["--destructive", "error"], readonly ["--border", "outline-variant"], readonly ["--input", "outline"], readonly ["--ring", "primary"], readonly ["--chart-1", "primary-fixed"], readonly ["--chart-2", "secondary-fixed"], readonly ["--chart-3", "tertiary-fixed"], readonly ["--chart-4", "primary-fixed-dim"], readonly ["--chart-5", "secondary-fixed-dim"], readonly ["--sidebar", "surface-container-low"], readonly ["--sidebar-foreground", "on-surface"], readonly ["--sidebar-primary", "primary"], readonly ["--sidebar-primary-foreground", "on-primary"], readonly ["--sidebar-accent", "secondary-container"], readonly ["--sidebar-accent-foreground", "on-secondary-container"], readonly ["--sidebar-border", "outline-variant"], readonly ["--sidebar-ring", "primary"]];
|
|
17
|
+
type StripDashes<T extends string> = T extends `--${infer Bare}` ? Bare : never;
|
|
18
|
+
/** A bare shadcn color variable name, e.g. `primary` or `chart-1`. */
|
|
19
|
+
type ShadcnVarName = StripDashes<(typeof SHADCN_MAPPING)[number][0]>;
|
|
20
|
+
/**
|
|
21
|
+
* Concrete shadcn colors, split by mode.
|
|
22
|
+
*
|
|
23
|
+
* Shaped exactly like a shadcn registry item's `cssVars` field, so it can be
|
|
24
|
+
* assigned there without rewriting keys or values.
|
|
25
|
+
*/
|
|
26
|
+
type ShadcnTheme = {
|
|
27
|
+
light: Record<ShadcnVarName, string>;
|
|
28
|
+
dark: Record<ShadcnVarName, string>;
|
|
29
|
+
};
|
|
30
|
+
|
|
3
31
|
/** DTCG color value (direct, non-alias) */
|
|
4
32
|
type DtcgColorValue = {
|
|
5
33
|
colorSpace: string;
|
|
@@ -50,8 +78,8 @@ type HexCustomColor = Omit<CustomColor, "value"> & {
|
|
|
50
78
|
/** Available Material You color scheme variants. */
|
|
51
79
|
declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
|
|
52
80
|
type SchemeName = (typeof schemeNames)[number];
|
|
53
|
-
/** Configuration for the Material
|
|
54
|
-
type
|
|
81
|
+
/** Configuration for the Material Theme Builder. */
|
|
82
|
+
type MtbConfig = {
|
|
55
83
|
/** Source color in hex format (e.g., "#6750A4") used to generate the color scheme */
|
|
56
84
|
source: string;
|
|
57
85
|
/** Color scheme variant. Default: "tonalSpot" */
|
|
@@ -91,6 +119,11 @@ type McuConfig = {
|
|
|
91
119
|
*/
|
|
92
120
|
prefix?: string;
|
|
93
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated Renamed `MtbConfig` — same shape. This alias will be removed in
|
|
124
|
+
* the next major.
|
|
125
|
+
*/
|
|
126
|
+
type McuConfig = MtbConfig;
|
|
94
127
|
/**
|
|
95
128
|
* Build a Material You color theme from a hex source color.
|
|
96
129
|
*
|
|
@@ -104,7 +137,7 @@ type McuConfig = {
|
|
|
104
137
|
* const json = theme.toJson();
|
|
105
138
|
* ```
|
|
106
139
|
*/
|
|
107
|
-
declare function builder(hexSource:
|
|
140
|
+
declare function builder(hexSource: MtbConfig["source"], { scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, customColors: hexCustomColors, prefix, }?: Omit<MtbConfig, "source">): {
|
|
108
141
|
toCss: () => string;
|
|
109
142
|
toJson: () => {
|
|
110
143
|
seed: string;
|
|
@@ -144,6 +177,7 @@ declare function builder(hexSource: McuConfig["source"], { scheme, contrast, pri
|
|
|
144
177
|
};
|
|
145
178
|
};
|
|
146
179
|
toTailwind: (options?: TailwindOptions) => string;
|
|
180
|
+
toShadcn: () => ShadcnTheme;
|
|
147
181
|
toFlutter: () => string;
|
|
148
182
|
mergedColorsLight: {
|
|
149
183
|
[x: string]: number;
|
|
@@ -161,4 +195,4 @@ declare function builder(hexSource: McuConfig["source"], { scheme, contrast, pri
|
|
|
161
195
|
};
|
|
162
196
|
};
|
|
163
197
|
|
|
164
|
-
export { type McuConfig, builder };
|
|
198
|
+
export { type McuConfig, type MtbConfig, type ShadcnTheme, type ShadcnVarName, builder };
|
package/dist/index.js
CHANGED
|
@@ -586,29 +586,8 @@ function buildJson(ctx) {
|
|
|
586
586
|
};
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
// src/lib/builder.
|
|
590
|
-
import {
|
|
591
|
-
var SHADE_TO_TONE = [
|
|
592
|
-
[50, 95],
|
|
593
|
-
[100, 90],
|
|
594
|
-
[200, 80],
|
|
595
|
-
[300, 70],
|
|
596
|
-
[400, 60],
|
|
597
|
-
[500, 50],
|
|
598
|
-
[600, 40],
|
|
599
|
-
[700, 30],
|
|
600
|
-
[800, 20],
|
|
601
|
-
[900, 10],
|
|
602
|
-
[950, 5]
|
|
603
|
-
];
|
|
604
|
-
var CORE_PALETTES = [
|
|
605
|
-
"primary",
|
|
606
|
-
"secondary",
|
|
607
|
-
"tertiary",
|
|
608
|
-
"error",
|
|
609
|
-
"neutral",
|
|
610
|
-
"neutral-variant"
|
|
611
|
-
];
|
|
589
|
+
// src/lib/builder.shadcn.ts
|
|
590
|
+
import { camelCase } from "lodash-es";
|
|
612
591
|
var SHADCN_MAPPING = [
|
|
613
592
|
["--background", "surface"],
|
|
614
593
|
["--foreground", "on-surface"],
|
|
@@ -642,6 +621,69 @@ var SHADCN_MAPPING = [
|
|
|
642
621
|
["--sidebar-border", "outline-variant"],
|
|
643
622
|
["--sidebar-ring", "primary"]
|
|
644
623
|
];
|
|
624
|
+
function srgbToLinear(channel) {
|
|
625
|
+
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
626
|
+
}
|
|
627
|
+
function round(n) {
|
|
628
|
+
return String(Number(n.toFixed(3)));
|
|
629
|
+
}
|
|
630
|
+
function argbToOklch(argb) {
|
|
631
|
+
const r = srgbToLinear((argb >> 16 & 255) / 255);
|
|
632
|
+
const g = srgbToLinear((argb >> 8 & 255) / 255);
|
|
633
|
+
const b = srgbToLinear((argb & 255) / 255);
|
|
634
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
635
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
636
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
637
|
+
const lightness = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
638
|
+
const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
639
|
+
const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
640
|
+
const chroma = Math.hypot(a, bb);
|
|
641
|
+
const hue = Number(chroma.toFixed(3)) === 0 ? 0 : (Math.atan2(bb, a) * 180 / Math.PI + 360) % 360;
|
|
642
|
+
return `oklch(${round(lightness)} ${round(chroma)} ${round(hue)})`;
|
|
643
|
+
}
|
|
644
|
+
function toShadcnVars(mergedColors) {
|
|
645
|
+
const entries = SHADCN_MAPPING.map(([cssVar, m3Token]) => {
|
|
646
|
+
const argb = mergedColors[camelCase(m3Token)];
|
|
647
|
+
if (argb === void 0) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
`M3 token '${m3Token}' is missing from the scheme, needed by '${cssVar}'. This is likely a bug in the implementation.`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return [cssVar.slice(2), argbToOklch(argb)];
|
|
653
|
+
});
|
|
654
|
+
return Object.fromEntries(entries);
|
|
655
|
+
}
|
|
656
|
+
function buildShadcn(ctx) {
|
|
657
|
+
const { mergedColorsLight, mergedColorsDark } = ctx;
|
|
658
|
+
return {
|
|
659
|
+
light: toShadcnVars(mergedColorsLight),
|
|
660
|
+
dark: toShadcnVars(mergedColorsDark)
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/lib/builder.tailwind.ts
|
|
665
|
+
import { kebabCase as kebabCase3 } from "lodash-es";
|
|
666
|
+
var SHADE_TO_TONE = [
|
|
667
|
+
[50, 95],
|
|
668
|
+
[100, 90],
|
|
669
|
+
[200, 80],
|
|
670
|
+
[300, 70],
|
|
671
|
+
[400, 60],
|
|
672
|
+
[500, 50],
|
|
673
|
+
[600, 40],
|
|
674
|
+
[700, 30],
|
|
675
|
+
[800, 20],
|
|
676
|
+
[900, 10],
|
|
677
|
+
[950, 5]
|
|
678
|
+
];
|
|
679
|
+
var CORE_PALETTES = [
|
|
680
|
+
"primary",
|
|
681
|
+
"secondary",
|
|
682
|
+
"tertiary",
|
|
683
|
+
"error",
|
|
684
|
+
"neutral",
|
|
685
|
+
"neutral-variant"
|
|
686
|
+
];
|
|
645
687
|
function buildTailwind(ctx, options) {
|
|
646
688
|
const { prefix, mergedColorsLight, hexCustomColors } = ctx;
|
|
647
689
|
const lines = [];
|
|
@@ -1068,6 +1110,7 @@ function builder(hexSource, {
|
|
|
1068
1110
|
toFigmaVariables: () => buildFigmaVariables(ctx),
|
|
1069
1111
|
toFigmaTokens: () => buildFigmaTokens(ctx),
|
|
1070
1112
|
toTailwind: (options) => buildTailwind(ctx, options),
|
|
1113
|
+
toShadcn: () => buildShadcn(ctx),
|
|
1071
1114
|
toFlutter: () => buildFlutter(ctx),
|
|
1072
1115
|
mergedColorsLight,
|
|
1073
1116
|
mergedColorsDark,
|
package/dist/react.d.ts
CHANGED
|
@@ -62,8 +62,8 @@ type HexCustomColor = Omit<CustomColor, "value"> & {
|
|
|
62
62
|
/** Available Material You color scheme variants. */
|
|
63
63
|
declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
|
|
64
64
|
type SchemeName = (typeof schemeNames)[number];
|
|
65
|
-
/** Configuration for the Material
|
|
66
|
-
type
|
|
65
|
+
/** Configuration for the Material Theme Builder. */
|
|
66
|
+
type MtbConfig = {
|
|
67
67
|
/** Source color in hex format (e.g., "#6750A4") used to generate the color scheme */
|
|
68
68
|
source: string;
|
|
69
69
|
/** Color scheme variant. Default: "tonalSpot" */
|
|
@@ -166,7 +166,7 @@ type TokenName = keyof typeof tokenDescriptions;
|
|
|
166
166
|
|
|
167
167
|
interface ExportButtonProps {
|
|
168
168
|
/** Current theme configuration used to generate the exported tokens. */
|
|
169
|
-
config:
|
|
169
|
+
config: MtbConfig;
|
|
170
170
|
}
|
|
171
171
|
/**
|
|
172
172
|
* FAB that exports the current theme as Figma DTCG token JSON files.
|
|
@@ -176,20 +176,45 @@ declare function ExportButton({ config }: ExportButtonProps): react_jsx_runtime.
|
|
|
176
176
|
/**
|
|
177
177
|
* Root component that generates and injects a Material You color theme into the page.
|
|
178
178
|
*/
|
|
179
|
-
declare function
|
|
179
|
+
declare function Mtb({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, prefix, children, }: MtbConfig & {
|
|
180
180
|
/** Content to render inside the themed scope. */
|
|
181
181
|
children?: React.ReactNode;
|
|
182
182
|
}): react_jsx_runtime.JSX.Element;
|
|
183
|
+
/**
|
|
184
|
+
* @deprecated Renamed `Mtb` — same component, same props. This alias will be
|
|
185
|
+
* removed in the next major.
|
|
186
|
+
*/
|
|
187
|
+
declare const Mcu: typeof Mtb;
|
|
183
188
|
|
|
184
189
|
type Api = {
|
|
185
|
-
initials:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
initials: MtbConfig;
|
|
191
|
+
mtbConfig: MtbConfig;
|
|
192
|
+
setMtbConfig: (config: MtbConfig) => void;
|
|
193
|
+
getMtbColor: (colorName: TokenName, theme?: string) => string;
|
|
189
194
|
allPalettes: Record<string, TonalPalette>;
|
|
190
195
|
figmaTokens: FigmaTokens;
|
|
191
196
|
figmaVariables: FigmaVariable[];
|
|
197
|
+
/**
|
|
198
|
+
* @deprecated Renamed `mtbConfig` — same value. This alias will be removed in
|
|
199
|
+
* the next major.
|
|
200
|
+
*/
|
|
201
|
+
mcuConfig: MtbConfig;
|
|
202
|
+
/**
|
|
203
|
+
* @deprecated Renamed `setMtbConfig` — same setter. This alias will be
|
|
204
|
+
* removed in the next major.
|
|
205
|
+
*/
|
|
206
|
+
setMcuConfig: (config: MtbConfig) => void;
|
|
207
|
+
/**
|
|
208
|
+
* @deprecated Renamed `getMtbColor` — same function. This alias will be
|
|
209
|
+
* removed in the next major.
|
|
210
|
+
*/
|
|
211
|
+
getMcuColor: (colorName: TokenName, theme?: string) => string;
|
|
192
212
|
};
|
|
213
|
+
declare const useMtb: () => Api;
|
|
214
|
+
/**
|
|
215
|
+
* @deprecated Renamed `useMtb` — same hook, same return value. This alias will
|
|
216
|
+
* be removed in the next major.
|
|
217
|
+
*/
|
|
193
218
|
declare const useMcu: () => Api;
|
|
194
219
|
|
|
195
|
-
export { ExportButton, Mcu, useMcu };
|
|
220
|
+
export { ExportButton, Mcu, Mtb, useMcu, useMtb };
|
package/dist/react.js
CHANGED
|
@@ -3906,29 +3906,8 @@ function buildJson(ctx) {
|
|
|
3906
3906
|
};
|
|
3907
3907
|
}
|
|
3908
3908
|
|
|
3909
|
-
// src/lib/builder.
|
|
3910
|
-
import {
|
|
3911
|
-
var SHADE_TO_TONE = [
|
|
3912
|
-
[50, 95],
|
|
3913
|
-
[100, 90],
|
|
3914
|
-
[200, 80],
|
|
3915
|
-
[300, 70],
|
|
3916
|
-
[400, 60],
|
|
3917
|
-
[500, 50],
|
|
3918
|
-
[600, 40],
|
|
3919
|
-
[700, 30],
|
|
3920
|
-
[800, 20],
|
|
3921
|
-
[900, 10],
|
|
3922
|
-
[950, 5]
|
|
3923
|
-
];
|
|
3924
|
-
var CORE_PALETTES = [
|
|
3925
|
-
"primary",
|
|
3926
|
-
"secondary",
|
|
3927
|
-
"tertiary",
|
|
3928
|
-
"error",
|
|
3929
|
-
"neutral",
|
|
3930
|
-
"neutral-variant"
|
|
3931
|
-
];
|
|
3909
|
+
// src/lib/builder.shadcn.ts
|
|
3910
|
+
import { camelCase } from "lodash-es";
|
|
3932
3911
|
var SHADCN_MAPPING = [
|
|
3933
3912
|
["--background", "surface"],
|
|
3934
3913
|
["--foreground", "on-surface"],
|
|
@@ -3962,6 +3941,69 @@ var SHADCN_MAPPING = [
|
|
|
3962
3941
|
["--sidebar-border", "outline-variant"],
|
|
3963
3942
|
["--sidebar-ring", "primary"]
|
|
3964
3943
|
];
|
|
3944
|
+
function srgbToLinear(channel) {
|
|
3945
|
+
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
3946
|
+
}
|
|
3947
|
+
function round(n) {
|
|
3948
|
+
return String(Number(n.toFixed(3)));
|
|
3949
|
+
}
|
|
3950
|
+
function argbToOklch(argb) {
|
|
3951
|
+
const r2 = srgbToLinear((argb >> 16 & 255) / 255);
|
|
3952
|
+
const g = srgbToLinear((argb >> 8 & 255) / 255);
|
|
3953
|
+
const b = srgbToLinear((argb & 255) / 255);
|
|
3954
|
+
const l = Math.cbrt(0.4122214708 * r2 + 0.5363325363 * g + 0.0514459929 * b);
|
|
3955
|
+
const m = Math.cbrt(0.2119034982 * r2 + 0.6806995451 * g + 0.1073969566 * b);
|
|
3956
|
+
const s = Math.cbrt(0.0883024619 * r2 + 0.2817188376 * g + 0.6299787005 * b);
|
|
3957
|
+
const lightness = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
3958
|
+
const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
3959
|
+
const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
3960
|
+
const chroma = Math.hypot(a, bb);
|
|
3961
|
+
const hue = Number(chroma.toFixed(3)) === 0 ? 0 : (Math.atan2(bb, a) * 180 / Math.PI + 360) % 360;
|
|
3962
|
+
return `oklch(${round(lightness)} ${round(chroma)} ${round(hue)})`;
|
|
3963
|
+
}
|
|
3964
|
+
function toShadcnVars(mergedColors) {
|
|
3965
|
+
const entries = SHADCN_MAPPING.map(([cssVar, m3Token]) => {
|
|
3966
|
+
const argb = mergedColors[camelCase(m3Token)];
|
|
3967
|
+
if (argb === void 0) {
|
|
3968
|
+
throw new Error(
|
|
3969
|
+
`M3 token '${m3Token}' is missing from the scheme, needed by '${cssVar}'. This is likely a bug in the implementation.`
|
|
3970
|
+
);
|
|
3971
|
+
}
|
|
3972
|
+
return [cssVar.slice(2), argbToOklch(argb)];
|
|
3973
|
+
});
|
|
3974
|
+
return Object.fromEntries(entries);
|
|
3975
|
+
}
|
|
3976
|
+
function buildShadcn(ctx) {
|
|
3977
|
+
const { mergedColorsLight, mergedColorsDark } = ctx;
|
|
3978
|
+
return {
|
|
3979
|
+
light: toShadcnVars(mergedColorsLight),
|
|
3980
|
+
dark: toShadcnVars(mergedColorsDark)
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
// src/lib/builder.tailwind.ts
|
|
3985
|
+
import { kebabCase as kebabCase3 } from "lodash-es";
|
|
3986
|
+
var SHADE_TO_TONE = [
|
|
3987
|
+
[50, 95],
|
|
3988
|
+
[100, 90],
|
|
3989
|
+
[200, 80],
|
|
3990
|
+
[300, 70],
|
|
3991
|
+
[400, 60],
|
|
3992
|
+
[500, 50],
|
|
3993
|
+
[600, 40],
|
|
3994
|
+
[700, 30],
|
|
3995
|
+
[800, 20],
|
|
3996
|
+
[900, 10],
|
|
3997
|
+
[950, 5]
|
|
3998
|
+
];
|
|
3999
|
+
var CORE_PALETTES = [
|
|
4000
|
+
"primary",
|
|
4001
|
+
"secondary",
|
|
4002
|
+
"tertiary",
|
|
4003
|
+
"error",
|
|
4004
|
+
"neutral",
|
|
4005
|
+
"neutral-variant"
|
|
4006
|
+
];
|
|
3965
4007
|
function buildTailwind(ctx, options) {
|
|
3966
4008
|
const { prefix, mergedColorsLight, hexCustomColors } = ctx;
|
|
3967
4009
|
const lines = [];
|
|
@@ -4388,6 +4430,7 @@ function builder(hexSource, {
|
|
|
4388
4430
|
toFigmaVariables: () => buildFigmaVariables(ctx),
|
|
4389
4431
|
toFigmaTokens: () => buildFigmaTokens(ctx),
|
|
4390
4432
|
toTailwind: (options) => buildTailwind(ctx, options),
|
|
4433
|
+
toShadcn: () => buildShadcn(ctx),
|
|
4391
4434
|
toFlutter: () => buildFlutter(ctx),
|
|
4392
4435
|
mergedColorsLight,
|
|
4393
4436
|
mergedColorsDark,
|
|
@@ -4454,10 +4497,10 @@ function ExportButton({ config }) {
|
|
|
4454
4497
|
);
|
|
4455
4498
|
}
|
|
4456
4499
|
|
|
4457
|
-
// src/
|
|
4500
|
+
// src/Mtb.tsx
|
|
4458
4501
|
import { useMemo as useMemo2 } from "react";
|
|
4459
4502
|
|
|
4460
|
-
// src/
|
|
4503
|
+
// src/Mtb.context.tsx
|
|
4461
4504
|
import {
|
|
4462
4505
|
hexFromArgb as hexFromArgb5
|
|
4463
4506
|
} from "@material/material-color-utilities";
|
|
@@ -4477,19 +4520,19 @@ var createRequiredContext = () => {
|
|
|
4477
4520
|
return [useCtx, Ctx.Provider, Ctx];
|
|
4478
4521
|
};
|
|
4479
4522
|
|
|
4480
|
-
// src/
|
|
4523
|
+
// src/Mtb.context.tsx
|
|
4481
4524
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4482
|
-
var [
|
|
4483
|
-
var
|
|
4484
|
-
styleId,
|
|
4525
|
+
var [useMtb, Provider, MtbContext] = createRequiredContext();
|
|
4526
|
+
var MtbProvider = ({
|
|
4527
|
+
styleId: styleId2,
|
|
4485
4528
|
children,
|
|
4486
4529
|
...configProps
|
|
4487
4530
|
}) => {
|
|
4488
4531
|
const [initials] = useState(() => configProps);
|
|
4489
|
-
const [
|
|
4532
|
+
const [mtbConfig, setMtbConfig] = useState(initials);
|
|
4490
4533
|
const configKey = JSON.stringify(configProps);
|
|
4491
4534
|
React.useEffect(() => {
|
|
4492
|
-
|
|
4535
|
+
setMtbConfig(configProps);
|
|
4493
4536
|
}, [configKey]);
|
|
4494
4537
|
const {
|
|
4495
4538
|
css,
|
|
@@ -4500,8 +4543,8 @@ var McuProvider = ({
|
|
|
4500
4543
|
figmaVariables
|
|
4501
4544
|
} = useMemo(() => {
|
|
4502
4545
|
const { toCss, toFigmaTokens, toFigmaVariables, ...rest } = builder(
|
|
4503
|
-
|
|
4504
|
-
|
|
4546
|
+
mtbConfig.source,
|
|
4547
|
+
mtbConfig
|
|
4505
4548
|
);
|
|
4506
4549
|
return {
|
|
4507
4550
|
css: toCss(),
|
|
@@ -4509,13 +4552,13 @@ var McuProvider = ({
|
|
|
4509
4552
|
figmaVariables: toFigmaVariables(),
|
|
4510
4553
|
...rest
|
|
4511
4554
|
};
|
|
4512
|
-
}, [
|
|
4513
|
-
const
|
|
4555
|
+
}, [mtbConfig]);
|
|
4556
|
+
const getMtbColor = useCallback(
|
|
4514
4557
|
(colorName, theme) => {
|
|
4515
4558
|
const mergedColors = theme === "light" ? mergedColorsLight : mergedColorsDark;
|
|
4516
4559
|
const colorValue = mergedColors[colorName];
|
|
4517
4560
|
if (colorValue === void 0) {
|
|
4518
|
-
throw new Error(`Unknown
|
|
4561
|
+
throw new Error(`Unknown token '${colorName}'`);
|
|
4519
4562
|
}
|
|
4520
4563
|
return hexFromArgb5(colorValue);
|
|
4521
4564
|
},
|
|
@@ -4524,33 +4567,38 @@ var McuProvider = ({
|
|
|
4524
4567
|
const value = useMemo(
|
|
4525
4568
|
() => ({
|
|
4526
4569
|
initials,
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4570
|
+
mtbConfig,
|
|
4571
|
+
setMtbConfig,
|
|
4572
|
+
getMtbColor,
|
|
4530
4573
|
allPalettes,
|
|
4531
4574
|
figmaTokens,
|
|
4532
|
-
figmaVariables
|
|
4575
|
+
figmaVariables,
|
|
4576
|
+
// deprecated aliases
|
|
4577
|
+
mcuConfig: mtbConfig,
|
|
4578
|
+
setMcuConfig: setMtbConfig,
|
|
4579
|
+
getMcuColor: getMtbColor
|
|
4533
4580
|
}),
|
|
4534
4581
|
[
|
|
4535
|
-
|
|
4582
|
+
getMtbColor,
|
|
4536
4583
|
initials,
|
|
4537
|
-
|
|
4584
|
+
mtbConfig,
|
|
4538
4585
|
allPalettes,
|
|
4539
4586
|
figmaTokens,
|
|
4540
4587
|
figmaVariables
|
|
4541
4588
|
]
|
|
4542
4589
|
);
|
|
4543
4590
|
return /* @__PURE__ */ jsxs2(Provider, { value, children: [
|
|
4544
|
-
/* @__PURE__ */ jsx3("style", { id:
|
|
4591
|
+
/* @__PURE__ */ jsx3("style", { id: styleId2, dangerouslySetInnerHTML: { __html: css } }),
|
|
4545
4592
|
children
|
|
4546
4593
|
] });
|
|
4547
4594
|
};
|
|
4595
|
+
var useMcu = useMtb;
|
|
4548
4596
|
|
|
4549
|
-
// src/
|
|
4597
|
+
// src/Mtb.tsx
|
|
4550
4598
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
4551
|
-
var
|
|
4599
|
+
var styleId = "mcu-styles";
|
|
4552
4600
|
var DEFAULT_COLOR_MATCH = false;
|
|
4553
|
-
function
|
|
4601
|
+
function Mtb({
|
|
4554
4602
|
source,
|
|
4555
4603
|
scheme = DEFAULT_SCHEME,
|
|
4556
4604
|
contrast = DEFAULT_CONTRAST,
|
|
@@ -4595,10 +4643,13 @@ function Mcu({
|
|
|
4595
4643
|
prefix
|
|
4596
4644
|
]
|
|
4597
4645
|
);
|
|
4598
|
-
return /* @__PURE__ */ jsx4(
|
|
4646
|
+
return /* @__PURE__ */ jsx4(MtbProvider, { ...config, styleId, children });
|
|
4599
4647
|
}
|
|
4648
|
+
var Mcu = Mtb;
|
|
4600
4649
|
export {
|
|
4601
4650
|
ExportButton,
|
|
4602
4651
|
Mcu,
|
|
4603
|
-
|
|
4652
|
+
Mtb,
|
|
4653
|
+
useMcu,
|
|
4654
|
+
useMtb
|
|
4604
4655
|
};
|