@kbach/ui 0.1.0-beta.6 → 0.1.0-beta.7

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/KBACH.md CHANGED
@@ -122,12 +122,19 @@ const Button = styled('button', 'bg-blue-7 hover:bg-blue-8 rounded-xl px-6 py-3'
122
122
  On web, `styled()` forwards the full class string as `className` so CSS rules (group-hover:, before:, print:) match the element.
123
123
 
124
124
  ### useStyles(classes)
125
- Resolve classes to a style object inside a component.
125
+ Resolve classes to a style object inside a component. This is also the escape hatch for genuinely dynamic class strings (`` `bg-${color}-${shade}` ``) that the static Vite plugin can't discover by scanning source text — it always resolves at render time regardless.
126
126
  ```jsx
127
127
  import { useStyles } from '@kbach/ui';
128
128
  const style = useStyles('bg-blue-6 px-3 py-1 rounded-full');
129
129
  return <span style={style}>Badge</span>;
130
130
  ```
131
+ Want that same dynamic set of classes as real CSS in the static file instead of an inline style? Generate the full list yourself and pass it to the plugin's `safelist` option:
132
+ ```ts
133
+ // vite.config.ts
134
+ const families = ['red', 'blue' /* … */];
135
+ const shades = [1, 2, 3 /* … */];
136
+ kbach({ safelist: families.flatMap((f) => shades.map((s) => `bg-${f}-${s}`)) })
137
+ ```
131
138
 
132
139
  ### kb(classes)
133
140
  Resolve outside a component (static contexts).
@@ -150,6 +150,24 @@ interface KbachPluginOptions {
150
150
  framework?: FrameworkConfig;
151
151
  /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */
152
152
  include?: string[];
153
+ /**
154
+ * Class names to always include in the generated kbach.css, even if no
155
+ * scan finds them literally in your source. Static extraction only ever
156
+ * sees complete class strings as they appear in your files — it can't
157
+ * evaluate `` `bg-${color}-${shade}` `` or similar runtime-built strings,
158
+ * because the actual value doesn't exist until the component renders (see
159
+ * useStyles()'s own docblock for the same limitation from the other
160
+ * direction). Same purpose as Tailwind's `safelist` config: list the
161
+ * complete class names explicitly, e.g. generated from the same
162
+ * color/shade arrays your component already loops over —
163
+ *
164
+ * ```js
165
+ * const families = ['red', 'blue', 'green'];
166
+ * const shades = [1, 2, 3];
167
+ * safelist: families.flatMap((f) => shades.map((s) => `bg-${f}-${s}`))
168
+ * ```
169
+ */
170
+ safelist?: string[];
153
171
  }
154
172
  declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin;
155
173
 
@@ -150,6 +150,24 @@ interface KbachPluginOptions {
150
150
  framework?: FrameworkConfig;
151
151
  /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */
152
152
  include?: string[];
153
+ /**
154
+ * Class names to always include in the generated kbach.css, even if no
155
+ * scan finds them literally in your source. Static extraction only ever
156
+ * sees complete class strings as they appear in your files — it can't
157
+ * evaluate `` `bg-${color}-${shade}` `` or similar runtime-built strings,
158
+ * because the actual value doesn't exist until the component renders (see
159
+ * useStyles()'s own docblock for the same limitation from the other
160
+ * direction). Same purpose as Tailwind's `safelist` config: list the
161
+ * complete class names explicitly, e.g. generated from the same
162
+ * color/shade arrays your component already loops over —
163
+ *
164
+ * ```js
165
+ * const families = ['red', 'blue', 'green'];
166
+ * const shades = [1, 2, 3];
167
+ * safelist: families.flatMap((f) => shades.map((s) => `bg-${f}-${s}`))
168
+ * ```
169
+ */
170
+ safelist?: string[];
153
171
  }
154
172
  declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin;
155
173
 
@@ -3860,9 +3860,10 @@ function stripModifierPrefix(token) {
3860
3860
  }
3861
3861
  var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
3862
3862
  function kbach(userConfigOrOptions) {
3863
- const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
3863
+ const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions || "safelist" in userConfigOrOptions);
3864
3864
  const userConfig = isOptions ? userConfigOrOptions.framework : userConfigOrOptions;
3865
3865
  const includeDirs = (isOptions ? userConfigOrOptions.include : void 0) ?? DEFAULT_SCAN_DIRS;
3866
+ const safelist = (isOptions ? userConfigOrOptions.safelist : void 0) ?? [];
3866
3867
  const cfg = buildConfig(userConfig ?? {});
3867
3868
  const screens = toNumericScreens(cfg.theme.screens ?? {});
3868
3869
  const screenKeys = Object.keys(cfg.theme.screens ?? {});
@@ -3895,6 +3896,17 @@ function kbach(userConfigOrOptions) {
3895
3896
  }
3896
3897
  fileTokens.set(key, tokens);
3897
3898
  }
