@mgcrea/react-native-tailwind 0.11.0 → 0.12.0

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
@@ -1887,6 +1887,135 @@ const styles = StyleSheet.create({
1887
1887
  - Choose a name that won't conflict with existing variables in your files
1888
1888
  - The same identifier is used across all files in your project
1889
1889
 
1890
+ ### Custom Color Scheme Hook
1891
+
1892
+ By default, the plugin uses React Native's built-in `useColorScheme()` hook for `dark:` and `light:` modifiers. You can configure it to use a custom color scheme hook from theme providers like React Navigation, Expo, or your own implementation.
1893
+
1894
+ **Configuration:**
1895
+
1896
+ ```javascript
1897
+ // babel.config.js
1898
+ module.exports = {
1899
+ plugins: [
1900
+ [
1901
+ "@mgcrea/react-native-tailwind/babel",
1902
+ {
1903
+ colorScheme: {
1904
+ importFrom: "@/hooks/useColorScheme", // Module to import from
1905
+ importName: "useColorScheme", // Hook name to import
1906
+ },
1907
+ },
1908
+ ],
1909
+ ],
1910
+ };
1911
+ ```
1912
+
1913
+ **Use Cases:**
1914
+
1915
+ #### 1. Custom Theme Provider
1916
+
1917
+ Override system color scheme with user preferences from a store:
1918
+
1919
+ ```typescript
1920
+ // src/hooks/useColorScheme.ts
1921
+ import { useColorScheme as useSystemColorScheme } from "react-native";
1922
+ import { profileStore } from "@/stores/profileStore";
1923
+ import { type ColorSchemeName } from "react-native";
1924
+
1925
+ export const useColorScheme = (): ColorSchemeName => {
1926
+ const systemColorScheme = useSystemColorScheme();
1927
+ const userTheme = profileStore.theme; // 'dark' | 'light' | 'auto'
1928
+
1929
+ // Return user preference, or fall back to system if set to 'auto'
1930
+ return userTheme === 'auto' ? systemColorScheme : userTheme;
1931
+ };
1932
+ ```
1933
+
1934
+ ```javascript
1935
+ // babel.config.js
1936
+ {
1937
+ colorScheme: {
1938
+ importFrom: "@/hooks/useColorScheme",
1939
+ importName: "useColorScheme"
1940
+ }
1941
+ }
1942
+ ```
1943
+
1944
+ #### 2. React Navigation Theme
1945
+
1946
+ Integrate with React Navigation's theme system:
1947
+
1948
+ ```typescript
1949
+ // Wrap React Navigation's useTheme to return ColorSchemeName
1950
+ import { useTheme as useNavTheme } from "@react-navigation/native";
1951
+ import { type ColorSchemeName } from "react-native";
1952
+
1953
+ export const useColorScheme = (): ColorSchemeName => {
1954
+ const { dark } = useNavTheme();
1955
+ return dark ? "dark" : "light";
1956
+ };
1957
+ ```
1958
+
1959
+ #### 3. Expo Router Theme
1960
+
1961
+ Use Expo Router's theme hook:
1962
+
1963
+ ```javascript
1964
+ // babel.config.js
1965
+ {
1966
+ colorScheme: {
1967
+ importFrom: "expo-router",
1968
+ importName: "useColorScheme"
1969
+ }
1970
+ }
1971
+ ```
1972
+
1973
+ #### 4. Testing
1974
+
1975
+ Mock color scheme for tests:
1976
+
1977
+ ```typescript
1978
+ // test/mocks/useColorScheme.ts
1979
+ export const useColorScheme = () => "light"; // Or "dark" for dark mode tests
1980
+ ```
1981
+
1982
+ ```javascript
1983
+ // babel.config.js (test environment)
1984
+ {
1985
+ colorScheme: {
1986
+ importFrom: "@/test/mocks/useColorScheme",
1987
+ importName: "useColorScheme"
1988
+ }
1989
+ }
1990
+ ```
1991
+
1992
+ #### How it works
1993
+
1994
+ When you use `dark:` or `light:` modifiers:
1995
+
1996
+ ```tsx
1997
+ <View className="bg-white dark:bg-gray-900" />
1998
+ ```
1999
+
2000
+ The plugin will:
2001
+
2002
+ 1. Import your custom hook: `import { useColorScheme } from "@/hooks/useColorScheme"`
2003
+ 2. Inject it in components: `const _twColorScheme = useColorScheme();`
2004
+ 3. Generate conditionals: `_twColorScheme === "dark" && styles._dark_bg_gray_900`
2005
+
2006
+ #### Default behavior (no configuration)
2007
+
2008
+ Without custom configuration, the plugin uses React Native's built-in hook:
2009
+
2010
+ - Import: `import { useColorScheme } from "react-native"`
2011
+ - This works out of the box for basic system color scheme detection
2012
+
2013
+ #### Requirements
2014
+
2015
+ - Your custom hook must return `ColorSchemeName` (type from React Native: `"light" | "dark" | null | undefined`)
2016
+ - The hook must be compatible with React's rules of hooks (can only be called in function components)
2017
+ - Import merging works automatically if you already import from the same source
2018
+
1890
2019
  ### Arbitrary Values
1891
2020
 
1892
2021
  Use arbitrary values for custom sizes, spacing, and borders not in the preset scales:
@@ -1194,14 +1194,14 @@ var SPACING_SCALE = {
1194
1194
  96: 384
1195
1195
  };
1196
1196
  function parseArbitrarySpacing(value) {
1197
- const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
1197
+ const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?)(?:px)?\]$/);
1198
1198
  if (pxMatch) {
1199
- return parseInt(pxMatch[1], 10);
1199
+ return parseFloat(pxMatch[1]);
1200
1200
  }
