material-theme-builder 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -27,6 +27,31 @@ type ShadcnTheme = {
27
27
  light: Record<ShadcnVarName, string>;
28
28
  dark: Record<ShadcnVarName, string>;
29
29
  };
30
+ /**
31
+ * A shadcn registry item carrying the alias mapping, as
32
+ * `toShadcnRegistryItem()` returns it.
33
+ *
34
+ * @see https://ui.shadcn.com/schema/registry-item.json
35
+ */
36
+ type ShadcnRegistryItem = {
37
+ $schema: string;
38
+ name: string;
39
+ type: "registry:theme";
40
+ title: string;
41
+ description: string;
42
+ cssVars: ShadcnTheme;
43
+ };
44
+ /** Options for `toShadcnRegistryItem()`. */
45
+ type ShadcnRegistryItemOptions = {
46
+ /**
47
+ * Embed this theme's concrete colors as the `var()` fallbacks, so the item
48
+ * also works where nothing declares the M3 custom properties. Off by default
49
+ * — see `buildShadcnRegistryItem()` for why.
50
+ *
51
+ * @default false
52
+ */
53
+ fallback?: boolean;
54
+ };
30
55
 
31
56
  /** DTCG color value (direct, non-alias) */
32
57
  type DtcgColorValue = {
@@ -110,6 +135,8 @@ type MtbConfig = {
110
135
  /**
111
136
  * Array of custom colors to include in the generated palette.
112
137
  * Each custom color can be blended with the source color for harmonization.
138
+ *
139
+ * @see https://m3.material.io/blog/dynamic-color-harmony
113
140
  */
114
141
  customColors?: HexCustomColor[];
115
142
  /**
@@ -178,6 +205,8 @@ declare function builder(hexSource: MtbConfig["source"], { scheme, contrast, pri
178
205
  };
179
206
  toTailwind: (options?: TailwindOptions) => string;
180
207
  toShadcn: () => ShadcnTheme;
208
+ toShadcnAliases: () => string;
209
+ toShadcnRegistryItem: (options?: ShadcnRegistryItemOptions) => ShadcnRegistryItem;
181
210
  toFlutter: () => string;
182
211
  mergedColorsLight: {
183
212
  [x: string]: number;
@@ -195,4 +224,4 @@ declare function builder(hexSource: MtbConfig["source"], { scheme, contrast, pri
195
224
  };
196
225
  };
197
226
 
198
- export { type McuConfig, type MtbConfig, type ShadcnTheme, type ShadcnVarName, builder };
227
+ export { type McuConfig, type MtbConfig, type ShadcnRegistryItem, type ShadcnTheme, type ShadcnVarName, builder };
package/dist/index.js CHANGED
@@ -653,6 +653,39 @@ function toShadcnVars(mergedColors) {
653
653
  });
654
654
  return Object.fromEntries(entries);
655
655
  }
656
+ function toShadcnAliasVars(prefix, fallbacks) {
657
+ const entries = SHADCN_MAPPING.map(([cssVar, m3Token]) => {
658
+ const bare = cssVar.slice(2);
659
+ const property = `--${prefix}-sys-color-${m3Token}`;
660
+ return [
661
+ bare,
662
+ fallbacks ? `var(${property}, ${fallbacks[bare]})` : `var(${property})`
663
+ ];
664
+ });
665
+ return Object.fromEntries(entries);
666
+ }
667
+ function buildShadcnAliases(ctx) {
668
+ const lines = Object.entries(toShadcnAliasVars(ctx.prefix)).map(
669
+ ([name, value]) => `--${name}: ${value};`
670
+ );
671
+ return `:root,
672
+ .dark {
673
+ ${lines.join("\n ")}
674
+ }
675
+ `;
676
+ }
677
+ function buildShadcnRegistryItem(ctx, { fallback = false } = {}) {
678
+ const concrete = fallback ? buildShadcn(ctx) : void 0;
679
+ const vars = (mode) => toShadcnAliasVars(ctx.prefix, concrete?.[mode]);
680
+ return {
681
+ $schema: "https://ui.shadcn.com/schema/registry-item.json",
682
+ name: "material-theme-builder",
683
+ type: "registry:theme",
684
+ title: "Material Theme Builder",
685
+ description: `Points shadcn's CSS variables at the M3 custom properties \`<Mtb>\` emits, so every shadcn component follows whichever theme is above it in the tree.${fallback ? " Falls back to this theme's own colors where no `<Mtb>` is mounted." : ""}`,
686
+ cssVars: { light: vars("light"), dark: vars("dark") }
687
+ };
688
+ }
656
689
  function buildShadcn(ctx) {
657
690
  const { mergedColorsLight, mergedColorsDark } = ctx;
658
691
  return {
@@ -663,6 +696,64 @@ function buildShadcn(ctx) {
663
696
 
664
697
  // src/lib/builder.tailwind.ts
665
698
  import { kebabCase as kebabCase3 } from "lodash-es";
699
+
700
+ // src/lib/tokens.ts
701
+ var DEFAULT_PREFIX = "md";
702
+ var tokenDescriptions = {
703
+ background: "Default background color for screens and large surfaces.",
704
+ error: "Color for error states, used on elements like error text and icons.",
705
+ errorContainer: "Fill color for error container elements like error banners.",
706
+ inverseOnSurface: "Color for text and icons on inverse surface backgrounds.",
707
+ inversePrimary: "Primary color used on inverse surface, e.g. buttons on snackbars.",
708
+ inverseSurface: "Background for elements that require reverse contrast, such as snackbars.",
709
+ onBackground: "Color for text and icons displayed on the background.",
710
+ onError: "Color for text and icons on error-colored elements.",
711
+ onErrorContainer: "Color for text and icons on error container elements.",
712
+ onPrimary: "Color for text and icons on primary-colored elements like filled buttons.",
713
+ onPrimaryContainer: "Color for text and icons on primary container elements like tonal buttons.",
714
+ onPrimaryFixed: "Color for text and icons on primary fixed elements, constant across themes.",
715
+ onPrimaryFixedVariant: "Lower-emphasis color for text and icons on primary fixed elements.",
716
+ onSecondary: "Color for text and icons on secondary-colored elements.",
717
+ onSecondaryContainer: "Color for text and icons on secondary container elements.",
718
+ onSecondaryFixed: "Color for text and icons on secondary fixed elements, constant across themes.",
719
+ onSecondaryFixedVariant: "Lower-emphasis color for text and icons on secondary fixed elements.",
720
+ onSurface: "High-emphasis color for text and icons on surface backgrounds.",
721
+ onSurfaceVariant: "Medium-emphasis color for text and icons on surface variant backgrounds.",
722
+ onTertiary: "Color for text and icons on tertiary-colored elements.",
723
+ onTertiaryContainer: "Color for text and icons on tertiary container elements.",
724
+ onTertiaryFixed: "Color for text and icons on tertiary fixed elements, constant across themes.",
725
+ onTertiaryFixedVariant: "Lower-emphasis color for text and icons on tertiary fixed elements.",
726
+ outline: "Subtle color for borders and dividers to create visual separation.",
727
+ outlineVariant: "Lower-emphasis border color used for decorative dividers.",
728
+ primary: "Main brand color, used for key components like filled buttons and active states.",
729
+ primaryContainer: "Fill color for large primary elements like cards and tonal buttons.",
730
+ primaryFixed: "Fixed primary color that stays the same in light and dark themes.",
731
+ primaryFixedDim: "Dimmed variant of the fixed primary color for lower emphasis.",
732
+ scrim: "Color overlay for modals and dialogs to obscure background content.",
733
+ secondary: "Accent color for less prominent elements like filter chips and selections.",
734
+ secondaryContainer: "Fill color for secondary container elements like tonal buttons and input fields.",
735
+ secondaryFixed: "Fixed secondary color that stays the same in light and dark themes.",
736
+ secondaryFixedDim: "Dimmed variant of the fixed secondary color for lower emphasis.",
737
+ shadow: "Color for elevation shadows applied to surfaces and components.",
738
+ surface: "Default surface color for cards, sheets, and dialogs.",
739
+ surfaceBright: "Brightest surface variant, used for elevated surfaces in dark themes.",
740
+ surfaceContainer: "Middle-emphasis container color for grouping related content.",
741
+ surfaceContainerHigh: "Higher-emphasis container color for elements like cards.",
742
+ surfaceContainerHighest: "Highest-emphasis container color for text fields and other input areas.",
743
+ surfaceContainerLow: "Lower-emphasis container color for subtle surface groupings.",
744
+ surfaceContainerLowest: "Lowest-emphasis container, typically the lightest surface in light theme.",
745
+ surfaceDim: "Dimmest surface variant, used for recessed areas or dark theme backgrounds.",
746
+ surfaceTint: "Tint color applied to surfaces for subtle primary color elevation overlay.",
747
+ surfaceVariant: "Alternative surface color for differentiated areas like sidebar backgrounds.",
748
+ tertiary: "Third accent color for complementary elements that balance primary and secondary.",
749
+ tertiaryContainer: "Fill color for tertiary container elements like complementary cards.",
750
+ tertiaryFixed: "Fixed tertiary color that stays the same in light and dark themes.",
751
+ tertiaryFixedDim: "Dimmed variant of the fixed tertiary color for lower emphasis."
752
+ };
753
+ function isTokenName(key) {
754
+ return key in tokenDescriptions;
755
+ }
756
+ var tokenNames = Object.keys(tokenDescriptions).filter(isTokenName);
666
757
  var SHADE_TO_TONE = [
667
758
  [50, 95],
668
759
  [100, 90],
@@ -684,6 +775,8 @@ var CORE_PALETTES = [
684
775
  "neutral",
685
776
  "neutral-variant"
686
777
  ];
778
+
779
+ // src/lib/builder.tailwind.ts
687
780
  function buildTailwind(ctx, options) {
688
781
  const { prefix, mergedColorsLight, hexCustomColors } = ctx;
689
782
  const lines = [];
@@ -716,17 +809,8 @@ function buildTailwind(ctx, options) {
716
809
  ${lines.join("\n ")}
717
810
  }
718
811
  `;
719
- if (options?.shadcn) {
720
- const shadcnLines = SHADCN_MAPPING.map(
721
- ([shadcnVar, m3Token]) => `${shadcnVar}: var(--${prefix}-sys-color-${m3Token});`
722
- );
723
- output += `
724
- :root,
725
- .dark {
726
- ${shadcnLines.join("\n ")}
727
- }
728
- `;
729
- }
812
+ if (options?.shadcn) output += `
813
+ ${buildShadcnAliases(ctx)}`;
730
814
  return output;
731
815
  }
732
816
 
@@ -744,7 +828,24 @@ var DEFAULT_SCHEME = "tonalSpot";
744
828
  var DEFAULT_CONTRAST = 0;
745
829
  var DEFAULT_CUSTOM_COLORS = [];
746
830
  var DEFAULT_BLEND = true;
747
- var DEFAULT_PREFIX = "md";
831
+ var HEX_COLOR = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
832
+ function isHexColor(value) {
833
+ return HEX_COLOR.test(value);
834
+ }
835
+ function assertHexColor(label, value) {
836
+ if (!isHexColor(value))
837
+ throw new Error(
838
+ `Invalid ${label}: '${value}'. Expected a hex color \u2014 3, 6 or 8 hex digits, with or without '#' (e.g. #6750A4).`
839
+ );
840
+ }
841
+ function assertHexInputs(source, cores, customColors) {
842
+ assertHexColor("source", source);
843
+ for (const [name, hex] of Object.entries(cores))
844
+ if (hex !== void 0) assertHexColor(name, hex);
845
+ customColors.forEach(
846
+ (color, i) => assertHexColor(`customColors[${i}].hex`, color.hex)
847
+ );
848
+ }
748
849
  var STANDARD_TONES = [
749
850
  0,
750
851
  4,
@@ -795,61 +896,6 @@ var schemeToVariant = {
795
896
  fidelity: Variant.FIDELITY,
796
897
  content: Variant.CONTENT
797
898
  };
798
- var tokenDescriptions = {
799
- background: "Default background color for screens and large surfaces.",
800
- error: "Color for error states, used on elements like error text and icons.",
801
- errorContainer: "Fill color for error container elements like error banners.",
802
- inverseOnSurface: "Color for text and icons on inverse surface backgrounds.",
803
- inversePrimary: "Primary color used on inverse surface, e.g. buttons on snackbars.",
804
- inverseSurface: "Background for elements that require reverse contrast, such as snackbars.",
805
- onBackground: "Color for text and icons displayed on the background.",
806
- onError: "Color for text and icons on error-colored elements.",
807
- onErrorContainer: "Color for text and icons on error container elements.",
808
- onPrimary: "Color for text and icons on primary-colored elements like filled buttons.",
809
- onPrimaryContainer: "Color for text and icons on primary container elements like tonal buttons.",
810
- onPrimaryFixed: "Color for text and icons on primary fixed elements, constant across themes.",
811
- onPrimaryFixedVariant: "Lower-emphasis color for text and icons on primary fixed elements.",
812
- onSecondary: "Color for text and icons on secondary-colored elements.",
813
- onSecondaryContainer: "Color for text and icons on secondary container elements.",
814
- onSecondaryFixed: "Color for text and icons on secondary fixed elements, constant across themes.",
815
- onSecondaryFixedVariant: "Lower-emphasis color for text and icons on secondary fixed elements.",
816
- onSurface: "High-emphasis color for text and icons on surface backgrounds.",
817
- onSurfaceVariant: "Medium-emphasis color for text and icons on surface variant backgrounds.",
818
- onTertiary: "Color for text and icons on tertiary-colored elements.",
819
- onTertiaryContainer: "Color for text and icons on tertiary container elements.",
820
- onTertiaryFixed: "Color for text and icons on tertiary fixed elements, constant across themes.",
821
- onTertiaryFixedVariant: "Lower-emphasis color for text and icons on tertiary fixed elements.",
822
- outline: "Subtle color for borders and dividers to create visual separation.",
823
- outlineVariant: "Lower-emphasis border color used for decorative dividers.",
824
- primary: "Main brand color, used for key components like filled buttons and active states.",
825
- primaryContainer: "Fill color for large primary elements like cards and tonal buttons.",
826
- primaryFixed: "Fixed primary color that stays the same in light and dark themes.",
827
- primaryFixedDim: "Dimmed variant of the fixed primary color for lower emphasis.",
828
- scrim: "Color overlay for modals and dialogs to obscure background content.",
829
- secondary: "Accent color for less prominent elements like filter chips and selections.",
830
- secondaryContainer: "Fill color for secondary container elements like tonal buttons and input fields.",
831
- secondaryFixed: "Fixed secondary color that stays the same in light and dark themes.",
832
- secondaryFixedDim: "Dimmed variant of the fixed secondary color for lower emphasis.",
833
- shadow: "Color for elevation shadows applied to surfaces and components.",
834
- surface: "Default surface color for cards, sheets, and dialogs.",
835
- surfaceBright: "Brightest surface variant, used for elevated surfaces in dark themes.",
836
- surfaceContainer: "Middle-emphasis container color for grouping related content.",
837
- surfaceContainerHigh: "Higher-emphasis container color for elements like cards.",
838
- surfaceContainerHighest: "Highest-emphasis container color for text fields and other input areas.",
839
- surfaceContainerLow: "Lower-emphasis container color for subtle surface groupings.",
840
- surfaceContainerLowest: "Lowest-emphasis container, typically the lightest surface in light theme.",
841
- surfaceDim: "Dimmest surface variant, used for recessed areas or dark theme backgrounds.",
842
- surfaceTint: "Tint color applied to surfaces for subtle primary color elevation overlay.",
843
- surfaceVariant: "Alternative surface color for differentiated areas like sidebar backgrounds.",
844
- tertiary: "Third accent color for complementary elements that balance primary and secondary.",
845
- tertiaryContainer: "Fill color for tertiary container elements like complementary cards.",
846
- tertiaryFixed: "Fixed tertiary color that stays the same in light and dark themes.",
847
- tertiaryFixedDim: "Dimmed variant of the fixed tertiary color for lower emphasis."
848
- };
849
- function isTokenName(key) {
850
- return key in tokenDescriptions;
851
- }
852
- var tokenNames = Object.keys(tokenDescriptions).filter(isTokenName);
853
899
  function deriveCustomPaletteName(tokenName, allPaletteNamesKebab) {
854
900
  let baseName = tokenName;
855
901
  if (/^on[A-Z]/.test(baseName) && baseName.length > 2) {
@@ -968,6 +1014,11 @@ function builder(hexSource, {
968
1014
  customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
969
1015
  prefix = DEFAULT_PREFIX
970
1016
  } = {}) {
1017
+ assertHexInputs(
1018
+ hexSource,
1019
+ { primary, secondary, tertiary, error, neutral, neutralVariant },
1020
+ hexCustomColors
1021
+ );
971
1022
  const sourceArgb = argbFromHex2(hexSource);
972
1023
  const sourceHct = Hct2.fromInt(sourceArgb);
973
1024
  const effectiveSource = primary || hexSource;
@@ -1111,6 +1162,8 @@ function builder(hexSource, {
1111
1162
  toFigmaTokens: () => buildFigmaTokens(ctx),
1112
1163
  toTailwind: (options) => buildTailwind(ctx, options),
1113
1164
  toShadcn: () => buildShadcn(ctx),
1165
+ toShadcnAliases: () => buildShadcnAliases(ctx),
1166
+ toShadcnRegistryItem: (options) => buildShadcnRegistryItem(ctx, options),
1114
1167
  toFlutter: () => buildFlutter(ctx),
1115
1168
  mergedColorsLight,
1116
1169
  mergedColorsDark,
package/dist/react.d.ts CHANGED
@@ -55,60 +55,29 @@ type FigmaVariable = {
55
55
  values: Record<string, FigmaVariableValue>;
56
56
  };
57
57
 
58
- /** A custom color defined with a hex string instead of an ARGB integer. */
59
- type HexCustomColor = Omit<CustomColor, "value"> & {
60
- hex: string;
61
- };
62
- /** Available Material You color scheme variants. */
63
- declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
64
- type SchemeName = (typeof schemeNames)[number];
65
- /** Configuration for the Material Theme Builder. */
66
- type MtbConfig = {
67
- /** Source color in hex format (e.g., "#6750A4") used to generate the color scheme */
68
- source: string;
69
- /** Color scheme variant. Default: "tonalSpot" */
70
- scheme?: SchemeName;
71
- /** Contrast level from -1.0 (reduced) to 1.0 (increased). Default: 0 (standard) */
72
- contrast?: number;
73
- /** Primary color - the main brand color. Overrides the default palette generation. */
74
- primary?: string;
75
- /** Secondary color - accent color. Overrides the default palette generation. */
76
- secondary?: string;
77
- /** Tertiary color - additional accent color. Overrides the default palette generation. */
78
- tertiary?: string;
79
- /** Neutral color - used for surfaces. Overrides the default palette generation. */
80
- neutral?: string;
81
- /** Neutral variant color - used for surfaces with slight tint. Overrides the default palette generation. */
82
- neutralVariant?: string;
83
- /** Error color - used for error states. Overrides the default palette generation. */
84
- error?: string;
85
- /**
86
- * Color match mode for core colors.
87
- * When true, stays true to input colors without harmonization.
88
- * When false (default), colors may be adjusted for better harmonization.
89
- * Corresponds to "Color match - Stay true to my color inputs" in Material Theme Builder.
90
- *
91
- * @deprecated Not yet implemented. This prop is currently ignored.
92
- */
93
- colorMatch?: boolean;
94
- /**
95
- * Array of custom colors to include in the generated palette.
96
- * Each custom color can be blended with the source color for harmonization.
97
- */
98
- customColors?: HexCustomColor[];
99
- /**
100
- * Prefix for generated CSS custom properties and Figma token css.variable extensions.
101
- * Scheme tokens use `--{prefix}-sys-color-*`, palette tones use `--{prefix}-ref-palette-*`.
102
- * Default: "md" (Material Design convention).
103
- */
104
- prefix?: string;
105
- };
106
58
  /**
107
59
  * Material Design 3 token names and their descriptions.
108
60
  *
109
61
  * Centralizes both the canonical list of scheme tokens and their M3 color role semantics.
110
62
  *
111
- * @see https://m3.material.io/styles/color/the-color-system/color-roles
63
+ * The Material Design blog is the best source on *why* these roles are shaped
64
+ * the way they are — the spec pages state the what, the blog posts the
65
+ * reasoning, and they are where role changes get announced first (the
66
+ * tone-based surfaces post is what documents `surface-variant` giving way to
67
+ * `surface-container-highest`).
68
+ *
69
+ * Note that the list below is wider than the spec's own inventory — "26
70
+ * standard color roles organized into six groups" — because it also carries
71
+ * the add-on roles (fixed accents, surface dim/bright, inverse) and the ones
72
+ * the spec has since dropped but the exporters still emit: `background`,
73
+ * `onBackground`, `surfaceVariant`, `surfaceTint`.
74
+ *
75
+ * Deep links below are section anchors; the spec site is a client-rendered SPA,
76
+ * so `#:~:text=` fragments are stripped on load and only these work.
77
+ *
78
+ * @see https://m3.material.io/styles/color/roles
79
+ * @see https://m3.material.io/blog/tone-based-surface-color-m3
80
+ * @see https://m3.material.io/blog/science-of-color-design
112
81
  */
113
82
  declare const tokenDescriptions: {
114
83
  readonly background: "Default background color for screens and large surfaces.";
@@ -164,6 +133,57 @@ declare const tokenDescriptions: {
164
133
  /** Union of all known M3 color token names. */
165
134
  type TokenName = keyof typeof tokenDescriptions;
166
135
 
136
+ /** A custom color defined with a hex string instead of an ARGB integer. */
137
+ type HexCustomColor = Omit<CustomColor, "value"> & {
138
+ hex: string;
139
+ };
140
+ /** Available Material You color scheme variants. */
141
+ declare const schemeNames: readonly ["tonalSpot", "monochrome", "neutral", "vibrant", "expressive", "fidelity", "content"];
142
+ type SchemeName = (typeof schemeNames)[number];
143
+ /** Configuration for the Material Theme Builder. */
144
+ type MtbConfig = {
145
+ /** Source color in hex format (e.g., "#6750A4") used to generate the color scheme */
146
+ source: string;
147
+ /** Color scheme variant. Default: "tonalSpot" */
148
+ scheme?: SchemeName;
149
+ /** Contrast level from -1.0 (reduced) to 1.0 (increased). Default: 0 (standard) */
150
+ contrast?: number;
151
+ /** Primary color - the main brand color. Overrides the default palette generation. */
152
+ primary?: string;
153
+ /** Secondary color - accent color. Overrides the default palette generation. */
154
+ secondary?: string;
155
+ /** Tertiary color - additional accent color. Overrides the default palette generation. */
156
+ tertiary?: string;
157
+ /** Neutral color - used for surfaces. Overrides the default palette generation. */
158
+ neutral?: string;
159
+ /** Neutral variant color - used for surfaces with slight tint. Overrides the default palette generation. */
160
+ neutralVariant?: string;
161
+ /** Error color - used for error states. Overrides the default palette generation. */
162
+ error?: string;
163
+ /**
164
+ * Color match mode for core colors.
165
+ * When true, stays true to input colors without harmonization.
166
+ * When false (default), colors may be adjusted for better harmonization.
167
+ * Corresponds to "Color match - Stay true to my color inputs" in Material Theme Builder.
168
+ *
169
+ * @deprecated Not yet implemented. This prop is currently ignored.
170
+ */
171
+ colorMatch?: boolean;
172
+ /**
173
+ * Array of custom colors to include in the generated palette.
174
+ * Each custom color can be blended with the source color for harmonization.
175
+ *
176
+ * @see https://m3.material.io/blog/dynamic-color-harmony
177
+ */
178
+ customColors?: HexCustomColor[];
179
+ /**
180
+ * Prefix for generated CSS custom properties and Figma token css.variable extensions.
181
+ * Scheme tokens use `--{prefix}-sys-color-*`, palette tones use `--{prefix}-ref-palette-*`.
182
+ * Default: "md" (Material Design convention).
183
+ */
184
+ prefix?: string;
185
+ };
186
+
167
187
  interface ExportButtonProps {
168
188
  /** Current theme configuration used to generate the exported tokens. */
169
189
  config: MtbConfig;