3899
+ const SAFELIST_KEY = "\0kbach-safelist";
3900
+ function processSafelist() {
3901
+ if (safelist.length === 0) return;
3902
+ const tokens = new Set(safelist);
3903
+ for (const tok of tokens) {
3904
+ if (!tokenCSS.has(tok)) {
3905
+ tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
3906
+ }
3907
+ }
3908
+ fileTokens.set(SAFELIST_KEY, tokens);
3909
+ }
3898
3910
  function scanProjectCssSelectors() {
3899
3911
  for (const dir of includeDirs) scanCssSelectorsInto((0, import_path.join)(root, dir), projectCssClasses);
3900
3912
  }
@@ -3992,6 +4004,7 @@ ${code}`,
3992
4004
  mainCSSFile = findKbachCSS(root);
3993
4005
  scanProjectCssSelectors();
3994
4006
  initialScan();
4007
+ processSafelist();
3995
4008
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3996
4009
  },
3997
4010
  // Vite's `handleHotUpdate` hook only fires for `type === "update"` (plain
@@ -3824,9 +3824,10 @@ function stripModifierPrefix(token) {
3824
3824
  }
3825
3825
  var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
3826
3826
  function kbach(userConfigOrOptions) {
3827
- const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
3827
+ const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions || "safelist" in userConfigOrOptions);
3828
3828
  const userConfig = isOptions ? userConfigOrOptions.framework : userConfigOrOptions;
3829
3829
  const includeDirs = (isOptions ? userConfigOrOptions.include : void 0) ?? DEFAULT_SCAN_DIRS;
3830
+ const safelist = (isOptions ? userConfigOrOptions.safelist : void 0) ?? [];
3830
3831
  const cfg = buildConfig(userConfig ?? {});
3831
3832
  const screens = toNumericScreens(cfg.theme.screens ?? {});
3832
3833
  const screenKeys = Object.keys(cfg.theme.screens ?? {});
@@ -3859,6 +3860,17 @@ function kbach(userConfigOrOptions) {
3859
3860
  }
3860
3861
  fileTokens.set(key, tokens);
3861
3862
  }
3863
+ const SAFELIST_KEY = "\0kbach-safelist";
3864
+ function processSafelist() {
3865
+ if (safelist.length === 0) return;
3866
+ const tokens = new Set(safelist);
3867
+ for (const tok of tokens) {
3868
+ if (!tokenCSS.has(tok)) {
3869
+ tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
3870
+ }
3871
+ }
3872
+ fileTokens.set(SAFELIST_KEY, tokens);
3873
+ }
3862
3874
  function scanProjectCssSelectors() {
3863
3875
  for (const dir of includeDirs) scanCssSelectorsInto(join(root, dir), projectCssClasses);
3864
3876
  }
@@ -3956,6 +3968,7 @@ ${code}`,
3956
3968
  mainCSSFile = findKbachCSS(root);
3957
3969
  scanProjectCssSelectors();
3958
3970
  initialScan();
3971
+ processSafelist();
3959
3972
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3960
3973
  },
3961
3974
  // Vite's `handleHotUpdate` hook only fires for `type === "update"` (plain
package/kbach-ui.md CHANGED
@@ -120,6 +120,27 @@ kbach({
120
120
  })
121
121
  ```
122
122
 
123
+ `kbach()` also accepts an options wrapper — `{ framework, include, safelist }` — instead of a bare framework config directly:
124
+
125
+ ```ts
126
+ kbach({
127
+ framework: { darkMode: 'attribute', theme: { colors: { brand: { 6: '#6366f1' } } } },
128
+ include: ['src'], // dirs to scan (relative to Vite root) — default: src, app, pages, components, views, layouts
129
+ safelist: [ /* see below */ ],
130
+ })
131
+ ```
132
+
133
+ **`safelist`** — class names to always include, even if no scan finds them. Static extraction only ever sees complete class strings as they appear in your source — it can't evaluate `` `bg-${family}-${shade}` `` or similar runtime-built strings, since the actual value doesn't exist until the component renders (this is exactly why `useStyles()` exists — see its own docs above — it resolves those to a real inline style instead). If you want a genuinely dynamic set of classes in the static file anyway, generate the full list yourself and pass it in — same purpose as Tailwind's own `safelist` config:
134
+
135
+ ```ts
136
+ const families = ['red', 'blue', 'green' /* … */];
137
+ const shades = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
138
+
139
+ kbach({
140
+ safelist: families.flatMap((f) => shades.map((s) => `bg-${f}-${s}`)),
141
+ })
142
+ ```
143
+
123
144
  Generated output format:
124
145
  ```css
125
146
  /* kbach:start */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/ui",
3
- "version": "0.1.0-beta.6",
3
+ "version": "0.1.0-beta.7",
4
4
  "description": "Tailwind-like utility classes and components for React — web, React Native, and Expo",
5
5
  "license": "MIT",
6
6
  "repository": {