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 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 { Mcu } from "material-theme-builder";
70
+ import { Mtb } from "material-theme-builder/react";
77
71
 
78
- <Mcu
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
- </Mcu>
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
- ## `useMcu`
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 { useMcu } from "material-theme-builder";
117
+ import { useMtb } from "material-theme-builder/react";
116
118
 
117
- const { initials, setMcuConfig, getMcuColor } = useMcu();
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.tailwind.ts
598
- import { kebabCase as kebabCase3 } from "lodash-es";
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 CSS variable remapping (with --format tailwind)"
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
- if (opts.format === "css") {
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
- export { ExportButton, Mcu, useMcu } from './react.js';
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 Color Utilities builder. */
55
- type McuConfig = {
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: McuConfig["source"], { scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, customColors: hexCustomColors, prefix, }?: Omit<McuConfig, "source">): {
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.tailwind.ts
590
- import { kebabCase as kebabCase3 } from "lodash-es";
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
- ExportButton,
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 Color Utilities builder. */
66
- type McuConfig = {
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: McuConfig;
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 Mcu({ source, scheme, contrast, primary, secondary, tertiary, neutral, neutralVariant, error, colorMatch, customColors, prefix, children, }: McuConfig & {
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: McuConfig;
186
- mcuConfig: McuConfig;
187
- setMcuConfig: (config: McuConfig) => void;
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.tailwind.ts
3910
- import { kebabCase as kebabCase3 } from "lodash-es";
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/Mcu.tsx
4500
+ // src/Mtb.tsx
4458
4501
  import { useMemo as useMemo2 } from "react";
4459
4502
 
4460
- // src/Mcu.context.tsx
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/Mcu.context.tsx
4523
+ // src/Mtb.context.tsx
4481
4524
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
4482
- var [useMcu, Provider, McuContext] = createRequiredContext();
4483
- var McuProvider = ({
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/Mcu.tsx
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 Mcu({
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(McuProvider, { ...config, styleId: mcuStyleId, children });
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
- useMcu
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": "2.2.0",
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",