@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 CHANGED
@@ -20,9 +20,9 @@ npm install @kbach/react
20
20
 
21
21
  ## Setup
22
22
 
23
- Two steps: configure the JSX runtime (always), then pick one styling method.
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
- ### Step 2 — Styling method
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 | **Quick setup** — Static CSS doesn't apply (webpack/Turbopack, not Vite) |
42
- | React Router, framework mode | **Static CSS setup** — and skip `@vitejs/plugin-react`, see note below |
43
- | Vite, React Router library mode, CRA, other | Either — Quick is faster to try, Static CSS is zero-cost for production |
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
- #### Quick setup — any bundler, client-side CSS injection
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
- 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`.
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
- #### Static CSS setup — Vite only, zero runtime cost
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
- A build-time plugin writes real CSS into a file you import — nothing generated client-side.
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 Quick setup instead
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.
@@ -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 map = /* @__PURE__ */ new Map();
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 === "string" && /^#[0-9a-fA-F]{3,8}$/.test(val)) {
3160
- if (usedHexValues.has(val.toLowerCase())) {
3161
- map.set(val, `--color-${fullName}`);
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 map;
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 sortedColorVars = [...colorVars].sort(([a], [b]) => b.length - a.length);
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 [val, varName] of sortedColorVars)
3174
- css = css.split(val).join(`var(${varName})`);
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 (colorVars.size > 0) {
3283
+ if (declarations.size > 0) {
3186
3284
  out.push("\n:root {");
3187
- for (const [val, varName] of colorVars) out.push(` ${varName}: ${val};`);
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
  }
@@ -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 map = /* @__PURE__ */ new Map();
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 === "string" && /^#[0-9a-fA-F]{3,8}$/.test(val)) {
3126
- if (usedHexValues.has(val.toLowerCase())) {
3127
- map.set(val, `--color-${fullName}`);
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 map;
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 sortedColorVars = [...colorVars].sort(([a], [b]) => b.length - a.length);
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 [val, varName] of sortedColorVars)
3140
- css = css.split(val).join(`var(${varName})`);
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 (colorVars.size > 0) {
3249
+ if (declarations.size > 0) {
3152
3250
  out.push("\n:root {");
3153
- for (const [val, varName] of colorVars) out.push(` ${varName}: ${val};`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.16",
3
+ "version": "0.6.18",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",