1201
1201
  if (value.startsWith("[") && value.endsWith("]")) {
1202
1202
  if (process.env.NODE_ENV !== "production") {
1203
1203
  console.warn(
1204
- `[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px] or [16]).`
1204
+ `[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px], [16], [4.5px], [4.5]).`
1205
1205
  );
1206
1206
  }
1207
1207
  return null;
@@ -2518,33 +2518,33 @@ function addPlatformImport(path2, t) {
2518
2518
  path2.unshiftContainer("body", importDeclaration);
2519
2519
  }
2520
2520
  }
2521
- function addColorSchemeImport(path2, t) {
2521
+ function addColorSchemeImport(path2, importSource, hookName, t) {
2522
2522
  const body = path2.node.body;
2523
- let reactNativeImport = null;
2523
+ let existingValueImport = null;
2524
2524
  for (const statement of body) {
2525
- if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2526
- reactNativeImport = statement;
2527
- break;
2525
+ if (t.isImportDeclaration(statement) && statement.source.value === importSource) {
2526
+ if (statement.importKind !== "type") {
2527
+ existingValueImport = statement;
2528
+ break;
2529
+ }
2528
2530
  }
2529
2531
  }
2530
- if (reactNativeImport) {
2531
- const hasUseColorScheme = reactNativeImport.specifiers.some(
2532
- (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "useColorScheme"
2532
+ if (existingValueImport) {
2533
+ const hasHook = existingValueImport.specifiers.some(
2534
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === hookName
2533
2535
  );
2534
- if (!hasUseColorScheme) {
2535
- reactNativeImport.specifiers.push(
2536
- t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))
2537
- );
2536
+ if (!hasHook) {
2537
+ existingValueImport.specifiers.push(t.importSpecifier(t.identifier(hookName), t.identifier(hookName)));
2538
2538
  }
2539
2539
  } else {
2540
2540
  const importDeclaration = t.importDeclaration(
2541
- [t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))],
2542
- t.stringLiteral("react-native")
2541
+ [t.importSpecifier(t.identifier(hookName), t.identifier(hookName))],
2542
+ t.stringLiteral(importSource)
2543
2543
  );
2544
2544
  path2.unshiftContainer("body", importDeclaration);
2545
2545
  }
2546
2546
  }
2547
- function injectColorSchemeHook(functionPath, colorSchemeVariableName, t) {
2547
+ function injectColorSchemeHook(functionPath, colorSchemeVariableName, hookName, localIdentifier, t) {
2548
2548
  let body = functionPath.node.body;
2549
2549
  if (!t.isBlockStatement(body)) {
2550
2550
  if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
@@ -2566,10 +2566,11 @@ function injectColorSchemeHook(functionPath, colorSchemeVariableName, t) {
2566
2566
  if (hasHook) {
2567
2567
  return false;
2568
2568
  }
2569
+ const identifierToCall = localIdentifier ?? hookName;
2569
2570
  const hookCall = t.variableDeclaration("const", [
2570
2571
  t.variableDeclarator(
2571
2572
  t.identifier(colorSchemeVariableName),
2572
- t.callExpression(t.identifier("useColorScheme"), [])
2573
+ t.callExpression(t.identifier(identifierToCall), [])
2573
2574
  )
2574
2575
  ]);
2575
2576
  body.body.unshift(hookCall);
@@ -2724,7 +2725,7 @@ function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
2724
2725
  }
2725
2726
 
2726
2727
  // src/babel/utils/twProcessing.ts
2727
- function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, t) {
2728
+ function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, findComponentScope2, t) {
2728
2729
  const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
2729
2730
  const modifierClasses = [];
2730
2731
  for (const modifier of rawModifierClasses) {
@@ -2755,8 +2756,145 @@ function processTwCall(className, path2, state, parseClassName2, generateStyleKe
2755
2756
  } else {
2756
2757
  objectProperties.push(t.objectProperty(t.identifier("style"), t.objectExpression([])));
2757
2758
  }
2759
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
2760
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
2761
+ const otherModifiers = modifierClasses.filter(
2762
+ (m) => !isColorSchemeModifier(m.modifier) && !isPlatformModifier(m.modifier)
2763
+ );
2764
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
2765
+ let componentScope = null;
2766
+ if (hasColorSchemeModifiers) {
2767
+ componentScope = findComponentScope2(path2, t);
2768
+ if (!componentScope) {
2769
+ if (process.env.NODE_ENV !== "production") {
2770
+ console.warn(
2771
+ `[react-native-tailwind] Color scheme modifiers (dark:, light:) in tw/twStyle calls must be used inside a React component. Modifiers will be ignored.`
2772
+ );
2773
+ }
2774
+ } else {
2775
+ state.functionComponentsNeedingColorScheme.add(componentScope);
2776
+ }
2777
+ }
2778
+ if (hasColorSchemeModifiers && componentScope) {
2779
+ const colorSchemeConditionals = processColorSchemeModifiers(
2780
+ colorSchemeModifiers,
2781
+ state,
2782
+ parseClassName2,
2783
+ generateStyleKey2,
2784
+ t
2785
+ );
2786
+ const styleArrayElements = [];
2787
+ if (baseClasses.length > 0) {
2788
+ const baseClassName = baseClasses.join(" ");
2789
+ const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
2790
+ const baseStyleKey = generateStyleKey2(baseClassName);
2791
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2792
+ styleArrayElements.push(
2793
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2794
+ );
2795
+ }
2796
+ styleArrayElements.push(...colorSchemeConditionals);
2797
+ objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
2798
+ const darkModifiers = colorSchemeModifiers.filter((m) => m.modifier === "dark");
2799
+ const lightModifiers = colorSchemeModifiers.filter((m) => m.modifier === "light");
2800
+ if (darkModifiers.length > 0) {
2801
+ const darkClassNames = darkModifiers.map((m) => m.baseClass).join(" ");
2802
+ const darkStyleObject = parseClassName2(darkClassNames, state.customTheme);
2803
+ const darkStyleKey = generateStyleKey2(`dark_${darkClassNames}`);
2804
+ state.styleRegistry.set(darkStyleKey, darkStyleObject);
2805
+ objectProperties.push(
2806
+ t.objectProperty(
2807
+ t.identifier("darkStyle"),
2808
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(darkStyleKey))
2809
+ )
2810
+ );
2811
+ }
2812
+ if (lightModifiers.length > 0) {
2813
+ const lightClassNames = lightModifiers.map((m) => m.baseClass).join(" ");
2814
+ const lightStyleObject = parseClassName2(lightClassNames, state.customTheme);
2815
+ const lightStyleKey = generateStyleKey2(`light_${lightClassNames}`);
2816
+ state.styleRegistry.set(lightStyleKey, lightStyleObject);
2817
+ objectProperties.push(
2818
+ t.objectProperty(
2819
+ t.identifier("lightStyle"),
2820
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(lightStyleKey))
2821
+ )
2822
+ );
2823
+ }
2824
+ }
2825
+ const hasPlatformModifiers = platformModifiers.length > 0;
2826
+ if (hasPlatformModifiers) {
2827
+ state.needsPlatformImport = true;
2828
+ const platformSelectExpression = processPlatformModifiers(
2829
+ platformModifiers,
2830
+ state,
2831
+ parseClassName2,
2832
+ generateStyleKey2,
2833
+ t
2834
+ );
2835
+ if (hasColorSchemeModifiers && componentScope) {
2836
+ const styleProperty = objectProperties.find(
2837
+ (prop) => t.isIdentifier(prop.key) && prop.key.name === "style"
2838
+ );
2839
+ if (styleProperty && t.isArrayExpression(styleProperty.value)) {
2840
+ styleProperty.value.elements.push(platformSelectExpression);
2841
+ }
2842
+ } else {
2843
+ const styleArrayElements = [];
2844
+ if (baseClasses.length > 0) {
2845
+ const baseClassName = baseClasses.join(" ");
2846
+ const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
2847
+ const baseStyleKey = generateStyleKey2(baseClassName);
2848
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
2849
+ styleArrayElements.push(
2850
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2851
+ );
2852
+ }
2853
+ styleArrayElements.push(platformSelectExpression);
2854
+ objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
2855
+ }
2856
+ const iosModifiers = platformModifiers.filter((m) => m.modifier === "ios");
2857
+ const androidModifiers = platformModifiers.filter((m) => m.modifier === "android");
2858
+ const webModifiers = platformModifiers.filter((m) => m.modifier === "web");
2859
+ if (iosModifiers.length > 0) {
2860
+ const iosClassNames = iosModifiers.map((m) => m.baseClass).join(" ");
2861
+ const iosStyleObject = parseClassName2(iosClassNames, state.customTheme);
2862
+ const iosStyleKey = generateStyleKey2(`ios_${iosClassNames}`);
2863
+ state.styleRegistry.set(iosStyleKey, iosStyleObject);
2864
+ objectProperties.push(
2865
+ t.objectProperty(
2866
+ t.identifier("iosStyle"),
2867
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(iosStyleKey))
2868
+ )
2869
+ );
2870
+ }
2871
+ if (androidModifiers.length > 0) {
2872
+ const androidClassNames = androidModifiers.map((m) => m.baseClass).join(" ");
2873
+ const androidStyleObject = parseClassName2(androidClassNames, state.customTheme);
2874
+ const androidStyleKey = generateStyleKey2(`android_${androidClassNames}`);
2875
+ state.styleRegistry.set(androidStyleKey, androidStyleObject);
2876
+ objectProperties.push(
2877
+ t.objectProperty(
2878
+ t.identifier("androidStyle"),
2879
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(androidStyleKey))
2880
+ )
2881
+ );
2882
+ }
2883
+ if (webModifiers.length > 0) {
2884
+ const webClassNames = webModifiers.map((m) => m.baseClass).join(" ");
2885
+ const webStyleObject = parseClassName2(webClassNames, state.customTheme);
2886
+ const webStyleKey = generateStyleKey2(`web_${webClassNames}`);
2887
+ state.styleRegistry.set(webStyleKey, webStyleObject);
2888
+ objectProperties.push(
2889
+ t.objectProperty(
2890
+ t.identifier("webStyle"),
2891
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(webStyleKey))
2892
+ )
2893
+ );
2894
+ }
2895
+ }
2758
2896
  const modifiersByType = /* @__PURE__ */ new Map();
