@mgcrea/react-native-tailwind 0.9.1 → 0.10.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.
Files changed (39) hide show
  1. package/README.md +356 -30
  2. package/dist/babel/config-loader.test.ts +152 -0
  3. package/dist/babel/index.cjs +547 -47
  4. package/dist/babel/plugin.d.ts +21 -0
  5. package/dist/babel/plugin.test.ts +331 -0
  6. package/dist/babel/plugin.ts +258 -28
  7. package/dist/babel/utils/colorSchemeModifierProcessing.d.ts +34 -0
  8. package/dist/babel/utils/colorSchemeModifierProcessing.ts +89 -0
  9. package/dist/babel/utils/dynamicProcessing.d.ts +33 -2
  10. package/dist/babel/utils/dynamicProcessing.ts +352 -33
  11. package/dist/babel/utils/styleInjection.d.ts +13 -0
  12. package/dist/babel/utils/styleInjection.ts +101 -0
  13. package/dist/babel/utils/styleTransforms.test.ts +56 -0
  14. package/dist/babel/utils/twProcessing.d.ts +2 -0
  15. package/dist/babel/utils/twProcessing.ts +22 -1
  16. package/dist/parser/index.d.ts +2 -2
  17. package/dist/parser/index.js +1 -1
  18. package/dist/parser/modifiers.d.ts +48 -2
  19. package/dist/parser/modifiers.js +1 -1
  20. package/dist/parser/modifiers.test.js +1 -1
  21. package/dist/runtime.cjs +1 -1
  22. package/dist/runtime.cjs.map +3 -3
  23. package/dist/runtime.js +1 -1
  24. package/dist/runtime.js.map +3 -3
  25. package/dist/types/config.d.ts +7 -0
  26. package/dist/types/config.js +0 -0
  27. package/package.json +3 -2
  28. package/src/babel/config-loader.test.ts +152 -0
  29. package/src/babel/plugin.test.ts +331 -0
  30. package/src/babel/plugin.ts +258 -28
  31. package/src/babel/utils/colorSchemeModifierProcessing.ts +89 -0
  32. package/src/babel/utils/dynamicProcessing.ts +352 -33
  33. package/src/babel/utils/styleInjection.ts +101 -0
  34. package/src/babel/utils/styleTransforms.test.ts +56 -0
  35. package/src/babel/utils/twProcessing.ts +22 -1
  36. package/src/parser/index.ts +12 -1
  37. package/src/parser/modifiers.test.ts +151 -1
  38. package/src/parser/modifiers.ts +139 -4
  39. package/src/types/config.ts +7 -0
@@ -1741,7 +1741,14 @@ var STATE_MODIFIERS = [
1741
1741
  "placeholder"
1742
1742
  ];
1743
1743
  var PLATFORM_MODIFIERS = ["ios", "android", "web"];
