@mgcrea/react-native-tailwind 0.12.1 → 0.14.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 (104) hide show
  1. package/README.md +45 -2031
  2. package/dist/babel/index.cjs +1726 -1094
  3. package/dist/babel/plugin/componentScope.d.ts +26 -0
  4. package/dist/babel/plugin/componentScope.ts +87 -0
  5. package/dist/babel/plugin/state.d.ts +123 -0
  6. package/dist/babel/plugin/state.ts +185 -0
  7. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  8. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
  9. package/dist/babel/plugin/visitors/className.ts +652 -0
  10. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  11. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  12. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  13. package/dist/babel/plugin/visitors/imports.ts +116 -0
  14. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  15. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  16. package/dist/babel/plugin/visitors/program.ts +116 -0
  17. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  18. package/dist/babel/plugin/visitors/tw.test.ts +771 -0
  19. package/dist/babel/plugin/visitors/tw.ts +148 -0
  20. package/dist/babel/plugin.d.ts +3 -96
  21. package/dist/babel/plugin.test.ts +470 -0
  22. package/dist/babel/plugin.ts +28 -963
  23. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  24. package/dist/babel/utils/componentSupport.test.ts +20 -7
  25. package/dist/babel/utils/componentSupport.ts +2 -0
  26. package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
  27. package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
  28. package/dist/babel/utils/modifierProcessing.ts +21 -0
  29. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  30. package/dist/babel/utils/styleInjection.d.ts +31 -0
  31. package/dist/babel/utils/styleInjection.ts +253 -7
  32. package/dist/babel/utils/twProcessing.d.ts +2 -0
  33. package/dist/babel/utils/twProcessing.ts +103 -3
  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 +2 -2
  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/modifiers.d.ts +32 -2
  54. package/dist/parser/modifiers.js +1 -1
  55. package/dist/parser/modifiers.test.js +1 -1
  56. package/dist/parser/sizing.js +1 -1
  57. package/dist/parser/spacing.d.ts +1 -1
  58. package/dist/parser/spacing.js +1 -1
  59. package/dist/parser/spacing.test.js +1 -1
  60. package/dist/parser/typography.test.js +1 -1
  61. package/dist/runtime.cjs +1 -1
  62. package/dist/runtime.cjs.map +4 -4
  63. package/dist/runtime.js +1 -1
  64. package/dist/runtime.js.map +4 -4
  65. package/package.json +6 -6
  66. package/src/babel/plugin/componentScope.ts +87 -0
  67. package/src/babel/plugin/state.ts +185 -0
  68. package/src/babel/plugin/visitors/className.test.ts +1625 -0
  69. package/src/babel/plugin/visitors/className.ts +652 -0
  70. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  71. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  72. package/src/babel/plugin/visitors/imports.ts +116 -0
  73. package/src/babel/plugin/visitors/program.test.ts +325 -0
  74. package/src/babel/plugin/visitors/program.ts +116 -0
  75. package/src/babel/plugin/visitors/tw.test.ts +771 -0
  76. package/src/babel/plugin/visitors/tw.ts +148 -0
  77. package/src/babel/plugin.ts +28 -963
  78. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  79. package/src/babel/utils/componentSupport.test.ts +20 -7
  80. package/src/babel/utils/componentSupport.ts +2 -0
  81. package/src/babel/utils/directionalModifierProcessing.ts +99 -0
  82. package/src/babel/utils/modifierProcessing.ts +21 -0
  83. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  84. package/src/babel/utils/styleInjection.ts +253 -7
  85. package/src/babel/utils/twProcessing.ts +103 -3
  86. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  87. package/src/components/TouchableOpacity.tsx +71 -0
  88. package/src/components/index.ts +3 -0
  89. package/src/config/markers.ts +5 -0
  90. package/src/index.ts +4 -5
  91. package/src/parser/borders.test.ts +162 -0
  92. package/src/parser/borders.ts +67 -9
  93. package/src/parser/colors.test.ts +249 -0
  94. package/src/parser/colors.ts +38 -0
  95. package/src/parser/index.ts +4 -2
  96. package/src/parser/layout.test.ts +74 -0
  97. package/src/parser/layout.ts +94 -0
  98. package/src/parser/modifiers.test.ts +206 -0
  99. package/src/parser/modifiers.ts +62 -3
  100. package/src/parser/sizing.ts +11 -0
  101. package/src/parser/spacing.test.ts +66 -0
  102. package/src/parser/spacing.ts +15 -5
  103. package/src/parser/typography.test.ts +8 -0
  104. package/src/parser/typography.ts +4 -0
