@kbach/react 0.6.30 → 0.6.32

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.
@@ -615,7 +615,15 @@ var defaultTheme = {
615
615
  animation: {}
616
616
  };
617
617
 
618
+ // src/core/colorValue.ts
619
+ function isModeAwareColor(v) {
620
+ return typeof v === "object" && v !== null && "light" in v && "dark" in v;
621
+ }
622
+
618
623
  // src/core/resolvers/color.ts
624
+ function pickModeAwareFallback(v) {
625
+ return isModeAwareColor(v) ? v.light : v;
626
+ }
619
627
  function resolveColor(value, colors, isArbitrary) {
620
628
  if (isArbitrary) return value;
621
629
  const slashIdx = value.indexOf("/");
@@ -627,15 +635,17 @@ function resolveColor(value, colors, isArbitrary) {
627
635
  } else if (colorPart in colors) {
628
636
  const entry = colors[colorPart];
629
637
  if (typeof entry === "string") hex = entry;
630
- else if (typeof entry === "object" && "6" in entry) hex = entry["6"];
638
+ else if (isModeAwareColor(entry)) hex = pickModeAwareFallback(entry);
639
+ else if (typeof entry === "object" && "6" in entry) hex = pickModeAwareFallback(entry["6"]);
631
640
  } else {
632
641
  const lastDash = colorPart.lastIndexOf("-");
633
642
  if (lastDash > 0) {
634
643
  const colorName = colorPart.slice(0, lastDash);
635
644
  const shade = colorPart.slice(lastDash + 1);
636
645
  const scale = colors[colorName];
637
- if (scale && typeof scale === "object" && shade in scale) {
638
- hex = scale[shade] ?? null;
646
+ if (scale && typeof scale === "object" && !isModeAwareColor(scale) && shade in scale) {
647
+ const shadeVal = scale[shade];
648
+ hex = shadeVal !== void 0 ? pickModeAwareFallback(shadeVal) : null;
639
649
  }
640
650
  }
641
651
  }
@@ -2721,6 +2731,62 @@ function findOuterColon(s) {
2721
2731
  return -1;
2722
2732
  }
2723
2733
 
2734
+ // src/core/modeAwareColors.ts
2735
+ var _modeAwareMapCache = /* @__PURE__ */ new WeakMap();
2736
+ function buildModeAwareMap(colors) {
2737
+ const map = /* @__PURE__ */ new Map();
2738
+ for (const [name, entry] of Object.entries(colors)) {
2739
+ if (isModeAwareColor(entry)) {
2740
+ map.set(name, entry);
2741
+ } else if (entry && typeof entry === "object") {
2742
+ for (const [shade, val] of Object.entries(entry)) {
2743
+ if (isModeAwareColor(val)) map.set(`${name}-${shade}`, val);
2744
+ }
2745
+ }
2746
+ }
2747
+ return map;
2748
+ }
2749
+ function getModeAwareMap(colors) {
2750
+ let map = _modeAwareMapCache.get(colors);
2751
+ if (!map) {
2752
+ map = buildModeAwareMap(colors);
2753
+ _modeAwareMapCache.set(colors, map);
2754
+ }
2755
+ return map;
2756
+ }
2757
+ function expandModeAwareColorClasses(classString, colors) {
2758
+ const map = getModeAwareMap(colors);
2759
+ if (map.size === 0) return classString;
2760
+ const tokens = splitClassTokens(classString);
2761
+ let changed = false;
2762
+ const out = [];
2763
+ for (const token of tokens) {
2764
+ const parsed = parseClass(token);
2765
+ if (!parsed || parsed.isArbitrary) {
2766
+ out.push(token);
2767
+ continue;
2768
+ }
2769
+ if (parsed.modifiers.includes("dark") || parsed.modifiers.includes("light")) {
2770
+ out.push(token);
2771
+ continue;
2772
+ }
2773
+ const slashIdx = parsed.value.indexOf("/");
2774
+ const colorPart = slashIdx > 0 ? parsed.value.slice(0, slashIdx) : parsed.value;
2775
+ const opacitySuffix = slashIdx > 0 ? parsed.value.slice(slashIdx) : "";
2776
+ const pair = map.get(colorPart);
2777
+ if (!pair) {
2778
+ out.push(token);
2779
+ continue;
2780
+ }
2781
+ changed = true;
2782
+ const bang = parsed.important ? "!" : "";
2783
+ const modPrefix = parsed.modifiers.map((m) => `${m}:`).join("");
2784
+ out.push(`${bang}${modPrefix}${parsed.utility}-[${pair.light}]${opacitySuffix}`);
2785
+ out.push(`${bang}dark:${modPrefix}${parsed.utility}-[${pair.dark}]${opacitySuffix}`);
2786
+ }
2787
+ return changed ? out.join(" ") : classString;
2788
+ }
2789
+
2724
2790
  // src/core/resolver.ts
2725
2791
  var _defaultFontFamily;
2726
2792
  function setDefaultFontFamily(font) {
@@ -2943,7 +3009,7 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2943
3009
  }
2944
3010
  function generateClassCSS(classString, theme, darkMode = "attribute", screens = {}) {
2945
3011
  const rules = [];
2946
- for (const parsed of parseClasses(classString)) {
3012
+ for (const parsed of parseClasses(expandModeAwareColorClasses(classString, theme.colors))) {
2947
3013
  const styles = resolveUtility(parsed, theme);
2948
3014
  if (!styles) continue;
2949
3015
  const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
@@ -2977,8 +3043,9 @@ function resolveOneRef(ref, colors) {
2977
3043
  const shade = ref.slice(lastDash + 1);
2978
3044
  if (!/^\d+$/.test(shade)) return null;
2979
3045
  const entry = colors[name];
2980
- if (!entry || typeof entry !== "object") return null;
2981
- return entry[shade] ?? null;
3046
+ if (!entry || typeof entry !== "object" || isModeAwareColor(entry)) return null;
3047
+ const target = entry[shade];
3048
+ return typeof target === "string" ? target : null;
2982
3049
  }
2983
3050
  function resolveColorRefs(colors) {
2984
3051
  const MAX_DEPTH = 5;
@@ -2991,14 +3058,17 @@ function resolveColorRefs(colors) {
2991
3058
  }
2992
3059
  return current;
2993
3060
  }
3061
+ function resolveValue(val) {
3062
+ return typeof val === "string" ? resolveChain(val) : { light: resolveChain(val.light), dark: resolveChain(val.dark) };
3063
+ }
2994
3064
  const out = {};
2995
3065
  for (const [key, val] of Object.entries(colors)) {
2996
- if (typeof val === "string") {
2997
- out[key] = resolveChain(val);
3066
+ if (typeof val === "string" || isModeAwareColor(val)) {
3067
+ out[key] = resolveValue(val);
2998
3068
  } else {
2999
3069
  const shades = {};
3000
- for (const [shade, hex] of Object.entries(val)) {
3001
- shades[shade] = resolveChain(hex);
3070
+ for (const [shade, v] of Object.entries(val)) {
3071
+ shades[shade] = resolveValue(v);
3002
3072
  }
3003
3073
  out[key] = shades;
3004
3074
  }
package/kbach-react.md CHANGED
@@ -712,6 +712,29 @@ module.exports = {
712
712
 
713
713
  **Global default font (web):** Setting `fontFamily.sans` to anything other than `'System'` auto-injects `body { font-family: <font> }`.
714
714
 
715
+ ### Mode-aware colors (dark mode without `dark:`)
716
+
717
+ A color value can be `{ light, dark }` instead of a plain string — define it once, and every class using that color automatically picks the right side, with no `dark:` variant needed at the call site:
718
+
719
+ ```js
720
+ extend: {
721
+ theme: {
722
+ colors: {
723
+ surface: { light: '#ffffff', dark: '#111827' },
724
+ // each side can itself be an alias — resolved independently
725
+ accent: { light: 'blue-6', dark: 'blue-4' },
726
+ },
727
+ },
728
+ },
729
+ ```
730
+
731
+ ```jsx
732
+ <div className="bg-surface text-accent" />
733
+ // equivalent to writing bg-white dark:bg-gray-9 text-blue-6 dark:text-blue-4 by hand
734
+ ```
735
+
736
+ Works everywhere a color does — `useColors()` (returns the active side directly), opacity composition (`bg-surface/50`), and per-shade within a scale (`brand: { 6: { light: '#3b82f6', dark: '#60a5fa' } }`). An explicit `dark:`/`light:` modifier stacked on an already mode-aware color is not supported (falls back to the light side) — the whole point is not needing `dark:` for these.
737
+
715
738
  ### Runtime update
716
739
  ```js
717
740
  import { updateConfig, clearCache } from '@kbach/react';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.30",
3
+ "version": "0.6.32",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "repository": {
6
6
  "type": "git",