1744
- var SUPPORTED_MODIFIERS = [...STATE_MODIFIERS, ...PLATFORM_MODIFIERS];
1744
+ var COLOR_SCHEME_MODIFIERS = ["dark", "light"];
1745
+ var SCHEME_MODIFIERS = ["scheme"];
1746
+ var SUPPORTED_MODIFIERS = [
1747
+ ...STATE_MODIFIERS,
1748
+ ...PLATFORM_MODIFIERS,
1749
+ ...COLOR_SCHEME_MODIFIERS,
1750
+ ...SCHEME_MODIFIERS
1751
+ ];
1745
1752
  function parseModifier(cls) {
1746
1753
  const colonIndex = cls.indexOf(":");
1747
1754
  if (colonIndex === -1) {
@@ -1769,6 +1776,56 @@ function isStateModifier(modifier) {
1769
1776
  function isPlatformModifier(modifier) {
1770
1777
  return PLATFORM_MODIFIERS.includes(modifier);
1771
1778
  }
1779
+ function isColorSchemeModifier(modifier) {
1780
+ return COLOR_SCHEME_MODIFIERS.includes(modifier);
1781
+ }
1782
+ function isSchemeModifier(modifier) {
1783
+ return SCHEME_MODIFIERS.includes(modifier);
1784
+ }
1785
+ function isColorClass(className) {
1786
+ return className.startsWith("text-") || className.startsWith("bg-") || className.startsWith("border-");
1787
+ }
1788
+ function expandSchemeModifier(schemeModifier, customColors, darkSuffix = "-dark", lightSuffix = "-light") {
1789
+ const { baseClass } = schemeModifier;
1790
+ if (!isColorClass(baseClass)) {
1791
+ if (process.env.NODE_ENV !== "production") {
1792
+ console.warn(
1793
+ `[react-native-tailwind] scheme: modifier only supports color classes (text-*, bg-*, border-*). Found: "${baseClass}". This modifier will be ignored.`
1794
+ );
1795
+ }
1796
+ return [];
1797
+ }
1798
+ const match = baseClass.match(/^(text|bg|border)-(.+)$/);
1799
+ if (!match) {
1800
+ return [];
1801
+ }
1802
+ const [, prefix, colorName] = match;
1803
+ const darkColorName = `${colorName}${darkSuffix}`;
1804
+ const lightColorName = `${colorName}${lightSuffix}`;
1805
+ const darkColorExists = customColors[darkColorName] !== void 0;
1806
+ const lightColorExists = customColors[lightColorName] !== void 0;
1807
+ if (!darkColorExists || !lightColorExists) {
1808
+ if (process.env.NODE_ENV !== "production") {
1809
+ const missing = [];
1810
+ if (!darkColorExists) missing.push(`${colorName}${darkSuffix}`);
1811
+ if (!lightColorExists) missing.push(`${colorName}${lightSuffix}`);
1812
+ console.warn(
1813
+ `[react-native-tailwind] scheme:${baseClass} requires both color variants to exist. Missing: ${missing.join(", ")}. This modifier will be ignored.`
1814
+ );
1815
+ }
1816
+ return [];
1817
+ }
1818
+ return [
1819
+ {
1820
+ modifier: "dark",
1821
+ baseClass: `${prefix}-${darkColorName}`
1822
+ },
1823
+ {
1824
+ modifier: "light",
1825
+ baseClass: `${prefix}-${lightColorName}`
1826
+ }
1827
+ ];
1828
+ }
1772
1829
  function splitModifierClasses(className) {
1773
1830
  const classes = className.trim().split(/\s+/).filter(Boolean);
1774
1831
  const baseClasses = [];
@@ -1923,6 +1980,38 @@ function getTargetStyleProp(attributeName) {
1923
1980
  return attributeName.endsWith("ClassName") ? attributeName.replace("ClassName", "Style") : "style";
1924
1981
  }
1925
1982
 
1983
+ // src/babel/utils/colorSchemeModifierProcessing.ts
1984
+ function processColorSchemeModifiers(colorSchemeModifiers, state, parseClassName2, generateStyleKey2, t) {
1985
+ state.needsColorSchemeImport = true;
1986
+ const modifiersByScheme = /* @__PURE__ */ new Map();
1987
+ for (const mod of colorSchemeModifiers) {
1988
+ const scheme = mod.modifier;
1989
+ if (!modifiersByScheme.has(scheme)) {
1990
+ modifiersByScheme.set(scheme, []);
1991
+ }
1992
+ const schemeGroup = modifiersByScheme.get(scheme);
1993
+ if (schemeGroup) {
1994
+ schemeGroup.push(mod);
1995
+ }
1996
+ }
1997
+ const conditionalExpressions = [];
1998
+ for (const [scheme, modifiers] of modifiersByScheme) {
1999
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
2000
+ const styleObject = parseClassName2(classNames, state.customColors);
2001
+ const styleKey = generateStyleKey2(`${scheme}_${classNames}`);
2002
+ state.styleRegistry.set(styleKey, styleObject);
2003
+ const colorSchemeCheck = t.binaryExpression(
2004
+ "===",
2005
+ t.identifier(state.colorSchemeVariableName),
2006
+ t.stringLiteral(scheme)
2007
+ );
2008
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2009
+ const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
2010
+ conditionalExpressions.push(conditionalExpression);
2011
+ }
2012
+ return conditionalExpressions;
2013
+ }
2014
+
1926
2015
  // src/babel/utils/componentSupport.ts
1927
2016
  function getComponentModifierSupport(jsxElement, t) {
1928
2017
  if (!t.isJSXOpeningElement(jsxElement)) {
@@ -1967,32 +2056,89 @@ function getStatePropertyForModifier(modifier) {
1967
2056
  }
1968
2057
 
1969
2058
  // src/babel/utils/dynamicProcessing.ts
1970
- function processDynamicExpression(expression, state, parseClassName2, generateStyleKey2, t) {
2059
+ function processDynamicExpression(expression, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
1971
2060
  if (t.isTemplateLiteral(expression)) {
1972
- return processTemplateLiteral(expression, state, parseClassName2, generateStyleKey2, t);
2061
+ return processTemplateLiteral(
2062
+ expression,
2063
+ state,
2064
+ parseClassName2,
2065
+ generateStyleKey2,
2066
+ splitModifierClasses2,
2067
+ processPlatformModifiers2,
2068
+ processColorSchemeModifiers2,
2069
+ componentScope,
2070
+ isPlatformModifier2,
2071
+ isColorSchemeModifier2,
2072
+ isSchemeModifier2,
2073
+ expandSchemeModifier2,
2074
+ t
2075
+ );
1973
2076
  }
1974
2077
  if (t.isConditionalExpression(expression)) {
1975
- return processConditionalExpression(expression, state, parseClassName2, generateStyleKey2, t);
2078
+ return processConditionalExpression(
2079
+ expression,
2080
+ state,
2081
+ parseClassName2,
2082
+ generateStyleKey2,
2083
+ splitModifierClasses2,
2084
+ processPlatformModifiers2,
2085
+ processColorSchemeModifiers2,
2086
+ componentScope,
2087
+ isPlatformModifier2,
2088
+ isColorSchemeModifier2,
2089
+ isSchemeModifier2,
2090
+ expandSchemeModifier2,
2091
+ t
2092
+ );
1976
2093
  }
1977
2094
  if (t.isLogicalExpression(expression)) {
1978
- return processLogicalExpression(expression, state, parseClassName2, generateStyleKey2, t);
2095
+ return processLogicalExpression(
2096
+ expression,
2097
+ state,
2098
+ parseClassName2,
2099
+ generateStyleKey2,
2100
+ splitModifierClasses2,
2101
+ processPlatformModifiers2,
2102
+ processColorSchemeModifiers2,
2103
+ componentScope,
2104
+ isPlatformModifier2,
2105
+ isColorSchemeModifier2,
2106
+ isSchemeModifier2,
2107
+ expandSchemeModifier2,
2108
+ t
2109
+ );
1979
2110
  }
1980
2111
  return null;
1981
2112
  }
1982
- function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2, t) {
2113
+ function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
1983
2114
  const parts = [];
1984
2115
  const staticParts = [];
1985
2116
  for (let i = 0; i < node.quasis.length; i++) {
1986
2117
  const quasi = node.quasis[i];
1987
2118
  const staticText = quasi.value.cooked?.trim();
1988
2119
  if (staticText) {
1989
- const classes = staticText.split(/\s+/).filter(Boolean);
1990
- for (const cls of classes) {
1991
- const styleObject = parseClassName2(cls, state.customColors);
1992
- const styleKey = generateStyleKey2(cls);
1993
- state.styleRegistry.set(styleKey, styleObject);
1994
- staticParts.push(cls);
1995
- parts.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
2120
+ const processedExpression = processStringOrExpressionHelper(
2121
+ t.stringLiteral(staticText),
2122
+ state,
2123
+ parseClassName2,
2124
+ generateStyleKey2,
2125
+ splitModifierClasses2,
2126
+ processPlatformModifiers2,
2127
+ processColorSchemeModifiers2,
2128
+ componentScope,
2129
+ isPlatformModifier2,
2130
+ isColorSchemeModifier2,
2131
+ isSchemeModifier2,
2132
+ expandSchemeModifier2,
2133
+ t
2134
+ );
2135
+ if (processedExpression) {
2136
+ staticParts.push(staticText);
2137
+ if (t.isArrayExpression(processedExpression)) {
2138
+ parts.push(...processedExpression.elements);
2139
+ } else {
2140
+ parts.push(processedExpression);
2141
+ }
1996
2142
  }
1997
2143
  }
1998
2144
  if (i < node.expressions.length) {
@@ -2002,6 +2148,14 @@ function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2,
2002
2148
  state,
2003
2149
  parseClassName2,
2004
2150
  generateStyleKey2,
2151
+ splitModifierClasses2,
2152
+ processPlatformModifiers2,
2153
+ processColorSchemeModifiers2,
2154
+ componentScope,
2155
+ isPlatformModifier2,
2156
+ isColorSchemeModifier2,
2157
+ isSchemeModifier2,
2158
+ expandSchemeModifier2,
2005
2159
  t
2006
2160
  );
2007
2161
  if (result) {
@@ -2020,9 +2174,37 @@ function processTemplateLiteral(node, state, parseClassName2, generateStyleKey2,
2020
2174
  staticParts: staticParts.length > 0 ? staticParts : void 0
2021
2175
  };
2022
2176
  }
2023
- function processConditionalExpression(node, state, parseClassName2, generateStyleKey2, t) {
2024
- const consequent = processStringOrExpression(node.consequent, state, parseClassName2, generateStyleKey2, t);
2025
- const alternate = processStringOrExpression(node.alternate, state, parseClassName2, generateStyleKey2, t);
2177
+ function processConditionalExpression(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2178
+ const consequent = processStringOrExpressionHelper(
2179
+ node.consequent,
2180
+ state,
2181
+ parseClassName2,
2182
+ generateStyleKey2,
2183
+ splitModifierClasses2,
2184
+ processPlatformModifiers2,
2185
+ processColorSchemeModifiers2,
2186
+ componentScope,
2187
+ isPlatformModifier2,
2188
+ isColorSchemeModifier2,
2189
+ isSchemeModifier2,
2190
+ expandSchemeModifier2,
2191
+ t
2192
+ );
2193
+ const alternate = processStringOrExpressionHelper(
2194
+ node.alternate,
2195
+ state,
2196
+ parseClassName2,
2197
+ generateStyleKey2,
2198
+ splitModifierClasses2,
2199
+ processPlatformModifiers2,
2200
+ processColorSchemeModifiers2,
2201
+ componentScope,
2202
+ isPlatformModifier2,
2203
+ isColorSchemeModifier2,
2204
+ isSchemeModifier2,
2205
+ expandSchemeModifier2,
2206
+ t
2207
+ );
2026
2208
  if (!consequent && !alternate) {
2027
2209
  return null;
2028
2210
  }
@@ -2033,38 +2215,153 @@ function processConditionalExpression(node, state, parseClassName2, generateStyl
2033
2215
  );
2034
2216
  return { expression };
2035
2217
  }
2036
- function processLogicalExpression(node, state, parseClassName2, generateStyleKey2, t) {
2218
+ function processLogicalExpression(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2037
2219
  if (node.operator !== "&&") {
2038
2220
  return null;
2039
2221
  }
2040
- const right = processStringOrExpression(node.right, state, parseClassName2, generateStyleKey2, t);
2222
+ const right = processStringOrExpressionHelper(
2223
+ node.right,
2224
+ state,
2225
+ parseClassName2,
2226
+ generateStyleKey2,
2227
+ splitModifierClasses2,
2228
+ processPlatformModifiers2,
2229
+ processColorSchemeModifiers2,
2230
+ componentScope,
2231
+ isPlatformModifier2,
2232
+ isColorSchemeModifier2,
2233
+ isSchemeModifier2,
2234
+ expandSchemeModifier2,
2235
+ t
2236
+ );
2041
2237
  if (!right) {
2042
2238
  return null;
2043
2239
  }
2044
2240
  const expression = t.logicalExpression("&&", node.left, right);
2045
2241
  return { expression };
2046
2242
  }
2047
- function processStringOrExpression(node, state, parseClassName2, generateStyleKey2, t) {
2243
+ function processStringOrExpressionHelper(node, state, parseClassName2, generateStyleKey2, splitModifierClasses2, processPlatformModifiers2, processColorSchemeModifiers2, componentScope, isPlatformModifier2, isColorSchemeModifier2, isSchemeModifier2, expandSchemeModifier2, t) {
2048
2244
  if (t.isStringLiteral(node)) {
2049
2245
  const className = node.value.trim();
2050
2246
  if (!className) {
2051
2247
  return null;
2052
2248
  }
2053
- const styleObject = parseClassName2(className, state.customColors);
2054
- const styleKey = generateStyleKey2(className);
2055
- state.styleRegistry.set(styleKey, styleObject);
2056
- return t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
2249
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
2250
+ const modifierClasses = [];
2251
+ for (const modifier of rawModifierClasses) {
2252
+ if (isSchemeModifier2(modifier.modifier)) {
2253
+ const expanded = expandSchemeModifier2(
2254
+ modifier,
2255
+ state.customColors,
2256
+ state.schemeModifierConfig.darkSuffix ?? "-dark",
2257
+ state.schemeModifierConfig.lightSuffix ?? "-light"
2258
+ );
2259
+ modifierClasses.push(...expanded);
2260
+ } else {
2261
+ modifierClasses.push(modifier);
2262
+ }
2263
+ }
2264
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier2(m.modifier));
2265
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier2(m.modifier));
2266
+ const styleElements = [];
2267
+ if (baseClasses.length > 0) {
2268
+ const baseClassName = baseClasses.join(" ");
2269
+ const styleObject = parseClassName2(baseClassName, state.customColors);
2270
+ const styleKey = generateStyleKey2(baseClassName);
2271
+ state.styleRegistry.set(styleKey, styleObject);
2272
+ styleElements.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
2273
+ }
2274
+ if (platformModifiers.length > 0) {
2275
+ state.needsPlatformImport = true;
2276
+ const platformExpression = processPlatformModifiers2(
2277
+ platformModifiers,
2278
+ state,
2279
+ parseClassName2,
2280
+ generateStyleKey2,
2281
+ t
2282
+ );
2283
+ styleElements.push(platformExpression);
2284
+ }
2285
+ if (colorSchemeModifiers.length > 0) {
2286
+ if (componentScope) {
2287
+ state.needsColorSchemeImport = true;
2288
+ state.functionComponentsNeedingColorScheme.add(componentScope);
2289
+ const colorSchemeExpressions = processColorSchemeModifiers2(
2290
+ colorSchemeModifiers,
2291
+ state,
2292
+ parseClassName2,
2293
+ generateStyleKey2,
2294
+ t
2295
+ );
2296
+ styleElements.push(...colorSchemeExpressions);
2297
+ } else {
2298
+ if (process.env.NODE_ENV !== "production") {
2299
+ console.warn(
2300
+ "[react-native-tailwind] dark:/light: modifiers in dynamic expressions require a function component scope. These modifiers will be ignored."
2301
+ );
2302
+ }
2303
+ }
2304
+ }
2305
+ if (styleElements.length === 0) {
2306
+ return null;
2307
+ }
2308
+ if (styleElements.length === 1) {
2309
+ return styleElements[0];
2310
+ }
2311
+ return t.arrayExpression(styleElements);
2057
2312
  }
2058
2313
  if (t.isConditionalExpression(node)) {
2059
- const result = processConditionalExpression(node, state, parseClassName2, generateStyleKey2, t);
2314
+ const result = processConditionalExpression(
2315
+ node,
2316
+ state,
2317
+ parseClassName2,
2318
+ generateStyleKey2,
2319
+ splitModifierClasses2,
2320
+ processPlatformModifiers2,
2321
+ processColorSchemeModifiers2,
2322
+ componentScope,
2323
+ isPlatformModifier2,
2324
+ isColorSchemeModifier2,
2325
+ isSchemeModifier2,
2326
+ expandSchemeModifier2,
2327
+ t
2328
+ );
2060
2329
  return result?.expression ?? null;
2061
2330
  }
2062
2331
  if (t.isLogicalExpression(node)) {
2063
- const result = processLogicalExpression(node, state, parseClassName2, generateStyleKey2, t);
2332
+ const result = processLogicalExpression(
2333
+ node,
2334
+ state,
2335
+ parseClassName2,
2336
+ generateStyleKey2,
2337
+ splitModifierClasses2,
2338
+ processPlatformModifiers2,
2339
+ processColorSchemeModifiers2,
2340
+ componentScope,
2341
+ isPlatformModifier2,
2342
+ isColorSchemeModifier2,
2343
+ isSchemeModifier2,
2344
+ expandSchemeModifier2,
2345
+ t
2346
+ );
2064
2347
  return result?.expression ?? null;
2065
2348
  }
2066
2349
  if (t.isTemplateLiteral(node)) {
2067
- const result = processTemplateLiteral(node, state, parseClassName2, generateStyleKey2, t);
2350
+ const result = processTemplateLiteral(
2351
+ node,
2352
+ state,
2353
+ parseClassName2,
2354
+ generateStyleKey2,
2355
+ splitModifierClasses2,
2356
+ processPlatformModifiers2,
2357
+ processColorSchemeModifiers2,
2358
+ componentScope,
2359
+ isPlatformModifier2,
2360
+ isColorSchemeModifier2,
2361
+ isSchemeModifier2,
2362
+ expandSchemeModifier2,
2363
+ t
2364
+ );
2068
2365
  return result?.expression ?? null;
2069
2366
  }
2070
2367
  return null;
@@ -2198,6 +2495,63 @@ function addPlatformImport(path2, t) {
2198
2495
  path2.unshiftContainer("body", importDeclaration);
2199
2496
  }
2200
2497
  }
2498
+ function addColorSchemeImport(path2, t) {
2499
+ const body = path2.node.body;
2500
+ let reactNativeImport = null;
2501
+ for (const statement of body) {
2502
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
2503
+ reactNativeImport = statement;
2504
+ break;
2505
+ }
2506
+ }
2507
+ if (reactNativeImport) {
2508
+ const hasUseColorScheme = reactNativeImport.specifiers.some(
2509
+ (spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === "useColorScheme"
2510
+ );
2511
+ if (!hasUseColorScheme) {
2512
+ reactNativeImport.specifiers.push(
2513
+ t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))
2514
+ );
2515
+ }
2516
+ } else {
2517
+ const importDeclaration = t.importDeclaration(
2518
+ [t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))],
2519
+ t.stringLiteral("react-native")
2520
+ );
2521
+ path2.unshiftContainer("body", importDeclaration);
2522
+ }
2523
+ }
2524
+ function injectColorSchemeHook(functionPath, colorSchemeVariableName, t) {
2525
+ let body = functionPath.node.body;
2526
+ if (!t.isBlockStatement(body)) {
2527
+ if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
2528
+ const returnStatement = t.returnStatement(body);
2529
+ const blockStatement = t.blockStatement([returnStatement]);
2530
+ functionPath.node.body = blockStatement;
2531
+ body = blockStatement;
2532
+ } else {
2533
+ return false;
2534
+ }
2535
+ }
2536
+ const hasHook = body.body.some((statement) => {
2537
+ if (t.isVariableDeclaration(statement) && statement.declarations.length > 0 && t.isVariableDeclarator(statement.declarations[0])) {
2538
+ const declarator = statement.declarations[0];
2539
+ return t.isIdentifier(declarator.id) && declarator.id.name === colorSchemeVariableName;
2540
+ }
2541
+ return false;
2542
+ });
2543
+ if (hasHook) {
2544
+ return false;
2545
+ }
2546
+ const hookCall = t.variableDeclaration("const", [
2547
+ t.variableDeclarator(
2548
+ t.identifier(colorSchemeVariableName),
2549
+ t.callExpression(t.identifier("useColorScheme"), [])
2550
+ )
2551
+ ]);
2552
+ body.body.unshift(hookCall);
2553
+ return true;
2554
+ }
2201
2555
  function injectStylesAtTop(path2, styleRegistry, stylesIdentifier, t) {
2202
2556
  const styleProperties = [];
2203
2557
  for (const [key, styleObject] of styleRegistry) {
@@ -2348,7 +2702,21 @@ function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
2348
2702
 
2349
2703
  // src/babel/utils/twProcessing.ts
2350
2704
  function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, t) {
2351
- const { baseClasses, modifierClasses } = splitModifierClasses2(className);
2705
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
2706
+ const modifierClasses = [];
2707
+ for (const modifier of rawModifierClasses) {
2708
+ if (isSchemeModifier(modifier.modifier)) {
2709
+ const expanded = expandSchemeModifier(
2710
+ modifier,
2711
+ state.customColors,
2712
+ state.schemeModifierConfig.darkSuffix ?? "-dark",
2713
+ state.schemeModifierConfig.lightSuffix ?? "-light"
2714
+ );
2715
+ modifierClasses.push(...expanded);
2716
+ } else {
2717
+ modifierClasses.push(modifier);
2718
+ }
2719
+ }
2352
2720
  const objectProperties = [];
2353
2721
  if (baseClasses.length > 0) {
2354
2722
  const baseClassName = baseClasses.join(" ");
@@ -2415,10 +2783,52 @@ function removeTwImports(path2, t) {
2415
2783
 
2416
2784
  // src/babel/plugin.ts
2417
2785
  var DEFAULT_STYLES_IDENTIFIER = "_twStyles";
2786
+ function isComponentScope(functionPath, t) {
2787
+ const node = functionPath.node;
2788
+ const parent = functionPath.parent;
2789
+ const parentPath = functionPath.parentPath;
2790
+ if (t.isClassMethod(parent)) {
2791
+ return false;
2792
+ }
2793
+ if (functionPath.findParent((p) => t.isClassBody(p.node))) {
2794
+ return false;
2795
+ }
2796
+ if (t.isFunctionDeclaration(node)) {
2797
+ if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
2798
+ return true;
2799
+ }
2800
+ }
2801
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
2802
+ if (t.isVariableDeclarator(parent)) {
2803
+ const varDeclarationPath = parentPath?.parentPath;
2804
+ if (varDeclarationPath && t.isVariableDeclaration(varDeclarationPath.node) && (t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))) {
2805
+ if (t.isIdentifier(parent.id)) {
2806
+ const name = parent.id.name;
2807
+ return /^[A-Z]/.test(name);
2808
+ }
2809
+ }
2810
+ }
2811
+ }
2812
+ return false;
2813
+ }
2814
+ function findComponentScope(path2, t) {
2815
+ let current = path2.getFunctionParent();
2816
+ while (current) {
2817
+ if (t.isFunction(current.node) && isComponentScope(current, t)) {
2818
+ return current;
2819
+ }
2820
+ current = current.getFunctionParent();
2821
+ }
2822
+ return null;
2823
+ }
2418
2824
  function reactNativeTailwindBabelPlugin({ types: t }, options) {
2419
2825
  const attributes = options?.attributes ?? [...DEFAULT_CLASS_ATTRIBUTES];
2420
2826
  const { exactMatches, patterns } = buildAttributeMatchers(attributes);
2421
2827
  const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
2828
+ const schemeModifierConfig = {
2829
+ darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
2830
+ lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light"
2831
+ };
2422
2832
  return {
2423
2833
  name: "react-native-tailwind",
2424
2834
  visitor: {
@@ -2429,12 +2839,17 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2429
2839
  state.hasStyleSheetImport = false;
2430
2840
  state.hasPlatformImport = false;
2431
2841
  state.needsPlatformImport = false;
2842
+ state.hasColorSchemeImport = false;
2843
+ state.needsColorSchemeImport = false;
2844
+ state.colorSchemeVariableName = "_twColorScheme";
2432
2845
  state.supportedAttributes = exactMatches;
2433
2846
  state.attributePatterns = patterns;
2434
2847
  state.stylesIdentifier = stylesIdentifier;
2435
2848
  state.twImportNames = /* @__PURE__ */ new Set();
2436
2849
  state.hasTwImport = false;
2850
+ state.functionComponentsNeedingColorScheme = /* @__PURE__ */ new Set();
2437
2851
  state.customColors = extractCustomColors(state.file.opts.filename ?? "");
2852
+ state.schemeModifierConfig = schemeModifierConfig;
2438
2853
  },
2439
2854
  exit(path2, state) {
2440
2855
  if (state.hasTwImport) {
@@ -2449,6 +2864,14 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2449
2864
  if (state.needsPlatformImport && !state.hasPlatformImport) {
2450
2865
  addPlatformImport(path2, t);
2451
2866
  }
2867
+ if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
2868
+ addColorSchemeImport(path2, t);
2869
+ }
2870
+ if (state.needsColorSchemeImport) {
2871
+ for (const functionPath of state.functionComponentsNeedingColorScheme) {
2872
+ injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
2873
+ }
2874
+ }
2452
2875
  injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
2453
2876
  }
2454
2877
  },
@@ -2469,12 +2892,21 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2469
2892
  }
2470
2893
  return false;
2471
2894
  });
2895
+ const hasUseColorScheme = specifiers.some((spec) => {
2896
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
2897
+ return spec.imported.name === "useColorScheme";
2898
+ }
2899
+ return false;
2900
+ });
2472
2901
  if (hasStyleSheet) {
2473
2902
  state.hasStyleSheetImport = true;
2474
2903
  }
2475
2904
  if (hasPlatform) {
2476
2905
  state.hasPlatformImport = true;
2477
2906
  }
2907
+ if (hasUseColorScheme) {
2908
+ state.hasColorSchemeImport = true;
2909
+ }
2478
2910
  state.reactNativeImportPath = path2;
2479
2911
  }
2480
2912
  if (node.source.value === "@mgcrea/react-native-tailwind") {
@@ -2576,9 +3008,24 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2576
3008
  return;
2577
3009
  }
2578
3010
  state.hasClassNames = true;
2579
- const { baseClasses, modifierClasses } = splitModifierClasses(className);
3011
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
3012
+ const modifierClasses = [];
3013
+ for (const modifier of rawModifierClasses) {
3014
+ if (isSchemeModifier(modifier.modifier)) {
3015
+ const expanded = expandSchemeModifier(
3016
+ modifier,
3017
+ state.customColors,
3018
+ state.schemeModifierConfig.darkSuffix,
3019
+ state.schemeModifierConfig.lightSuffix
3020
+ );
3021
+ modifierClasses.push(...expanded);
3022
+ } else {
3023
+ modifierClasses.push(modifier);
3024
+ }
3025
+ }
2580
3026
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
2581
3027
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
3028
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
2582
3029
  const stateModifiers = modifierClasses.filter(
2583
3030
  (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder"
2584
3031
  );
@@ -2600,9 +3047,23 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2600
3047
  }
2601
3048
  }
2602
3049
  const hasPlatformModifiers = platformModifiers.length > 0;
3050
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
2603
3051
  const hasStateModifiers = stateModifiers.length > 0;
2604
3052
  const hasBaseClasses = baseClasses.length > 0;
2605
- if (hasStateModifiers && hasPlatformModifiers) {
3053
+ let componentScope = null;
3054
+ if (hasColorSchemeModifiers) {
3055
+ componentScope = findComponentScope(path2, t);
3056
+ if (componentScope) {
3057
+ state.functionComponentsNeedingColorScheme.add(componentScope);
3058
+ } else {
3059
+ if (process.env.NODE_ENV !== "production") {
3060
+ console.warn(
3061
+ `[react-native-tailwind] dark:/light: modifiers require a function component scope. Found in non-component context at ${state.file.opts.filename ?? "unknown"}. These modifiers are not supported in class components or nested callbacks.`
3062
+ );
3063
+ }
3064
+ }
3065
+ }
3066
+ if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
2606
3067
  const jsxOpeningElement = path2.parent;
2607
3068
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
2608
3069
  if (componentSupport) {
@@ -2616,14 +3077,26 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2616
3077
  t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2617
3078
  );
2618
3079
  }
2619
- const platformSelectExpression = processPlatformModifiers(
2620
- platformModifiers,
2621
- state,
2622
- parseClassName,
2623
- generateStyleKey,
2624
- t
2625
- );
2626
- styleArrayElements.push(platformSelectExpression);
3080
+ if (hasPlatformModifiers) {
3081
+ const platformSelectExpression = processPlatformModifiers(
3082
+ platformModifiers,
3083
+ state,
3084
+ parseClassName,
3085
+ generateStyleKey,
3086
+ t
3087
+ );
3088
+ styleArrayElements.push(platformSelectExpression);
3089
+ }
3090
+ if (hasColorSchemeModifiers && componentScope) {
3091
+ const colorSchemeConditionals = processColorSchemeModifiers(
3092
+ colorSchemeModifiers,
3093
+ state,
3094
+ parseClassName,
3095
+ generateStyleKey,
3096
+ t
3097
+ );
3098
+ styleArrayElements.push(...colorSchemeConditionals);
3099
+ }
2627
3100
  const modifiersByType = /* @__PURE__ */ new Map();
2628
3101
  for (const mod of stateModifiers) {
2629
3102
  const modType = mod.modifier;
@@ -2663,7 +3136,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2663
3136
  } else {
2664
3137
  }
2665
3138
  }
