@kbach/react 0.5.26 → 0.5.28

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.
@@ -1,21 +1,6 @@
1
1
  import { Plugin } from 'vite';
2
2
  import { F as FrameworkConfig } from './types-CV5vXnSH.mjs';
3
3
 
4
- /**
5
- * @kbach/react/vite
6
- *
7
- * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
- * Zero setup: just add kbach() to your plugins array.
9
- * Only generates CSS for classes you actually use (like Tailwind).
10
- *
11
- * How it works (same as Tailwind):
12
- * 1. Scans your source files at startup for className/kb props
13
- * 2. Generates CSS rules for only those classes
14
- * 3. Appends them to your imported CSS file (e.g. app.css) via Vite's transform
15
- * 4. Vite serves the CSS through its normal pipeline → <link rel="stylesheet">
16
- * 5. When you add a new class in dev, the CSS file is hot-reloaded
17
- */
18
-
19
4
  declare const VIRTUAL_ID = "virtual:kbach/styles.css";
20
5
  declare function kbach(userConfig?: FrameworkConfig): Plugin;
21
6
 
@@ -1,21 +1,6 @@
1
1
  import { Plugin } from 'vite';
2
2
  import { F as FrameworkConfig } from './types-CV5vXnSH.js';
3
3
 
4
- /**
5
- * @kbach/react/vite
6
- *
7
- * Vite plugin — scans source files for Kbach className/kb props and generates CSS.
8
- * Zero setup: just add kbach() to your plugins array.
9
- * Only generates CSS for classes you actually use (like Tailwind).
10
- *
11
- * How it works (same as Tailwind):
12
- * 1. Scans your source files at startup for className/kb props
13
- * 2. Generates CSS rules for only those classes
14
- * 3. Appends them to your imported CSS file (e.g. app.css) via Vite's transform
15
- * 4. Vite serves the CSS through its normal pipeline → <link rel="stylesheet">
16
- * 5. When you add a new class in dev, the CSS file is hot-reloaded
17
- */
18
-
19
4
  declare const VIRTUAL_ID = "virtual:kbach/styles.css";
20
5
  declare function kbach(userConfig?: FrameworkConfig): Plugin;
21
6
 
@@ -3352,14 +3352,89 @@ var VIRTUAL_ID = "virtual:kbach/styles.css";
3352
3352
  var RESOLVED_ID = "\0virtual:kbach/styles.css";
3353
3353
  var KBACH_START = "/* kbach:start */";
3354
3354
  var KBACH_END = "/* kbach:end */";