@@ -0,0 +1,325 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { transform } from "../../../../test/helpers/babelTransform.js";
3
+
4
+ describe("program visitor - Program.exit behavior", () => {
5
+ describe("StyleSheet injection", () => {
6
+ it("should inject StyleSheet.create when className is used", () => {
7
+ const input = `
8
+ import { View } from 'react-native';
9
+
10
+ function MyComponent() {
11
+ return <View className="m-4 p-2 bg-blue-500" />;
12
+ }
13
+ `;
14
+
15
+ const output = transform(input, undefined, true);
16
+
17
+ // Should have StyleSheet import
18
+ expect(output).toContain("StyleSheet");
19
+
20
+ // Should inject StyleSheet.create
21
+ expect(output).toContain("StyleSheet.create");
22
+ expect(output).toContain("_twStyles");
23
+
24
+ // Should have the styles
25
+ expect(output).toMatch(/margin:\s*16/);
26
+ expect(output).toMatch(/padding:\s*8/);
27
+ expect(output).toMatch(/backgroundColor:/);
28
+ });
29
+
30
+ it("should not inject StyleSheet when no className is used", () => {
31
+ const input = `
32
+ import { View } from 'react-native';
33
+
34
+ function MyComponent() {
35
+ return <View />;
36
+ }
37
+ `;
38
+
39
+ const output = transform(input, undefined, true);
40
+
41
+ // Should not add StyleSheet import
42
+ expect(output).not.toContain("StyleSheet");
43
+ expect(output).not.toContain("_twStyles");
44
+ });
45
+
46
+ it("should inject styles at top of file after imports", () => {
47
+ const input = `
48
+ import React from 'react';
49
+ import { View } from 'react-native';
50
+
51
+ function MyComponent() {
52
+ return <View className="m-4" />;
53
+ }
54
+
55
+ function AnotherComponent() {
56
+ return <View className="p-2" />;
57
+ }
58
+ `;
59
+
60
+ const output = transform(input, undefined, true);
61
+
62
+ // StyleSheet.create should come before component definitions
63
+ const styleSheetIndex = output.indexOf("StyleSheet.create");
64
+ const firstComponentIndex = output.indexOf("function MyComponent");
65
+
66
+ expect(styleSheetIndex).toBeGreaterThan(0);
67
+ expect(styleSheetIndex).toBeLessThan(firstComponentIndex);
68
+ });
69
+ });
70
+
71
+ describe("tw import removal", () => {
72
+ it("should remove tw import when tw tagged template is used", () => {
73
+ const input = `
74
+ import { tw } from '@mgcrea/react-native-tailwind';
75
+ import { View } from 'react-native';
76
+
77
+ function MyComponent() {
78
+ const styles = tw\`m-4 p-2\`;
79
+ return <View style={styles.style} />;
80
+ }
81
+ `;
82
+
83
+ const output = transform(input, undefined, true);
84
+
85
+ // Should not have tw import anymore
86
+ expect(output).not.toMatch(/import.*tw.*from ['"]@mgcrea\/react-native-tailwind['"]/);
87
+
88
+ // Should have StyleSheet instead
89
+ expect(output).toContain("StyleSheet");
90
+ });
91
+
92
+ it("should remove twStyle import when twStyle call is used", () => {
93
+ const input = `
94
+ import { twStyle } from '@mgcrea/react-native-tailwind';
95
+ import { View } from 'react-native';
96
+
97
+ function MyComponent() {
98
+ const styles = twStyle('m-4 p-2');
99
+ return <View style={styles.style} />;
100
+ }
101
+ `;
102
+
103
+ const output = transform(input, undefined, true);
104
+
105
+ // Should not have twStyle import anymore
106
+ expect(output).not.toMatch(/import.*twStyle.*from ['"]@mgcrea\/react-native-tailwind['"]/);
107
+
108
+ // Should have StyleSheet instead
109
+ expect(output).toContain("StyleSheet");
110
+ });
111
+ });
112
+
113
+ describe("hook injection - color scheme", () => {
114
+ it("should inject useColorScheme hook when dark: modifier is used", () => {
115
+ const input = `
116
+ import { View } from 'react-native';
117
+
118
+ function MyComponent() {
119
+ return <View className="bg-white dark:bg-gray-900" />;
120
+ }
121
+ `;
122
+
123
+ const output = transform(input, undefined, true);
124
+
125
+ // Should import useColorScheme
126
+ expect(output).toContain("useColorScheme");
127
+
128
+ // Should inject hook call
129
+ expect(output).toContain("_twColorScheme");
130
+ expect(output).toContain("useColorScheme()");
131
+
132
+ // Should use in conditional
133
+ expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/);
134
+ });
135
+
136
+ it("should inject useColorScheme hook when light: modifier is used", () => {
137
+ const input = `
138
+ import { View } from 'react-native';
139
+
140
+ function MyComponent() {
141
+ return <View className="bg-gray-900 light:bg-white" />;
142
+ }
143
+ `;
144
+
145
+ const output = transform(input, undefined, true);
146
+
147
+ // Should import and inject hook
148
+ expect(output).toContain("useColorScheme");
149
+ expect(output).toContain("_twColorScheme");
150
+
151
+ // Should use in conditional
152
+ expect(output).toMatch(/_twColorScheme\s*===\s*['"]light['"]/);
153
+ });
154
+
155
+ it("should inject hook only once for multiple color scheme modifiers", () => {
156
+ const input = `
157
+ import { View } from 'react-native';
158
+
159
+ function MyComponent() {
160
+ return (
161
+ <>
162
+ <View className="dark:bg-gray-900" />
163
+ <View className="light:bg-white" />
164
+ <View className="dark:text-white" />
165
+ </>
166
+ );
167
+ }
168
+ `;
169
+
170
+ const output = transform(input, undefined, true);
171
+
172
+ // Count hook injections (should be exactly 1)
173
+ const hookMatches = output.match(/_twColorScheme\s*=\s*useColorScheme\(\)/g) ?? [];
174
+ expect(hookMatches.length).toBe(1);
175
+ });
176
+
177
+ it("should use custom color scheme hook when configured", () => {
178
+ const input = `
179
+ import { View } from 'react-native';
180
+
181
+ function MyComponent() {
182
+ return <View className="dark:bg-gray-900" />;
183
+ }
184
+ `;
185
+
186
+ const output = transform(
187
+ input,
188
+ {
189
+ colorScheme: {
190
+ importFrom: "@react-navigation/native",
191
+ importName: "useTheme",
192
+ },
193
+ },
194
+ true,
195
+ );
196
+
197
+ // Should import from custom source
198
+ expect(output).toContain("@react-navigation/native");
199
+ expect(output).toContain("useTheme");
200
+
201
+ // Should use custom hook
202
+ expect(output).toContain("useTheme()");
203
+ });
204
+ });
205
+
206
+ describe("hook injection - window dimensions", () => {
207
+ it("should inject useWindowDimensions hook when w-screen is used", () => {
208
+ const input = `
209
+ import { View } from 'react-native';
210
+
211
+ function MyComponent() {
212
+ return <View className="w-screen bg-white" />;
213
+ }
214
+ `;
215
+
216
+ const output = transform(input, undefined, true);
217
+
218
+ // Should import useWindowDimensions
219
+ expect(output).toContain("useWindowDimensions");
220
+
221
+ // Should inject hook call
222
+ expect(output).toContain("_twDimensions");
223
+ expect(output).toContain("useWindowDimensions()");
224
+
225
+ // Should use in inline style
226
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
227
+ });
228
+
229
+ it("should inject useWindowDimensions hook when h-screen is used", () => {
230
+ const input = `
231
+ import { View } from 'react-native';
232
+
233
+ function MyComponent() {
234
+ return <View className="h-screen bg-white" />;
235
+ }
236
+ `;
237
+
238
+ const output = transform(input, undefined, true);
239
+
240
+ // Should import and inject hook
241
+ expect(output).toContain("useWindowDimensions");
242
+ expect(output).toContain("_twDimensions");
243
+
244
+ // Should use in inline style
245
+ expect(output).toMatch(/height:\s*_twDimensions\.height/);
246
+ });
247
+
248
+ it("should inject hook only once for multiple w-screen/h-screen uses", () => {
249
+ const input = `
250
+ import { View } from 'react-native';
251
+
252
+ function MyComponent() {
253
+ return (
254
+ <>
255
+ <View className="w-screen" />
256
+ <View className="h-screen" />
257
+ <View className="w-screen h-screen" />
258
+ </>
259
+ );
260
+ }
261
+ `;
262
+
263
+ const output = transform(input, undefined, true);
264
+
265
+ // Count hook injections (should be exactly 1)
266
+ const hookMatches = output.match(/_twDimensions\s*=\s*useWindowDimensions\(\)/g) ?? [];
267
+ expect(hookMatches.length).toBe(1);
268
+ });
269
+ });
270
+
271
+ describe("combined scenarios", () => {
272
+ it("should inject color scheme and platform imports together", () => {
273
+ const input = `
274
+ import { View } from 'react-native';
275
+
276
+ function MyComponent() {
277
+ return (
278
+ <View className="ios:p-4 android:p-2 dark:bg-gray-900 light:bg-white" />
279
+ );
280
+ }
281
+ `;
282
+
283
+ const output = transform(input, undefined, true);
284
+
285
+ // Should have all imports
286
+ expect(output).toContain("StyleSheet");
287
+ expect(output).toContain("Platform");
288
+ expect(output).toContain("useColorScheme");
289
+
290
+ // Should have color scheme hook
291
+ expect(output).toContain("_twColorScheme");
292
+ expect(output).toContain("useColorScheme()");
293
+
294
+ // Should have platform select
295
+ expect(output).toContain("Platform.select");
296
+ });
297
+
298
+ it("should inject window dimensions and color scheme hooks in separate components", () => {
299
+ const input = `
300
+ import { View } from 'react-native';
301
+
302
+ function ComponentA() {
303
+ return <View className="w-screen h-screen" />;
304
+ }
305
+
306
+ function ComponentB() {
307
+ return <View className="dark:bg-gray-900 light:bg-white" />;
308
+ }
309
+ `;
310
+
311
+ const output = transform(input, undefined, true);
312
+
313
+ // Should have both hooks
314
+ expect(output).toContain("useColorScheme");
315
+ expect(output).toContain("useWindowDimensions");
316
+
317
+ // Should have both hook calls
318
+ expect(output).toContain("_twColorScheme");
319
+ expect(output).toContain("_twDimensions");
320
+
321
+ // Should have StyleSheet
322
+ expect(output).toContain("StyleSheet.create");
323
+ });
324
+ });
325
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Program visitor - entry and exit points for file processing
3
+ */
4
+
5
+ import type { NodePath } from "@babel/core";
6
+ import type * as BabelTypes from "@babel/types";
7
+ import {
8
+ addColorSchemeImport,
9
+ addI18nManagerImport,
10
+ addPlatformImport,
11
+ addStyleSheetImport,
12
+ addWindowDimensionsImport,
13
+ injectColorSchemeHook,
14
+ injectI18nManagerVariable,
15
+ injectStylesAtTop,
16
+ injectWindowDimensionsHook,
17
+ } from "../../utils/styleInjection.js";
18
+ import { removeTwImports } from "../../utils/twProcessing.js";
19
+ import type { PluginState } from "../state.js";
20
+
21
+ /**
22
+ * Program enter visitor - initialize state for each file
23
+ */
24
+ export function programEnter(_path: NodePath<BabelTypes.Program>, _state: PluginState): void {
25
+ // Note: State initialization is already handled by the createInitialState function
26
+ // This is called before any other visitors and receives the initialized state
27
+ // The actual initialization happens in the main plugin.ts file via Object.assign
28
+ }
29
+
30
+ /**
31
+ * Program exit visitor - finalize transformations
32
+ * Injects imports, hooks, and StyleSheet.create
33
+ */
34
+ export function programExit(
35
+ path: NodePath<BabelTypes.Program>,
36
+ state: PluginState,
37
+ t: typeof BabelTypes,
38
+ ): void {
39
+ // Remove tw/twStyle imports if they were used (and transformed)
40
+ if (state.hasTwImport) {
41
+ removeTwImports(path, t);
42
+ }
43
+
44
+ // If no classNames were found and no hooks/imports needed, skip processing
45
+ if (
46
+ !state.hasClassNames &&
47
+ !state.needsWindowDimensionsImport &&
48
+ !state.needsColorSchemeImport &&
49
+ !state.needsI18nManagerImport
50
+ ) {
51
+ return;
52
+ }
53
+
54
+ // Add StyleSheet import if not already present (and we have styles to inject)
55
+ if (!state.hasStyleSheetImport && state.styleRegistry.size > 0) {
56
+ addStyleSheetImport(path, t);
57
+ }
58
+
59
+ // Add Platform import if platform modifiers were used and not already present
60
+ if (state.needsPlatformImport && !state.hasPlatformImport) {
61
+ addPlatformImport(path, t);
62
+ }
63
+
64
+ // Add I18nManager import if directional modifiers were used and not already present
65
+ if (state.needsI18nManagerImport && !state.hasI18nManagerImport) {
66
+ addI18nManagerImport(path, t);
67
+ }
68
+
69
+ // Inject I18nManager.isRTL variable at module level (not a hook, so no component scope needed)
70
+ if (state.needsI18nManagerImport) {
71
+ injectI18nManagerVariable(path, state.i18nManagerVariableName, state.i18nManagerLocalIdentifier, t);
72
+ }
73
+
74
+ // Add color scheme hook import if color scheme modifiers were used and not already present
75
+ if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
76
+ addColorSchemeImport(path, state.colorSchemeImportSource, state.colorSchemeHookName, t);
77
+ }
78
+
79
+ // Inject color scheme hook in function components that need it
80
+ if (state.needsColorSchemeImport) {
81
+ for (const functionPath of state.functionComponentsNeedingColorScheme) {
82
+ injectColorSchemeHook(
83
+ functionPath,
84
+ state.colorSchemeVariableName,
85
+ state.colorSchemeHookName,
86
+ state.colorSchemeLocalIdentifier,
87
+ t,
88
+ );
89
+ }
90
+ }
91
+
92
+ // Add useWindowDimensions import if w-screen/h-screen classes were used and not already present
93
+ if (state.needsWindowDimensionsImport && !state.hasWindowDimensionsImport) {
94
+ addWindowDimensionsImport(path, t);
95
+ }
96
+
97
+ // Inject useWindowDimensions hook in function components that need it
98
+ if (state.needsWindowDimensionsImport) {
99
+ for (const functionPath of state.functionComponentsNeedingWindowDimensions) {
100
+ injectWindowDimensionsHook(
101
+ functionPath,
102
+ state.windowDimensionsVariableName,
103
+ "useWindowDimensions",
104
+ state.windowDimensionsLocalIdentifier,
105
+ t,
106
+ );
107
+ }
108
+ }
109
+
110
+ // Generate and inject StyleSheet.create at the beginning of the file (after imports)
111
+ // This ensures _twStyles is defined before any code that references it
112
+ // Only inject if we actually have styles to inject
113
+ if (state.styleRegistry.size > 0) {
114
+ injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
115
+ }
116
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * tw and twStyle visitors - handles compile-time tw tagged templates and twStyle calls
3
+ */
4
+ import type { NodePath } from "@babel/core";
5
+ import type * as BabelTypes from "@babel/types";
6
+ import type { PluginState } from "../state.js";
7
+ /**
8
+ * TaggedTemplateExpression visitor
9
+ * Handles tw`...` tagged template expressions
10
+ */
11
+ export declare function taggedTemplateVisitor(path: NodePath<BabelTypes.TaggedTemplateExpression>, state: PluginState, t: typeof BabelTypes): void;
12
+ /**
13
+ * CallExpression visitor
14
+ * Handles twStyle('...') call expressions
15
+ */
16
+ export declare function callExpressionVisitor(path: NodePath<BabelTypes.CallExpression>, state: PluginState, t: typeof BabelTypes): void;