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 +210 -164
- package/dist/cli.js +65 -283
- package/dist/index.js +22 -15
- package/dist/react.js +29 -30
- package/dist/shadcn.css +6 -6
- package/package.json +4 -2
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
|
-
>
|
|
104
|
-
>
|
|
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
|
-
>
|
|
109
|
-
> `
|
|
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)
|
|
133
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
-
>
|
|
169
|
-
>
|
|
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
|
-
|
|
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
|
-
```
|
|
238
|
-
|
|
239
|
-
|
|
230
|
+
```css
|
|
231
|
+
@import "tailwindcss";
|
|
232
|
+
@import "tw-animate-css";
|
|
233
|
+
@import "shadcn/tailwind.css";
|
|
240
234
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
material-theme-builder"
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
--
|
|
251
|
+
--radius: 0.625rem;
|
|
252
|
+
--background: oklch(1 0 0);
|
|
253
|
+
...
|
|
280
254
|
}
|
|
281
255
|
|
|
282
256
|
.dark {
|
|
283
|
-
--
|
|
257
|
+
--background: oklch(0.145 0 0);
|
|
258
|
+
...
|
|
284
259
|
}
|
|
285
260
|
```
|
|
286
261
|
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
>
|
|
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
|
-
>
|
|
314
|
-
>
|
|
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
|
-
|
|
319
|
-
|
|
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
|
-
|
|
322
|
-
|
|
288
|
+
```
|
|
289
|
+
bg-secondary → --color-secondary → var(--secondary) → var(--md-sys-color-secondary-container)
|
|
290
|
+
```
|
|
323
291
|
|
|
324
|
-
|
|
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
|
-
```
|
|
328
|
-
|
|
294
|
+
```
|
|
295
|
+
bg-secondary → --color-secondary → var(--md-sys-color-secondary)
|
|
329
296
|
```
|
|
330
297
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
302
|
+
If you ever want the M3 role itself, `<Mtb>` still emits it:
|
|
339
303
|
|
|
340
|
-
|
|
341
|
-
<
|
|
304
|
+
```html
|
|
305
|
+
<div class="bg-[var(--md-sys-color-secondary)]"></div>
|
|
306
|
+
```
|
|
342
307
|
|
|
343
|
-
|
|
344
|
-
one line they can delete:
|
|
308
|
+
or give it a name of its own:
|
|
345
309
|
|
|
346
310
|
```css
|
|
347
|
-
@
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
374
|
-
drift
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
|
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
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
|
1269
|
+
import "commander";
|
|
1277
1270
|
var NPX = ["npx", "--yes"];
|
|
1278
1271
|
var DEFAULT_SHADCN = "shadcn@latest";
|
|
1279
|
-
var
|
|
1280
|
-
var INIT_TARGET = "shadcn init";
|
|
1281
|
-
var ADD_TARGET = "shadcn add";
|
|
1272
|
+
var YES = "--yes";
|
|
1282
1273
|
var ITEM = "mtb.json";
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
|
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
|
|
1474
|
-
|
|
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
|
-
|
|
1515
|
-
|
|
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
|
|
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
|
-
).
|
|
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
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
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
|
|
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
|
-
|
|
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: "
|
|
4538
|
-
|
|
4539
|
-
|
|
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
|
|
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__ */
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `shadcn add`, which writes the same mapping into those blocks
|
|
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
|
+
"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
|
}
|