@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
@@ -2,7 +2,10 @@
2
2
  * Utility functions for processing dynamic className expressions
3
3
  */
4
4
 
5
+ import type { NodePath } from "@babel/core";
5
6
  import type * as BabelTypes from "@babel/types";
7
+ import type { ParsedModifier } from "../../parser/index.js";
8
+ import type { SchemeModifierConfig } from "../../types/config.js";
6
9
  import type { StyleObject } from "../../types/core.js";
7
10
 
8
11
  /**
@@ -12,9 +15,59 @@ import type { StyleObject } from "../../types/core.js";
12
15
  export interface DynamicProcessingState {
13
16
  styleRegistry: Map<string, StyleObject>;
14
17
  customColors: Record<string, string>;
18
+ schemeModifierConfig: SchemeModifierConfig;
15
19
  stylesIdentifier: string;
20
+ needsPlatformImport: boolean;
21
+ needsColorSchemeImport: boolean;
22
+ colorSchemeVariableName: string;
23
+ functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
16
24
  }
17
25
 
26
+ /**
27
+ * Type for the splitModifierClasses function
28
+ */
29
+ export type SplitModifierClassesFn = (className: string) => {
30
+ baseClasses: string[];
31
+ modifierClasses: ParsedModifier[];
32
+ };
33
+
34
+ /**
35
+ * Type for the processPlatformModifiers function
36
+ */
37
+ export type ProcessPlatformModifiersFn = (
38
+ modifiers: ParsedModifier[],
39
+ state: DynamicProcessingState,
40
+ parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
41
+ generateStyleKey: (className: string) => string,
42
+ t: typeof BabelTypes,
43
+ ) => BabelTypes.Expression;
44
+
45
+ /**
46
+ * Type for the processColorSchemeModifiers function
47
+ */
48
+ export type ProcessColorSchemeModifiersFn = (
49
+ modifiers: ParsedModifier[],
50
+ state: DynamicProcessingState,
51
+ parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
52
+ generateStyleKey: (className: string) => string,
53
+ t: typeof BabelTypes,
54
+ ) => BabelTypes.Expression[];
55
+
56
+ /**
57
+ * Type for modifier type guard functions
58
+ */
59
+ export type ModifierTypeGuardFn = (modifier: unknown) => boolean;
60
+
61
+ /**
62
+ * Type for the expandSchemeModifier function
63
+ */
64
+ export type ExpandSchemeModifierFn = (
65
+ modifier: ParsedModifier,
66
+ customColors: Record<string, string>,
67
+ darkSuffix: string,
68
+ lightSuffix: string,
69
+ ) => ParsedModifier[];
70
+
18
71
  /**
19
72
  * Result of processing a dynamic expression
20
73
  */
