@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
@@ -7,7 +7,10 @@ import type { NodePath, PluginObj, PluginPass } from "@babel/core";
7
7
  import * as BabelTypes from "@babel/types";
8
8
  import type { ParsedModifier, StateModifierType } from "../parser/index.js";
9
9
  import {
10
+ expandSchemeModifier,
11
+ isColorSchemeModifier,
10
12
  isPlatformModifier,
13
+ isSchemeModifier,
11
14
  isStateModifier,
12
15
  parseClassName,
13
16
  parsePlaceholderClasses,
@@ -18,17 +21,25 @@ import { generateStyleKey } from "../utils/styleKey.js";
18
21
  import { extractCustomColors } from "./config-loader.js";
19
22
 
20
23
  // Import utility functions
24
+ import type { SchemeModifierConfig } from "../types/config.js";
21
25
  import {
22
26
  DEFAULT_CLASS_ATTRIBUTES,
23
27
  buildAttributeMatchers,
24
28
  getTargetStyleProp,
25
29
  isAttributeSupported,
26
30
  } from "./utils/attributeMatchers.js";
31
+ import { processColorSchemeModifiers } from "./utils/colorSchemeModifierProcessing.js";
27
32
  import { getComponentModifierSupport, getStatePropertyForModifier } from "./utils/componentSupport.js";
28
33
  import { processDynamicExpression } from "./utils/dynamicProcessing.js";
29
34
  import { createStyleFunction, processStaticClassNameWithModifiers } from "./utils/modifierProcessing.js";
30
35
  import { processPlatformModifiers } from "./utils/platformModifierProcessing.js";
31
- import { addPlatformImport, addStyleSheetImport, injectStylesAtTop } from "./utils/styleInjection.js";
36
+ import {
37
+ addColorSchemeImport,
38
+ addPlatformImport,
39
+ addStyleSheetImport,
40
+ injectColorSchemeHook,
41
+ injectStylesAtTop,
42
+ } from "./utils/styleInjection.js";
32
43
  import {
33
44
  addOrMergePlaceholderTextColorProp,
34
45
  findStyleAttribute,
@@ -61,6 +72,22 @@ export type PluginOptions = {
61
72
  * @default '_twStyles'
62
73
  */
63
74
  stylesIdentifier?: string;
75
+
76
+ /**
77
+ * Configuration for the scheme: modifier that expands to both dark: and light: modifiers
78
+ *
79
+ * @example
80
+ * {
81
+ * darkSuffix: '-dark', // scheme:bg-primary -> dark:bg-primary-dark
82
+ * lightSuffix: '-light' // scheme:bg-primary -> light:bg-primary-light
83
+ * }
84
+ *
85
+ * @default { darkSuffix: '-dark', lightSuffix: '-light' }
86
+ */
87
+ schemeModifier?: {
88
+ darkSuffix?: string;
89
+ lightSuffix?: string;
90
+ };
64
91
  };
65
92
 
66
93
  type PluginState = PluginPass & {
@@ -69,7 +96,11 @@ type PluginState = PluginPass & {
69
96
  hasStyleSheetImport: boolean;
70
97
  hasPlatformImport: boolean;
71
98
  needsPlatformImport: boolean;
99
+ hasColorSchemeImport: boolean;
100
+ needsColorSchemeImport: boolean;
101
+ colorSchemeVariableName: string;
72
102
  customColors: Record<string, string>;
103
+ schemeModifierConfig: SchemeModifierConfig;
73
104
  supportedAttributes: Set<string>;
74
105
  attributePatterns: RegExp[];
75
106
  stylesIdentifier: string;
@@ -78,11 +109,91 @@ type PluginState = PluginPass & {
78
109
  hasTwImport: boolean;
79
110
  // Track react-native import path for conditional StyleSheet/Platform injection
80
111
  reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
112
+ // Track function components that need colorScheme hook injection
113
+ functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
81
114
  };
82
115
 
83
116
  // Default identifier for the generated StyleSheet constant
84
117
  const DEFAULT_STYLES_IDENTIFIER = "_twStyles";
85
118
 
119
+ /**
120
+ * Check if a function path represents a valid component scope for hook injection
121
+ * Valid scopes:
122
+ * - Top-level FunctionDeclaration
123
+ * - FunctionExpression/ArrowFunctionExpression in top-level VariableDeclarator (with PascalCase name)
124
+ * - NOT class methods, NOT nested functions, NOT inline callbacks
125
+ *
126
+ * @param functionPath - Path to the function to check
127
+ * @param t - Babel types
128
+ * @returns true if function is a valid component scope
129
+ */
130
+ function isComponentScope(functionPath: NodePath<BabelTypes.Function>, t: typeof BabelTypes): boolean {
131
+ const node = functionPath.node;
132
+ const parent = functionPath.parent;
133
+ const parentPath = functionPath.parentPath;
134
+
135
+ // Reject class methods (class components not supported for hooks)
136
+ if (t.isClassMethod(parent)) {
137
+ return false;
138
+ }
139
+
140
+ // Reject if inside a class body
141
+ if (functionPath.findParent((p) => t.isClassBody(p.node))) {
142
+ return false;
143
+ }
144
+
145
+ // Accept top-level FunctionDeclaration
146
+ if (t.isFunctionDeclaration(node)) {
147
+ // Check if it's at program level or in export
148
+ if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
149
+ return true;
150
+ }
151
+ }
152
+
153
+ // Accept FunctionExpression/ArrowFunctionExpression in VariableDeclarator
154
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
155
+ if (t.isVariableDeclarator(parent)) {
156
+ // Check if it's at program level (via VariableDeclaration)
157
+ const varDeclarationPath = parentPath?.parentPath;
158
+ if (
159
+ varDeclarationPath &&
160
+ t.isVariableDeclaration(varDeclarationPath.node) &&
161
+ (t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))
162
+ ) {
163
+ // Check for PascalCase naming (component convention)
164
+ if (t.isIdentifier(parent.id)) {
165
+ const name = parent.id.name;
166
+ return /^[A-Z]/.test(name); // Starts with uppercase
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * Find the nearest valid component scope for hook injection
177
+ * Climbs the AST from the current path to find a component-level function
178
+ *
179
+ * @param path - Starting path (e.g., JSXAttribute)
180
+ * @param t - Babel types
181
+ * @returns NodePath to component function, or null if not found
182
+ */
183
+ function findComponentScope(path: NodePath, t: typeof BabelTypes): NodePath<BabelTypes.Function> | null {
184
+ let current = path.getFunctionParent();
185
+
186
+ while (current) {
187
+ if (t.isFunction(current.node) && isComponentScope(current, t)) {
188
+ return current;
189
+ }
190
+ // Climb to next parent function
191
+ current = current.getFunctionParent();
192
+ }
193
+
194
+ return null;
195
+ }
196
+
86
197
  export default function reactNativeTailwindBabelPlugin(
87
198
  { types: t }: { types: typeof BabelTypes },
88
199
  options?: PluginOptions,
@@ -92,6 +203,12 @@ export default function reactNativeTailwindBabelPlugin(
92
203
  const { exactMatches, patterns } = buildAttributeMatchers(attributes);
93
204
  const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
94
205
 
206
+ // Scheme modifier configuration from plugin options
207
+ const schemeModifierConfig = {
208
+ darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
209
+ lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light",
210
+ };
211
+
95
212
  return {
96
213
  name: "react-native-tailwind",
97
214
 
@@ -104,14 +221,21 @@ export default function reactNativeTailwindBabelPlugin(
104
221
  state.hasStyleSheetImport = false;
105
222
  state.hasPlatformImport = false;
106
223
  state.needsPlatformImport = false;
224
+ state.hasColorSchemeImport = false;
225
+ state.needsColorSchemeImport = false;
226
+ state.colorSchemeVariableName = "_twColorScheme";
107
227
  state.supportedAttributes = exactMatches;
108
228
  state.attributePatterns = patterns;
109
229
  state.stylesIdentifier = stylesIdentifier;
110
230
  state.twImportNames = new Set();
111
231
  state.hasTwImport = false;
232
+ state.functionComponentsNeedingColorScheme = new Set();
112
233
 
113
234
  // Load custom colors from tailwind.config.*
114
235
  state.customColors = extractCustomColors(state.file.opts.filename ?? "");
236
+
237
+ // Use scheme modifier config from plugin options
238
+ state.schemeModifierConfig = schemeModifierConfig;
115
239
  },
116
240
 
117
241
  exit(path, state) {
@@ -135,6 +259,18 @@ export default function reactNativeTailwindBabelPlugin(
135
259
  addPlatformImport(path, t);
136
260
  }
137
261
 
262
+ // Add useColorScheme import if color scheme modifiers were used and not already present
263
+ if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
264
+ addColorSchemeImport(path, t);
265
+ }
266
+
267
+ // Inject useColorScheme hook in function components that need it
268
+ if (state.needsColorSchemeImport) {
269
+ for (const functionPath of state.functionComponentsNeedingColorScheme) {
270
+ injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
271
+ }
272
+ }
273
+
138
274
  // Generate and inject StyleSheet.create at the beginning of the file (after imports)
139
275
  // This ensures _twStyles is defined before any code that references it
140
276
  injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
@@ -163,6 +299,13 @@ export default function reactNativeTailwindBabelPlugin(
163
299
  return false;
164
300
  });
165
301
 
302
+ const hasUseColorScheme = specifiers.some((spec) => {
303
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
304
+ return spec.imported.name === "useColorScheme";
305
+ }
306
+ return false;
307
+ });
308
+
166
309
  // Only track if imports exist - don't mutate yet
167
310
  // Actual import injection happens in Program.exit only if needed
168
311
  if (hasStyleSheet) {
@@ -173,6 +316,10 @@ export default function reactNativeTailwindBabelPlugin(
173
316
  state.hasPlatformImport = true;
174
317
  }
175
318
 
319
+ if (hasUseColorScheme) {
320
+ state.hasColorSchemeImport = true;
321
+ }
322
+
176
323
  // Store reference to the react-native import for later modification if needed
177
324
  state.reactNativeImportPath = path;
178
325
  }
@@ -323,12 +470,31 @@ export default function reactNativeTailwindBabelPlugin(
323
470
 
324
471
  state.hasClassNames = true;
325
472
 
326
- // Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
327
- const { baseClasses, modifierClasses } = splitModifierClasses(className);
473
+ // Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:, dark:, light:, scheme:)
474
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
475
+
476
+ // Expand scheme: modifiers into dark: and light: modifiers
477
+ const modifierClasses: ParsedModifier[] = [];
478
+ for (const modifier of rawModifierClasses) {
479
+ if (isSchemeModifier(modifier.modifier)) {
480
+ // Expand scheme: into dark: and light:
481
+ const expanded = expandSchemeModifier(
482
+ modifier,
483
+ state.customColors,
484
+ state.schemeModifierConfig.darkSuffix,
485
+ state.schemeModifierConfig.lightSuffix,
486
+ );
487
+ modifierClasses.push(...expanded);
488
+ } else {
489
+ // Keep other modifiers as-is
490
+ modifierClasses.push(modifier);
491
+ }
492
+ }
328
493
 
329
494
  // Separate modifiers by type
330
495
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
331
496
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
497
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
332
498
  const stateModifiers = modifierClasses.filter(
333
499
  (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
334
500
  );
@@ -359,18 +525,37 @@ export default function reactNativeTailwindBabelPlugin(
359
525
 
360
526
  // Handle combination of modifiers
361
527
  const hasPlatformModifiers = platformModifiers.length > 0;
528
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
362
529
  const hasStateModifiers = stateModifiers.length > 0;
363
530
  const hasBaseClasses = baseClasses.length > 0;
364
531
 
365
- // If we have both state and platform modifiers, or platform modifiers with complex state,
366
- // we need to combine them in an array expression wrapped in an arrow function
367
- if (hasStateModifiers && hasPlatformModifiers) {
532
+ // If we have color scheme modifiers, we need to track the parent function component
533
+ let componentScope: NodePath<BabelTypes.Function> | null = null;
534
+ if (hasColorSchemeModifiers) {
535
+ componentScope = findComponentScope(path, t);
536
+ if (componentScope) {
537
+ state.functionComponentsNeedingColorScheme.add(componentScope);
538
+ } else {
539
+ // Warn if color scheme modifiers used in invalid context (class component, nested function)
540
+ if (process.env.NODE_ENV !== "production") {
541
+ console.warn(
542
+ `[react-native-tailwind] dark:/light: modifiers require a function component scope. ` +
543
+ `Found in non-component context at ${state.file.opts.filename ?? "unknown"}. ` +
544
+ `These modifiers are not supported in class components or nested callbacks.`,
545
+ );
546
+ }
547
+ }
548
+ }
549
+
550
+ // If we have multiple modifier types, combine them in an array expression
551
+ // For state modifiers, wrap in arrow function; for color scheme, they're just conditionals
552
+ if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
368
553
  // Get the JSX opening element for component support checking
369
554
  const jsxOpeningElement = path.parent;
370
555
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
371
556
 
372
557
  if (componentSupport) {
373
- // Build style array: [baseStyle, Platform.select(...), stateConditionals]
558
+ // Build style array: [baseStyle, Platform.select(...), colorSchemeConditionals, stateConditionals]
374
559
  const styleArrayElements: BabelTypes.Expression[] = [];
375
560
 
376
561
  // Add base classes
@@ -385,14 +570,28 @@ export default function reactNativeTailwindBabelPlugin(
385
570
  }
386
571
 
387
572
  // Add platform modifiers as Platform.select()
388
- const platformSelectExpression = processPlatformModifiers(
389
- platformModifiers,
390
- state,
391
- parseClassName,
392
- generateStyleKey,
393
- t,
394
- );
395
- styleArrayElements.push(platformSelectExpression);
573
+ if (hasPlatformModifiers) {
574
+ const platformSelectExpression = processPlatformModifiers(
575
+ platformModifiers,
576
+ state,
577
+ parseClassName,
578
+ generateStyleKey,
579
+ t,
580
+ );
581
+ styleArrayElements.push(platformSelectExpression);
582
+ }
583
+
584
+ // Add color scheme modifiers as conditionals (only if component scope exists)
585
+ if (hasColorSchemeModifiers && componentScope) {
586
+ const colorSchemeConditionals = processColorSchemeModifiers(
587
+ colorSchemeModifiers,
588
+ state,
589
+ parseClassName,
590
+ generateStyleKey,
591
+ t,
592
+ );
593
+ styleArrayElements.push(...colorSchemeConditionals);
594
+ }
396
595
 
397
596
  // Add state modifiers as conditionals
398
597
  // Group by modifier type
@@ -446,9 +645,9 @@ export default function reactNativeTailwindBabelPlugin(
446
645
  }
447
646
  }
448
647
 
449
- // Handle platform-only modifiers (no state modifiers)
450
- if (hasPlatformModifiers && !hasStateModifiers) {
451
- // Build style array/expression: [baseStyle, Platform.select(...)]
648
+ // Handle platform and/or color scheme modifiers (no state modifiers)
649
+ if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
650
+ // Build style array/expression: [baseStyle, Platform.select(...), colorSchemeConditionals]
452
651
  const styleExpressions: BabelTypes.Expression[] = [];
453
652
 
454
653
  // Add base classes
@@ -463,14 +662,28 @@ export default function reactNativeTailwindBabelPlugin(
463
662
  }
464
663
 
465
664
  // Add platform modifiers as Platform.select()
466
- const platformSelectExpression = processPlatformModifiers(
467
- platformModifiers,
468
- state,
469
- parseClassName,
470
- generateStyleKey,
471
- t,
472
- );
473
- styleExpressions.push(platformSelectExpression);
665
+ if (hasPlatformModifiers) {
666
+ const platformSelectExpression = processPlatformModifiers(
667
+ platformModifiers,
668
+ state,
669
+ parseClassName,
670
+ generateStyleKey,
671
+ t,
672
+ );
673
+ styleExpressions.push(platformSelectExpression);
674
+ }
675
+
676
+ // Add color scheme modifiers as conditionals (only if we have a valid component scope)
677
+ if (hasColorSchemeModifiers && componentScope) {
678
+ const colorSchemeConditionals = processColorSchemeModifiers(
679
+ colorSchemeModifiers,
680
+ state,
681
+ parseClassName,
682
+ generateStyleKey,
683
+ t,
684
+ );
685
+ styleExpressions.push(...colorSchemeConditionals);
686
+ }
474
687
 
475
688
  // Generate style attribute
476
689
  const styleExpression =
@@ -629,8 +842,25 @@ export default function reactNativeTailwindBabelPlugin(
629
842
  }
630
843
 
631
844
  try {
632
- // Process dynamic expression
633
- const result = processDynamicExpression(expression, state, parseClassName, generateStyleKey, t);
845
+ // Find component scope for color scheme modifiers
846
+ const componentScope = findComponentScope(path, t);
847
+
848
+ // Process dynamic expression with modifier support
849
+ const result = processDynamicExpression(
850
+ expression,
851
+ state,
852
+ parseClassName,
853
+ generateStyleKey,
854
+ splitModifierClasses,
855
+ processPlatformModifiers,
856
+ processColorSchemeModifiers,
857
+ componentScope,
858
+ isPlatformModifier as (modifier: unknown) => boolean,
859
+ isColorSchemeModifier as (modifier: unknown) => boolean,
860
+ isSchemeModifier as (modifier: unknown) => boolean,
861
+ expandSchemeModifier,
862
+ t,
863
+ );
634
864
 
635
865
  if (result) {
636
866
  state.hasClassNames = true;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Utility functions for processing color scheme modifiers (dark:, light:)
3
+ */
4
+ import type * as BabelTypes from "@babel/types";
5
+ import type { ParsedModifier } from "../../parser/index.js";
6
+ import type { StyleObject } from "../../types/core.js";
7
+ /**
8
+ * Plugin state interface (subset needed for color scheme modifier processing)
9
+ */
10
+ export interface ColorSchemeModifierProcessingState {
11
+ styleRegistry: Map<string, StyleObject>;
12
+ customColors: Record<string, string>;
13
+ stylesIdentifier: string;
14
+ needsColorSchemeImport: boolean;
15
+ colorSchemeVariableName: string;
16
+ }
17
+ /**
18
+ * Process color scheme modifiers and generate conditional style expressions
19
+ *
20
+ * @param colorSchemeModifiers - Array of parsed color scheme modifiers
21
+ * @param state - Plugin state
22
+ * @param parseClassName - Function to parse class names into style objects
23
+ * @param generateStyleKey - Function to generate unique style keys
24
+ * @param t - Babel types
25
+ * @returns Array of AST nodes for conditional expressions
26
+ *
27
+ * @example
28
+ * Input: [{ modifier: "dark", baseClass: "bg-gray-900" }, { modifier: "light", baseClass: "bg-white" }]
29
+ * Output: [
30
+ * _twColorScheme === 'dark' && styles._dark_bg_gray_900,
31
+ * _twColorScheme === 'light' && styles._light_bg_white
32
+ * ]
33
+ */
34
+ export declare function processColorSchemeModifiers(colorSchemeModifiers: ParsedModifier[], state: ColorSchemeModifierProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): BabelTypes.Expression[];
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Utility functions for processing color scheme modifiers (dark:, light:)
3
+ */
4
+
5
+ import type * as BabelTypes from "@babel/types";
6
+ import type { ColorSchemeModifierType, ParsedModifier } from "../../parser/index.js";
7
+ import type { StyleObject } from "../../types/core.js";
8
+
9
+ /**
10
+ * Plugin state interface (subset needed for color scheme modifier processing)
11
+ */
12
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
13
+ export interface ColorSchemeModifierProcessingState {
14
+ styleRegistry: Map<string, StyleObject>;
15
+ customColors: Record<string, string>;
16
+ stylesIdentifier: string;
17
+ needsColorSchemeImport: boolean;
18
+ colorSchemeVariableName: string;
19
+ }
20
+
21
+ /**
22
+ * Process color scheme modifiers and generate conditional style expressions
23
+ *
24
+ * @param colorSchemeModifiers - Array of parsed color scheme modifiers
25
+ * @param state - Plugin state
26
+ * @param parseClassName - Function to parse class names into style objects
27
+ * @param generateStyleKey - Function to generate unique style keys
28
+ * @param t - Babel types
29
+ * @returns Array of AST nodes for conditional expressions
30
+ *
31
+ * @example
32
+ * Input: [{ modifier: "dark", baseClass: "bg-gray-900" }, { modifier: "light", baseClass: "bg-white" }]
33
+ * Output: [
34
+ * _twColorScheme === 'dark' && styles._dark_bg_gray_900,
35
+ * _twColorScheme === 'light' && styles._light_bg_white
36
+ * ]
37
+ */
38
+ export function processColorSchemeModifiers(
39
+ colorSchemeModifiers: ParsedModifier[],
40
+ state: ColorSchemeModifierProcessingState,
41
+ parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
42
+ generateStyleKey: (className: string) => string,
43
+ t: typeof BabelTypes,
44
+ ): BabelTypes.Expression[] {
45
+ // Mark that we need useColorScheme import and hook injection
46
+ state.needsColorSchemeImport = true;
47
+
48
+ // Group modifiers by color scheme (dark, light)
49
+ const modifiersByScheme = new Map<ColorSchemeModifierType, ParsedModifier[]>();
50
+
51
+ for (const mod of colorSchemeModifiers) {
52
+ const scheme = mod.modifier as ColorSchemeModifierType;
53
+ if (!modifiersByScheme.has(scheme)) {
54
+ modifiersByScheme.set(scheme, []);
55
+ }
56
+ const schemeGroup = modifiersByScheme.get(scheme);
57
+ if (schemeGroup) {
58
+ schemeGroup.push(mod);
59
+ }
60
+ }
61
+
62
+ // Build conditional expressions for each color scheme
63
+ const conditionalExpressions: BabelTypes.Expression[] = [];
64
+
65
+ for (const [scheme, modifiers] of modifiersByScheme) {
66
+ // Parse all classes for this color scheme together
67
+ const classNames = modifiers.map((m) => m.baseClass).join(" ");
68
+ const styleObject = parseClassName(classNames, state.customColors);
69
+ const styleKey = generateStyleKey(`${scheme}_${classNames}`);
70
+
71
+ // Register style in the registry
72
+ state.styleRegistry.set(styleKey, styleObject);
73
+
74
+ // Create conditional: _twColorScheme === 'dark' && styles._dark_bg_gray_900
75
+ const colorSchemeCheck = t.binaryExpression(
76
+ "===",
77
+ t.identifier(state.colorSchemeVariableName),
78
+ t.stringLiteral(scheme),
79
+ );
80
+
81
+ const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
82
+
83
+ const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
84
+
85
+ conditionalExpressions.push(conditionalExpression);
86
+ }
87
+
88
+ return conditionalExpressions;
89
+ }
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * Utility functions for processing dynamic className expressions
3
3
  */
4
+ import type { NodePath } from "@babel/core";
4
5
  import type * as BabelTypes from "@babel/types";
6
+ import type { ParsedModifier } from "../../parser/index.js";
7
+ import type { SchemeModifierConfig } from "../../types/config.js";
5
8
  import type { StyleObject } from "../../types/core.js";
6
9
  /**
7
10
  * Plugin state interface (subset needed for dynamic processing)
@@ -9,8 +12,36 @@ import type { StyleObject } from "../../types/core.js";
9
12
  export interface DynamicProcessingState {
10
13
  styleRegistry: Map<string, StyleObject>;
11
14
  customColors: Record<string, string>;
15
+ schemeModifierConfig: SchemeModifierConfig;
12
16
  stylesIdentifier: string;
17
+ needsPlatformImport: boolean;
18
+ needsColorSchemeImport: boolean;
19
+ colorSchemeVariableName: string;
20
+ functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
13
21
  }
22
+ /**
23
+ * Type for the splitModifierClasses function
24
+ */
25
+ export type SplitModifierClassesFn = (className: string) => {
26
+ baseClasses: string[];
27
+ modifierClasses: ParsedModifier[];
28
+ };
29
+ /**
30
+ * Type for the processPlatformModifiers function
31
+ */
32
+ export type ProcessPlatformModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression;
33
+ /**
34
+ * Type for the processColorSchemeModifiers function
35
+ */
36
+ export type ProcessColorSchemeModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression[];
37
+ /**
38
+ * Type for modifier type guard functions
39
+ */
40
+ export type ModifierTypeGuardFn = (modifier: unknown) => boolean;
41
+ /**
42
+ * Type for the expandSchemeModifier function
43
+ */
44
+ export type ExpandSchemeModifierFn = (modifier: ParsedModifier, customColors: Record<string, string>, darkSuffix: string, lightSuffix: string) => ParsedModifier[];
14
45
  /**
15
46
  * Result of processing a dynamic expression
16
47
  */
@@ -22,8 +53,8 @@ export type DynamicExpressionResult = {
22
53
  * Process a dynamic className expression
23
54
  * Extracts static strings and transforms the expression to use pre-compiled styles
24
55
  */
25
- export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): {
26
- expression: BabelTypes.ArrayExpression | BabelTypes.MemberExpression;
56
+ export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, splitModifierClasses: SplitModifierClassesFn, processPlatformModifiers: ProcessPlatformModifiersFn, processColorSchemeModifiers: ProcessColorSchemeModifiersFn, componentScope: NodePath<BabelTypes.Function> | null, isPlatformModifier: ModifierTypeGuardFn, isColorSchemeModifier: ModifierTypeGuardFn, isSchemeModifier: ModifierTypeGuardFn, expandSchemeModifier: ExpandSchemeModifierFn, t: typeof BabelTypes): {
57
+ expression: BabelTypes.Expression;
27
58
  staticParts: string[] | undefined;
28
59
  } | {
29
60
  expression: BabelTypes.ConditionalExpression;