2759
- for (const mod of modifierClasses) {
2897
+ for (const mod of otherModifiers) {
2760
2898
  if (!modifiersByType.has(mod.modifier)) {
2761
2899
  modifiersByType.set(mod.modifier, []);
2762
2900
  }
@@ -2852,6 +2990,8 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2852
2990
  darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
2853
2991
  lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light"
2854
2992
  };
2993
+ const colorSchemeImportSource = options?.colorScheme?.importFrom ?? "react-native";
2994
+ const colorSchemeHookName = options?.colorScheme?.importName ?? "useColorScheme";
2855
2995
  return {
2856
2996
  name: "react-native-tailwind",
2857
2997
  visitor: {
@@ -2865,12 +3005,18 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2865
3005
  state.hasColorSchemeImport = false;
2866
3006
  state.needsColorSchemeImport = false;
2867
3007
  state.colorSchemeVariableName = "_twColorScheme";
3008
+ state.colorSchemeImportSource = colorSchemeImportSource;
3009
+ state.colorSchemeHookName = colorSchemeHookName;
2868
3010
  state.supportedAttributes = exactMatches;
2869
3011
  state.attributePatterns = patterns;
2870
3012
  state.stylesIdentifier = stylesIdentifier;
2871
3013
  state.twImportNames = /* @__PURE__ */ new Set();
2872
3014
  state.hasTwImport = false;
2873
3015
  state.functionComponentsNeedingColorScheme = /* @__PURE__ */ new Set();
3016
+ state.hasColorSchemeImport = false;
3017
+ state.colorSchemeLocalIdentifier = void 0;
3018
+ state.needsPlatformImport = false;
3019
+ state.hasPlatformImport = false;
2874
3020
  state.customTheme = extractCustomTheme(state.file.opts.filename ?? "");
2875
3021
  state.schemeModifierConfig = schemeModifierConfig;
2876
3022
  },
@@ -2888,11 +3034,17 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2888
3034
  addPlatformImport(path2, t);
2889
3035
  }
2890
3036
  if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
2891
- addColorSchemeImport(path2, t);
3037
+ addColorSchemeImport(path2, state.colorSchemeImportSource, state.colorSchemeHookName, t);
2892
3038
  }