@@ -34,21 +87,71 @@ export function processDynamicExpression(
34
87
  state: DynamicProcessingState,
35
88
  parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
36
89
  generateStyleKey: (className: string) => string,
90
+ splitModifierClasses: SplitModifierClassesFn,
91
+ processPlatformModifiers: ProcessPlatformModifiersFn,
92
+ processColorSchemeModifiers: ProcessColorSchemeModifiersFn,
93
+ componentScope: NodePath<BabelTypes.Function> | null,
94
+ isPlatformModifier: ModifierTypeGuardFn,
95
+ isColorSchemeModifier: ModifierTypeGuardFn,
96
+ isSchemeModifier: ModifierTypeGuardFn,
97
+ expandSchemeModifier: ExpandSchemeModifierFn,
37
98
  t: typeof BabelTypes,
38
99
  ) {
39
100
  // Handle template literals: `m-4 ${condition ? "p-4" : "p-2"}`
40
101
  if (t.isTemplateLiteral(expression)) {
41
- return processTemplateLiteral(expression, state, parseClassName, generateStyleKey, t);
102
+ return processTemplateLiteral(
103
+ expression,
104
+ state,
105
+ parseClassName,
106
+ generateStyleKey,
107
+ splitModifierClasses,
108
+ processPlatformModifiers,
109
+ processColorSchemeModifiers,
110
+ componentScope,
111
+ isPlatformModifier,
112
+ isColorSchemeModifier,
113
+ isSchemeModifier,
114
+ expandSchemeModifier,
115
+ t,
116
+ );
42
117
  }
43
118
 
44
119
  // Handle conditional expressions: condition ? "m-4" : "p-2"
45
120
  if (t.isConditionalExpression(expression)) {
46
- return processConditionalExpression(expression, state, parseClassName, generateStyleKey, t);
121
+ return processConditionalExpression(
122
+ expression,
123
+ state,
124
+ parseClassName,
125
+ generateStyleKey,
126
+ splitModifierClasses,
127
+ processPlatformModifiers,
128
+ processColorSchemeModifiers,
129
+ componentScope,
130
+ isPlatformModifier,
131
+ isColorSchemeModifier,
132
+ isSchemeModifier,
133
+ expandSchemeModifier,
134
+ t,
135
+ );
47
136
  }
48
137
 
49
138
  // Handle logical expressions: condition && "m-4"
50
139
  if (t.isLogicalExpression(expression)) {
51
- return processLogicalExpression(expression, state, parseClassName, generateStyleKey, t);
140
+ return processLogicalExpression(
141
+ expression,
142
+ state,
143
+ parseClassName,
144
+ generateStyleKey,
145
+ splitModifierClasses,
146
+ processPlatformModifiers,
147
+ processColorSchemeModifiers,
148
+ componentScope,
149
+ isPlatformModifier,
150
+ isColorSchemeModifier,
151
+ isSchemeModifier,
152
+ expandSchemeModifier,
153
+ t,
154
+ );
52
155
  }
53
156
 
54
157
  // Unsupported expression type
@@ -63,9 +166,17 @@ function processTemplateLiteral(
63
166
  state: DynamicProcessingState,
64
167
  parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
65
168
  generateStyleKey: (className: string) => string,
169
+ splitModifierClasses: SplitModifierClassesFn,
170
+ processPlatformModifiers: ProcessPlatformModifiersFn,
171
+ processColorSchemeModifiers: ProcessColorSchemeModifiersFn,
172
+ componentScope: NodePath<BabelTypes.Function> | null,
173
+ isPlatformModifier: ModifierTypeGuardFn,
174
+ isColorSchemeModifier: ModifierTypeGuardFn,
175
+ isSchemeModifier: ModifierTypeGuardFn,
176
+ expandSchemeModifier: ExpandSchemeModifierFn,
66
177
  t: typeof BabelTypes,
67
178
  ) {
68
- const parts: BabelTypes.MemberExpression[] = [];
179
+ const parts: BabelTypes.Expression[] = [];
69
180
  const staticParts: string[] = [];
70
181
 
71
182
  // Process quasis (static parts) and expressions (dynamic parts)
@@ -75,16 +186,31 @@ function processTemplateLiteral(
75
186
 
76
187
  // Add static part if not empty
77
188
  if (staticText) {
78
- // Parse static classes and add to registry
79
- const classes = staticText.split(/\s+/).filter(Boolean);
80
- for (const cls of classes) {
81
- const styleObject = parseClassName(cls, state.customColors);
82
- const styleKey = generateStyleKey(cls);
83
- state.styleRegistry.set(styleKey, styleObject);
84
- staticParts.push(cls);
85
-
86
- // Add to parts array
87
- parts.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
189
+ // Parse static classes with modifier support
190
+ const processedExpression = processStringOrExpressionHelper(
191
+ t.stringLiteral(staticText),
192
+ state,
193
+ parseClassName,
194
+ generateStyleKey,
195
+ splitModifierClasses,
196
+ processPlatformModifiers,
197
+ processColorSchemeModifiers,
198
+ componentScope,
199
+ isPlatformModifier,
200
+ isColorSchemeModifier,
201
+ isSchemeModifier,
202
+ expandSchemeModifier,
203
+ t,
204
+ );
205
+
206
+ if (processedExpression) {
207
+ staticParts.push(staticText);
208
+ // Handle array or single expression
209
+ if (t.isArrayExpression(processedExpression)) {
210
+ parts.push(...(processedExpression.elements as BabelTypes.Expression[]));
211
+ } else {
212
+ parts.push(processedExpression);
213
+ }
88
214
  }
89
215
  }
90
216
 
@@ -98,14 +224,21 @@ function processTemplateLiteral(
98
224
  state,
99
225
  parseClassName,
100
226
  generateStyleKey,
227
+ splitModifierClasses,
228
+ processPlatformModifiers,
229
+ processColorSchemeModifiers,
230
+ componentScope,
231
+ isPlatformModifier,
232
+ isColorSchemeModifier,
233
+ isSchemeModifier,
234
+ expandSchemeModifier,
101
235
  t,
102
236
  );
103
237
  if (result) {
104
- parts.push(result.expression as BabelTypes.MemberExpression);
238
+ parts.push(result.expression);
105
239
  } else {
106
240
  // For unsupported expressions, keep them as-is
107
- // This won't work at runtime but maintains the structure
108
- parts.push(expr as BabelTypes.MemberExpression);
241
+ parts.push(expr as BabelTypes.Expression);
109
242
  }
110
243
  }
111
244
  }
@@ -131,10 +264,46 @@ function processConditionalExpression(
131
264
  state: DynamicProcessingState,
132
265
  parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
133
266
  generateStyleKey: (className: string) => string,
267
+ splitModifierClasses: SplitModifierClassesFn,
268
+ processPlatformModifiers: ProcessPlatformModifiersFn,
269
+ processColorSchemeModifiers: ProcessColorSchemeModifiersFn,
270
+ componentScope: NodePath<BabelTypes.Function> | null,
271
+ isPlatformModifier: ModifierTypeGuardFn,
272
+ isColorSchemeModifier: ModifierTypeGuardFn,
273
+ isSchemeModifier: ModifierTypeGuardFn,
274
+ expandSchemeModifier: ExpandSchemeModifierFn,
134
275
  t: typeof BabelTypes,
135
276
  ) {
136
- const consequent = processStringOrExpression(node.consequent, state, parseClassName, generateStyleKey, t);
137
- const alternate = processStringOrExpression(node.alternate, state, parseClassName, generateStyleKey, t);
277
+ const consequent = processStringOrExpressionHelper(
278
+ node.consequent,
279
+ state,
280
+ parseClassName,
281
+ generateStyleKey,
282
+ splitModifierClasses,
283
+ processPlatformModifiers,
284
+ processColorSchemeModifiers,
285
+ componentScope,
286
+ isPlatformModifier,
287
+ isColorSchemeModifier,
288
+ isSchemeModifier,
289
+ expandSchemeModifier,
290
+ t,
291
+ );
292
+ const alternate = processStringOrExpressionHelper(
293
+ node.alternate,
294
+ state,
295
+ parseClassName,
296
+ generateStyleKey,
297
+ splitModifierClasses,
298
+ processPlatformModifiers,
299
+ processColorSchemeModifiers,
300
+ componentScope,
301
+ isPlatformModifier,
302
+ isColorSchemeModifier,
303
+ isSchemeModifier,
304
+ expandSchemeModifier,
305
+ t,
306
+ );
138
307
 
139
308
  if (!consequent && !alternate) {
140
309
  return null;
@@ -143,8 +312,8 @@ function processConditionalExpression(
143
312
  // Build conditional: condition ? consequentStyle : alternateStyle
144
313
  const expression = t.conditionalExpression(
145
314
  node.test,
146
- (consequent as BabelTypes.Expression) ?? t.nullLiteral(),
147
- (alternate as BabelTypes.Expression) ?? t.nullLiteral(),
315
+ consequent ?? t.nullLiteral(),
316
+ alternate ?? t.nullLiteral(),
148
317
  );
149
318
 
150
319
  return { expression };
@@ -158,6 +327,14 @@ function processLogicalExpression(
158
327
  state: DynamicProcessingState,
159
328
  parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
160
329
  generateStyleKey: (className: string) => string,
330
+ splitModifierClasses: SplitModifierClassesFn,
331
+ processPlatformModifiers: ProcessPlatformModifiersFn,
332
+ processColorSchemeModifiers: ProcessColorSchemeModifiersFn,
333
+ componentScope: NodePath<BabelTypes.Function> | null,
334
+ isPlatformModifier: ModifierTypeGuardFn,
335
+ isColorSchemeModifier: ModifierTypeGuardFn,
336
+ isSchemeModifier: ModifierTypeGuardFn,
337
+ expandSchemeModifier: ExpandSchemeModifierFn,
161
338
  t: typeof BabelTypes,
162
339
  ) {
163
340
  // Only handle AND (&&) expressions
@@ -165,28 +342,52 @@ function processLogicalExpression(
165
342
  return null;
166
343
  }
167
344
 
168
- const right = processStringOrExpression(node.right, state, parseClassName, generateStyleKey, t);
345
+ const right = processStringOrExpressionHelper(
346
+ node.right,
347
+ state,
348
+ parseClassName,
349
+ generateStyleKey,
350
+ splitModifierClasses,
351
+ processPlatformModifiers,
352
+ processColorSchemeModifiers,
353
+ componentScope,
354
+ isPlatformModifier,
355
+ isColorSchemeModifier,
356
+ isSchemeModifier,
357
+ expandSchemeModifier,
358
+ t,
359
+ );
169
360
 
170
361
  if (!right) {
171
362
  return null;
172
363
  }
173
364
 
174
365
  // Build logical: condition && style
175
- const expression = t.logicalExpression("&&", node.left, right as BabelTypes.Expression);
366
+ const expression = t.logicalExpression("&&", node.left, right);
176
367
 
177
368
  return { expression };
178
369
  }
179
370
 
180
371
  /**
181
372
  * Process a node that might be a string literal or another expression
373
+ *
374
+ * This helper is called by processStringOrExpression below
182
375
  */
183
- function processStringOrExpression(
376
+ function processStringOrExpressionHelper(
184
377
  node: BabelTypes.StringLiteral | BabelTypes.Expression,
185
378
  state: DynamicProcessingState,
186
379
  parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
187
380
  generateStyleKey: (className: string) => string,
381
+ splitModifierClasses: SplitModifierClassesFn,
382
+ processPlatformModifiers: ProcessPlatformModifiersFn,
383
+ processColorSchemeModifiers: ProcessColorSchemeModifiersFn,
384
+ componentScope: NodePath<BabelTypes.Function> | null,
385
+ isPlatformModifier: ModifierTypeGuardFn,
386
+ isColorSchemeModifier: ModifierTypeGuardFn,
387
+ isSchemeModifier: ModifierTypeGuardFn,
388
+ expandSchemeModifier: ExpandSchemeModifierFn,
188
389
  t: typeof BabelTypes,
189
- ) {
390
+ ): BabelTypes.Expression | BabelTypes.ArrayExpression | null {
190
391
  // Handle string literals
191
392
  if (t.isStringLiteral(node)) {
192
393
  const className = node.value.trim();
@@ -194,27 +395,145 @@ function processStringOrExpression(
194
395
  return null;
195
396
  }
196
397
 
197
- // Parse and register styles
198
- const styleObject = parseClassName(className, state.customColors);
199
- const styleKey = generateStyleKey(className);
200
- state.styleRegistry.set(styleKey, styleObject);
398
+ // Split into base and modifier classes
399
+ const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
400
+
401
+ // Expand scheme: modifiers into dark: and light: modifiers
402
+ const modifierClasses: Array<import("../../parser/index.js").ParsedModifier> = [];
403
+ for (const modifier of rawModifierClasses) {
404
+ if (isSchemeModifier(modifier.modifier)) {
405
+ // Expand scheme: into dark: and light:
406
+ const expanded = expandSchemeModifier(
407
+ modifier,
408
+ state.customColors,
409
+ state.schemeModifierConfig.darkSuffix ?? "-dark",
410
+ state.schemeModifierConfig.lightSuffix ?? "-light",
411
+ );
412
+ modifierClasses.push(...expanded);
413
+ } else {
414
+ // Keep other modifiers as-is
415
+ modifierClasses.push(modifier);
416
+ }
417
+ }
418
+
419
+ // Separate modifiers by type
420
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
421
+ const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
422
+
423
+ const styleElements: BabelTypes.Expression[] = [];
201
424
 
202
- return t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
425
+ // Process base classes
426
+ if (baseClasses.length > 0) {
427
+ const baseClassName = baseClasses.join(" ");
428
+ const styleObject = parseClassName(baseClassName, state.customColors);
429
+ const styleKey = generateStyleKey(baseClassName);
430
+ state.styleRegistry.set(styleKey, styleObject);
431
+ styleElements.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
432
+ }
433
+
434
+ // Process platform modifiers
435
+ if (platformModifiers.length > 0) {
436
+ state.needsPlatformImport = true;
437
+ const platformExpression = processPlatformModifiers(
438
+ platformModifiers,
439
+ state,
440
+ parseClassName,
441
+ generateStyleKey,
442
+ t,
443
+ );
444
+ styleElements.push(platformExpression);
445
+ }
446
+
447
+ // Process color scheme modifiers (only if component scope exists)
448
+ if (colorSchemeModifiers.length > 0) {
449
+ if (componentScope) {
450
+ state.needsColorSchemeImport = true;
451
+ state.functionComponentsNeedingColorScheme.add(componentScope);
452
+ const colorSchemeExpressions = processColorSchemeModifiers(
453
+ colorSchemeModifiers,
454
+ state,
455
+ parseClassName,
456
+ generateStyleKey,
457
+ t,
458
+ );
459
+ styleElements.push(...colorSchemeExpressions);
460
+ } else {
461
+ // Warn in development: color scheme modifiers without valid component scope
462
+ // Skip transformation - these modifiers will be ignored
463
+ if (process.env.NODE_ENV !== "production") {
464
+ console.warn(
465
+ "[react-native-tailwind] dark:/light: modifiers in dynamic expressions require a function component scope. " +
466
+ "These modifiers will be ignored.",
467
+ );
468
+ }
469
+ }
470
+ }
471
+
472
+ // Return single element or array
473
+ if (styleElements.length === 0) {
474
+ return null;
475
+ }
476
+ if (styleElements.length === 1) {
477
+ return styleElements[0];
478
+ }
479
+ return t.arrayExpression(styleElements);
203
480
  }
204
481
 
205
482
  // Handle nested expressions recursively
206
483
  if (t.isConditionalExpression(node)) {
207
- const result = processConditionalExpression(node, state, parseClassName, generateStyleKey, t);
484
+ const result = processConditionalExpression(
485
+ node,
486
+ state,
487
+ parseClassName,
488
+ generateStyleKey,
489
+ splitModifierClasses,
490
+ processPlatformModifiers,
491
+ processColorSchemeModifiers,
492
+ componentScope,
493
+ isPlatformModifier,
494
+ isColorSchemeModifier,
495
+ isSchemeModifier,
496
+ expandSchemeModifier,
497
+ t,
498
+ );
208
499
  return result?.expression ?? null;
209
500
  }
210
501
 
211
502
  if (t.isLogicalExpression(node)) {
212
- const result = processLogicalExpression(node, state, parseClassName, generateStyleKey, t);
503
+ const result = processLogicalExpression(
504
+ node,
505
+ state,
506
+ parseClassName,
507
+ generateStyleKey,
508
+ splitModifierClasses,
509
+ processPlatformModifiers,
510
+ processColorSchemeModifiers,
511
+ componentScope,
512
+ isPlatformModifier,
513
+ isColorSchemeModifier,
514
+ isSchemeModifier,
515
+ expandSchemeModifier,
516
+ t,
517
+ );
213
518
  return result?.expression ?? null;
214
519
  }
215
520
 
216
521
  if (t.isTemplateLiteral(node)) {
217
- const result = processTemplateLiteral(node, state, parseClassName, generateStyleKey, t);
522
+ const result = processTemplateLiteral(
523
+ node,
524
+ state,
525
+ parseClassName,
526
+ generateStyleKey,
527
+ splitModifierClasses,
528
+ processPlatformModifiers,
529
+ processColorSchemeModifiers,
530
+ componentScope,
531
+ isPlatformModifier,
532
+ isColorSchemeModifier,
533
+ isSchemeModifier,
534
+ expandSchemeModifier,
535
+ t,
536
+ );
218
537
  return result?.expression ?? null;
219
538
  }
220
539
 
@@ -7,16 +7,33 @@ import type * as BabelTypes from "@babel/types";
7
7
  import type { StyleObject } from "../../types/core.js";
8
8
 
9
9
  /**
10
- * Add StyleSheet import to the file
10
+ * Add StyleSheet import to the file or merge with existing react-native import
11
11
  */
12
12
  export function addStyleSheetImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
13
- const importDeclaration = t.importDeclaration(
14
- [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
15
- t.stringLiteral("react-native"),
16
- );
13
+ // Check if there's already a react-native import
14
+ const body = path.node.body;
15
+ let reactNativeImport: BabelTypes.ImportDeclaration | null = null;
16
+
17
+ for (const statement of body) {
18
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
19
+ reactNativeImport = statement;
20
+ break;
21
+ }
22
+ }
17
23
 
18
- // Add import at the top of the file
19
- path.unshiftContainer("body", importDeclaration);
24
+ if (reactNativeImport) {
25
+ // Add StyleSheet to existing react-native import
26
+ reactNativeImport.specifiers.push(
27
+ t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")),
28
+ );
29
+ } else {
30
+ // Create new react-native import with StyleSheet
31
+ const importDeclaration = t.importDeclaration(
32
+ [t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet"))],
33
+ t.stringLiteral("react-native"),
34
+ );
35
+ path.unshiftContainer("body", importDeclaration);
36
+ }
20
37
  }
21
38
 
22
39
  /**
@@ -47,6 +64,107 @@ export function addPlatformImport(path: NodePath<BabelTypes.Program>, t: typeof
47
64
  }
48
65
  }
49
66
 
67
+ /**
68
+ * Add useColorScheme import to the file or merge with existing react-native import
69
+ */
70
+ export function addColorSchemeImport(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
71
+ // Check if there's already a react-native import
72
+ const body = path.node.body;
73
+ let reactNativeImport: BabelTypes.ImportDeclaration | null = null;
74
+
75
+ for (const statement of body) {
76
+ if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
77
+ reactNativeImport = statement;
78
+ break;
79
+ }
80
+ }
81
+
82
+ if (reactNativeImport) {
83
+ // Check if useColorScheme is already imported
84
+ const hasUseColorScheme = reactNativeImport.specifiers.some(
85
+ (spec) =>
86
+ t.isImportSpecifier(spec) &&
87
+ spec.imported.type === "Identifier" &&
88
+ spec.imported.name === "useColorScheme",
89
+ );
90
+
91
+ if (!hasUseColorScheme) {
92
+ // Add useColorScheme to existing react-native import
93
+ reactNativeImport.specifiers.push(
94
+ t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme")),
95
+ );
96
+ }
97
+ } else {
98
+ // Create new react-native import with useColorScheme
99
+ const importDeclaration = t.importDeclaration(
100
+ [t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))],
101
+ t.stringLiteral("react-native"),
102
+ );
103
+ path.unshiftContainer("body", importDeclaration);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Inject useColorScheme hook call at the top of a function component
109
+ *
110
+ * @param functionPath - Path to the function component
111
+ * @param colorSchemeVariableName - Name for the color scheme variable
112
+ * @param t - Babel types
113
+ * @returns true if hook was injected, false if already exists
114
+ */
115
+ export function injectColorSchemeHook(
116
+ functionPath: NodePath<BabelTypes.Function>,
117
+ colorSchemeVariableName: string,
118
+ t: typeof BabelTypes,
119
+ ): boolean {
120
+ let body = functionPath.node.body;
121
+
122
+ // Handle concise arrow functions: () => <JSX />
123
+ // Convert to block statement: () => { const _twColorScheme = useColorScheme(); return <JSX />; }
124
+ if (!t.isBlockStatement(body)) {
125
+ if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
126
+ // Convert concise body to block statement with return
127
+ const returnStatement = t.returnStatement(body);
128
+ const blockStatement = t.blockStatement([returnStatement]);
129
+ functionPath.node.body = blockStatement;
130
+ body = blockStatement;
131
+ } else {
132
+ // Other non-block functions (shouldn't happen for components, but be safe)
133
+ return false;
134
+ }
135
+ }
136
+
137
+ // Check if hook is already injected
138
+ const hasHook = body.body.some((statement) => {
139
+ if (
140
+ t.isVariableDeclaration(statement) &&
141
+ statement.declarations.length > 0 &&
142
+ t.isVariableDeclarator(statement.declarations[0])
143
+ ) {
144
+ const declarator = statement.declarations[0];
145
+ return t.isIdentifier(declarator.id) && declarator.id.name === colorSchemeVariableName;
146
+ }
147
+ return false;
148
+ });
149
+
150
+ if (hasHook) {
151
+ return false; // Already injected
152
+ }
153
+
154
+ // Create: const _twColorScheme = useColorScheme();
155
+ const hookCall = t.variableDeclaration("const", [
156
+ t.variableDeclarator(
157
+ t.identifier(colorSchemeVariableName),
158
+ t.callExpression(t.identifier("useColorScheme"), []),
159
+ ),
160
+ ]);
161
+
162
+ // Insert at the beginning of function body
163
+ body.body.unshift(hookCall);
164
+
165
+ return true;
166
+ }
167
+
50
168
  /**
51
169
  * Inject StyleSheet.create with all collected styles at the top of the file
52
170
  * This ensures the styles object is defined before any code that references it