2666
- if (hasPlatformModifiers && !hasStateModifiers) {
3139
+ if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
2667
3140
  const styleExpressions = [];
2668
3141
  if (hasBaseClasses) {
2669
3142
  const baseClassName = baseClasses.join(" ");
@@ -2674,14 +3147,26 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2674
3147
  t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
2675
3148
  );
2676
3149
  }
2677
- const platformSelectExpression = processPlatformModifiers(
2678
- platformModifiers,
2679
- state,
2680
- parseClassName,
2681
- generateStyleKey,
2682
- t
2683
- );
2684
- styleExpressions.push(platformSelectExpression);
3150
+ if (hasPlatformModifiers) {
3151
+ const platformSelectExpression = processPlatformModifiers(
3152
+ platformModifiers,
3153
+ state,
3154
+ parseClassName,
3155
+ generateStyleKey,
3156
+ t
3157
+ );
3158
+ styleExpressions.push(platformSelectExpression);
3159
+ }
3160
+ if (hasColorSchemeModifiers && componentScope) {
3161
+ const colorSchemeConditionals = processColorSchemeModifiers(
3162
+ colorSchemeModifiers,
3163
+ state,
3164
+ parseClassName,
3165
+ generateStyleKey,
3166
+ t
3167
+ );
3168
+ styleExpressions.push(...colorSchemeConditionals);
3169
+ }
2685
3170
  const styleExpression = styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
2686
3171
  const styleAttribute2 = findStyleAttribute(path2, targetStyleProp, t);
2687
3172
  if (styleAttribute2) {
@@ -2788,7 +3273,22 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
2788
3273
  return;
2789
3274
  }
2790
3275
  try {
2791
- const result = processDynamicExpression(expression, state, parseClassName, generateStyleKey, t);
3276
+ const componentScope = findComponentScope(path2, t);
3277
+ const result = processDynamicExpression(
3278
+ expression,
3279
+ state,
3280
+ parseClassName,
3281
+ generateStyleKey,
3282
+ splitModifierClasses,
3283
+ processPlatformModifiers,
3284
+ processColorSchemeModifiers,
3285
+ componentScope,
3286
+ isPlatformModifier,
3287
+ isColorSchemeModifier,
3288
+ isSchemeModifier,
3289
+ expandSchemeModifier,
3290
+ t
3291
+ );
2792
3292
  if (result) {
2793
3293
  state.hasClassNames = true;
2794
3294
  const styleAttribute = findStyleAttribute(path2, targetStyleProp, t);