2893
3039
  if (state.needsColorSchemeImport) {
2894
3040
  for (const functionPath of state.functionComponentsNeedingColorScheme) {
2895
- injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
3041
+ injectColorSchemeHook(
3042
+ functionPath,
3043
+ state.colorSchemeVariableName,
3044
+ state.colorSchemeHookName,
3045
+ state.colorSchemeLocalIdentifier,
3046
+ t
3047
+ );
2896
3048
  }
2897
3049
  }
2898
3050
  injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
@@ -2915,23 +3067,26 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2915
3067
  }
2916
3068
  return false;
2917
3069
  });
2918
- const hasUseColorScheme = specifiers.some((spec) => {
2919
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
2920
- return spec.imported.name === "useColorScheme";
2921
- }
2922
- return false;
2923
- });
2924
3070
  if (hasStyleSheet) {
2925
3071
  state.hasStyleSheetImport = true;
2926
3072
  }
2927
3073
  if (hasPlatform) {
2928
3074
  state.hasPlatformImport = true;
2929
3075
  }
2930
- if (hasUseColorScheme) {
2931
- state.hasColorSchemeImport = true;
2932
- }
2933
3076
  state.reactNativeImportPath = path2;
2934
3077
  }
3078
+ if (node.source.value === state.colorSchemeImportSource && node.importKind !== "type") {
3079
+ const specifiers = node.specifiers;
3080
+ for (const spec of specifiers) {
3081
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
3082
+ if (spec.imported.name === state.colorSchemeHookName) {
3083
+ state.hasColorSchemeImport = true;
3084
+ state.colorSchemeLocalIdentifier = spec.local.name;
3085
+ break;
3086
+ }
3087
+ }
3088
+ }
3089
+ }
2935
3090
  if (node.source.value === "@mgcrea/react-native-tailwind") {
2936
3091
  const specifiers = node.specifiers;
2937
3092
  specifiers.forEach((spec) => {
@@ -2976,7 +3131,16 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2976
3131
  return;
2977
3132
  }
2978
3133
  state.hasClassNames = true;
2979
- processTwCall(className, path2, state, parseClassName, generateStyleKey, splitModifierClasses, t);
3134
+ processTwCall(
3135
+ className,
3136
+ path2,
3137
+ state,
3138
+ parseClassName,
3139
+ generateStyleKey,
3140
+ splitModifierClasses,
3141
+ findComponentScope,
3142
+ t
3143
+ );
2980
3144
  },
2981
3145
  // Handle twStyle('...') call expressions
2982
3146
  CallExpression(path2, state) {
@@ -3011,7 +3175,16 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3011
3175
  return;
3012
3176
  }
3013
3177
  state.hasClassNames = true;
3014
- processTwCall(className, path2, state, parseClassName, generateStyleKey, splitModifierClasses, t);
3178
+ processTwCall(
3179
+ className,
3180
+ path2,
3181
+ state,
3182
+ parseClassName,
3183
+ generateStyleKey,
3184
+ splitModifierClasses,
3185
+ findComponentScope,
3186
+ t
3187
+ );
3015
3188
  },
