@mgcrea/react-native-tailwind 0.12.0 → 0.13.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 (97) hide show
  1. package/README.md +29 -2014
  2. package/dist/babel/config-loader.d.ts +3 -0
  3. package/dist/babel/config-loader.test.ts +2 -2
  4. package/dist/babel/config-loader.ts +37 -2
  5. package/dist/babel/index.cjs +2855 -2434
  6. package/dist/babel/plugin/componentScope.d.ts +26 -0
  7. package/dist/babel/plugin/componentScope.ts +87 -0
  8. package/dist/babel/plugin/state.d.ts +119 -0
  9. package/dist/babel/plugin/state.ts +177 -0
  10. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  11. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +74 -674
  12. package/dist/babel/plugin/visitors/className.ts +624 -0
  13. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  14. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  15. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  16. package/dist/babel/plugin/visitors/imports.ts +101 -0
  17. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  18. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  19. package/dist/babel/plugin/visitors/program.ts +99 -0
  20. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  21. package/dist/babel/plugin/visitors/tw.test.ts +620 -0
  22. package/dist/babel/plugin/visitors/tw.ts +148 -0
  23. package/dist/babel/plugin.d.ts +3 -96
  24. package/dist/babel/plugin.test.ts +470 -0
  25. package/dist/babel/plugin.ts +28 -953
  26. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  27. package/dist/babel/utils/componentSupport.test.ts +20 -7
  28. package/dist/babel/utils/componentSupport.ts +2 -0
  29. package/dist/babel/utils/modifierProcessing.ts +21 -0
  30. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  31. package/dist/babel/utils/styleInjection.d.ts +15 -0
  32. package/dist/babel/utils/styleInjection.ts +172 -17
  33. package/dist/babel/utils/twProcessing.ts +11 -0
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +1 -0
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/sizing.js +1 -1
  54. package/dist/parser/typography.d.ts +2 -1
  55. package/dist/parser/typography.js +1 -1
  56. package/dist/parser/typography.test.js +1 -1
  57. package/dist/runtime.cjs +1 -1
  58. package/dist/runtime.cjs.map +4 -4
  59. package/dist/runtime.js +1 -1
  60. package/dist/runtime.js.map +4 -4
  61. package/package.json +1 -1
  62. package/src/babel/config-loader.test.ts +2 -2
  63. package/src/babel/config-loader.ts +37 -2
  64. package/src/babel/plugin/componentScope.ts +87 -0
  65. package/src/babel/plugin/state.ts +177 -0
  66. package/src/babel/plugin/visitors/className.test.ts +1312 -0
  67. package/src/babel/plugin/visitors/className.ts +624 -0
  68. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  69. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  70. package/src/babel/plugin/visitors/imports.ts +101 -0
  71. package/src/babel/plugin/visitors/program.test.ts +325 -0
  72. package/src/babel/plugin/visitors/program.ts +99 -0
  73. package/src/babel/plugin/visitors/tw.test.ts +620 -0
  74. package/src/babel/plugin/visitors/tw.ts +148 -0
  75. package/src/babel/plugin.ts +28 -953
  76. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  77. package/src/babel/utils/componentSupport.test.ts +20 -7
  78. package/src/babel/utils/componentSupport.ts +2 -0
  79. package/src/babel/utils/modifierProcessing.ts +21 -0
  80. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  81. package/src/babel/utils/styleInjection.ts +172 -17
  82. package/src/babel/utils/twProcessing.ts +11 -0
  83. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  84. package/src/components/TouchableOpacity.tsx +71 -0
  85. package/src/components/index.ts +3 -0
  86. package/src/config/markers.ts +5 -0
  87. package/src/index.ts +4 -5
  88. package/src/parser/borders.test.ts +58 -0
  89. package/src/parser/borders.ts +18 -3
  90. package/src/parser/colors.test.ts +249 -0
  91. package/src/parser/colors.ts +38 -0
  92. package/src/parser/index.ts +4 -3
  93. package/src/parser/layout.test.ts +61 -0
  94. package/src/parser/layout.ts +55 -1
  95. package/src/parser/sizing.ts +11 -0
  96. package/src/parser/typography.test.ts +102 -0
  97. package/src/parser/typography.ts +61 -15
@@ -1,236 +1,8 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-function */
2
- import { transformSync } from "@babel/core";
3
2
  import { describe, expect, it, vi } from "vitest";
