material-theme-builder 2.2.0 → 3.1.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 -14
- package/dist/cli.js +100 -43
- package/dist/index.d.ts +38 -5
- package/dist/index.js +67 -30
- package/dist/react.d.ts +18 -8
- package/dist/react.js +79 -32
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -50,14 +50,6 @@ theme.toFlutter();
|
|
|
50
50
|
theme.toShadcn();
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
> [!NOTE]
|
|
54
|
-
>
|
|
55
|
-
> `builder` is server-safe. Only the React entry points (`Mcu`, `useMcu`,
|
|
56
|
-
> `ExportButton`) carry `"use client"`, so you can call it from a
|
|
57
|
-
> [React Server Component](https://react.dev/reference/rsc/server-components)
|
|
58
|
-
> — handy to emit `toCss()` into the document yourself rather than let `<Mcu>`
|
|
59
|
-
> do it on the client.
|
|
60
|
-
|
|
61
53
|
## CLI
|
|
62
54
|
|
|
63
55
|
```sh
|
|
@@ -70,12 +62,14 @@ See `npx material-theme-builder --help` for all available options.
|
|
|
70
62
|
|
|
71
63
|
## React
|
|
72
64
|
|
|
65
|
+
The React bindings live on their own entry point, `material-theme-builder/react`.
|
|
66
|
+
|
|
73
67
|
CSS variables are injected into the page:
|
|
74
68
|
|
|
75
69
|
```tsx
|
|
76
|
-
import {
|
|
70
|
+
import { Mtb } from "material-theme-builder/react";
|
|
77
71
|
|
|
78
|
-
<
|
|
72
|
+
<Mtb
|
|
79
73
|
source="#0e1216"
|
|
80
74
|
scheme="vibrant"
|
|
81
75
|
contrast={0.5}
|
|
@@ -94,7 +88,7 @@ import { Mcu } from "material-theme-builder";
|
|
|
94
88
|
color: "var(--md-sys-color-on-my-custom-color-1)",
|
|
95
89
|
}}>colors<span>!
|
|
96
90
|
</p>
|
|
97
|
-
</
|
|
91
|
+
</Mtb>
|
|
98
92
|
```
|
|
99
93
|
|
|
100
94
|
> [!TIP]
|
|
@@ -107,14 +101,22 @@ import { Mcu } from "material-theme-builder";
|
|
|
107
101
|
> CSS varnames are always kebab-cased, e.g. `myCustomColor1` →
|
|
108
102
|
> `--md-sys-color-my-custom-color-1` / `--md-ref-palette-my-custom-color-1-<tone>`
|
|
109
103
|
|
|
110
|
-
|
|
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`
|
|
111
113
|
|
|
112
114
|
A hook is also provided:
|
|
113
115
|
|
|
114
116
|
```tsx
|
|
115
|
-
import {
|
|
117
|
+
import { useMtb } from "material-theme-builder/react";
|
|
116
118
|
|
|
117
|
-
const { initials, setMcuConfig, getMcuColor } =
|
|
119
|
+
const { initials, setMcuConfig, getMcuColor } = useMtb();
|
|
118
120
|
|
|
119
121
|
return (
|
|
120
122
|
<button onClick={() => setMcuConfig({ ...initials, source: "#FF5722" })}>
|
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,32 @@
|
|
|
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
|
+
};
|
|
3
30
|
|
|
4
31
|
/** DTCG color value (direct, non-alias) */
|
|
5
32
|
type DtcgColorValue = {
|
|
@@ -51,8 +78,8 @@ type HexCustomColor = Omit<CustomColor, "value"> & {
|
|
|
51
78
|
/** Available Material You color scheme variants. */
|
|
52
79
|
declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
|
|
53
80
|
type SchemeName = (typeof schemeNames)[number];
|
|
54
|
-
/** Configuration for the Material
|
|
55
|
-
type
|
|
81
|
+
/** Configuration for the Material Theme Builder. */
|
|
82
|
+
type MtbConfig = {
|
|
56
83
|
/** Source color in hex format (e.g., "#6750A4") used to generate the color scheme */
|
|
57
84
|
source: string;
|
|
58
85
|
/** Color scheme variant. Default: "tonalSpot" */
|
|
@@ -92,6 +119,11 @@ type McuConfig = {
|
|
|
92
119
|
*/
|
|
93
120
|
prefix?: string;
|
|
94
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated Renamed `MtbConfig` — same shape. This alias will be removed in
|
|
124
|
+
* the next major.
|
|
125
|
+
*/
|
|
126
|
+
type McuConfig = MtbConfig;
|
|
95
127
|
/**
|
|
96
128
|
* Build a Material You color theme from a hex source color.
|
|
97
129
|
*
|
|
@@ -105,7 +137,7 @@ type McuConfig = {
|
|
|
105
137
|
* const json = theme.toJson();
|
|
106
138
|
* ```
|
|
107
139
|
*/
|
|
108
|
-
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">): {
|
|
109
141
|
toCss: () => string;
|
|
110
142
|
toJson: () => {
|
|
111
143
|
seed: string;
|
|
@@ -145,6 +177,7 @@ declare function builder(hexSource: McuConfig["source"], { scheme, contrast, pri
|
|
|
145
177
|
};
|
|
146
178
|
};
|
|
147
179
|
toTailwind: (options?: TailwindOptions) => string;
|
|
180
|
+
toShadcn: () => ShadcnTheme;
|
|
148
181
|
toFlutter: () => string;
|
|
149
182
|
mergedColorsLight: {
|
|
150
183
|
[x: string]: number;
|
|
@@ -162,4 +195,4 @@ declare function builder(hexSource: McuConfig["source"], { scheme, contrast, pri
|
|
|
162
195
|
};
|
|
163
196
|
};
|
|
164
197
|
|
|
165
|
-
export { 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,18 +1110,13 @@ 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,
|
|
1074
1117
|
allPalettes
|
|
1075
1118
|
};
|
|
1076
1119
|
}
|
|
1077
|
-
|
|
1078
|
-
// src/index.ts
|
|
1079
|
-
import { ExportButton, Mcu, useMcu } from "./react.js";
|
|
1080
1120
|
export {
|
|
1081
|
-
|
|
1082
|
-
Mcu,
|
|
1083
|
-
builder,
|
|
1084
|
-
useMcu
|
|
1121
|
+
builder
|
|
1085
1122
|
};
|
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,30 @@ 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
|
-
mcuConfig:
|
|
187
|
-
setMcuConfig: (config:
|
|
190
|
+
initials: MtbConfig;
|
|
191
|
+
mcuConfig: MtbConfig;
|
|
192
|
+
setMcuConfig: (config: MtbConfig) => void;
|
|
188
193
|
getMcuColor: (colorName: TokenName, theme?: string) => string;
|
|
189
194
|
allPalettes: Record<string, TonalPalette>;
|
|
190
195
|
figmaTokens: FigmaTokens;
|
|
191
196
|
figmaVariables: FigmaVariable[];
|
|
192
197
|
};
|
|
198
|
+
declare const useMtb: () => Api;
|
|
199
|
+
/**
|
|
200
|
+
* @deprecated Renamed `useMtb` — same hook, same return value. This alias will
|
|
201
|
+
* be removed in the next major.
|
|
202
|
+
*/
|
|
193
203
|
declare const useMcu: () => Api;
|
|
194
204
|
|
|
195
|
-
export { ExportButton, Mcu, useMcu };
|
|
205
|
+
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,10 +4520,10 @@ 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
|
|
4525
|
+
var [useMtb, Provider, MtbContext] = createRequiredContext();
|
|
4526
|
+
var MtbProvider = ({
|
|
4484
4527
|
styleId,
|
|
4485
4528
|
children,
|
|
4486
4529
|
...configProps
|
|
@@ -4545,12 +4588,13 @@ var McuProvider = ({
|
|
|
4545
4588
|
children
|
|
4546
4589
|
] });
|
|
4547
4590
|
};
|
|
4591
|
+
var useMcu = useMtb;
|
|
4548
4592
|
|
|
4549
|
-
// src/
|
|
4593
|
+
// src/Mtb.tsx
|
|
4550
4594
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
4551
4595
|
var mcuStyleId = "mcu-styles";
|
|
4552
4596
|
var DEFAULT_COLOR_MATCH = false;
|
|
4553
|
-
function
|
|
4597
|
+
function Mtb({
|
|
4554
4598
|
source,
|
|
4555
4599
|
scheme = DEFAULT_SCHEME,
|
|
4556
4600
|
contrast = DEFAULT_CONTRAST,
|
|
@@ -4595,10 +4639,13 @@ function Mcu({
|
|
|
4595
4639
|
prefix
|
|
4596
4640
|
]
|
|
4597
4641
|
);
|
|
4598
|
-
return /* @__PURE__ */ jsx4(
|
|
4642
|
+
return /* @__PURE__ */ jsx4(MtbProvider, { ...config, styleId: mcuStyleId, children });
|
|
4599
4643
|
}
|
|
4644
|
+
var Mcu = Mtb;
|
|
4600
4645
|
export {
|
|
4601
4646
|
ExportButton,
|
|
4602
4647
|
Mcu,
|
|
4603
|
-
|
|
4648
|
+
Mtb,
|
|
4649
|
+
useMcu,
|
|
4650
|
+
useMtb
|
|
4604
4651
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "material-theme-builder",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "m3 color-system for JS/TS ecosystem",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"import": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./react": {
|
|
16
|
+
"types": "./dist/react.d.ts",
|
|
17
|
+
"import": "./dist/react.js"
|
|
18
|
+
},
|
|
15
19
|
"./tailwind.css": "./dist/tailwind.css"
|
|
16
20
|
},
|
|
17
21
|
"homepage": "https://github.com/abernier/material-theme-builder",
|