@mgcrea/react-native-tailwind 0.9.0 → 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 (73) hide show
  1. package/README.md +356 -30
  2. package/dist/babel/config-loader.test.ts +152 -0
  3. package/dist/babel/index.cjs +575 -60
  4. package/dist/babel/plugin.d.ts +23 -1
  5. package/dist/babel/plugin.test.ts +417 -0
  6. package/dist/babel/plugin.ts +265 -32
  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 +14 -1
  12. package/dist/babel/utils/styleInjection.ts +125 -7
  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/aspectRatio.js +1 -1
  17. package/dist/parser/aspectRatio.test.js +1 -1
  18. package/dist/parser/index.d.ts +2 -2
  19. package/dist/parser/index.js +1 -1
  20. package/dist/parser/modifiers.d.ts +48 -2
  21. package/dist/parser/modifiers.js +1 -1
  22. package/dist/parser/modifiers.test.js +1 -1
  23. package/dist/parser/spacing.d.ts +1 -1
  24. package/dist/parser/spacing.js +1 -1
  25. package/dist/parser/spacing.test.js +1 -1
  26. package/dist/runtime.cjs +1 -1
  27. package/dist/runtime.cjs.map +3 -3
  28. package/dist/runtime.js +1 -1
  29. package/dist/runtime.js.map +3 -3
  30. package/dist/runtime.test.js +1 -1
  31. package/dist/types/config.d.ts +7 -0
  32. package/dist/types/config.js +0 -0
  33. package/package.json +4 -4
  34. package/src/babel/config-loader.test.ts +152 -0
  35. package/src/babel/plugin.test.ts +417 -0
  36. package/src/babel/plugin.ts +265 -32
  37. package/src/babel/utils/colorSchemeModifierProcessing.ts +89 -0
  38. package/src/babel/utils/dynamicProcessing.ts +352 -33
  39. package/src/babel/utils/styleInjection.ts +125 -7
  40. package/src/babel/utils/styleTransforms.test.ts +56 -0
  41. package/src/babel/utils/twProcessing.ts +22 -1
  42. package/src/parser/aspectRatio.test.ts +25 -2
  43. package/src/parser/aspectRatio.ts +3 -3
  44. package/src/parser/index.ts +12 -1
  45. package/src/parser/modifiers.test.ts +151 -1
  46. package/src/parser/modifiers.ts +139 -4
  47. package/src/parser/spacing.test.ts +63 -0
  48. package/src/parser/spacing.ts +10 -6
  49. package/src/runtime.test.ts +27 -0
  50. package/src/runtime.ts +2 -1
  51. package/src/types/config.ts +7 -0
  52. package/dist/babel/index.test.ts +0 -481
  53. package/dist/config/palettes.d.ts +0 -302
  54. package/dist/config/palettes.js +0 -1
  55. package/dist/parser/__snapshots__/aspectRatio.test.js.snap +0 -9
  56. package/dist/parser/__snapshots__/borders.test.js.snap +0 -23
  57. package/dist/parser/__snapshots__/colors.test.js.snap +0 -251
  58. package/dist/parser/__snapshots__/shadows.test.js.snap +0 -76
  59. package/dist/parser/__snapshots__/sizing.test.js.snap +0 -61
  60. package/dist/parser/__snapshots__/spacing.test.js.snap +0 -40
  61. package/dist/parser/__snapshots__/transforms.test.js.snap +0 -58
  62. package/dist/parser/__snapshots__/typography.test.js.snap +0 -30
  63. package/dist/parser/aspectRatio.test.d.ts +0 -1
  64. package/dist/parser/borders.test.d.ts +0 -1
  65. package/dist/parser/colors.test.d.ts +0 -1
  66. package/dist/parser/layout.test.d.ts +0 -1
  67. package/dist/parser/modifiers.test.d.ts +0 -1
  68. package/dist/parser/shadows.test.d.ts +0 -1
  69. package/dist/parser/sizing.test.d.ts +0 -1
  70. package/dist/parser/spacing.test.d.ts +0 -1
  71. package/dist/parser/typography.test.d.ts +0 -1
  72. package/dist/types.d.ts +0 -42
  73. package/dist/types.js +0 -1
@@ -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,18 +96,104 @@ 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;
76
107
  // Track tw/twStyle imports from main package
77
108
  twImportNames: Set<string>; // e.g., ['tw', 'twStyle'] or ['tw as customTw']
78
109
  hasTwImport: boolean;
110
+ // Track react-native import path for conditional StyleSheet/Platform injection
111
+ reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
112
+ // Track function components that need colorScheme hook injection
113
+ functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
79
114
  };
80
115
 
81
116
  // Default identifier for the generated StyleSheet constant
82
117
  const DEFAULT_STYLES_IDENTIFIER = "_twStyles";