4
- import babelPlugin, { type PluginOptions } from "./plugin.js";
5
-
6
- /**
7
- * Helper to transform code with the Babel plugin
8
- */
9
- function transform(code: string, options?: PluginOptions, includeJsx = false) {
10
- const presets = includeJsx
11
- ? ["@babel/preset-react", ["@babel/preset-typescript", { isTSX: true, allExtensions: true }]]
12
- : [];
13
-
14
- const result = transformSync(code, {
15
- presets,
16
- plugins: [[babelPlugin, options]],
17
- filename: "test.tsx",
18
- configFile: false,
19
- babelrc: false,
20
- });
21
-
22
- return result?.code ?? "";
23
- }
24
-
25
- describe("Babel plugin - tw template tag transformation", () => {
26
- it("should transform simple tw template literal", () => {
27
- const input = `
28
- import { tw } from '@mgcrea/react-native-tailwind';
29
- const styles = tw\`bg-blue-500 m-4\`;
30
- `;
31
-
32
- const output = transform(input);
33
-
34
- // Should have StyleSheet import (either ESM or CommonJS)
35
- expect(output).toMatch(/import.*StyleSheet.*from "react-native"|require\("react-native"\)/);
36
- expect(output).toContain("StyleSheet");
37
-
38
- // Should have _twStyles definition
39
- expect(output).toContain("_twStyles");
40
- expect(output).toContain("StyleSheet.create");
41
-
42
- // Should transform tw call to object with style property
43
- expect(output).toContain("style:");
44
- expect(output).toContain("_twStyles._bg_blue_500_m_4");
45
-
46
- // Should remove tw import
47
- expect(output).not.toContain("from '@mgcrea/react-native-tailwind'");
48
- });
49
-
50
- it("should transform tw with state modifiers", () => {
51
- const input = `
52
- import { tw } from '@mgcrea/react-native-tailwind';
53
- const styles = tw\`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300\`;
54
- `;
55
-
56
- const output = transform(input);
57
-
58
- // Should have base style
59
- expect(output).toContain("style:");
60
- expect(output).toContain("_bg_blue_500");
61
-
62
- // Should have activeStyle
63
- expect(output).toContain("activeStyle:");
64
- expect(output).toContain("_active_bg_blue_700");
65
-
66
- // Should have disabledStyle
67
- expect(output).toContain("disabledStyle:");
68
- expect(output).toContain("_disabled_bg_gray_300");
69
-
70
- // Should create StyleSheet with all styles
71
- expect(output).toContain("backgroundColor:");
72
- });
73
-
74
- it("should inject StyleSheet.create after imports", () => {
75
- const input = `
76
- import { tw } from '@mgcrea/react-native-tailwind';
77
- import { View } from 'react-native';
78
-
79
- const styles = tw\`m-4\`;
80
- `;
81
-
82
- const output = transform(input);
83
-
84
- // Find the position of imports and StyleSheet.create
85
- const viewImportPos = output.indexOf('require("react-native")');
86
- const styleSheetCreatePos = output.indexOf("_twStyles");
87
-
88
- // StyleSheet.create should come after imports
89
- expect(styleSheetCreatePos).toBeGreaterThan(viewImportPos);
90
- });
91
-
92
- it("should handle tw in object literals", () => {
93
- const input = `
94
- import { tw } from '@mgcrea/react-native-tailwind';
95
-
96
- const sizeVariants = {
97
- sm: {
98
- container: tw\`h-9 px-3\`,
99
- text: tw\`text-sm\`,
100
- },
101
- };
102
- `;
103
-
104
- const output = transform(input);
105
-
106
- // Should define _twStyles before object literal
107
- const twStylesPos = output.indexOf("_twStyles");
108
- const sizeVariantsPos = output.indexOf("sizeVariants");
109
-
110
- expect(twStylesPos).toBeGreaterThan(0);
111
- expect(twStylesPos).toBeLessThan(sizeVariantsPos);
112
-
113
- // Should have both styles
114
- expect(output).toContain("_h_9_px_3");
115
- expect(output).toContain("_text_sm");
116
- });
117
-
118
- it("should handle empty tw template literal", () => {
119
- const input = `
120
- import { tw } from '@mgcrea/react-native-tailwind';
121
- const styles = tw\`\`;
122
- `;
123
-
124
- const output = transform(input);
125
-
126
- // Should replace with empty style object
127
- expect(output).toContain("style:");
128
- expect(output).toContain("{}");
129
- });
130
-
131
- it("should preserve other imports from the same package", () => {
132
- const input = `
133
- import { tw, TwStyle, COLORS } from '@mgcrea/react-native-tailwind';
134
- const styles = tw\`m-4\`;
135
- `;
136
-
137
- const output = transform(input);
138
-
139
- // Should remove tw but keep other imports
140
- expect(output).not.toContain('"tw"');
141
- expect(output).toContain("TwStyle");
142
- expect(output).toContain("COLORS");
143
- });
144
-
145
- it("should handle renamed tw import", () => {
146
- const input = `
147
- import { tw as customTw } from '@mgcrea/react-native-tailwind';
148
- const styles = customTw\`m-4 p-2\`;
149
- `;
150
-
151
- const output = transform(input);
152
-
153
- // Should still transform the renamed import
154
- expect(output).toContain("_twStyles");
155
- expect(output).toContain("_m_4_p_2");
156
- expect(output).not.toContain("customTw");
157
- });
158
-
159
- it("should handle multiple tw calls", () => {
160
- const input = `
161
- import { tw } from '@mgcrea/react-native-tailwind';
162
- const style1 = tw\`bg-red-500\`;
163
- const style2 = tw\`bg-blue-500\`;
164
- const style3 = tw\`bg-green-500\`;
165
- `;
166
-
167
- const output = transform(input);
168
-
169
- // Should have all three styles in StyleSheet
170
- expect(output).toContain("_bg_red_500");
171
- expect(output).toContain("_bg_blue_500");
172
- expect(output).toContain("_bg_green_500");
173
-
174
- // Should have StyleSheet.create with all styles
175
- expect(output).toContain("StyleSheet.create");
176
- });
177
-
178
- it("should use custom stylesIdentifier option", () => {
179
- const input = `
180
- import { tw } from '@mgcrea/react-native-tailwind';
181
- const styles = tw\`m-4\`;
182
- `;
183
-
184
- const output = transform(input, { stylesIdentifier: "myStyles" });
185
-
186
- // Should use custom identifier
187
- expect(output).toContain("myStyles");
188
- expect(output).toContain("myStyles._m_4");
189
- expect(output).not.toContain("_twStyles");
190
- });
191
- });
192
-
193
- describe("Babel plugin - twStyle function transformation", () => {
194
- it("should transform twStyle function call", () => {
195
- const input = `
196
- import { twStyle } from '@mgcrea/react-native-tailwind';
197
- const styles = twStyle('bg-blue-500 m-4');
198
- `;
199
-
200
- const output = transform(input);
201
-
202
- // Should transform to object with style property
203
- expect(output).toContain("style:");
204
- expect(output).toContain("_twStyles._bg_blue_500_m_4");
205
- });
206
-
207
- it("should transform twStyle with modifiers", () => {
208
- const input = `
209
- import { twStyle } from '@mgcrea/react-native-tailwind';
210
- const styles = twStyle('bg-blue-500 active:bg-blue-700');
211
- `;
212
-
213
- const output = transform(input);
214
-
215
- expect(output).toContain("style:");
216
- expect(output).toContain("activeStyle:");
217
- });
218
-
219
- it("should handle empty twStyle call", () => {
220
- const input = `
221
- import { twStyle } from '@mgcrea/react-native-tailwind';
222
- const styles = twStyle('');
223
- `;
224
-
225
- const output = transform(input);
226
-
227
- // Should replace with undefined
228
- expect(output).toContain("undefined");
229
- });
230
- });
3
+ import { transform } from "../../../../test/helpers/babelTransform.js";
231
4
 