3016
3189
  JSXAttribute(path2, state) {
3017
3190
  const node = path2.node;
@@ -3024,14 +3197,14 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3024
3197
  }
3025
3198
  const value = node.value;
3026
3199
  const targetStyleProp = getTargetStyleProp(attributeName);
3027
- if (t.isStringLiteral(value)) {
3028
- const className = value.value.trim();
3029
- if (!className) {
3200
+ const processStaticClassName = (className) => {
3201
+ const trimmedClassName = className.trim();
3202
+ if (!trimmedClassName) {
3030
3203
  path2.remove();
3031
- return;
3204
+ return true;
3032
3205
  }
3033
3206
  state.hasClassNames = true;
3034
- const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
3207
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(trimmedClassName);
3035
3208
  const modifierClasses = [];
3036
3209
  for (const modifier of rawModifierClasses) {
3037
3210
  if (isSchemeModifier(modifier.modifier)) {
@@ -3155,7 +3328,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3155
3328
  } else {
3156
3329
  replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3157
3330
  }
3158
- return;
3331
+ return true;
3159
3332
  } else {
3160
3333
  }
3161
3334
  }
@@ -3206,7 +3379,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3206
3379
  path2.node.name = t.jsxIdentifier(targetStyleProp);
3207
3380
  path2.node.value = t.jsxExpressionContainer(styleExpression);
3208
3381
  }
3209
- return;
3382
+ return true;
3210
3383
  }