3355
+ var GROUPS = [
3356
+ ["Layout", /^(flex(?!-\d|-auto|-none|-initial|-1)|flex-(col|row|wrap|nowrap)|grid|block|inline|hidden|table|contents|overflow|items-|justify-|self-|place-|grow|shrink|float-|clear-|static|fixed|absolute|relative|sticky|z-|top-|right-|bottom-|left-|inset|aspect)/],
3357
+ ["Sizing", /^(w-|h-|min-w-|max-w-|min-h-|max-h-|size-|flex-1|flex-auto|flex-none|flex-initial|basis-)/],
3358
+ ["Spacing", /^(p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|gap-|space-)/],
3359
+ ["Typography", /^(text-(xs|sm|base|lg|\dxl)|font-|leading-|tracking-|uppercase|lowercase|capitalize|normal-case|italic|not-italic|underline|overline|line-through|no-underline|truncate|whitespace-|text-(left|center|right|justify|start|end)|list-|align-|line-clamp)/],
3360
+ ["Colors", /^(bg-|text-|color-|fill-|stroke-|caret-|accent-)/],
3361
+ ["Borders", /^(border|rounded|ring|outline|divide)/],
3362
+ ["Effects", /^(shadow|opacity|blur|brightness|contrast|grayscale|transition|duration|ease|delay|animate|scale|rotate|translate|transform|cursor|pointer|select-)/]
3363
+ ];
3364
+ var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Utilities", "Dark Mode", "Responsive"];
3365
+ function classifyToken(token) {
3366
+ if (/^dark:/.test(token)) return "Dark Mode";
3367
+ if (/^(sm|md|lg|xl|2xl):/.test(token)) return "Responsive";
3368
+ const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
3369
+ for (const [label, re] of GROUPS) {
3370
+ if (re.test(base)) return label;
3371
+ }
3372
+ return "Utilities";
3373
+ }
3374
+ function buildColorVarMap(theme, usedCSS) {
3375
+ const map = /* @__PURE__ */ new Map();
3376
+ for (const [name, shades] of Object.entries(theme.colors ?? {})) {
3377
+ const entries = typeof shades === "string" ? [[name, shades]] : Object.entries(shades).map(([s, v]) => [`${name}-${s}`, v]);
3378
+ for (const [fullName, val] of entries) {
3379
+ if (typeof val === "string" && /^#[0-9a-fA-F]{3,8}$/.test(val) && usedCSS.includes(val)) {
3380
+ map.set(val, `--color-${fullName}`);
3381
+ }
3382
+ }
3383
+ }
3384
+ return map;
3385
+ }
3386
+ function formatKbachCSS(tokenCSS, theme) {
3387
+ const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
3388
+ const colorVars = buildColorVarMap(theme, allRaw);
3389
+ function applyVars(css) {
3390
+ for (const [val, varName] of colorVars) {
3391
+ css = css.split(val).join(`var(${varName})`);
3392
+ }
3393
+ return css;
3394
+ }
3395
+ const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
3396
+ for (const [token, css] of tokenCSS) {
3397
+ if (!css) continue;
3398
+ const label = classifyToken(token);
3399
+ if (!groups.has(label)) groups.set(label, []);
3400
+ groups.get(label).push(applyVars(css));
3401
+ }
3402
+ const out = ["/* Generated by Kbach \u2014 do not edit */"];
3403
+ if (colorVars.size > 0) {
3404
+ out.push("\n:root {");
3405
+ for (const [val, varName] of colorVars) {
3406
+ out.push(` ${varName}: ${val};`);
3407
+ }
3408
+ out.push("}");
3409
+ }
3410
+ const globalCSS = buildThemeGlobal(theme);
3411
+ if (globalCSS) {
3412
+ out.push(`
3413
+ /* Global */
3414
+ ${globalCSS}`);
3415
+ }
3416
+ for (const label of GROUP_ORDER) {
3417
+ const items = groups.get(label) ?? [];
3418
+ if (!items.length) continue;
3419
+ out.push(`
3420
+ /* ${label} */`);
3421
+ out.push(items.join("\n"));
3422
+ }
3423
+ return out.join("\n");
3424
+ }
3425
+ function buildThemeGlobal(theme) {
3426
+ const ff = theme.fontFamily;
3427
+ if (!ff?.sans || ff.sans === "System") return "";
3428
+ const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
3429
+ return `body { font-family: ${family}; }`;
3430
+ }
3355
3431
  function findMainCSSFile(root) {
3356
- const candidates = [
3432
+ for (const c of [
3357
3433
  (0, import_path.join)(root, "app", "app.css"),
3358
3434
  (0, import_path.join)(root, "src", "index.css"),
3359
3435
  (0, import_path.join)(root, "src", "app.css"),
3360
3436
  (0, import_path.join)(root, "app", "index.css")
3361
- ];
3362
- for (const c of candidates) {
3437
+ ]) {
3363
3438
  if ((0, import_fs.existsSync)(c)) return c;
3364
3439
  }
3365
3440
  return null;
@@ -3393,11 +3468,8 @@ function scanDir(dir, onFile) {
3393
3468
  const full = (0, import_path.join)(dir, entry);
3394
3469
  try {
3395
3470
  const st = (0, import_fs.statSync)(full);
3396
- if (st.isDirectory()) {
3397
- scanDir(full, onFile);
3398
- } else if (/\.(tsx?|jsx?)$/.test(entry)) {
3399
- onFile((0, import_fs.readFileSync)(full, "utf-8"));
3400
- }
3471
+ if (st.isDirectory()) scanDir(full, onFile);
3472
+ else if (/\.(tsx?|jsx?)$/.test(entry)) onFile((0, import_fs.readFileSync)(full, "utf-8"));
3401
3473
  } catch {
3402
3474
  }
3403
3475
  }
@@ -3414,12 +3486,6 @@ function extractClassStrings(code) {
3414
3486
  }
3415
3487
  return found;
3416
3488
  }
3417
- function buildGlobalCSS(theme) {
3418
- const ff = theme.fontFamily;
3419
- if (!ff?.sans || ff.sans === "System") return "";
3420
- const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
3421
- return `body { font-family: ${family}; }`;
3422
- }
3423
3489
  function kbach(userConfig) {
3424
3490
  const cfg = buildConfig(userConfig ?? {});
3425
3491
  const screens = toNumericScreens(cfg.theme.screens ?? {});
@@ -3433,21 +3499,13 @@ function kbach(userConfig) {
3433
3499
  }
3434
3500
  function extractFromCode(code) {
3435
3501
  let added = false;
3436
- for (const cls of extractClassStrings(code)) {
3437
- for (const token of cls.split(/\s+/).filter(Boolean)) {
3502
+ for (const cls of extractClassStrings(code))
3503
+ for (const token of cls.split(/\s+/).filter(Boolean))
3438
3504
  if (processToken(token)) added = true;
3439
- }
3440
- }
3441
3505
  return added;
3442
3506
  }
3443
- function getAllCSS() {
3444
- const parts = [];
3445
- const global = buildGlobalCSS(cfg.theme);
3446
- if (global) parts.push(global);
3447
- for (const css of tokenCSS.values()) {
3448
- if (css) parts.push(css);
3449
- }
3450
- return parts.join("\n");
3507
+ function getFormattedCSS() {
3508
+ return formatKbachCSS(tokenCSS, cfg.theme);
3451
3509
  }
3452
3510
  return {
3453
3511
  name: "kbach",
@@ -3456,33 +3514,27 @@ function kbach(userConfig) {
3456
3514
  root = resolved.root;
3457
3515
  },
3458
3516
  buildStart() {
3459
- for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
3517
+ for (const dir of ["src", "app", "pages", "components", "views", "layouts"])
3460
3518
  scanDir((0, import_path.join)(root, dir), (code) => extractFromCode(code));
3461
- }
3462
3519
  mainCSSFile = findMainCSSFile(root);
3463
- if (mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
3520
+ if (mainCSSFile) writeKbachToFile(mainCSSFile, getFormattedCSS());
3464
3521
  },
3465
3522
  transform(code, id) {
3466
3523
  const cleanId = id.split("?")[0];
3467
- if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules")) {
3524
+ if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules"))
3468
3525
  extractFromCode(code);
3469
- }
3470
3526
  },
3471
- // ── Virtual module (opt-in explicit import) ───────────────────────────────
3472
3527
  resolveId(id) {
3473
3528
  if (id === VIRTUAL_ID) return RESOLVED_ID;
3474
3529
  },
3475
3530
  load(id) {
3476
- if (id === RESOLVED_ID) return getAllCSS();
3531
+ if (id === RESOLVED_ID) return getFormattedCSS();
3477
3532
  },
3478
- // ── HMR: write updated CSS to file when new classes are found ────────────
3479
- // Vite's own file watcher detects the change and hot-reloads the CSS —
3480
- // no manual invalidation needed.
3481
3533
  handleHotUpdate({ file }) {
3482
3534
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
3483
3535
  try {
3484
3536
  const added = extractFromCode((0, import_fs.readFileSync)(file, "utf-8"));
3485
- if (added && mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
3537
+ if (added && mainCSSFile) writeKbachToFile(mainCSSFile, getFormattedCSS());
3486
3538
  } catch {
3487
3539
  }
3488
3540
  }
@@ -10,14 +10,89 @@ var VIRTUAL_ID = "virtual:kbach/styles.css";
10
10
  var RESOLVED_ID = "\0virtual:kbach/styles.css";
11
11
  var KBACH_START = "/* kbach:start */";
12
12
  var KBACH_END = "/* kbach:end */";
13
+ var GROUPS = [
14
+ ["Layout", /^(flex(?!-\d|-auto|-none|-initial|-1)|flex-(col|row|wrap|nowrap)|grid|block|inline|hidden|table|contents|overflow|items-|justify-|self-|place-|grow|shrink|float-|clear-|static|fixed|absolute|relative|sticky|z-|top-|right-|bottom-|left-|inset|aspect)/],
15
+ ["Sizing", /^(w-|h-|min-w-|max-w-|min-h-|max-h-|size-|flex-1|flex-auto|flex-none|flex-initial|basis-)/],
16
+ ["Spacing", /^(p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|gap-|space-)/],
17
+ ["Typography", /^(text-(xs|sm|base|lg|\dxl)|font-|leading-|tracking-|uppercase|lowercase|capitalize|normal-case|italic|not-italic|underline|overline|line-through|no-underline|truncate|whitespace-|text-(left|center|right|justify|start|end)|list-|align-|line-clamp)/],
18
+ ["Colors", /^(bg-|text-|color-|fill-|stroke-|caret-|accent-)/],
19
+ ["Borders", /^(border|rounded|ring|outline|divide)/],
20
+ ["Effects", /^(shadow|opacity|blur|brightness|contrast|grayscale|transition|duration|ease|delay|animate|scale|rotate|translate|transform|cursor|pointer|select-)/]
21
+ ];
22
+ var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Utilities", "Dark Mode", "Responsive"];
23
+ function classifyToken(token) {
24
+ if (/^dark:/.test(token)) return "Dark Mode";
25
+ if (/^(sm|md|lg|xl|2xl):/.test(token)) return "Responsive";
26
+ const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
27
+ for (const [label, re] of GROUPS) {
28
+ if (re.test(base)) return label;
29
+ }
30
+ return "Utilities";
31
+ }
32
+ function buildColorVarMap(theme, usedCSS) {
33
+ const map = /* @__PURE__ */ new Map();
34
+ for (const [name, shades] of Object.entries(theme.colors ?? {})) {
35
+ const entries = typeof shades === "string" ? [[name, shades]] : Object.entries(shades).map(([s, v]) => [`${name}-${s}`, v]);
36
+ for (const [fullName, val] of entries) {
37
+ if (typeof val === "string" && /^#[0-9a-fA-F]{3,8}$/.test(val) && usedCSS.includes(val)) {
38
+ map.set(val, `--color-${fullName}`);
39
+ }
40
+ }
41
+ }
42
+ return map;
43
+ }
44
+ function formatKbachCSS(tokenCSS, theme) {
45
+ const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
46
+ const colorVars = buildColorVarMap(theme, allRaw);
47
+ function applyVars(css) {
48
+ for (const [val, varName] of colorVars) {
49
+ css = css.split(val).join(`var(${varName})`);
50
+ }
51
+ return css;
52
+ }
53
+ const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
54
+ for (const [token, css] of tokenCSS) {
55
+ if (!css) continue;
56
+ const label = classifyToken(token);
57
+ if (!groups.has(label)) groups.set(label, []);
58
+ groups.get(label).push(applyVars(css));
59
+ }
60
+ const out = ["/* Generated by Kbach \u2014 do not edit */"];
61
+ if (colorVars.size > 0) {
62
+ out.push("\n:root {");
63
+ for (const [val, varName] of colorVars) {
64
+ out.push(` ${varName}: ${val};`);
65
+ }
66
+ out.push("}");
67
+ }
68
+ const globalCSS = buildThemeGlobal(theme);
69
+ if (globalCSS) {
70
+ out.push(`
71
+ /* Global */
72
+ ${globalCSS}`);
73
+ }
74
+ for (const label of GROUP_ORDER) {
75
+ const items = groups.get(label) ?? [];
76
+ if (!items.length) continue;
77
+ out.push(`
78
+ /* ${label} */`);
79
+ out.push(items.join("\n"));
80
+ }
81
+ return out.join("\n");
82
+ }
83
+ function buildThemeGlobal(theme) {
84
+ const ff = theme.fontFamily;
85
+ if (!ff?.sans || ff.sans === "System") return "";
86
+ const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
87
+ return `body { font-family: ${family}; }`;
88
+ }
13
89
  function findMainCSSFile(root) {
14
- const candidates = [
90
+ for (const c of [
15
91
  join(root, "app", "app.css"),
16
92
  join(root, "src", "index.css"),
17
93
  join(root, "src", "app.css"),
18
94
  join(root, "app", "index.css")
19
- ];
20
- for (const c of candidates) {
95
+ ]) {
21
96
  if (existsSync(c)) return c;
22
97
  }
23
98
  return null;
@@ -51,11 +126,8 @@ function scanDir(dir, onFile) {
51
126
  const full = join(dir, entry);
52
127
  try {
53
128
  const st = statSync(full);
54
- if (st.isDirectory()) {
55
- scanDir(full, onFile);
56
- } else if (/\.(tsx?|jsx?)$/.test(entry)) {
57
- onFile(readFileSync(full, "utf-8"));
58
- }
129
+ if (st.isDirectory()) scanDir(full, onFile);
130
+ else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(readFileSync(full, "utf-8"));
59
131
  } catch {
60
132
  }
61
133
  }
@@ -72,12 +144,6 @@ function extractClassStrings(code) {
72
144
  }
73
145
  return found;
74
146
  }
75
- function buildGlobalCSS(theme) {
76
- const ff = theme.fontFamily;
77
- if (!ff?.sans || ff.sans === "System") return "";
78
- const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
79
- return `body { font-family: ${family}; }`;
80
- }
81
147
  function kbach(userConfig) {
82
148
  const cfg = buildConfig(userConfig ?? {});
83
149
  const screens = toNumericScreens(cfg.theme.screens ?? {});
@@ -91,21 +157,13 @@ function kbach(userConfig) {
91
157
  }
92
158
  function extractFromCode(code) {
93
159
  let added = false;
94
- for (const cls of extractClassStrings(code)) {
95
- for (const token of cls.split(/\s+/).filter(Boolean)) {
160
+ for (const cls of extractClassStrings(code))
161
+ for (const token of cls.split(/\s+/).filter(Boolean))
96
162
  if (processToken(token)) added = true;
97
- }
98
- }
99
163
  return added;
100
164
  }
101
- function getAllCSS() {
102
- const parts = [];
103
- const global = buildGlobalCSS(cfg.theme);
104
- if (global) parts.push(global);
105
- for (const css of tokenCSS.values()) {
106
- if (css) parts.push(css);
107
- }
108
- return parts.join("\n");
165
+ function getFormattedCSS() {
166
+ return formatKbachCSS(tokenCSS, cfg.theme);
109
167
  }
110
168
  return {
111
169
  name: "kbach",
@@ -114,33 +172,27 @@ function kbach(userConfig) {
114
172
  root = resolved.root;
115
173
  },
116
174
  buildStart() {
117
- for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
175
+ for (const dir of ["src", "app", "pages", "components", "views", "layouts"])
118
176
  scanDir(join(root, dir), (code) => extractFromCode(code));
119
- }
120
177
  mainCSSFile = findMainCSSFile(root);
121
- if (mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
178
+ if (mainCSSFile) writeKbachToFile(mainCSSFile, getFormattedCSS());
122
179
  },
123
180
  transform(code, id) {
124
181
  const cleanId = id.split("?")[0];
125
- if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules")) {
182
+ if (/\.(tsx?|jsx?)$/.test(cleanId) && !id.includes("node_modules"))
126
183
  extractFromCode(code);
127
- }
128
184
  },
129
- // ── Virtual module (opt-in explicit import) ───────────────────────────────
130
185
  resolveId(id) {
131
186
  if (id === VIRTUAL_ID) return RESOLVED_ID;
132
187
  },
133
188
  load(id) {
134
- if (id === RESOLVED_ID) return getAllCSS();
189
+ if (id === RESOLVED_ID) return getFormattedCSS();
135
190
  },
136
- // ── HMR: write updated CSS to file when new classes are found ────────────
137
- // Vite's own file watcher detects the change and hot-reloads the CSS —
138
- // no manual invalidation needed.
139
191
  handleHotUpdate({ file }) {
140
192
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
141
193
  try {
142
194
  const added = extractFromCode(readFileSync(file, "utf-8"));
143
- if (added && mainCSSFile) writeKbachToFile(mainCSSFile, getAllCSS());
195
+ if (added && mainCSSFile) writeKbachToFile(mainCSSFile, getFormattedCSS());
144
196
  } catch {
145
197
  }
146
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.26",
3
+ "version": "0.5.28",
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",