232
- // Note: JSX tests require @babel/preset-react
233
- describe("Babel plugin - className transformation (existing behavior)", () => {
5
+ describe("className visitor - basic transformation", () => {
234
6
  it("should still transform className props", () => {
235
7
  const input = `
236
8
  import { View } from 'react-native';
@@ -419,7 +191,7 @@ describe("Babel plugin - className transformation (existing behavior)", () => {
419
191
  });
420
192
  });
421
193
 
422
- describe("Babel plugin - placeholder: modifier transformation", () => {
194
+ describe("className visitor - placeholder: modifier", () => {
423
195
  it("should transform placeholder:text-{color} to placeholderTextColor prop", () => {
424
196
  const input = `
425
197
  import { TextInput } from 'react-native';
@@ -566,7 +338,7 @@ describe("Babel plugin - placeholder: modifier transformation", () => {
566
338
  });
567
339
  });
568
340
 
569
- describe("Babel plugin - platform modifier transformation", () => {
341
+ describe("className visitor - platform modifiers", () => {
570
342
  it("should transform platform modifiers to Platform.select()", () => {
571
343
  const input = `
572
344
  import React from 'react';
@@ -807,7 +579,7 @@ describe("Babel plugin - platform modifier transformation", () => {
807
579
  });
808
580
  });
809
581
 
810
- describe("Babel plugin - color scheme modifier transformation", () => {
582
+ describe("className visitor - color scheme modifiers", () => {
811
583
  it("should transform dark: modifier to conditional expression", () => {
812
584
  const input = `
813
585
  import React from 'react';
@@ -1107,7 +879,7 @@ describe("Babel plugin - color scheme modifier transformation", () => {
1107
879
  });
1108
880
  });
1109
881
 
1110
- describe("Babel plugin - custom color scheme hook import", () => {
882
+ describe("className visitor - custom color scheme hook", () => {
1111
883
  it("should use custom import source for color scheme hook", () => {
1112
884
  const input = `
1113
885
  import React from 'react';
@@ -1407,506 +1179,134 @@ describe("Babel plugin - custom color scheme hook import", () => {
1407
1179
  });
1408
1180
  });
1409
1181
 
1410
- describe("Babel plugin - import injection", () => {
1411
- it("should not add StyleSheet import to files without className usage", () => {
1182
+ describe("className visitor - directional border colors", () => {
1183
+ it("should transform directional border colors with preset values", () => {
1412
1184
  const input = `
1413
- import { View, Text } from 'react-native';
1414
-
1415
- function MyComponent() {
1416
- return <View><Text>Hello</Text></View>;
1185
+ import { View } from 'react-native';
1186
+ export function Component() {
1187
+ return <View className="border-t-red-500 border-l-blue-500" />;
1417
1188
  }
1418
1189
  `;
1419
1190
 
1420
1191
  const output = transform(input, undefined, true);
1421
1192
 
1422
- // Should not mutate the import by adding StyleSheet
1423
- // Count occurrences of "StyleSheet" in output
1424
- const styleSheetCount = (output.match(/StyleSheet/g) ?? []).length;
1425
- expect(styleSheetCount).toBe(0);
1193
+ // Should have StyleSheet
1194
+ expect(output).toContain("StyleSheet.create");
1426
1195
 
1427
- // Should not have _twStyles definition
1428
- expect(output).not.toContain("_twStyles");
1429
- expect(output).not.toContain("StyleSheet.create");
1196
+ // Should generate styles with borderTopColor and borderLeftColor
1197
+ expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1198
+ expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1430
1199
 
1431
- // Original imports should remain unchanged
1432
- expect(output).toContain("View");
1433
- expect(output).toContain("Text");
1200
+ // Should not have className in output
1201
+ expect(output).not.toContain("className");
1434
1202
  });
1435
1203
 
1436
- it("should add StyleSheet import only when className is used", () => {
1204
+ it("should combine directional border width and color", () => {
1437
1205
  const input = `
1438
1206
  import { View } from 'react-native';
1439
-
1440
- function MyComponent() {
1441
- return <View className="m-4 p-2" />;
1207
+ export function Component() {
1208
+ return <View className="border-l-2 border-l-red-500" />;
1442
1209
  }
1443
1210
  `;
1444
1211
 
1445
1212
  const output = transform(input, undefined, true);
1446
1213
 
1447
- // Should have StyleSheet import (both single and double quotes)
1448
- expect(output).toMatch(/import.*StyleSheet.*from ['"]react-native['"]|require\(['"]react-native['"]\)/);
1214
+ // Should have both borderLeftWidth and borderLeftColor in the StyleSheet
1215
+ expect(output).toMatch(/borderLeftWidth[:\s]*2/);
1216
+ expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1449
1217
 
1450
- // Should have _twStyles definition
1451
- expect(output).toContain("_twStyles");
1452
- expect(output).toContain("StyleSheet.create");
1218
+ // Should not have className in output
1219
+ expect(output).not.toContain("className");
1453
1220
  });
1454
1221
 
1455
- it("should add Platform import only when platform modifiers are used", () => {
1222
+ it("should support directional border colors with opacity", () => {
1456
1223
  const input = `
1457
1224
  import { View } from 'react-native';
1458
-
1459
- function MyComponent() {
1460
- return <View className="ios:m-4 android:m-2" />;
1225
+ export function Component() {
1226
+ return <View className="border-t-red-500/50 border-b-blue-500/80" />;
1461
1227
  }
1462
1228
  `;
1463
1229
 
1464
1230
  const output = transform(input, undefined, true);
1465
1231
 
1466
- // Should have Platform import
1467
- expect(output).toContain("Platform");
1468
-
1469
- // Should have StyleSheet import too
1470
- expect(output).toContain("StyleSheet");
1471
-
1472
- // Should use Platform.select
1473
- expect(output).toContain("Platform.select");
1232
+ // Should have 8-digit hex colors with alpha channel
1233
+ expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9A-F]{8}['"]/i);
1234
+ expect(output).toMatch(/borderBottomColor[:\s]*['"]#[0-9A-F]{8}['"]/i);
1474
1235
  });
1475
1236
 
1476
- it("should not add Platform import without platform modifiers", () => {
1237
+ it("should support directional border colors with arbitrary hex values", () => {
1477
1238
  const input = `
1478
1239
  import { View } from 'react-native';
1479
-
1480
- function MyComponent() {
1481
- return <View className="m-4 p-2" />;
1240
+ export function Component() {
1241
+ return <View className="border-t-[#ff0000] border-r-[#abc]" />;
1482
1242
  }
1483
1243
  `;
1484
1244
 
1485
1245
  const output = transform(input, undefined, true);
1486
1246
 
1487
- // Should not have Platform import
1488
- const platformCount = (output.match(/Platform/g) ?? []).length;
1489
- expect(platformCount).toBe(0);
1490
-
1491
- // Should still have StyleSheet
1492
- expect(output).toContain("StyleSheet");
1247
+ // Should have borderTopColor and borderRightColor
1248
+ expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9a-fA-F]{6}['"]/);
1249
+ expect(output).toMatch(/borderRightColor[:\s]*['"]#[0-9a-fA-F]{6}['"]/);
1493
1250
  });
1494
- });
1495
1251
 
1496
- describe("Babel plugin - scheme: modifier", () => {
1497
- it.skip("should expand scheme: modifier into dark: and light: modifiers", () => {
1498
- // Note: This test requires tailwind.config.js with custom colors defined
1499
- // The scheme: modifier expands to dark: and light: modifiers which require
1500
- // the color variants to exist in customColors (e.g., systemGray-dark, systemGray-light)
1501
- //
1502
- // Integration test should be done in a real project with tailwind.config.js
1252
+ it("should support all four directional border colors", () => {
1503
1253
  const input = `
1504
1254
  import { View } from 'react-native';
1505
-
1506
- function MyComponent() {
1507
- return <View className="scheme:text-systemGray" />;
1255
+ export function Component() {
1256
+ return (
1257
+ <View className="border-t-red-500 border-r-blue-500 border-b-green-500 border-l-yellow-500" />
1258
+ );
1508
1259
  }
1509
1260
  `;
1510
1261
 
1511
1262
  const output = transform(input, undefined, true);
1512
1263
 
1513
- // Should generate both dark and light variants
1514
- expect(output).toContain("_dark_text_systemGray_dark");
1515
- expect(output).toContain("_light_text_systemGray_light");
1516
-
1517
- // Should inject useColorScheme hook
1518
- expect(output).toContain("useColorScheme");
1519
- expect(output).toContain("_twColorScheme");
1520
-
1521
- // Should have conditional expressions
1522
- expect(output).toContain("_twColorScheme === 'dark'");
1523
- expect(output).toContain("_twColorScheme === 'light'");
1524
- });
1525
- });
1526
-
1527
- describe("Babel plugin - color scheme modifiers in tw/twStyle", () => {
1528
- it("should transform tw with dark: modifier inside component", () => {
1529
- const input = `
1530
- import { tw } from '@mgcrea/react-native-tailwind';
1531
-
1532
- function MyComponent() {
1533
- const styles = tw\`bg-white dark:bg-gray-900\`;
1534
- return null;
1535
- }
1536
- `;
1537
-
1538
- const output = transform(input);
1539
-
1540
- // Should inject useColorScheme hook
1541
- expect(output).toContain("useColorScheme");
1542
- expect(output).toContain("_twColorScheme");
1543
-
1544
- // Should generate style array with conditionals
1545
- expect(output).toContain("style: [");
1546
- expect(output).toContain('_twColorScheme === "dark"');
1547
- expect(output).toContain("_twStyles._dark_bg_gray_900");
1548
- expect(output).toContain("_twStyles._bg_white");
1549
-
1550
- // Should have StyleSheet.create
1551
- expect(output).toContain("StyleSheet.create");
1264
+ // Should have all four directional color properties
1265
+ expect(output).toMatch(/borderTopColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1266
+ expect(output).toMatch(/borderRightColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1267
+ expect(output).toMatch(/borderBottomColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1268
+ expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1552
1269
  });
1553
1270
 
1554
- it("should transform twStyle with light: modifier inside component", () => {
1271
+ it("should combine directional widths, colors, and general border color", () => {
1555
1272
  const input = `
1556
- import { twStyle } from '@mgcrea/react-native-tailwind';
1557
-
1558
- export const MyComponent = () => {
1559
- const buttonStyles = twStyle('text-gray-900 light:text-gray-100');
1560
- return null;
1561
- };
1562
- `;
1563
-
1564
- const output = transform(input);
1565
-
1566
- // Should inject useColorScheme hook
1567
- expect(output).toContain("useColorScheme");
1568
- expect(output).toContain("_twColorScheme");
1569
-
1570
- // Should generate style array with conditionals
1571
- expect(output).toContain("style: [");
1572
- expect(output).toContain('_twColorScheme === "light"');
1573
- expect(output).toContain("_twStyles._light_text_gray_100");
1574
- expect(output).toContain("_twStyles._text_gray_900");
1575
- });
1576
-
1577
- it("should transform tw with both dark: and light: modifiers", () => {
1578
- const input = `
1579
- import { tw } from '@mgcrea/react-native-tailwind';
1580
-
1581
- function MyComponent() {
1582
- const styles = tw\`bg-blue-500 dark:bg-blue-900 light:bg-blue-100\`;
1583
- return null;
1584
- }
1585
- `;
1586
-
1587
- const output = transform(input);
1588
-
1589
- // Should have both conditionals
1590
- expect(output).toContain('_twColorScheme === "dark"');
1591
- expect(output).toContain('_twColorScheme === "light"');
1592
- expect(output).toContain("_twStyles._dark_bg_blue_900");
1593
- expect(output).toContain("_twStyles._light_bg_blue_100");
1594
- expect(output).toContain("_twStyles._bg_blue_500");
1595
- });
1596
-
1597
- it("should combine color scheme modifiers with state modifiers", () => {
1598
- const input = `
1599
- import { tw } from '@mgcrea/react-native-tailwind';
1600
-
1601
- function MyComponent() {
1602
- const styles = tw\`bg-white dark:bg-gray-900 active:bg-blue-500\`;
1603
- return null;
1604
- }
1605
- `;
1606
-
1607
- const output = transform(input);
1608
-
1609
- // Should have color scheme conditionals in style array
1610
- expect(output).toContain("style: [");
1611
- expect(output).toContain('_twColorScheme === "dark"');
1612
-
1613
- // Should have activeStyle property (separate from color scheme)
1614
- expect(output).toContain("activeStyle:");
1615
- expect(output).toContain("_twStyles._active_bg_blue_500");
1616
- });
1617
-
1618
- it("should warn if tw with color scheme modifiers used outside component", () => {
1619
- const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
1620
-
1621
- const input = `
1622
- import { tw } from '@mgcrea/react-native-tailwind';
1623
-
1624
- const globalStyles = tw\`bg-white dark:bg-gray-900\`;
1625
- `;
1626
-
1627
- const output = transform(input);
1628
-
1629
- // Should warn about usage outside component
1630
- expect(consoleWarnSpy).toHaveBeenCalledWith(
1631
- expect.stringContaining("Color scheme modifiers (dark:, light:) in tw/twStyle calls"),
1632
- );
1633
-
1634
- // Should not inject hook (no component scope)
1635
- expect(output).not.toContain("useColorScheme");
1636
-
1637
- // Should still generate styles but without runtime conditionals
1638
- expect(output).toContain("_twStyles");
1639
-
1640
- consoleWarnSpy.mockRestore();
1641
- });
1642
-
1643
- it("should handle tw with only dark: modifier (no base class)", () => {
1644
- const input = `
1645
- import { tw } from '@mgcrea/react-native-tailwind';
1646
-
1647
- function MyComponent() {
1648
- const styles = tw\`dark:bg-gray-900\`;
1649
- return null;
1650
- }
1651
- `;
1652
-
1653
- const output = transform(input);
1654
-
1655
- // Should still generate style array
1656
- expect(output).toContain("style: [");
1657
- expect(output).toContain('_twColorScheme === "dark"');
1658
- expect(output).toContain("_twStyles._dark_bg_gray_900");
1659
- });
1660
-
1661
- it("should work with custom color scheme hook import", () => {
1662
- const input = `
1663
- import { tw } from '@mgcrea/react-native-tailwind';
1664
- import { useTheme } from '@react-navigation/native';
1665
-
1666
- function MyComponent() {
1667
- const styles = tw\`bg-white dark:bg-gray-900\`;
1668
- return null;
1669
- }
1670
- `;
1671
-
1672
- const options: PluginOptions = {
1673
- colorScheme: {
1674
- importFrom: "@react-navigation/native",
1675
- importName: "useTheme",
1676
- },
1677
- };
1678
-
1679
- const output = transform(input, options);
1680
-
1681
- // Should use existing import (not duplicate)
1682
- const themeImportCount = (output.match(/useTheme/g) ?? []).length;
1683
- // Should appear in import statement and hook call
1684
- expect(themeImportCount).toBeGreaterThanOrEqual(2);
1685
-
1686
- // Should call the custom hook
1687
- expect(output).toContain("useTheme()");
1688
- });
1689
-
1690
- it("should generate both style array and darkStyle/lightStyle properties", () => {
1691
- const input = `
1692
- import { tw } from '@mgcrea/react-native-tailwind';
1693
-
1694
- function MyComponent() {
1695
- const styles = tw\`bg-white dark:bg-gray-900 light:bg-gray-50\`;
1696
- return null;
1697
- }
1698
- `;
1699
-
1700
- const output = transform(input);
1701
-
1702
- // Should have runtime conditional in style array
1703
- expect(output).toContain("style: [");
1704
- expect(output).toContain('_twColorScheme === "dark"');
1705
- expect(output).toContain('_twColorScheme === "light"');
1706
-
1707
- // Should ALSO have darkStyle and lightStyle properties for manual access
1708
- expect(output).toContain("darkStyle:");
1709
- expect(output).toContain("lightStyle:");
1710
- expect(output).toContain("_twStyles._dark_bg_gray_900");
1711
- expect(output).toContain("_twStyles._light_bg_gray_50");
1712
- });
1713
-
1714
- it("should allow accessing raw color values from darkStyle/lightStyle", () => {
1715
- const input = `
1716
- import { tw } from '@mgcrea/react-native-tailwind';
1717
-
1718
- function MyComponent() {
1719
- const btnStyles = tw\`bg-blue-500 dark:bg-blue-900\`;
1720
- // User can access raw hex for Reanimated
1721
- const darkBgColor = btnStyles.darkStyle?.backgroundColor;
1722
- return null;
1723
- }
1724
- `;
1725
-
1726
- const output = transform(input);
1727
-
1728
- // Should have darkStyle property available
1729
- expect(output).toContain("darkStyle:");
1730
- expect(output).toContain("_twStyles._dark_bg_blue_900");
1731
-
1732
- // The actual usage line should be preserved (TypeScript/Babel doesn't remove it)
1733
- expect(output).toContain("btnStyles.darkStyle");
1734
- });
1735
-
1736
- // Platform modifier tests for tw/twStyle
1737
- it("should transform tw with ios: modifier", () => {
1738
- const input = `
1739
- import { tw } from '@mgcrea/react-native-tailwind';
1740
-
1741
- function MyComponent() {
1742
- const styles = tw\`bg-white ios:p-6\`;
1743
- return null;
1744
- }
1745
- `;
1746
-
1747
- const output = transform(input);
1748
-
1749
- // Should add Platform import
1750
- expect(output).toContain("Platform");
1751
- expect(output).toContain('from "react-native"');
1752
-
1753
- // Should generate style array with Platform.select()
1754
- expect(output).toContain("style: [");
1755
- expect(output).toContain("Platform.select");
1756
- expect(output).toContain("ios:");
1757
- expect(output).toContain("_twStyles._ios_p_6");
1758
- expect(output).toContain("_twStyles._bg_white");
1759
-
1760
- // Should have StyleSheet.create
1761
- expect(output).toContain("StyleSheet.create");
1762
- });
1763
-
1764
- it("should transform twStyle with android: modifier", () => {
1765
- const input = `
1766
- import { twStyle } from '@mgcrea/react-native-tailwind';
1767
-
1768
- export const MyComponent = () => {
1769
- const buttonStyles = twStyle('bg-blue-500 android:p-8');
1770
- return null;
1771
- };
1772
- `;
1773
-
1774
- const output = transform(input);
1775
-
1776
- // Should add Platform import
1777
- expect(output).toContain("Platform");
1778
-
1779
- // Should generate style array with Platform.select()
1780
- expect(output).toContain("style: [");
1781
- expect(output).toContain("Platform.select");
1782
- expect(output).toContain("android:");
1783
- expect(output).toContain("_twStyles._android_p_8");
1784
- expect(output).toContain("_twStyles._bg_blue_500");
1785
- });
1786
-
1787
- it("should transform tw with multiple platform modifiers", () => {
1788
- const input = `
1789
- import { tw } from '@mgcrea/react-native-tailwind';
1790
-
1791
- function MyComponent() {
1792
- const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
1793
- return null;
1794
- }
1795
- `;
1796
-
1797
- const output = transform(input);
1798
-
1799
- // Should generate Platform.select() with all platforms
1800
- expect(output).toContain("Platform.select");
1801
- expect(output).toContain("ios:");
1802
- expect(output).toContain("android:");
1803
- expect(output).toContain("web:");
1804
- expect(output).toContain("_twStyles._ios_p_6");
1805
- expect(output).toContain("_twStyles._android_p_8");
1806
- expect(output).toContain("_twStyles._web_p_4");
1807
- });
1808
-
1809
- it("should combine platform modifiers with color-scheme modifiers", () => {
1810
- const input = `
1811
- import { tw } from '@mgcrea/react-native-tailwind';
1812
-
1813
- function MyComponent() {
1814
- const styles = tw\`bg-white ios:p-6 dark:bg-gray-900\`;
1815
- return null;
1816
- }
1817
- `;
1818
-
1819
- const output = transform(input);
1820
-
1821
- // Should have both Platform and useColorScheme
1822
- expect(output).toContain("Platform");
1823
- expect(output).toContain("useColorScheme");
1824
- expect(output).toContain("_twColorScheme");
1825
-
1826
- // Should have both conditionals in style array
1827
- expect(output).toContain("Platform.select");
1828
- expect(output).toContain('_twColorScheme === "dark"');
1829
- });
1830
-
1831
- it("should generate iosStyle/androidStyle/webStyle properties for manual access", () => {
1832
- const input = `
1833
- import { tw } from '@mgcrea/react-native-tailwind';
1834
-
1835
- function MyComponent() {
1836
- const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
1837
- return null;
1273
+ import { View } from 'react-native';
1274
+ export function Component() {
1275
+ return (
1276
+ <View className="border border-gray-300 border-l-4 border-l-blue-500" />
1277
+ );
1838
1278
  }
1839
1279
  `;
1840
1280
 
1841
- const output = transform(input);
1842
-
1843
- // Should have separate platform style properties
1844
- expect(output).toContain("iosStyle:");
1845
- expect(output).toContain("_twStyles._ios_p_6");
1846
- expect(output).toContain("androidStyle:");
1847
- expect(output).toContain("_twStyles._android_p_8");
1848
- expect(output).toContain("webStyle:");
1849
- expect(output).toContain("_twStyles._web_p_4");
1850
-
1851
- // Should also have runtime Platform.select() in style array
1852
- expect(output).toContain("Platform.select");
1853
- });
1854
-
1855
- it("should work with only platform modifiers (no base class)", () => {
1856
- const input = `
1857
- import { tw } from '@mgcrea/react-native-tailwind';
1858
-
1859
- function MyComponent() {
1860
- const styles = tw\`ios:p-6 android:p-8\`;
1861
- return null;
1862
- }
1863
- `;
1281
+ const output = transform(input, undefined, true);
1864
1282
 
1865
- const output = transform(input);
1283
+ // Should have general border properties
1284
+ expect(output).toMatch(/borderWidth[:\s]*1/);
1285
+ expect(output).toMatch(/borderColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1866
1286
 
1867
- // Should generate Platform.select() even without base classes
1868
- expect(output).toContain("Platform.select");
1869
- expect(output).toContain("_twStyles._ios_p_6");
1870
- expect(output).toContain("_twStyles._android_p_8");
1287
+ // Should have directional left border properties
1288
+ expect(output).toMatch(/borderLeftWidth[:\s]*4/);
1289
+ expect(output).toMatch(/borderLeftColor[:\s]*['"]#[0-9A-F]{6}['"]/i);
1871
1290
  });
1872
1291
 
1873
- it("should allow accessing platform-specific styles manually", () => {
1292
+ it("should work with dynamic className containing directional border colors", () => {
1874
1293
  const input = `
1875
- import { tw } from '@mgcrea/react-native-tailwind';
1876
-
1877
- function MyComponent() {
1878
- const btnStyles = tw\`bg-blue-500 ios:p-6\`;
1879
- const iosPadding = btnStyles.iosStyle;
1880
- return null;
1294
+ import { View } from 'react-native';
1295
+ export function Component({ isError }) {
1296
+ return (
1297
+ <View className={\`border-t-2 \${isError ? 'border-t-red-500' : 'border-t-gray-300'}\`} />
1298
+ );
1881
1299
  }
1882
1300
  `;
1883
1301
 
1884
- const output = transform(input);
1885
-
1886
- // Should have iosStyle property available
1887
- expect(output).toContain("iosStyle:");
1888
- expect(output).toContain("_twStyles._ios_p_6");
1889
-
1890
- // The actual usage line should be preserved
1891
- expect(output).toContain("btnStyles.iosStyle");
1892
- });
1893
-
1894
- it("should combine state modifiers with platform modifiers", () => {
1895
- const input = `
1896
- import { tw } from '@mgcrea/react-native-tailwind';
1897
-
1898
- function MyComponent() {
1899
- const styles = tw\`bg-white active:bg-blue-500 ios:p-6\`;
1900
- return null;
1901
- }
1902
- `;
1302
+ const output = transform(input, undefined, true);
1903
1303
 
1904
- const output = transform(input);
1304
+ // Should have StyleSheet with both color options
1305
+ expect(output).toContain("_border_t_2");
1306
+ expect(output).toContain("_border_t_red_500");
1307
+ expect(output).toContain("_border_t_gray_300");
1905
1308
 
1906
- // Should have both activeStyle and platform modifiers
1907
- expect(output).toContain("activeStyle:");
1908
- expect(output).toContain("_twStyles._active_bg_blue_500");
1909
- expect(output).toContain("Platform.select");
1910
- expect(output).toContain("_twStyles._ios_p_6");
1309
+ // Should have conditional expression with both styles
1310
+ expect(output).toMatch(/isError\s*\?\s*_twStyles\._border_t_red_500/);
1911
1311
  });
1912
1312
  });