3211
3384
  if (hasStateModifiers) {
3212
3385
  const jsxOpeningElement = path2.parent;
@@ -3244,11 +3417,11 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3244
3417
  } else {
3245
3418
  replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3246
3419
  }
3247
- return;
3420
+ return true;
3248
3421
  }
3249
3422
  } else {
3250
3423
  const styleExpression = processStaticClassNameWithModifiers(
3251
- className,
3424
+ trimmedClassName,
3252
3425
  state,
3253
3426
  parseClassName,
3254
3427
  generateStyleKey,
@@ -3263,7 +3436,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3263
3436
  } else {
3264
3437
  replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
3265
3438
  }
3266
- return;
3439
+ return true;
3267
3440
  }
3268
3441
  } else {
3269
3442
  if (process.env.NODE_ENV !== "production") {
@@ -3277,7 +3450,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3277
3450
  const classNameForStyle = baseClasses.join(" ");
3278
3451
  if (!classNameForStyle) {
3279
3452
  path2.remove();
3280
- return;
3453
+ return true;
3281
3454
  }
3282
3455
  const styleObject = parseClassName(classNameForStyle, state.customTheme);
3283
3456
  const styleKey = generateStyleKey(classNameForStyle);
@@ -3288,13 +3461,23 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
3288
3461
  } else {
3289
3462
  replaceWithStyleAttribute(path2, styleKey, targetStyleProp, state.stylesIdentifier, t);
3290
3463
  }
3291
- return;
3464
+ return true;
3465
+ };
3466
+ if (t.isStringLiteral(value)) {
3467
+ if (processStaticClassName(value.value)) {
3468
+ return;
3469
+ }
3292
3470
  }
