@kbach/react 0.6.16 → 0.6.18
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 +55 -15
- package/dist/vite-plugin.js +112 -14
- package/dist/vite-plugin.mjs +112 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,9 @@ npm install @kbach/react
|
|
|
20
20
|
|
|
21
21
|
## Setup
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
One step is always required, then pick **one** of the two setups below — they're independent, don't mix them.
|
|
24
24
|
|
|
25
|
-
### Step 1 — JSX runtime
|
|
25
|
+
### Step 1 — JSX runtime (always required)
|
|
26
26
|
|
|
27
27
|
**tsconfig.json:**
|
|
28
28
|
|
|
@@ -32,17 +32,17 @@ Two steps: configure the JSX runtime (always), then pick one styling method.
|
|
|
32
32
|
|
|
33
33
|
That's the only setting needed — Vite, Next.js, and React Router all read it. Don't *also* set `jsxImportSource` on a bundler plugin (e.g. `@vitejs/plugin-react`); one source of truth avoids conflicts.
|
|
34
34
|
|
|
35
|
-
###
|
|
36
|
-
|
|
37
|
-
Not sure which? Check your framework first:
|
|
35
|
+
### Which setup do I need?
|
|
38
36
|
|
|
39
37
|
| Framework | Use |
|
|
40
38
|
|---|---|
|
|
41
|
-
| Next.js | **
|
|
42
|
-
| React Router, framework mode | **Static CSS setup** — and skip `@vitejs/plugin-react`, see note
|
|
43
|
-
| Vite, React Router library mode, CRA, other | Either —
|
|
39
|
+
| Next.js | **[Runtime setup](#runtime-setup)** — Static CSS doesn't apply (webpack/Turbopack, not Vite) |
|
|
40
|
+
| React Router, framework mode | **[Static CSS setup](#static-css-setup)** — and skip `@vitejs/plugin-react`, see note in that section |
|
|
41
|
+
| Vite, React Router library mode, CRA, other | Either — Runtime is faster to try, Static CSS is zero-cost for production |
|
|
42
|
+
|
|
43
|
+
## Runtime setup
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Client-side CSS injection — works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …), no build plugin. This is the whole setup:
|
|
46
46
|
|
|
47
47
|
```jsx
|
|
48
48
|
import { ThemeProvider, KbachReset } from '@kbach/react';
|
|
@@ -57,11 +57,17 @@ export default function Root() {
|
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
That's it — done. `<KbachReset />` renders the base reset (see [CSS resets](#css-resets)) as real markup instead of waiting on client JS — matters most for SSR, where it avoids a flash of unstyled browser defaults before hydration. `@kbach/react` ships its own `"use client"` directive, so this works in a Next.js Server Component tree with no wrapper needed — in Next.js, render it once in the root `layout.tsx`.
|
|
61
|
+
|
|
62
|
+
Using a custom `kbach.config.js`? Pass it to `ThemeProvider` too — see [Wiring the config in](#wiring-the-config-in--required-for-both-setups).
|
|
63
|
+
|
|
64
|
+
Don't also set up Static CSS below in the same app — pick one.
|
|
65
|
+
|
|
66
|
+
## Static CSS setup
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
Vite only. A build-time plugin writes real CSS into a file you import at build time — nothing generated client-side, zero runtime cost. Three pieces, all required:
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
**1. Add the plugin:**
|
|
65
71
|
|
|
66
72
|
```ts
|
|
67
73
|
// vite.config.ts
|
|
@@ -71,6 +77,8 @@ import { kbach } from '@kbach/react/vite';
|
|
|
71
77
|
export default defineConfig({ plugins: [kbach()] });
|
|
72
78
|
```
|
|
73
79
|
|
|
80
|
+
**2. Create an empty stylesheet with the markers, and import it once:**
|
|
81
|
+
|
|
74
82
|
```css
|
|
75
83
|
/* src/kbach.css */
|
|
76
84
|
/* kbach:start */
|
|
@@ -82,16 +90,19 @@ export default defineConfig({ plugins: [kbach()] });
|
|
|
82
90
|
import './kbach.css';
|
|
83
91
|
```
|
|
84
92
|
|
|
93
|
+
**3. Wrap your app — no `<KbachReset />` here, `kbach.css` already includes the reset:**
|
|
94
|
+
|
|
85
95
|
```jsx
|
|
86
96
|
import { ThemeProvider } from '@kbach/react';
|
|
87
|
-
// No <KbachReset /> — kbach.css already includes the reset.
|
|
88
97
|
|
|
89
98
|
export default function Root() {
|
|
90
99
|
return <ThemeProvider defaultMode="system"><App /></ThemeProvider>;
|
|
91
100
|
}
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection. It also warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize as a real utility or an existing CSS rule elsewhere in the project — usually a typo.
|
|
103
|
+
Done. The plugin scans your source at build time and writes CSS between the markers — importing `kbach.css` auto-disables runtime injection, so there's no double-styling between this and the Runtime setup above. It also warns in the terminal (with a clickable `file:line`) for any class it doesn't recognize as a real utility or an existing CSS rule elsewhere in the project — usually a typo.
|
|
104
|
+
|
|
105
|
+
Using a custom `kbach.config.js`? It needs to be wired in **twice** here — once to `kbach()` above (step 1) so the generated CSS reflects it, and once to `ThemeProvider` (step 3) so dark mode/`useColors()`/animations do too. See [Wiring the config in](#wiring-the-config-in--required-for-both-setups) — easy to only do one and have the other silently fall back to defaults.
|
|
95
106
|
|
|
96
107
|
**React Router framework mode:** don't add `@vitejs/plugin-react` — `reactRouter()` already provides JSX handling, and both together crash the page (`Identifier 'RefreshRuntime' has already been declared`).
|
|
97
108
|
|
|
@@ -99,7 +110,7 @@ The plugin scans your source at build time and writes CSS between the markers
|
|
|
99
110
|
// vite.config.ts
|
|
100
111
|
import { reactRouter } from '@react-router/dev/vite';
|
|
101
112
|
import { defineConfig } from 'vite';
|
|
102
|
-
import { kbach } from '@kbach/react/vite'; // omit if using
|
|
113
|
+
import { kbach } from '@kbach/react/vite'; // omit if using Runtime setup instead
|
|
103
114
|
|
|
104
115
|
export default defineConfig({ plugins: [kbach(), reactRouter()] });
|
|
105
116
|
```
|
|
@@ -296,6 +307,35 @@ module.exports = {
|
|
|
296
307
|
- Colors can alias each other: `primary: 'blue-6'`, `brand: { 6: 'primary' }`
|
|
297
308
|
- Runtime update: `updateConfig({ extend: { ... } }); clearCache();`
|
|
298
309
|
|
|
310
|
+
### Wiring the config in — required for both setups
|
|
311
|
+
|
|
312
|
+
`kbach.config.js` isn't picked up automatically. It has to be imported and passed in explicitly, and **where** depends on which of the two things it affects:
|
|
313
|
+
|
|
314
|
+
- **Runtime** — dark mode strategy, `useColors()`, custom `@keyframes`/`animation`, and (for SSR) the default-font fallback rendered before your stylesheet takes over. Needed by **both** [Runtime setup](#runtime-setup) and [Static CSS setup](#static-css-setup) — pass it to `ThemeProvider`:
|
|
315
|
+
|
|
316
|
+
```jsx
|
|
317
|
+
import { ThemeProvider } from '@kbach/react';
|
|
318
|
+
import kbachConfig from '../kbach.config';
|
|
319
|
+
|
|
320
|
+
<ThemeProvider defaultMode="system" config={kbachConfig}>
|
|
321
|
+
<App />
|
|
322
|
+
</ThemeProvider>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
(Equivalent to calling `updateConfig(kbachConfig)` once before anything renders — `ThemeProvider`'s `config` prop does this for you and keeps the global store in sync if it ever changes.)
|
|
326
|
+
|
|
327
|
+
- **Build-time** — what the Vite plugin actually scans your source against and generates CSS for. Only relevant to [Static CSS setup](#static-css-setup) — pass it to the plugin itself:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
// vite.config.ts
|
|
331
|
+
import { kbach } from '@kbach/react/vite';
|
|
332
|
+
import kbachConfig from './kbach.config';
|
|
333
|
+
|
|
334
|
+
export default defineConfig({ plugins: [kbach(kbachConfig)] });
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Skipping the runtime one is an easy mistake under Static CSS setup specifically — the generated `kbach.css` will look correct (it *does* have your customizations baked in), while dark mode, `useColors()`, and custom animations silently fall back to Kbach's defaults instead of your config, since nothing ever told the running app what you'd customized.
|
|
338
|
+
|
|
299
339
|
## Full reference
|
|
300
340
|
|
|
301
341
|
[kbach-react.md](./kbach-react.md) — complete utility list, every modifier, all config options.
|
package/dist/vite-plugin.js
CHANGED
|
@@ -3152,26 +3152,124 @@ function buildColorVarMap(theme, usedCSS) {
|
|
|
3152
3152
|
const hexRe = /#[0-9a-fA-F]{3,8}\b/g;
|
|
3153
3153
|
let hm;
|
|
3154
3154
|
while ((hm = hexRe.exec(usedCSS)) !== null) usedHexValues.add(hm[0].toLowerCase());
|
|
3155
|
-
const
|
|
3155
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3156
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3156
3157
|
for (const [name, shades] of Object.entries(theme.colors ?? {})) {
|
|
3157
3158
|
const entries = typeof shades === "string" ? [[name, shades]] : Object.entries(shades).map(([s, v]) => [`${name}-${s}`, v]);
|
|
3158
3159
|
for (const [fullName, val] of entries) {
|
|
3159
|
-
if (typeof val
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
}
|
|
3160
|
+
if (typeof val !== "string" || !/^#[0-9a-fA-F]{3,8}$/.test(val)) continue;
|
|
3161
|
+
if (usedHexValues.has(val.toLowerCase())) {
|
|
3162
|
+
const varName = `--color-${fullName}`;
|
|
3163
|
+
replacements.set(val, `var(${varName})`);
|
|
3164
|
+
declarations.set(varName, val);
|
|
3165
|
+
}
|
|
3166
|
+
const rgb = parseHexRgb(val);
|
|
3167
|
+
if (!rgb) continue;
|
|
3168
|
+
const triplet = `${rgb[0]},${rgb[1]},${rgb[2]}`;
|
|
3169
|
+
const rgbaPrefix = `rgba(${triplet},`;
|
|
3170
|
+
if (usedCSS.includes(rgbaPrefix)) {
|
|
3171
|
+
const rgbVarName = `--color-${fullName}-rgb`;
|
|
3172
|
+
replacements.set(rgbaPrefix, `rgba(var(${rgbVarName}),`);
|
|
3173
|
+
declarations.set(rgbVarName, triplet);
|
|
3163
3174
|
}
|
|
3164
3175
|
}
|
|
3165
3176
|
}
|
|
3166
|
-
return
|
|
3177
|
+
return { replacements, declarations };
|
|
3178
|
+
}
|
|
3179
|
+
var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
3180
|
+
"padding",
|
|
3181
|
+
"padding-top",
|
|
3182
|
+
"padding-right",
|
|
3183
|
+
"padding-bottom",
|
|
3184
|
+
"padding-left",
|
|
3185
|
+
"margin",
|
|
3186
|
+
"margin-top",
|
|
3187
|
+
"margin-right",
|
|
3188
|
+
"margin-bottom",
|
|
3189
|
+
"margin-left",
|
|
3190
|
+
"gap",
|
|
3191
|
+
"column-gap",
|
|
3192
|
+
"row-gap",
|
|
3193
|
+
"width",
|
|
3194
|
+
"height",
|
|
3195
|
+
"min-width",
|
|
3196
|
+
"min-height",
|
|
3197
|
+
"max-width",
|
|
3198
|
+
"max-height",
|
|
3199
|
+
"top",
|
|
3200
|
+
"right",
|
|
3201
|
+
"bottom",
|
|
3202
|
+
"left",
|
|
3203
|
+
"flex-basis"
|
|
3204
|
+
]);
|
|
3205
|
+
function buildSpacingVarMap(theme, usedCSS) {
|
|
3206
|
+
const pxToKey = /* @__PURE__ */ new Map();
|
|
3207
|
+
for (const [key, val] of Object.entries(theme.spacing ?? {})) {
|
|
3208
|
+
if (typeof val === "number" && !pxToKey.has(val)) pxToKey.set(val, key);
|
|
3209
|
+
}
|
|
3210
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3211
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3212
|
+
const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
|
|
3213
|
+
const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
|
|
3214
|
+
let m;
|
|
3215
|
+
while ((m = declRe.exec(usedCSS)) !== null) {
|
|
3216
|
+
const [, prop, numStr] = m;
|
|
3217
|
+
if (!SPACING_PROPS.has(prop)) continue;
|
|
3218
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3219
|
+
if (key === void 0) continue;
|
|
3220
|
+
const varName = varNameFor(key);
|
|
3221
|
+
replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
|
|
3222
|
+
declarations.set(varName, `${numStr}px`);
|
|
3223
|
+
}
|
|
3224
|
+
const transformRe = /(translateX|translateY)\((\d+(?:\.\d+)?)px\)/g;
|
|
3225
|
+
while ((m = transformRe.exec(usedCSS)) !== null) {
|
|
3226
|
+
const [, fn, numStr] = m;
|
|
3227
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3228
|
+
if (key === void 0) continue;
|
|
3229
|
+
const varName = varNameFor(key);
|
|
3230
|
+
replacements.set(`${fn}(${numStr}px)`, `${fn}(var(${varName}))`);
|
|
3231
|
+
declarations.set(varName, `${numStr}px`);
|
|
3232
|
+
}
|
|
3233
|
+
return { replacements, declarations };
|
|
3234
|
+
}
|
|
3235
|
+
function prettifyCSS(css) {
|
|
3236
|
+
const out = [];
|
|
3237
|
+
let depth = 0;
|
|
3238
|
+
let current = "";
|
|
3239
|
+
for (const ch of css) {
|
|
3240
|
+
if (ch === "{") {
|
|
3241
|
+
const selector = current.trim();
|
|
3242
|
+
if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
|
|
3243
|
+
depth++;
|
|
3244
|
+
current = "";
|
|
3245
|
+
} else if (ch === "}") {
|
|
3246
|
+
const decl = current.trim();
|
|
3247
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3248
|
+
depth = Math.max(0, depth - 1);
|
|
3249
|
+
out.push(`${" ".repeat(depth)}}`);
|
|
3250
|
+
current = "";
|
|
3251
|
+
} else if (ch === ";") {
|
|
3252
|
+
const decl = current.trim();
|
|
3253
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3254
|
+
current = "";
|
|
3255
|
+
} else {
|
|
3256
|
+
current += ch;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
const trailing = current.trim();
|
|
3260
|
+
if (trailing) out.push(trailing);
|
|
3261
|
+
return out.join("\n");
|
|
3167
3262
|
}
|
|
3168
3263
|
function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
3169
3264
|
const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
|
|
3170
3265
|
const colorVars = buildColorVarMap(theme, allRaw);
|
|
3171
|
-
const
|
|
3266
|
+
const spacingVars = buildSpacingVarMap(theme, allRaw);
|
|
3267
|
+
const replacements = new Map([...colorVars.replacements, ...spacingVars.replacements]);
|
|
3268
|
+
const declarations = new Map([...colorVars.declarations, ...spacingVars.declarations]);
|
|
3269
|
+
const sortedReplacements = [...replacements].sort(([a], [b]) => b.length - a.length);
|
|
3172
3270
|
function applyVars(css) {
|
|
3173
|
-
for (const [
|
|
3174
|
-
css = css.split(
|
|
3271
|
+
for (const [pattern, replacement] of sortedReplacements)
|
|
3272
|
+
css = css.split(pattern).join(replacement);
|
|
3175
3273
|
return css;
|
|
3176
3274
|
}
|
|
3177
3275
|
const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
|
|
@@ -3179,24 +3277,24 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
|
3179
3277
|
if (!css) continue;
|
|
3180
3278
|
const label = classifyToken(token, responsiveRe);
|
|
3181
3279
|
if (!groups.has(label)) groups.set(label, []);
|
|
3182
|
-
groups.get(label).push(applyVars(css));
|
|
3280
|
+
groups.get(label).push(prettifyCSS(applyVars(css)));
|
|
3183
3281
|
}
|
|
3184
3282
|
const out = ["/* Generated by Kbach \u2014 do not edit */"];
|
|
3185
|
-
if (
|
|
3283
|
+
if (declarations.size > 0) {
|
|
3186
3284
|
out.push("\n:root {");
|
|
3187
|
-
for (const [
|
|
3285
|
+
for (const [varName, val] of declarations) out.push(` ${varName}: ${val};`);
|
|
3188
3286
|
out.push("}");
|
|
3189
3287
|
}
|
|
3190
3288
|
const globalCSS = buildThemeGlobal(theme);
|
|
3191
3289
|
if (globalCSS) out.push(`
|
|
3192
3290
|
/* Global */
|
|
3193
|
-
${globalCSS}`);
|
|
3291
|
+
${prettifyCSS(globalCSS)}`);
|
|
3194
3292
|
for (const label of GROUP_ORDER) {
|
|
3195
3293
|
const items = groups.get(label) ?? [];
|
|
3196
3294
|
if (!items.length) continue;
|
|
3197
3295
|
out.push(`
|
|
3198
3296
|
/* ${label} */`);
|
|
3199
|
-
out.push(items.join("\n"));
|
|
3297
|
+
out.push(items.join("\n\n"));
|
|
3200
3298
|
}
|
|
3201
3299
|
return out.join("\n");
|
|
3202
3300
|
}
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -3118,26 +3118,124 @@ function buildColorVarMap(theme, usedCSS) {
|
|
|
3118
3118
|
const hexRe = /#[0-9a-fA-F]{3,8}\b/g;
|
|
3119
3119
|
let hm;
|
|
3120
3120
|
while ((hm = hexRe.exec(usedCSS)) !== null) usedHexValues.add(hm[0].toLowerCase());
|
|
3121
|
-
const
|
|
3121
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3122
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3122
3123
|
for (const [name, shades] of Object.entries(theme.colors ?? {})) {
|
|
3123
3124
|
const entries = typeof shades === "string" ? [[name, shades]] : Object.entries(shades).map(([s, v]) => [`${name}-${s}`, v]);
|
|
3124
3125
|
for (const [fullName, val] of entries) {
|
|
3125
|
-
if (typeof val
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
}
|
|
3126
|
+
if (typeof val !== "string" || !/^#[0-9a-fA-F]{3,8}$/.test(val)) continue;
|
|
3127
|
+
if (usedHexValues.has(val.toLowerCase())) {
|
|
3128
|
+
const varName = `--color-${fullName}`;
|
|
3129
|
+
replacements.set(val, `var(${varName})`);
|
|
3130
|
+
declarations.set(varName, val);
|
|
3131
|
+
}
|
|
3132
|
+
const rgb = parseHexRgb(val);
|
|
3133
|
+
if (!rgb) continue;
|
|
3134
|
+
const triplet = `${rgb[0]},${rgb[1]},${rgb[2]}`;
|
|
3135
|
+
const rgbaPrefix = `rgba(${triplet},`;
|
|
3136
|
+
if (usedCSS.includes(rgbaPrefix)) {
|
|
3137
|
+
const rgbVarName = `--color-${fullName}-rgb`;
|
|
3138
|
+
replacements.set(rgbaPrefix, `rgba(var(${rgbVarName}),`);
|
|
3139
|
+
declarations.set(rgbVarName, triplet);
|
|
3129
3140
|
}
|
|
3130
3141
|
}
|
|
3131
3142
|
}
|
|
3132
|
-
return
|
|
3143
|
+
return { replacements, declarations };
|
|
3144
|
+
}
|
|
3145
|
+
var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
3146
|
+
"padding",
|
|
3147
|
+
"padding-top",
|
|
3148
|
+
"padding-right",
|
|
3149
|
+
"padding-bottom",
|
|
3150
|
+
"padding-left",
|
|
3151
|
+
"margin",
|
|
3152
|
+
"margin-top",
|
|
3153
|
+
"margin-right",
|
|
3154
|
+
"margin-bottom",
|
|
3155
|
+
"margin-left",
|
|
3156
|
+
"gap",
|
|
3157
|
+
"column-gap",
|
|
3158
|
+
"row-gap",
|
|
3159
|
+
"width",
|
|
3160
|
+
"height",
|
|
3161
|
+
"min-width",
|
|
3162
|
+
"min-height",
|
|
3163
|
+
"max-width",
|
|
3164
|
+
"max-height",
|
|
3165
|
+
"top",
|
|
3166
|
+
"right",
|
|
3167
|
+
"bottom",
|
|
3168
|
+
"left",
|
|
3169
|
+
"flex-basis"
|
|
3170
|
+
]);
|
|
3171
|
+
function buildSpacingVarMap(theme, usedCSS) {
|
|
3172
|
+
const pxToKey = /* @__PURE__ */ new Map();
|
|
3173
|
+
for (const [key, val] of Object.entries(theme.spacing ?? {})) {
|
|
3174
|
+
if (typeof val === "number" && !pxToKey.has(val)) pxToKey.set(val, key);
|
|
3175
|
+
}
|
|
3176
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3177
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3178
|
+
const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
|
|
3179
|
+
const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
|
|
3180
|
+
let m;
|
|
3181
|
+
while ((m = declRe.exec(usedCSS)) !== null) {
|
|
3182
|
+
const [, prop, numStr] = m;
|
|
3183
|
+
if (!SPACING_PROPS.has(prop)) continue;
|
|
3184
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3185
|
+
if (key === void 0) continue;
|
|
3186
|
+
const varName = varNameFor(key);
|
|
3187
|
+
replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
|
|
3188
|
+
declarations.set(varName, `${numStr}px`);
|
|
3189
|
+
}
|
|
3190
|
+
const transformRe = /(translateX|translateY)\((\d+(?:\.\d+)?)px\)/g;
|
|
3191
|
+
while ((m = transformRe.exec(usedCSS)) !== null) {
|
|
3192
|
+
const [, fn, numStr] = m;
|
|
3193
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
3194
|
+
if (key === void 0) continue;
|
|
3195
|
+
const varName = varNameFor(key);
|
|
3196
|
+
replacements.set(`${fn}(${numStr}px)`, `${fn}(var(${varName}))`);
|
|
3197
|
+
declarations.set(varName, `${numStr}px`);
|
|
3198
|
+
}
|
|
3199
|
+
return { replacements, declarations };
|
|
3200
|
+
}
|
|
3201
|
+
function prettifyCSS(css) {
|
|
3202
|
+
const out = [];
|
|
3203
|
+
let depth = 0;
|
|
3204
|
+
let current = "";
|
|
3205
|
+
for (const ch of css) {
|
|
3206
|
+
if (ch === "{") {
|
|
3207
|
+
const selector = current.trim();
|
|
3208
|
+
if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
|
|
3209
|
+
depth++;
|
|
3210
|
+
current = "";
|
|
3211
|
+
} else if (ch === "}") {
|
|
3212
|
+
const decl = current.trim();
|
|
3213
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3214
|
+
depth = Math.max(0, depth - 1);
|
|
3215
|
+
out.push(`${" ".repeat(depth)}}`);
|
|
3216
|
+
current = "";
|
|
3217
|
+
} else if (ch === ";") {
|
|
3218
|
+
const decl = current.trim();
|
|
3219
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
3220
|
+
current = "";
|
|
3221
|
+
} else {
|
|
3222
|
+
current += ch;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
const trailing = current.trim();
|
|
3226
|
+
if (trailing) out.push(trailing);
|
|
3227
|
+
return out.join("\n");
|
|
3133
3228
|
}
|
|
3134
3229
|
function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
3135
3230
|
const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
|
|
3136
3231
|
const colorVars = buildColorVarMap(theme, allRaw);
|
|
3137
|
-
const
|
|
3232
|
+
const spacingVars = buildSpacingVarMap(theme, allRaw);
|
|
3233
|
+
const replacements = new Map([...colorVars.replacements, ...spacingVars.replacements]);
|
|
3234
|
+
const declarations = new Map([...colorVars.declarations, ...spacingVars.declarations]);
|
|
3235
|
+
const sortedReplacements = [...replacements].sort(([a], [b]) => b.length - a.length);
|
|
3138
3236
|
function applyVars(css) {
|
|
3139
|
-
for (const [
|
|
3140
|
-
css = css.split(
|
|
3237
|
+
for (const [pattern, replacement] of sortedReplacements)
|
|
3238
|
+
css = css.split(pattern).join(replacement);
|
|
3141
3239
|
return css;
|
|
3142
3240
|
}
|
|
3143
3241
|
const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
|
|
@@ -3145,24 +3243,24 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
|
|
|
3145
3243
|
if (!css) continue;
|
|
3146
3244
|
const label = classifyToken(token, responsiveRe);
|
|
3147
3245
|
if (!groups.has(label)) groups.set(label, []);
|
|
3148
|
-
groups.get(label).push(applyVars(css));
|
|
3246
|
+
groups.get(label).push(prettifyCSS(applyVars(css)));
|
|
3149
3247
|
}
|
|
3150
3248
|
const out = ["/* Generated by Kbach \u2014 do not edit */"];
|
|
3151
|
-
if (
|
|
3249
|
+
if (declarations.size > 0) {
|
|
3152
3250
|
out.push("\n:root {");
|
|
3153
|
-
for (const [
|
|
3251
|
+
for (const [varName, val] of declarations) out.push(` ${varName}: ${val};`);
|
|
3154
3252
|
out.push("}");
|
|
3155
3253
|
}
|
|
3156
3254
|
const globalCSS = buildThemeGlobal(theme);
|
|
3157
3255
|
if (globalCSS) out.push(`
|
|
3158
3256
|
/* Global */
|
|
3159
|
-
${globalCSS}`);
|
|
3257
|
+
${prettifyCSS(globalCSS)}`);
|
|
3160
3258
|
for (const label of GROUP_ORDER) {
|
|
3161
3259
|
const items = groups.get(label) ?? [];
|
|
3162
3260
|
if (!items.length) continue;
|
|
3163
3261
|
out.push(`
|
|
3164
3262
|
/* ${label} */`);
|
|
3165
|
-
out.push(items.join("\n"));
|
|
3263
|
+
out.push(items.join("\n\n"));
|
|
3166
3264
|
}
|
|
3167
3265
|
return out.join("\n");
|
|
3168
3266
|
}
|