83
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
+
84
197
  export default function reactNativeTailwindBabelPlugin(
85
198
  { types: t }: { types: typeof BabelTypes },
86
199
  options?: PluginOptions,
@@ -90,6 +203,12 @@ export default function reactNativeTailwindBabelPlugin(
90
203
  const { exactMatches, patterns } = buildAttributeMatchers(attributes);
91
204
  const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
92
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
+
93
212
  return {
94
213
  name: "react-native-tailwind",
95
214
 
@@ -102,14 +221,21 @@ export default function reactNativeTailwindBabelPlugin(
102
221
  state.hasStyleSheetImport = false;
103
222
  state.hasPlatformImport = false;
104
223
  state.needsPlatformImport = false;
224
+ state.hasColorSchemeImport = false;
225
+ state.needsColorSchemeImport = false;
226
+ state.colorSchemeVariableName = "_twColorScheme";
105
227
  state.supportedAttributes = exactMatches;
106
228
  state.attributePatterns = patterns;
107
229
  state.stylesIdentifier = stylesIdentifier;
108
230
  state.twImportNames = new Set();
109
231
  state.hasTwImport = false;
232
+ state.functionComponentsNeedingColorScheme = new Set();
110
233
 
111
234
  // Load custom colors from tailwind.config.*
112
235
  state.customColors = extractCustomColors(state.file.opts.filename ?? "");
236
+
237
+ // Use scheme modifier config from plugin options
238
+ state.schemeModifierConfig = schemeModifierConfig;
113
239
  },
114
240
 
115
241
  exit(path, state) {
@@ -133,6 +259,18 @@ export default function reactNativeTailwindBabelPlugin(
133
259
  addPlatformImport(path, t);
134
260
  }
135
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
+
136
274
  // Generate and inject StyleSheet.create at the beginning of the file (after imports)
137
275
  // This ensures _twStyles is defined before any code that references it
138
276
  injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
@@ -161,17 +299,29 @@ export default function reactNativeTailwindBabelPlugin(
161
299
  return false;
162
300
  });
163
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
+
309
+ // Only track if imports exist - don't mutate yet
310
+ // Actual import injection happens in Program.exit only if needed
164
311
  if (hasStyleSheet) {
165
312
  state.hasStyleSheetImport = true;
166
- } else {
167
- // Add StyleSheet to existing import
168
- node.specifiers.push(t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")));
169
- state.hasStyleSheetImport = true;
170
313
  }
171
314
 
172
315
  if (hasPlatform) {
173
316
  state.hasPlatformImport = true;
174
317
  }
318
+
319
+ if (hasUseColorScheme) {
320
+ state.hasColorSchemeImport = true;
321
+ }
322
+
323
+ // Store reference to the react-native import for later modification if needed
324
+ state.reactNativeImportPath = path;
175
325
  }
176
326
 
177
327
  // Track tw/twStyle imports from main package (for compile-time transformation)
@@ -320,12 +470,31 @@ export default function reactNativeTailwindBabelPlugin(
320
470
 
321
471
  state.hasClassNames = true;
322
472
 
323
- // Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
324
- 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
+ }
325
493
 
326
494
  // Separate modifiers by type
327
495
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
328
496
  const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
497
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
329
498
  const stateModifiers = modifierClasses.filter(
330
499
  (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
331
500
  );
@@ -356,18 +525,37 @@ export default function reactNativeTailwindBabelPlugin(
356
525
 
357
526
  // Handle combination of modifiers
358
527
  const hasPlatformModifiers = platformModifiers.length > 0;
528
+ const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
359
529
  const hasStateModifiers = stateModifiers.length > 0;
360
530
  const hasBaseClasses = baseClasses.length > 0;
361
531
 
362
- // If we have both state and platform modifiers, or platform modifiers with complex state,
363
- // we need to combine them in an array expression wrapped in an arrow function
364
- 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)) {
365
553
  // Get the JSX opening element for component support checking
366
554
  const jsxOpeningElement = path.parent;
367
555
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
368
556
 
369
557
  if (componentSupport) {
370
- // Build style array: [baseStyle, Platform.select(...), stateConditionals]
558
+ // Build style array: [baseStyle, Platform.select(...), colorSchemeConditionals, stateConditionals]
371
559
  const styleArrayElements: BabelTypes.Expression[] = [];
372
560
 
373
561
  // Add base classes
@@ -382,14 +570,28 @@ export default function reactNativeTailwindBabelPlugin(
382
570
  }
383
571
 
384
572
  // Add platform modifiers as Platform.select()
385
- const platformSelectExpression = processPlatformModifiers(
386
- platformModifiers,
387
- state,
388
- parseClassName,
389
- generateStyleKey,
390
- t,
391
- );
392
- 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
+ }
393
595
 
394
596
  // Add state modifiers as conditionals
395
597
  // Group by modifier type
@@ -443,9 +645,9 @@ export default function reactNativeTailwindBabelPlugin(
443
645
  }
444
646
  }
445
647
 
446
- // Handle platform-only modifiers (no state modifiers)
447
- if (hasPlatformModifiers && !hasStateModifiers) {
448
- // 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]
449
651
  const styleExpressions: BabelTypes.Expression[] = [];
450
652
 
451
653
  // Add base classes
@@ -460,14 +662,28 @@ export default function reactNativeTailwindBabelPlugin(
460
662
  }
461
663
 
462
664
  // Add platform modifiers as Platform.select()
463
- const platformSelectExpression = processPlatformModifiers(
464
- platformModifiers,
465
- state,
466
- parseClassName,
467
- generateStyleKey,
468
- t,
469
- );
470
- 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
+ }
471
687
 
472
688
  // Generate style attribute
473
689
  const styleExpression =
@@ -626,8 +842,25 @@ export default function reactNativeTailwindBabelPlugin(
626
842
  }
627
843
 
628
844
  try {
629
- // Process dynamic expression
630
- 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
+ );
631
864
 
632
865
  if (result) {
633
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;