3293
3471
  if (t.isJSXExpressionContainer(value)) {
3294
3472
  const expression = value.expression;
3295
3473
  if (t.isJSXEmptyExpression(expression)) {
3296
3474
  return;
3297
3475
  }
3476
+ if (t.isStringLiteral(expression)) {
3477
+ if (processStaticClassName(expression.value)) {
3478
+ return;
3479
+ }
3480
+ }
3298
3481
  try {
3299
3482
  const componentScope = findComponentScope(path2, t);
3300
3483
  const result = processDynamicExpression(
@@ -41,6 +41,40 @@ export type PluginOptions = {
41
41
  darkSuffix?: string;
42
42
  lightSuffix?: string;
43
43
  };
44
+ /**
45
+ * Configuration for color scheme hook import (dark:/light: modifiers)
46
+ *
47
+ * Allows using custom color scheme hooks from theme providers instead of
48
+ * React Native's built-in useColorScheme.
49
+ *
50
+ * @example
51
+ * // Use custom hook from theme provider
52
+ * {
53
+ * importFrom: '@/hooks/useColorScheme',
54
+ * importName: 'useColorScheme'
55
+ * }
56
+ *
57
+ * @example
58
+ * // Use React Navigation theme
59
+ * {
60
+ * importFrom: '@react-navigation/native',
61
+ * importName: 'useTheme' // You'd wrap this to return ColorSchemeName
62
+ * }
63
+ *
64
+ * @default { importFrom: 'react-native', importName: 'useColorScheme' }
65
+ */
66
+ colorScheme?: {
67
+ /**
68
+ * Module to import the color scheme hook from
69
+ * @default 'react-native'
70
+ */
71
+ importFrom?: string;
72
+ /**
73
+ * Name of the hook to import
74
+ * @default 'useColorScheme'
75
+ */
76
+ importName?: string;
77
+ };
44
78
  };
45
79
  type PluginState = PluginPass & {
46
80
  styleRegistry: Map<string, StyleObject>;
@@ -51,6 +85,9 @@ type PluginState = PluginPass & {
51
85
  hasColorSchemeImport: boolean;
52
86
  needsColorSchemeImport: boolean;
53
87
  colorSchemeVariableName: string;
88
+ colorSchemeImportSource: string;
89
+ colorSchemeHookName: string;
90
+ colorSchemeLocalIdentifier?: string;
54
91
  customTheme: CustomTheme;
55
92
  schemeModifierConfig: SchemeModifierConfig;
56
93
  supportedAttributes: Set<string>;