material-theme-builder 3.3.0 → 4.0.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
@@ -97,19 +97,43 @@ import { Mtb } from "material-theme-builder/react";
97
97
  >
98
98
  > Typically wrapping `{children}` in a
99
99
  > [layout](https://nextjs.org/docs/app/getting-started/layouts-and-pages#creating-a-layout).
100
+ >
101
+ > `<Mtb>` renders its `<style>`, so it works both server- and client-side.
102
+ > Client-side is what you want when the theme has to be interactive through
103
+ > `setMtbConfig`.
100
104
 
101
105
  > [!NOTE]
102
106
  >
103
- > CSS varnames are always kebab-cased, e.g. `myCustomColor1` →
104
- > `--md-sys-color-my-custom-color-1` / `--md-ref-palette-my-custom-color-1-<tone>`
107
+ > For a theme that is not interactive / never changes at runtime, skip the
108
+ > component entirely: the root entry holds `builder` alone, so a Server
109
+ > Component can call it and emit `toCss()` into the document itself — no client
110
+ > JS, and no `useMtb`.
111
+ >
112
+ > ```tsx
113
+ > import { builder } from "material-theme-builder";
114
+ >
115
+ > const css = builder("#0e1216", { scheme: "vibrant" }).toCss();
116
+ >
117
+ > export default function RootLayout({
118
+ > children,
119
+ > }: {
120
+ > children: React.ReactNode;
121
+ > }) {
122
+ > return (
123
+ > <html lang="en">
124
+ > <head>
125
+ > <style dangerouslySetInnerHTML={{ __html: css }} />
126
+ > </head>
127
+ > <body>{children}</body>
128
+ > </html>
129
+ > );
130
+ > }
131
+ > ```
105
132
 
106
133
  > [!NOTE]
107
134
  >
108
- > `<Mtb>` injects the CSS from the client, and is the only thing here carrying
109
- > `"use client"`. The root entry holds `builder` alone — so from a
110
- > [React Server Component](https://react.dev/reference/rsc/server-components)
111
- > you can call it and emit `toCss()` into the document yourself, without
112
- > shipping components the page never renders.
135
+ > CSS varnames are always kebab-cased, e.g. `myCustomColor1` →
136
+ > `--md-sys-color-my-custom-color-1` / `--md-ref-palette-my-custom-color-1-<tone>`
113
137
 
114
138
  ## `useMtb`
115
139
 
@@ -129,9 +153,8 @@ return (
129
153
 
130
154
  ## Tailwind
131
155
 
132
- Compatible through [theme variables](https://tailwindcss.com/docs/theme), in two
133
- halves — a stylesheet for the standard tokens, and a plugin for the custom
134
- colors, the one part a shipped file cannot know:
156
+ Compatible through [theme variables](https://tailwindcss.com/docs/theme) — a
157
+ stylesheet for the standard tokens, and a plugin for the custom colors:
135
158
 
136
159
  ```css
137
160
  @import "tailwindcss";
@@ -142,12 +165,15 @@ colors, the one part a shipped file cannot know:
142
165
  }
143
166
  ```
144
167
 
145
- No hand-written block either side. Each name listed brings its four scheme roles
146
- and eleven shades — `bg-myCustomColor1`, `text-on-myCustomColor1`,
147
- `bg-myCustomColor1-container`, `bg-myCustomColor1-300`. Drop the `@plugin` line
148
- if you have no custom colors.
168
+ Drop the `@plugin` line if you have no custom colors.
169
+
170
+ <details>
171
+
172
+ Each name listed brings its four scheme roles and eleven shades —
173
+ `bg-myCustomColor1`, `text-on-myCustomColor1`, `bg-myCustomColor1-container`,
174
+ `bg-myCustomColor1-300`.
149
175
 
150
- The plugin takes a `prefix` too, mirroring `builder({ prefix })`:
176
+ `prefix` mirrors `builder({ prefix })`:
151
177
 
152
178
  ```css
153
179
  @plugin "material-theme-builder/tailwind" {
@@ -156,42 +182,15 @@ The plugin takes a `prefix` too, mirroring `builder({ prefix })`:
156
182
  }
157
183
  ```
158
184
 
159
- The plugin can carry the standard tokens on its own — `@plugin` without the
160
- `@import` — for a setup that would rather not import CSS at all. Read the
161
- warning below first if you also use shadcn.
185
+ </details>
162
186
 
163
187
  > [!TIP]
164
188
  >
165
- > Both halves declare their colors as
189
+ > Colors are declared as
166
190
  > [inlined theme values](https://tailwindcss.com/docs/theme#referencing-other-variables):
167
- > `bg-primary` compiles to `background-color: var(--md-sys-color-primary)`,
168
- > with no `--color-primary` in between. That matters for nesting — a
169
- > `--color-primary` declared on `:root` would resolve against `:root`, out of
170
- > reach of a nested `<Mtb>` re-declaring the M3 properties.
171
-
172
- > [!WARNING]
173
- >
174
- > Theme values a plugin contributes are _defaults_: an `@theme` block of your
175
- > own wins over them whatever the order, where the stylesheet — being CSS —
176
- > wins by import order.
177
- >
178
- > That is why the standard tokens are left to the stylesheet. shadcn's
179
- > `@theme inline` claims three names M3 also uses — `background`, `primary`,
180
- > `secondary` — and with the two halves above the stylesheet takes them back,
181
- > so shadcn changes nothing. Only if you drop the `@import` and let the plugin
182
- > carry the standard tokens do you have to hand those three back yourself:
183
- >
184
- > ```css
185
- > @theme inline {
186
- > --color-background: var(--md-sys-color-background);
187
- > --color-primary: var(--md-sys-color-primary);
188
- > --color-secondary: var(--md-sys-color-secondary);
189
- > }
190
- > ```
191
- >
192
- > Left alone, `bg-secondary` resolves through shadcn's `--secondary`, which the
193
- > [shadcn](#shadcn) section below remaps to `secondary-container` — a tone 90
194
- > where you asked for a tone 40, under text still colored `on-secondary`.
191
+ > `bg-primary` compiles to `background-color: var(--md-sys-color-primary)`, with
192
+ > no `--color-primary` in between. That one would sit on `:root`, out of reach
193
+ > of a nested `<Mtb>`.
195
194
 
196
195
  <details>
197
196
  <summary>The theme variables the stylesheet declares</summary>
@@ -225,157 +224,110 @@ Pre-requisites:
225
224
  - You should use
226
225
  [`tailwind.cssVariables`](https://ui.shadcn.com/docs/theming#css-variables)
227
226
 
228
- One command, from inside your project:
229
-
230
- ```sh
231
- $ npx material-theme-builder shadcn-apply "#6750A4"
232
- ```
233
-
234
- Or from nothing at all — `shadcn-init` scaffolds a stock shadcn app
235
- (`shadcn init --preset b0 --template vite`), themes it and starts it:
227
+ In your
228
+ [`globals.css`](https://ui.shadcn.com/docs/installation/manual#configure-styles):
236
229
 
237
- ```sh
238
- $ npx material-theme-builder shadcn-init "#6750A4"
239
- ```
230
+ ```css
231
+ @import "tailwindcss";
232
+ @import "tw-animate-css";
233
+ @import "shadcn/tailwind.css";
240
234
 
241
- The verbs are shadcn's own — there, `init` is the new project and `apply` the
242
- existing one — and they are prefixed because shadcn is one integration here
243
- among Figma, CSS, Tailwind and Flutter: a bare `init` would read as "initialize
244
- material-theme-builder", and would leave no room for a `tailwind-init` later.
245
-
246
- Anything after a `--` is forwarded verbatim to the shadcn command underneath —
247
- `shadcn init` for `shadcn-init`, `shadcn add` for `shadcn-apply` — so
248
- `shadcn-init "#6750A4" -- --template next -n my-app` scaffolds Next instead.
249
- Options of ours go before the separator; one written after it is refused, rather
250
- than forwarded into an error from shadcn about a flag it has never heard of.
251
- `--print` writes the equivalent shell chain and runs nothing.
252
-
253
- `--shadcn-cli <spec>` pins which shadcn runs — `--shadcn-cli shadcn@4.18.0`, a
254
- tag, a fork, anything `npx` resolves — defaulting to `shadcn@latest`. (Not
255
- `--shadcn`: the root command has used that name since 3.2.0 for something else
256
- entirely, a boolean that appends the alias block to `--format tailwind`.) It
257
- reaches for neighbouring versions rather than back in time, though: the defaults
258
- these commands pass are shadcn 4.x vocabulary (`--preset b0` is a 4.x preset
259
- code), so pinning far enough back also means passing that era's preset after the
260
- `--`.
261
-
262
- Both do the same two things by hand, if you would rather: generate a registry
263
- item for your source color, and install it the way you install any shadcn theme.
235
+ /* 👇🏻 ADD THIS 👇🏻 */
236
+ @import "material-theme-builder/tailwind.css"; /* the M3 tw classNames (optional) */
237
+ @import "material-theme-builder/shadcn.css"; /* shadcn's variables remapping on M3 */
238
+ @plugin "material-theme-builder/tailwind" { /* your custom colors (optional) */
239
+ custom-colors: myCustomColor1, myCustomColor2;
240
+ }
241
+ /* 👆🏻 ADD THIS 👆🏻 */
264
242
 
265
- ```sh
266
- $ npx material-theme-builder "#6750A4" --format registry-item > mtb.json
267
- $ npx shadcn@latest add ./mtb.json && rm mtb.json
268
- ```
243
+ @custom-variant dark (&:is(.dark *));
269
244
 
270
- Either way, that rewrites the values inside your existing `:root` and `.dark`
271
- blocks, in place, pointing
272
- [shadcn's CSS variables](https://ui.shadcn.com/docs/theming#list-of-variables)
273
- at the M3 custom properties `<Mtb>` emits — so every shadcn component follows
274
- whichever theme is above it in the tree — and leaves that theme's own colors in
275
- as the `var()` fallbacks:
245
+ @theme inline {
246
+ --color-background: var(--background);
247
+ ...
248
+ }
276
249
 
277
- ```css
278
250
  :root {
279
- --card: var(--md-sys-color-surface-container-low, oklch(0.968 0.012 317.742));
251
+ --radius: 0.625rem;
252
+ --background: oklch(1 0 0);
253
+ ...
280
254
  }
281
255
 
282
256
  .dark {
283
- --card: var(--md-sys-color-surface-container-low, oklch(0.227 0.01 303.714));
257
+ --background: oklch(0.145 0 0);
258
+ ...
284
259
  }
285
260
  ```
286
261
 
287
- So it works both ways round: live under an `<Mtb>`, and static — server-rendered,
288
- zero client JS — anywhere there is none.
262
+ `shadcn.css` is the one that matters: it points
263
+ [shadcn's variables](https://ui.shadcn.com/docs/theming#list-of-variables) at
264
+ the M3 custom properties, so every shadcn component follows whichever `<Mtb>` is
265
+ above it in the tree. It carries no colors of its own — mount an `<Mtb>`, or
266
+ emit [`toCss()`](#programmatic-api) server-side, or nothing resolves.
289
267
 
290
- Every option lands in those fallbacks, `--scheme` and `--contrast` included, and
291
- `shadcn-init` and `shadcn-apply` take them all — so the item they generate is the one the
292
- by-hand route would have produced:
268
+ The other two are optional. They are the [Tailwind](#tailwind) recipe unchanged,
269
+ and what they add is names to write yourself — `bg-surface-container-low`,
270
+ `text-on-primary`, your custom colors. Drop them and every shadcn component
271
+ still follows the theme.
293
272
 
294
- ```sh
295
- $ npx material-theme-builder shadcn-apply "#6750A4" --scheme vibrant --contrast 0.5
296
- ```
297
-
298
- `--no-fallback` leaves the fallbacks out, on all three. `--custom-colors` is the
299
- one option the two subcommands do not take, and that is not an oversight:
300
- shadcn's variable set is fixed, so no component reads a custom color and a
301
- registry item cannot carry one.
273
+ For the opposite trade — concrete `oklch()` values and no `var()` at all, frozen
274
+ at build time — see [`toShadcn()`](#programmatic-api).
302
275
 
303
- > [!WARNING]
304
- >
305
- > `shadcn add` overwrites shadcn's own `oklch()` values rather than keeping them
306
- > anywhere, so they are not a safety net. Where nothing declares the M3
307
- > properties _and_ there are no fallbacks, every variable resolves to nothing and
308
- > components render transparent — `git diff` your CSS, or re-run `shadcn init`,
309
- > to get shadcn's defaults back.
276
+ <details>
277
+ <summary>The three names both halves claim</summary>
310
278
 
311
279
  > [!NOTE]
312
280
  >
313
- > shadcn's CLI also appends a self-referential `--card: var(--card);` per
314
- > variable to your `@theme inline` block. It is noise, not a bug on your side:
315
- > those land _above_ your `:root`, so the real values win. Delete them if they
316
- > bother you.
281
+ > Written down for the record. It moves one utility by one role, and you almost
282
+ > certainly do not need to care.
317
283
 
318
- For the opposite trade — concrete `oklch()` values and no `var()` at all, frozen
319
- at build time — see [`toShadcn()`](#programmatic-api).
284
+ Material and shadcn picked the same name for three things — `background`,
285
+ `primary`, `secondary`. shadcn's `@theme inline` is the later of the two, so on
286
+ those three it wins, and the utility goes through the mapping above:
320
287
 
321
- <details>
322
- <summary>Install the mapping alone, without generating anything</summary>
288
+ ```
289
+ bg-secondary → --color-secondary → var(--secondary) → var(--md-sys-color-secondary-container)
290
+ ```
323
291
 
324
- The package publishes one too, so `shadcn add` has something to fetch without a
325
- build step of yours:
292
+ Without shadcn it is one hop shorter, and lands on the role of the same name:
326
293
 
327
- ```sh
328
- $ npx shadcn@latest add https://unpkg.com/material-theme-builder/registry-item.json
294
+ ```
295
+ bg-secondary → --color-secondary → var(--md-sys-color-secondary)
329
296
  ```
330
297
 
331
- It is the mapping and nothing else — 31 `var()` references, no colors. Which is
332
- why it can be published at all: it is the same file whatever your source color,
333
- scheme or contrast, because those arrive at runtime from `<Mtb>`. And that is
334
- also its one requirement — mount an `<Mtb>`, or emit
335
- [`toCss()`](#programmatic-api) server-side, or nothing resolves. Generate your
336
- own, as above, to have colors to fall back on.
298
+ Same destination either way, M3 — just not the same role. And only for
299
+ `secondary`: `primary` maps to `primary`, and M3 `background` and `surface` are
300
+ the same color.
337
301
 
338
- </details>
302
+ If you ever want the M3 role itself, `<Mtb>` still emits it:
339
303
 
340
- <details>
341
- <summary>Rather import a stylesheet than let the CLI edit your file</summary>
304
+ ```html
305
+ <div class="bg-[var(--md-sys-color-secondary)]"></div>
306
+ ```
342
307
 
343
- A stylesheet is shipped too, for setups that would rather keep the mapping in
344
- one line they can delete:
308
+ or give it a name of its own:
345
309
 
346
310
  ```css
347
- @import "tailwindcss";
348
- @import "./shadcn.css"; /* shadcn's own `:root` and `.dark` */
349
- @import "material-theme-builder/shadcn.css"; /* ...then ours */
311
+ @theme inline {
312
+ --color-m3-secondary: var(--md-sys-color-secondary);
313
+ }
350
314
  ```
351
315
 
352
- > [!IMPORTANT]
353
- >
354
- > It has to come AFTER shadcn's own `:root { ... }` and `.dark { ... }`, which
355
- > it overrides — so **not** at the top with your other imports, which is where
356
- > an `@import` normally goes and where this one silently loses:
357
- >
358
- > ```css
359
- > /* ✗ `--card` falls back to shadcn's grey; nothing warns you */
360
- > @import "tailwindcss";
361
- > @import "material-theme-builder/shadcn.css";
362
- > @import "./shadcn.css";
363
- > ```
364
- >
365
- > The registry item above exists to make this impossible to get wrong.
366
-
367
316
  </details>
368
317
 
369
318
  <details>
370
319
  <summary>The variables it remaps</summary>
371
320
 
372
321
  Both halves are generated from [`toShadcnAliases()`](#programmatic-api) and
373
- [`toShadcnRegistryItem()`](#programmatic-api), off one mapping, so neither can
374
- drift from the other:
322
+ [`toShadcnRegistryItem()`](#programmatic-api), off one mapping, so they cannot
323
+ drift. The selectors are doubled so the block outranks shadcn's own `:root` and
324
+ `.dark` on
325
+ [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Specificity#increasing_specificity_by_duplicating_selector)
326
+ rather than on order — which is what lets the `@import` sit with your others.
375
327
 
376
328
  ```css
377
- :root,
378
- .dark {
329
+ :root:root,
330
+ .dark.dark {
379
331
  --background: var(--md-sys-color-surface);
380
332
  --foreground: var(--md-sys-color-on-surface);
381
333
  --card: var(--md-sys-color-surface-container-low);
@@ -412,12 +364,106 @@ drift from the other:
412
364
 
413
365
  </details>
414
366
 
367
+ ### `shadcn-apply`
368
+
369
+ The alternative, for colors to fall back on and no import to place. One command,
370
+ from inside your project:
371
+
372
+ ```sh
373
+ $ npx material-theme-builder shadcn-apply "#6750A4"
374
+ ```
375
+
376
+ From nothing at all, scaffold with shadcn's own CLI first — what this repo
377
+ dogfoods:
378
+
379
+ ```sh
380
+ $ npx shadcn@latest init --preset b0 --name material-theme-app
381
+ $ cd material-theme-app && npx material-theme-builder shadcn-apply "#6750A4"
382
+ ```
383
+
384
+ It generates a registry item for your source color and hands it to `shadcn add`,
385
+ which rewrites the values inside your existing `:root` and `.dark` blocks, in
386
+ place. Same mapping as the stylesheet, with that theme's own colors left in as
387
+ the `var()` fallbacks:
388
+
389
+ ```css
390
+ :root {
391
+ --card: var(--md-sys-color-surface-container-low, oklch(0.968 0.012 317.742));
392
+ }
393
+
394
+ .dark {
395
+ --card: var(--md-sys-color-surface-container-low, oklch(0.227 0.01 303.714));
396
+ }
397
+ ```
398
+
399
+ So it works with no `<Mtb>` at all — the fallbacks render the theme statically,
400
+ server-rendered, zero client JS. Your old values are overwritten, not kept
401
+ anywhere: `git diff` is the undo.
402
+
403
+ Both steps by hand, if you would rather:
404
+
405
+ ```sh
406
+ $ npx material-theme-builder "#6750A4" --format registry-item > mtb.json
407
+ $ npx shadcn@latest add ./mtb.json && rm mtb.json
408
+ ```
409
+
410
+ `shadcn-apply` takes every theme option `material-theme-builder` itself takes,
411
+ and they all land in those fallbacks:
412
+
413
+ ```sh
414
+ $ npx material-theme-builder shadcn-apply "#6750A4" --scheme vibrant --contrast 0.5
415
+ ```
416
+
417
+ <details>
418
+ <summary>The rest of the options</summary>
419
+
420
+ `--no-fallback` leaves the fallbacks out, on both — so shadcn's own colors are
421
+ dropped rather than kept in reserve. Nothing then declares those variables
422
+ except an `<Mtb>` or a [`toCss()`](#programmatic-api): without one, they resolve
423
+ to nothing and the components render transparent.
424
+
425
+ `--custom-colors` is the one option missing: shadcn's variable set is fixed, so
426
+ a registry item cannot carry one.
427
+
428
+ Anything after a `--` is forwarded verbatim to `shadcn add`. Our options go
429
+ before it:
430
+
431
+ ```sh
432
+ $ npx material-theme-builder shadcn-apply "#6750A4" -- --overwrite --dry-run
433
+ ```
434
+
435
+ > [!NOTE]
436
+ >
437
+ > shadcn's CLI also appends a self-referential `--card: var(--card);` per
438
+ > variable to your `@theme inline` block. Noise, not a bug: they land _above_
439
+ > your `:root`, so the real values win. Delete them if they bother you.
440
+
441
+ </details>
442
+
443
+ <details>
444
+ <summary>Install the mapping alone, without generating anything</summary>
445
+
446
+ The package publishes a registry item too, so `shadcn add` has something to
447
+ fetch without a build step:
448
+
449
+ ```sh
450
+ $ npx shadcn@latest add https://unpkg.com/material-theme-builder/registry-item.json
451
+ ```
452
+
453
+ It is the stylesheet's content, installed the registry way: the mapping and
454
+ nothing else, no colors to fall back on. Generate your own, as above, to have
455
+ some.
456
+
457
+ </details>
458
+
415
459
  <details>
416
- <summary>mapping details</summary>
417
- see:
418
-
419
- - https://chatgpt.com/share/6899f20a-422c-8011-a072-62fb649589a0
420
- - https://gemini.google.com/share/51e072b6f1d2
460
+ <summary>mapping details</summary>
461
+
462
+ see:
463
+
464
+ - https://chatgpt.com/share/6899f20a-422c-8011-a072-62fb649589a0
465
+ - https://gemini.google.com/share/51e072b6f1d2
466
+
421
467
  </details>
422
468
 
423
469
  # Dev
package/dist/cli.js CHANGED
@@ -3,12 +3,11 @@
3
3
  // src/cli.ts
4
4
  import * as fs2 from "fs";
5
5
  import * as path2 from "path";
6
- import { Command as Command2 } from "commander";
6
+ import { Command, Option as Option2 } from "commander";
7
7
  import { z } from "zod";
8
8
 
9
9
  // src/cli.options.ts
10
10
  import {
11
- Command,
12
11
  InvalidArgumentError,
13
12
  Option
14
13
  } from "commander";
@@ -683,8 +682,8 @@ function buildShadcnAliases(ctx) {
683
682
  const lines = Object.entries(toShadcnAliasVars(ctx.prefix)).map(
684
683
  ([name, value]) => `--${name}: ${value};`
685
684
  );
686
- return `:root,
687
- .dark {
685
+ return `:root:root,
686
+ .dark.dark {
688
687
  ${lines.join("\n ")}
689
688
  }
690
689
  `;
@@ -870,6 +869,9 @@ function assertHexInputs(source, cores, customColors) {
870
869
  (color, i) => assertHexColor(`customColors[${i}].hex`, color.hex)
871
870
  );
872
871
  }
872
+ function optionalHex(hex) {
873
+ return hex?.trim() || void 0;
874
+ }
873
875
  var STANDARD_TONES = [
874
876
  0,
875
877
  4,
@@ -1038,16 +1040,20 @@ function builder(hexSource, {
1038
1040
  customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
1039
1041
  prefix = DEFAULT_PREFIX
1040
1042
  } = {}) {
1041
- assertHexInputs(
1042
- hexSource,
1043
- { primary, secondary, tertiary, error, neutral, neutralVariant },
1044
- hexCustomColors
1045
- );
1043
+ const cores = {
1044
+ primary: optionalHex(primary),
1045
+ secondary: optionalHex(secondary),
1046
+ tertiary: optionalHex(tertiary),
1047
+ error: optionalHex(error),
1048
+ neutral: optionalHex(neutral),
1049
+ neutralVariant: optionalHex(neutralVariant)
1050
+ };
1051
+ assertHexInputs(hexSource, cores, hexCustomColors);
1046
1052
  const sourceArgb = argbFromHex2(hexSource);
1047
1053
  const sourceHct = Hct2.fromInt(sourceArgb);
1048
- const effectiveSource = primary || hexSource;
1054
+ const effectiveSource = cores.primary || hexSource;
1049
1055
  const effectiveSourceArgb = argbFromHex2(effectiveSource);
1050
- const effectiveSourceForHarmonization = primary ? argbFromHex2(primary) : sourceArgb;
1056
+ const effectiveSourceForHarmonization = cores.primary ? argbFromHex2(cores.primary) : sourceArgb;
1051
1057
  const SchemeClass = schemesMap[scheme];
1052
1058
  const primaryHct = Hct2.fromInt(effectiveSourceArgb);
1053
1059
  const baseScheme = new SchemeClass(primaryHct, false, contrast);
@@ -1055,32 +1061,32 @@ function builder(hexSource, {
1055
1061
  // Core colors (hex may be undefined)
1056
1062
  {
1057
1063
  name: "primary",
1058
- hex: primary,
1064
+ hex: cores.primary,
1059
1065
  core: true,
1060
1066
  chromaSource: "primary"
1061
1067
  },
1062
1068
  {
1063
1069
  name: "secondary",
1064
- hex: secondary,
1070
+ hex: cores.secondary,
1065
1071
  core: true,
1066
1072
  chromaSource: "primary"
1067
1073
  },
1068
1074
  {
1069
1075
  name: "tertiary",
1070
- hex: tertiary,
1076
+ hex: cores.tertiary,
1071
1077
  core: true,
1072
1078
  chromaSource: "primary"
1073
1079
  },
1074
- { name: "error", hex: error, core: true, chromaSource: "primary" },
1080
+ { name: "error", hex: cores.error, core: true, chromaSource: "primary" },
1075
1081
  {
1076
1082
  name: "neutral",
1077
- hex: neutral,
1083
+ hex: cores.neutral,
1078
1084
  core: true,
1079
1085
  chromaSource: "neutral"
1080
1086
  },
1081
1087
  {
1082
1088
  name: "neutralVariant",
1083
- hex: neutralVariant,
1089
+ hex: cores.neutralVariant,
1084
1090
  core: true,
1085
1091
  chromaSource: "neutralVariant"
1086
1092
  },
@@ -1231,7 +1237,6 @@ function addThemeOptions(command) {
1231
1237
  DEFAULT_PREFIX
1232
1238
  );
1233
1239
  }
1234
- var THEME_OPTIONS = addThemeOptions(new Command()).options;
1235
1240
  function builderOptions(opts) {
1236
1241
  return {
1237
1242
  scheme: opts.scheme,
@@ -1245,16 +1250,6 @@ function builderOptions(opts) {
1245
1250
  prefix: opts.prefix
1246
1251
  };
1247
1252
  }
1248
- function specifiedThemeArgs(command) {
1249
- const args = [];
1250
- for (const option of THEME_OPTIONS) {
1251
- const name = option.attributeName();
1252
- if (command.getOptionValueSource(name) !== "cli") continue;
1253
- if (option.long) args.push(option.long);
1254
- if (!option.negate) args.push(String(command.opts()[name]));
1255
- }
1256
- return args;
1257
- }
1258
1253
  function themeFrom(command) {
1259
1254
  const opts = command.opts();
1260
1255
  return {
@@ -1263,256 +1258,48 @@ function themeFrom(command) {
1263
1258
  // opposite of the API's, and deliberately so: a CLI knows the source color,
1264
1259
  // which is what makes baking its colors in as the fallbacks free. See
1265
1260
  // `buildShadcnRegistryItem()`.
1266
- fallback: opts.fallback ?? true,
1267
- args: specifiedThemeArgs(command)
1261
+ fallback: opts.fallback ?? true
1268
1262
  };
1269
1263
  }
1270
1264
 
1271
1265
  // src/cli.shadcn.ts
1272
1266
  import { spawnSync } from "child_process";
1273
1267
  import * as fs from "fs";
1274
- import * as os from "os";
1275
1268
  import * as path from "path";
1276
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1269
+ import "commander";
1277
1270
  var NPX = ["npx", "--yes"];
1278
1271
  var DEFAULT_SHADCN = "shadcn@latest";
1279
- var SELF = "material-theme-builder";
1280
- var INIT_TARGET = "shadcn init";
1281
- var ADD_TARGET = "shadcn add";
1272
+ var YES = "--yes";
1282
1273
  var ITEM = "mtb.json";
1283
- var APP_NAME = "material-theme-app";
1284
- var NAME = { long: "--name", short: "-n", value: APP_NAME };
1285
- var YES = { long: "--yes", short: "-y" };
1286
- var INIT_DEFAULTS = [
1287
- { long: "--preset", short: "-p", value: "b0" },
1288
- { long: "--template", short: "-t", value: "vite" },
1289
- NAME,
1290
- YES
1291
- ];
1292
- var ADD_DEFAULTS = [YES];
1293
- function parseShadcnSpec(value) {
1294
- if (value.startsWith("-"))
1295
- throw new InvalidArgumentError2(
1296
- "A package spec cannot start with '-': npx would read it as one of its own options."
1297
- );
1298
- if (/^\d/.test(value))
1299
- throw new InvalidArgumentError2(
1300
- `That is a version, not a package spec -- write 'shadcn@${value}'.`
1301
- );
1302
- return value;
1303
- }
1304
- function addChainOptions(command) {
1305
- return command.option(
1306
- "--shadcn-cli <spec>",
1307
- "npx package spec for the shadcn CLI to run (a version, tag, fork or tarball \u2014 anything npx resolves)",
1308
- parseShadcnSpec,
1309
- DEFAULT_SHADCN
1310
- ).option(
1311
- "--print",
1312
- "Print the equivalent shell chain instead of running it"
1313
- );
1314
- }
1315
- function mentions(args, { long, short }) {
1316
- return args.some(
1317
- (arg) => arg === long || arg.startsWith(`${long}=`) || arg.startsWith(short)
1318
- );
1319
- }
1320
- function mergeDefaults(defaults, forwarded) {
1321
- const injected = defaults.filter((option) => !mentions(forwarded, option)).flatMap(
1322
- ({ long, value }) => value === void 0 ? [long] : [long, value]
1323
- );
1324
- return [...injected, ...forwarded];
1325
- }
1326
- function lastName(args) {
1327
- for (let i = args.length - 1; i >= 0; i--) {
1328
- const arg = args[i] ?? "";
1329
- if (arg === NAME.long || arg === NAME.short) return args[i + 1];
1330
- if (arg.startsWith(`${NAME.long}=`)) return arg.slice(NAME.long.length + 1);
1331
- if (arg.length > NAME.short.length && arg.startsWith(NAME.short))
1332
- return arg.slice(NAME.short.length);
1333
- }
1334
- return void 0;
1335
- }
1336
- function ownOptionIn(declared, forwarded) {
1337
- return forwarded.find(
1338
- (arg) => declared.some(
1339
- (option) => arg === option.long || arg === option.short || option.long !== void 0 && arg.startsWith(`${option.long}=`)
1340
- )
1341
- );
1342
- }
1343
- function refuseOwnOptions(command, forwarded, target) {
1344
- const misplaced = ownOptionIn(command.options, forwarded);
1345
- if (misplaced !== void 0)
1346
- fail(
1347
- `${misplaced} is ours, not shadcn's, so it belongs before the \`--\`. Everything after the separator is handed to \`${target}\` untouched.`
1348
- );
1349
- }
1350
- var DEFAULT_THEME = { options: {}, fallback: true, args: [] };
1351
- function chainOptionsFrom(command) {
1352
- return { theme: themeFrom(command), shadcn: command.opts().shadcnCli };
1353
- }
1354
- function initPlan(source, forwarded = [], {
1355
- theme = DEFAULT_THEME,
1356
- shadcn = DEFAULT_SHADCN
1357
- } = {}) {
1358
- const args = mergeDefaults(INIT_DEFAULTS, forwarded);
1359
- const dir = lastName(args) ?? APP_NAME;
1360
- return {
1361
- source,
1362
- theme,
1363
- dir,
1364
- steps: [
1365
- {
1366
- kind: "spawn",
1367
- label: INIT_TARGET,
1368
- argv: [...NPX, shadcn, "init", ...args],
1369
- where: "cwd"
1370
- },
1371
- { kind: "cd", dir },
1372
- { kind: "item", file: ITEM },
1373
- // The same spec as the step above, deliberately: a chain pinned for the
1374
- // scaffold and floating for the install is worse than one that floats
1375
- // throughout, since the two would disagree about the registry format
1376
- // without saying so.
1377
- {
1378
- kind: "spawn",
1379
- label: ADD_TARGET,
1380
- argv: [
1381
- ...NPX,
1382
- shadcn,
1383
- "add",
1384
- `./${ITEM}`,
1385
- ...mergeDefaults(ADD_DEFAULTS, [])
1386
- ],
1387
- where: "project"
1388
- },
1389
- { kind: "rm", file: ITEM },
1390
- // The handover: long-running by design, stdio inherited, ctrl-C reaching
1391
- // it. Package-manager detection is out of scope -- `shadcn init` under
1392
- // `npx` writes a `package-lock.json` and the `dev` script npm will run.
1393
- {
1394
- kind: "spawn",
1395
- label: "npm run dev",
1396
- argv: ["npm", "run", "dev"],
1397
- where: "project"
1398
- }
1399
- ]
1400
- };
1401
- }
1402
- function applyPlan(source, forwarded = [], {
1403
- theme = DEFAULT_THEME,
1404
- shadcn = DEFAULT_SHADCN
1405
- } = {}) {
1406
- return {
1407
- source,
1408
- theme,
1409
- steps: [
1410
- { kind: "item", file: ITEM },
1411
- {
1412
- kind: "spawn",
1413
- label: ADD_TARGET,
1414
- argv: [
1415
- ...NPX,
1416
- shadcn,
1417
- "add",
1418
- `./${ITEM}`,
1419
- ...mergeDefaults(ADD_DEFAULTS, forwarded)
1420
- ],
1421
- where: "cwd"
1422
- },
1423
- { kind: "rm", file: ITEM }
1424
- ]
1425
- };
1426
- }
1427
- var SHELL_SAFE = /^[\w@%+=:,./-]+$/;
1428
- function quote(argv) {
1429
- return argv.map(
1430
- (arg) => SHELL_SAFE.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`
1431
- ).join(" ");
1432
- }
1433
- function renderStep(plan, step) {
1434
- switch (step.kind) {
1435
- case "spawn":
1436
- return quote(step.argv);
1437
- case "cd":
1438
- return `cd ${step.dir}`;
1439
- // Rendered as the command someone could have run themselves -- theme options
1440
- // included, or the line would describe a different theme from the one the
1441
- // runner generates. The runner does the same thing in-process, being the very
1442
- // binary this spawns.
1443
- case "item":
1444
- return `${quote([...NPX, SELF, plan.source, ...plan.theme.args, "--format", "registry-item"])} > ${step.file}`;
1445
- case "rm":
1446
- return `rm ${step.file}`;
1447
- }
1448
- }
1449
- function renderChain(plan) {
1450
- return plan.steps.map((step) => renderStep(plan, step)).join(" && ");
1274
+ function addArgv(shadcn, forwarded = []) {
1275
+ return [...NPX, shadcn, "add", `./${ITEM}`, YES, ...forwarded];
1451
1276
  }
1452
1277
  function fail(message, code = 1) {
1453
1278
  console.error(`Error: ${message}`);
1454
1279
  process.exit(code);
1455
1280
  }
1456
- function run({ label, argv }, cwd) {
1457
- const [command = "", ...args] = argv;
1458
- const { error, status, signal } = spawnSync(command, args, {
1459
- cwd,
1460
- stdio: "inherit",
1461
- shell: false
1462
- });
1463
- if (error) fail(`${label} could not be started: ${error.message}`);
1464
- if (signal) process.exit(128 + (os.constants.signals[signal] ?? 0));
1465
- if (status !== 0) fail(`${label} failed`, status ?? 1);
1466
- }
1467
- function writeItem({ source, theme }, file) {
1281
+ function writeItem(source, command, file) {
1468
1282
  if (fs.existsSync(file))
1469
1283
  fail(
1470
1284
  `${path.basename(file)} already exists and would be overwritten, then deleted. Move it aside first.`
1471
1285
  );
1472
1286
  process.on("exit", () => fs.rmSync(file, { force: true }));
1473
- const item = builder(source, theme.options).toShadcnRegistryItem({
1474
- fallback: theme.fallback
1475
- });
1287
+ const { options, fallback } = themeFrom(command);
1288
+ const item = builder(source, options).toShadcnRegistryItem({ fallback });
1476
1289
  fs.writeFileSync(file, `${JSON.stringify(item, null, 2)}
1477
1290
  `);
1478
1291
  }
1479
- function runStep(plan, step, projectDir) {
1480
- switch (step.kind) {
1481
- case "spawn":
1482
- return run(step, step.where === "project" ? projectDir : process.cwd());
1483
- case "item":
1484
- return writeItem(plan, path.join(projectDir, step.file));
1485
- case "rm":
1486
- return fs.rmSync(path.join(projectDir, step.file), { force: true });
1487
- // Nothing to do: the runner resolved the directory up front, where a shell
1488
- // would have changed into it. The step exists so that `--print` can say so.
1489
- case "cd":
1490
- return;
1491
- }
1492
- }
1493
- function runChain(plan) {
1494
- const projectDir = plan.dir ? path.resolve(plan.dir) : process.cwd();
1495
- if (plan.dir && fs.existsSync(projectDir))
1496
- fail(
1497
- `./${plan.dir} already exists. Pick another name with \`-- -n other-name\`, or remove it first.`
1498
- );
1499
- for (const step of plan.steps) runStep(plan, step, projectDir);
1500
- }
1501
- function execute(plan, command) {
1502
- if (command.opts().print === true) {
1503
- process.stdout.write(`${renderChain(plan)}
1504
- `);
1505
- return;
1506
- }
1507
- runChain(plan);
1508
- }
1509
- function runInit(source, forwarded, command) {
1510
- refuseOwnOptions(command, forwarded, INIT_TARGET);
1511
- execute(initPlan(source, forwarded, chainOptionsFrom(command)), command);
1512
- }
1513
1292
  function runApply(source, forwarded, command) {
1514
- refuseOwnOptions(command, forwarded, ADD_TARGET);
1515
- execute(applyPlan(source, forwarded, chainOptionsFrom(command)), command);
1293
+ const file = path.resolve(ITEM);
1294
+ writeItem(source, command, file);
1295
+ const [bin = "", ...args] = addArgv(command.opts().shadcnCli, forwarded);
1296
+ const { error, status } = spawnSync(bin, args, {
1297
+ stdio: "inherit",
1298
+ shell: false
1299
+ });
1300
+ if (error) fail(`shadcn add could not be started: ${error.message}`);
1301
+ if (status !== 0) fail("shadcn add failed", status ?? 1);
1302
+ fs.rmSync(file, { force: true });
1516
1303
  }
1517
1304
 
1518
1305
  // src/cli.ts
@@ -1534,9 +1321,20 @@ function writeFigmaTokens(theme, outputDir) {
1534
1321
  console.error(`wrote ${filePath}`);
1535
1322
  }
1536
1323
  }
1324
+ var FORMATS = [
1325
+ "json",
1326
+ "css",
1327
+ "figma",
1328
+ "tailwind",
1329
+ "shadcn",
1330
+ "registry-item",
1331
+ "flutter"
1332
+ ];
1537
1333
  function writeOutput(theme, opts) {
1538
1334
  const json = (value) => JSON.stringify(value, null, 2) + "\n";
1539
1335
  switch (opts.format) {
1336
+ case "json":
1337
+ return process.stdout.write(json(theme.toJson()));
1540
1338
  case "css":
1541
1339
  return process.stdout.write(theme.toCss());
1542
1340
  case "tailwind":
@@ -1551,11 +1349,9 @@ function writeOutput(theme, opts) {
1551
1349
  return process.stdout.write(theme.toFlutter());
1552
1350
  case "figma":
1553
1351
  return writeFigmaTokens(theme, opts.output ?? "material-theme");
1554
- default:
1555
- return process.stdout.write(json(theme.toJson()));
1556
1352
  }
1557
1353
  }
1558
- var program = new Command2();
1354
+ var program = new Command();
1559
1355
  addThemeOptions(
1560
1356
  addSourceArgument(
1561
1357
  program.name("material-theme-builder").description("Generate a color theme from a source color")
@@ -1563,10 +1359,8 @@ addThemeOptions(
1563
1359
  ).option(
1564
1360
  "--custom-colors <json>",
1565
1361
  `Custom colors as JSON array (e.g. '[{"name":"brand","hex":"#FF5733","blend":true}]')`
1566
- ).option(
1567
- "--format <type>",
1568
- "Output format: json, css, figma, tailwind, shadcn, registry-item, or flutter",
1569
- "figma"
1362
+ ).addOption(
1363
+ new Option2("--format <type>", "Output format").choices(FORMATS).default("figma")
1570
1364
  ).option("--output <dir>", "Output directory (required for figma format)").option(
1571
1365
  "--shadcn",
1572
1366
  "Append the shadcn var() alias block to --format tailwind (for concrete values, use --format shadcn)"
@@ -1609,29 +1403,17 @@ addThemeOptions(
1609
1403
  });
1610
1404
  writeOutput(result, opts);
1611
1405
  });
1612
- addChainOptions(
1613
- addThemeOptions(
1614
- addSourceArgument(
1615
- program.command("shadcn-init").description(
1616
- "Scaffold a new shadcn app themed from a source color, and start it"
1617
- )
1618
- ).argument(
1619
- "[shadcn-args...]",
1620
- "Options after a `--`, forwarded verbatim to `shadcn init`"
1621
- )
1622
- )
1623
- ).action(
1624
- (source, shadcnArgs, _opts, command) => runInit(source, shadcnArgs, command)
1625
- );
1626
- addChainOptions(
1627
- addThemeOptions(
1628
- addSourceArgument(
1629
- program.command("shadcn-apply").description("Theme the shadcn project in the current directory")
1630
- ).argument(
1631
- "[shadcn-args...]",
1632
- "Options after a `--`, forwarded verbatim to `shadcn add`"
1633
- )
1406
+ addThemeOptions(
1407
+ addSourceArgument(
1408
+ program.command("shadcn-apply").description("Theme the shadcn project in the current directory")
1409
+ ).argument(
1410
+ "[shadcn-args...]",
1411
+ "Options after a `--`, forwarded verbatim to `shadcn add`"
1634
1412
  )
1413
+ ).option(
1414
+ "--shadcn-cli <spec>",
1415
+ "npx package spec for the shadcn CLI to run (a version, tag, fork or tarball \u2014 anything npx resolves)",
1416
+ DEFAULT_SHADCN
1635
1417
  ).action(
1636
1418
  (source, shadcnArgs, _opts, command) => runApply(source, shadcnArgs, command)
1637
1419
  );
package/dist/index.js CHANGED
@@ -668,8 +668,8 @@ function buildShadcnAliases(ctx) {
668
668
  const lines = Object.entries(toShadcnAliasVars(ctx.prefix)).map(
669
669
  ([name, value]) => `--${name}: ${value};`
670
670
  );
671
- return `:root,
672
- .dark {
671
+ return `:root:root,
672
+ .dark.dark {
673
673
  ${lines.join("\n ")}
674
674
  }
675
675
  `;
@@ -846,6 +846,9 @@ function assertHexInputs(source, cores, customColors) {
846
846
  (color, i) => assertHexColor(`customColors[${i}].hex`, color.hex)
847
847
  );
848
848
  }
849
+ function optionalHex(hex) {
850
+ return hex?.trim() || void 0;
851
+ }
849
852
  var STANDARD_TONES = [
850
853
  0,
851
854
  4,
@@ -1014,16 +1017,20 @@ function builder(hexSource, {
1014
1017
  customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
1015
1018
  prefix = DEFAULT_PREFIX
1016
1019
  } = {}) {
1017
- assertHexInputs(
1018
- hexSource,
1019
- { primary, secondary, tertiary, error, neutral, neutralVariant },
1020
- hexCustomColors
1021
- );
1020
+ const cores = {
1021
+ primary: optionalHex(primary),
1022
+ secondary: optionalHex(secondary),
1023
+ tertiary: optionalHex(tertiary),
1024
+ error: optionalHex(error),
1025
+ neutral: optionalHex(neutral),
1026
+ neutralVariant: optionalHex(neutralVariant)
1027
+ };
1028
+ assertHexInputs(hexSource, cores, hexCustomColors);
1022
1029
  const sourceArgb = argbFromHex2(hexSource);
1023
1030
  const sourceHct = Hct2.fromInt(sourceArgb);
1024
- const effectiveSource = primary || hexSource;
1031
+ const effectiveSource = cores.primary || hexSource;
1025
1032
  const effectiveSourceArgb = argbFromHex2(effectiveSource);
1026
- const effectiveSourceForHarmonization = primary ? argbFromHex2(primary) : sourceArgb;
1033
+ const effectiveSourceForHarmonization = cores.primary ? argbFromHex2(cores.primary) : sourceArgb;
1027
1034
  const SchemeClass = schemesMap[scheme];
1028
1035
  const primaryHct = Hct2.fromInt(effectiveSourceArgb);
1029
1036
  const baseScheme = new SchemeClass(primaryHct, false, contrast);
@@ -1031,32 +1038,32 @@ function builder(hexSource, {
1031
1038
  // Core colors (hex may be undefined)
1032
1039
  {
1033
1040
  name: "primary",
1034
- hex: primary,
1041
+ hex: cores.primary,
1035
1042
  core: true,
1036
1043
  chromaSource: "primary"
1037
1044
  },
1038
1045
  {
1039
1046
  name: "secondary",
1040
- hex: secondary,
1047
+ hex: cores.secondary,
1041
1048
  core: true,
1042
1049
  chromaSource: "primary"
1043
1050
  },
1044
1051
  {
1045
1052
  name: "tertiary",
1046
- hex: tertiary,
1053
+ hex: cores.tertiary,
1047
1054
  core: true,
1048
1055
  chromaSource: "primary"
1049
1056
  },
1050
- { name: "error", hex: error, core: true, chromaSource: "primary" },
1057
+ { name: "error", hex: cores.error, core: true, chromaSource: "primary" },
1051
1058
  {
1052
1059
  name: "neutral",
1053
- hex: neutral,
1060
+ hex: cores.neutral,
1054
1061
  core: true,
1055
1062
  chromaSource: "neutral"
1056
1063
  },
1057
1064
  {
1058
1065
  name: "neutralVariant",
1059
- hex: neutralVariant,
1066
+ hex: cores.neutralVariant,
1060
1067
  core: true,
1061
1068
  chromaSource: "neutralVariant"
1062
1069
  },
package/dist/react.js CHANGED
@@ -3988,8 +3988,8 @@ function buildShadcnAliases(ctx) {
3988
3988
  const lines = Object.entries(toShadcnAliasVars(ctx.prefix)).map(
3989
3989
  ([name, value]) => `--${name}: ${value};`
3990
3990
  );
3991
- return `:root,
3992
- .dark {
3991
+ return `:root:root,
3992
+ .dark.dark {
3993
3993
  ${lines.join("\n ")}
3994
3994
  }
3995
3995
  `;
@@ -4166,6 +4166,9 @@ function assertHexInputs(source, cores, customColors) {
4166
4166
  (color, i) => assertHexColor(`customColors[${i}].hex`, color.hex)
4167
4167
  );
4168
4168
  }
4169
+ function optionalHex(hex) {
4170
+ return hex?.trim() || void 0;
4171
+ }
4169
4172
  var STANDARD_TONES = [
4170
4173
  0,
4171
4174
  4,
@@ -4334,16 +4337,20 @@ function builder(hexSource, {
4334
4337
  customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
4335
4338
  prefix = DEFAULT_PREFIX
4336
4339
  } = {}) {
4337
- assertHexInputs(
4338
- hexSource,
4339
- { primary, secondary, tertiary, error, neutral, neutralVariant },
4340
- hexCustomColors
4341
- );
4340
+ const cores = {
4341
+ primary: optionalHex(primary),
4342
+ secondary: optionalHex(secondary),
4343
+ tertiary: optionalHex(tertiary),
4344
+ error: optionalHex(error),
4345
+ neutral: optionalHex(neutral),
4346
+ neutralVariant: optionalHex(neutralVariant)
4347
+ };
4348
+ assertHexInputs(hexSource, cores, hexCustomColors);
4342
4349
  const sourceArgb = argbFromHex2(hexSource);
4343
4350
  const sourceHct = Hct2.fromInt(sourceArgb);
4344
- const effectiveSource = primary || hexSource;
4351
+ const effectiveSource = cores.primary || hexSource;
4345
4352
  const effectiveSourceArgb = argbFromHex2(effectiveSource);
4346
- const effectiveSourceForHarmonization = primary ? argbFromHex2(primary) : sourceArgb;
4353
+ const effectiveSourceForHarmonization = cores.primary ? argbFromHex2(cores.primary) : sourceArgb;
4347
4354
  const SchemeClass = schemesMap[scheme];
4348
4355
  const primaryHct = Hct2.fromInt(effectiveSourceArgb);
4349
4356
  const baseScheme = new SchemeClass(primaryHct, false, contrast);
@@ -4351,32 +4358,32 @@ function builder(hexSource, {
4351
4358
  // Core colors (hex may be undefined)
4352
4359
  {
4353
4360
  name: "primary",
4354
- hex: primary,
4361
+ hex: cores.primary,
4355
4362
  core: true,
4356
4363
  chromaSource: "primary"
4357
4364
  },
4358
4365
  {
4359
4366
  name: "secondary",
4360
- hex: secondary,
4367
+ hex: cores.secondary,
4361
4368
  core: true,
4362
4369
  chromaSource: "primary"
4363
4370
  },
4364
4371
  {
4365
4372
  name: "tertiary",
4366
- hex: tertiary,
4373
+ hex: cores.tertiary,
4367
4374
  core: true,
4368
4375
  chromaSource: "primary"
4369
4376
  },
4370
- { name: "error", hex: error, core: true, chromaSource: "primary" },
4377
+ { name: "error", hex: cores.error, core: true, chromaSource: "primary" },
4371
4378
  {
4372
4379
  name: "neutral",
4373
- hex: neutral,
4380
+ hex: cores.neutral,
4374
4381
  core: true,
4375
4382
  chromaSource: "neutral"
4376
4383
  },
4377
4384
  {
4378
4385
  name: "neutralVariant",
4379
- hex: neutralVariant,
4386
+ hex: cores.neutralVariant,
4380
4387
  core: true,
4381
4388
  chromaSource: "neutralVariant"
4382
4389
  },
@@ -4492,7 +4499,7 @@ function builder(hexSource, {
4492
4499
  }
4493
4500
 
4494
4501
  // src/ExportButton.tsx
4495
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
4502
+ import { jsx as jsx2 } from "react/jsx-runtime";
4496
4503
  function ExportButton({ config }) {
4497
4504
  const handleExport = () => {
4498
4505
  try {
@@ -4526,24 +4533,16 @@ function ExportButton({ config }) {
4526
4533
  onClick: handleExport,
4527
4534
  title: "Export Figma Tokens",
4528
4535
  "aria-label": "Export Figma Tokens",
4529
- className: "fixed z-50 bottom-6 right-6",
4530
- children: /* @__PURE__ */ jsxs(
4536
+ children: /* @__PURE__ */ jsx2(
4531
4537
  "svg",
4532
4538
  {
4533
4539
  xmlns: "http://www.w3.org/2000/svg",
4534
4540
  width: "24",
4535
4541
  height: "24",
4536
4542
  viewBox: "0 0 24 24",
4537
- fill: "none",
4538
- stroke: "currentColor",
4539
- strokeWidth: "2",
4540
- strokeLinecap: "round",
4541
- strokeLinejoin: "round",
4542
- children: [
4543
- /* @__PURE__ */ jsx2("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
4544
- /* @__PURE__ */ jsx2("polyline", { points: "7 10 12 15 17 10" }),
4545
- /* @__PURE__ */ jsx2("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
4546
- ]
4543
+ fill: "currentColor",
4544
+ "aria-hidden": true,
4545
+ children: /* @__PURE__ */ jsx2("path", { d: "M8 24a4 4 0 0 1-4-4 4 4 0 0 1 4-4h4v4a4 4 0 0 1-4 4Zm0-6.5A2.5 2.5 0 0 0 5.5 20 2.5 2.5 0 0 0 8 22.5 2.5 2.5 0 0 0 10.5 20v-2.5H8ZM8 16a4 4 0 0 1-4-4 4 4 0 0 1 4-4h4v8H8Zm0-6.5A2.5 2.5 0 0 0 5.5 12 2.5 2.5 0 0 0 8 14.5h2.5v-5H8ZM8 8a4 4 0 0 1-4-4 4 4 0 0 1 4-4h4v8H8Zm0-6.5A2.5 2.5 0 0 0 5.5 4 2.5 2.5 0 0 0 8 6.5h2.5v-5H8Zm8 6.5h-4V0h4a4 4 0 0 1 4 4 4 4 0 0 1-4 4Zm-2.5-1.5H16A2.5 2.5 0 0 0 18.5 4 2.5 2.5 0 0 0 16 1.5h-2.5v5ZM16 16a4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 1 4 4 4 4 0 0 1-4 4Zm0-6.5a2.5 2.5 0 0 0-2.5 2.5 2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5A2.5 2.5 0 0 0 16 9.5Z" })
4547
4546
  }
4548
4547
  )
4549
4548
  }
@@ -4574,7 +4573,7 @@ var createRequiredContext = () => {
4574
4573
  };
4575
4574
 
4576
4575
  // src/Mtb.context.tsx
4577
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
4576
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
4578
4577
  var [useMtb, Provider, MtbContext] = createRequiredContext();
4579
4578
  var MtbProvider = ({
4580
4579
  styleId: styleId2,
@@ -4640,7 +4639,7 @@ var MtbProvider = ({
4640
4639
  figmaVariables
4641
4640
  ]
4642
4641
  );
4643
- return /* @__PURE__ */ jsxs2(Provider, { value, children: [
4642
+ return /* @__PURE__ */ jsxs(Provider, { value, children: [
4644
4643
  /* @__PURE__ */ jsx3("style", { id: styleId2, dangerouslySetInnerHTML: { __html: css } }),
4645
4644
  children
4646
4645
  ] });
package/dist/shadcn.css CHANGED
@@ -2,13 +2,13 @@
2
2
  * Generated by `scripts/generate.mjs` -- do not edit.
3
3
  *
4
4
  * Points shadcn's variables at the M3 custom properties `<Mtb>` emits, so
5
- * shadcn components follow the theme above them in the tree. Import it after
6
- * shadcn's own `:root` and `.dark` blocks, which it has to override -- or
7
- * skip the ordering question entirely and install `registry-item.json` with
8
- * `shadcn add`, which writes the same mapping into those blocks in place.
5
+ * shadcn components follow the theme above them in the tree. Import it with
6
+ * your other imports: the selectors are doubled, so it outranks shadcn's own
7
+ * `:root` and `.dark` wherever it lands. Or install `registry-item.json`
8
+ * with `shadcn add`, which writes the same mapping into those blocks.
9
9
  */
10
- :root,
11
- .dark {
10
+ :root:root,
11
+ .dark.dark {
12
12
  --background: var(--md-sys-color-surface);
13
13
  --foreground: var(--md-sys-color-on-surface);
14
14
  --card: var(--md-sys-color-surface-container-low);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "material-theme-builder",
3
- "version": "3.3.0",
3
+ "version": "4.0.0",
4
4
  "description": "m3 color-system for JS/TS ecosystem",
5
5
  "keywords": [
6
6
  "react",
@@ -56,6 +56,7 @@
56
56
  "@fontsource-variable/inter": "^5.3.0",
57
57
  "@storybook/addon-docs": "^10.1.11",
58
58
  "@storybook/addon-themes": "^10.1.11",
59
+ "@storybook/icons": "^2.1.0",
59
60
  "@storybook/react-vite": "^10.1.11",
60
61
  "@tailwindcss/postcss": "^4.1.18",
61
62
  "@tanstack/react-table": "^9.1.2",
@@ -141,6 +142,7 @@
141
142
  "build-storybook": "pnpm run build && storybook build",
142
143
  "build-figma": "vite build --config figma-plugin/vite.config.ts && tsup --config figma-plugin/tsup.config.ts",
143
144
  "chromatic": "chromatic --project-token $CHROMATIC_PROJECT_TOKEN",
144
- "changeset": "pnpm exec changeset"
145
+ "changeset": "pnpm exec changeset",
146
+ "dev": "pnpm run storybook"
145
147
  }
146
148
  }