@mgcrea/react-native-tailwind 0.6.0 → 0.7.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 (57) hide show
  1. package/README.md +437 -10
  2. package/dist/babel/config-loader.ts +1 -23
  3. package/dist/babel/index.cjs +543 -150
  4. package/dist/babel/index.d.ts +27 -2
  5. package/dist/babel/index.test.ts +268 -0
  6. package/dist/babel/index.ts +352 -44
  7. package/dist/components/Pressable.d.ts +2 -0
  8. package/dist/components/TextInput.d.ts +2 -0
  9. package/dist/config/palettes.d.ts +302 -0
  10. package/dist/config/palettes.js +1 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +1 -1
  13. package/dist/parser/__snapshots__/colors.test.js.snap +242 -90
  14. package/dist/parser/__snapshots__/transforms.test.js.snap +58 -0
  15. package/dist/parser/colors.js +1 -1
  16. package/dist/parser/colors.test.js +1 -1
  17. package/dist/parser/layout.js +1 -1
  18. package/dist/parser/layout.test.js +1 -1
  19. package/dist/parser/typography.js +1 -1
  20. package/dist/parser/typography.test.js +1 -1
  21. package/dist/runtime.cjs +2 -0
  22. package/dist/runtime.cjs.map +7 -0
  23. package/dist/runtime.d.ts +139 -0
  24. package/dist/runtime.js +2 -0
  25. package/dist/runtime.js.map +7 -0
  26. package/dist/runtime.test.js +1 -0
  27. package/dist/stubs/tw.d.ts +60 -0
  28. package/dist/stubs/tw.js +1 -0
  29. package/dist/utils/flattenColors.d.ts +16 -0
  30. package/dist/utils/flattenColors.js +1 -0
  31. package/dist/utils/flattenColors.test.js +1 -0
  32. package/dist/utils/modifiers.d.ts +29 -0
  33. package/dist/utils/modifiers.js +1 -0
  34. package/dist/utils/modifiers.test.js +1 -0
  35. package/dist/utils/styleKey.test.js +1 -0
  36. package/package.json +15 -3
  37. package/src/babel/config-loader.ts +1 -23
  38. package/src/babel/index.test.ts +268 -0
  39. package/src/babel/index.ts +352 -44
  40. package/src/components/Pressable.tsx +1 -0
  41. package/src/components/TextInput.tsx +1 -0
  42. package/src/config/palettes.ts +304 -0
  43. package/src/index.ts +5 -0
  44. package/src/parser/colors.test.ts +47 -31
  45. package/src/parser/colors.ts +5 -110
  46. package/src/parser/layout.test.ts +35 -0
  47. package/src/parser/layout.ts +26 -0
  48. package/src/parser/typography.test.ts +10 -0
  49. package/src/parser/typography.ts +8 -0
  50. package/src/runtime.test.ts +325 -0
  51. package/src/runtime.ts +280 -0
  52. package/src/stubs/tw.ts +80 -0
  53. package/src/utils/flattenColors.test.ts +361 -0
  54. package/src/utils/flattenColors.ts +32 -0
  55. package/src/utils/modifiers.test.ts +286 -0
  56. package/src/utils/modifiers.ts +63 -0
  57. package/src/utils/styleKey.test.ts +168 -0
@@ -5,13 +5,38 @@
5
5
  import type { PluginObj, PluginPass } from "@babel/core";
6
6
  import * as BabelTypes from "@babel/types";
7
7
  import { StyleObject } from "src/types.js";
8
+ /**
9
+ * Plugin options
10
+ */
11
+ export type PluginOptions = {
12
+ /**
13
+ * List of JSX attribute names to transform (in addition to or instead of 'className')
14
+ * Supports exact matches and glob patterns:
15
+ * - Exact: 'className', 'containerClassName'
16
+ * - Glob: '*ClassName' (matches any attribute ending in 'ClassName')
17
+ *
18
+ * @default ['className', 'contentContainerClassName', 'columnWrapperClassName', 'ListHeaderComponentClassName', 'ListFooterComponentClassName']
19
+ */
20
+ attributes?: string[];
21
+ /**
22
+ * Custom identifier name for the generated StyleSheet constant
23
+ *
24
+ * @default '_twStyles'
25
+ */
26
+ stylesIdentifier?: string;
27
+ };
8
28
  type PluginState = PluginPass & {
9
29
  styleRegistry: Map<string, StyleObject>;
10
30
  hasClassNames: boolean;
11
31
  hasStyleSheetImport: boolean;
12
32
  customColors: Record<string, string>;
33
+ supportedAttributes: Set<string>;
34
+ attributePatterns: RegExp[];
35
+ stylesIdentifier: string;
36
+ twImportNames: Set<string>;
37
+ hasTwImport: boolean;
13
38
  };
14
- export default function reactNativeTailwindBabelPlugin({ types: t, }: {
39
+ export default function reactNativeTailwindBabelPlugin({ types: t }: {
15
40
  types: typeof BabelTypes;
16
- }): PluginObj<PluginState>;
41
+ }, options?: PluginOptions): PluginObj<PluginState>;
17
42
  export {};
@@ -0,0 +1,268 @@
1
+ import { transformSync } from "@babel/core";
2
+ import { describe, expect, it } from "vitest";
3
+ import babelPlugin, { type PluginOptions } from "./index.js";
4
+
5
+ /**
6
+ * Helper to transform code with the Babel plugin
7
+ */
8
+ function transform(code: string, options?: PluginOptions, includeJsx = false) {
9
+ const presets = includeJsx ? ["@babel/preset-react"] : [];
10
+
11
+ const result = transformSync(code, {
12
+ presets,
13
+ plugins: [[babelPlugin, options]],
14
+ filename: "test.tsx",
15
+ configFile: false,
16
+ babelrc: false,
17
+ });
18
+
19
+ return result?.code ?? "";
20
+ }
21
+
22
+ describe("Babel plugin - tw template tag transformation", () => {
23
+ it("should transform simple tw template literal", () => {
24
+ const input = `
25
+ import { tw } from '@mgcrea/react-native-tailwind';
26
+ const styles = tw\`bg-blue-500 m-4\`;
27
+ `;
28
+
29
+ const output = transform(input);
30
+
31
+ // Should have StyleSheet import (either ESM or CommonJS)
32
+ expect(output).toMatch(/import.*StyleSheet.*from "react-native"|require\("react-native"\)/);
33
+ expect(output).toContain("StyleSheet");
34
+
35
+ // Should have _twStyles definition
36
+ expect(output).toContain("_twStyles");
37
+ expect(output).toContain("StyleSheet.create");
38
+
39
+ // Should transform tw call to object with style property
40
+ expect(output).toContain("style:");
41
+ expect(output).toContain("_twStyles._bg_blue_500_m_4");
42
+
43
+ // Should remove tw import
44
+ expect(output).not.toContain("from '@mgcrea/react-native-tailwind'");
45
+ });
46
+
47
+ it("should transform tw with state modifiers", () => {
48
+ const input = `
49
+ import { tw } from '@mgcrea/react-native-tailwind';
50
+ const styles = tw\`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300\`;
51
+ `;
52
+
53
+ const output = transform(input);
54
+
55
+ // Should have base style
56
+ expect(output).toContain("style:");
57
+ expect(output).toContain("_bg_blue_500");
58
+
59
+ // Should have activeStyle
60
+ expect(output).toContain("activeStyle:");
61
+ expect(output).toContain("_active_bg_blue_700");
62
+
63
+ // Should have disabledStyle
64
+ expect(output).toContain("disabledStyle:");
65
+ expect(output).toContain("_disabled_bg_gray_300");
66
+
67
+ // Should create StyleSheet with all styles
68
+ expect(output).toContain("backgroundColor:");
69
+ });
70
+
71
+ it("should inject StyleSheet.create after imports", () => {
72
+ const input = `
73
+ import { tw } from '@mgcrea/react-native-tailwind';
74
+ import { View } from 'react-native';
75
+
76
+ const styles = tw\`m-4\`;
77
+ `;
78
+
79
+ const output = transform(input);
80
+
81
+ // Find the position of imports and StyleSheet.create
82
+ const viewImportPos = output.indexOf('require("react-native")');
83
+ const styleSheetCreatePos = output.indexOf("_twStyles");
84
+
85
+ // StyleSheet.create should come after imports
86
+ expect(styleSheetCreatePos).toBeGreaterThan(viewImportPos);
87
+ });
88
+
89
+ it("should handle tw in object literals", () => {
90
+ const input = `
91
+ import { tw } from '@mgcrea/react-native-tailwind';
92
+
93
+ const sizeVariants = {
94
+ sm: {
95
+ container: tw\`h-9 px-3\`,
96
+ text: tw\`text-sm\`,
97
+ },
98
+ };
99
+ `;
100
+
101
+ const output = transform(input);
102
+
103
+ // Should define _twStyles before object literal
104
+ const twStylesPos = output.indexOf("_twStyles");
105
+ const sizeVariantsPos = output.indexOf("sizeVariants");
106
+
107
+ expect(twStylesPos).toBeGreaterThan(0);
108
+ expect(twStylesPos).toBeLessThan(sizeVariantsPos);
109
+
110
+ // Should have both styles
111
+ expect(output).toContain("_h_9_px_3");
112
+ expect(output).toContain("_text_sm");
113
+ });
114
+
115
+ it("should handle empty tw template literal", () => {
116
+ const input = `
117
+ import { tw } from '@mgcrea/react-native-tailwind';
118
+ const styles = tw\`\`;
119
+ `;
120
+
121
+ const output = transform(input);
122
+
123
+ // Should replace with empty style object
124
+ expect(output).toContain("style:");
125
+ expect(output).toContain("{}");
126
+ });
127
+
128
+ it("should preserve other imports from the same package", () => {
129
+ const input = `
130
+ import { tw, TwStyle, COLORS } from '@mgcrea/react-native-tailwind';
131
+ const styles = tw\`m-4\`;
132
+ `;
133
+
134
+ const output = transform(input);
135
+
136
+ // Should remove tw but keep other imports
137
+ expect(output).not.toContain('"tw"');
138
+ expect(output).toContain("TwStyle");
139
+ expect(output).toContain("COLORS");
140
+ });
141
+
142
+ it("should handle renamed tw import", () => {
143
+ const input = `
144
+ import { tw as customTw } from '@mgcrea/react-native-tailwind';
145
+ const styles = customTw\`m-4 p-2\`;
146
+ `;
147
+
148
+ const output = transform(input);
149
+
150
+ // Should still transform the renamed import
151
+ expect(output).toContain("_twStyles");
152
+ expect(output).toContain("_m_4_p_2");
153
+ expect(output).not.toContain("customTw");
154
+ });
155
+
156
+ it("should handle multiple tw calls", () => {
157
+ const input = `
158
+ import { tw } from '@mgcrea/react-native-tailwind';
159
+ const style1 = tw\`bg-red-500\`;
160
+ const style2 = tw\`bg-blue-500\`;
161
+ const style3 = tw\`bg-green-500\`;
162
+ `;
163
+
164
+ const output = transform(input);
165
+
166
+ // Should have all three styles in StyleSheet
167
+ expect(output).toContain("_bg_red_500");
168
+ expect(output).toContain("_bg_blue_500");
169
+ expect(output).toContain("_bg_green_500");
170
+
171
+ // Should have StyleSheet.create with all styles
172
+ expect(output).toContain("StyleSheet.create");
173
+ });
174
+
175
+ it("should use custom stylesIdentifier option", () => {
176
+ const input = `
177
+ import { tw } from '@mgcrea/react-native-tailwind';
178
+ const styles = tw\`m-4\`;
179
+ `;
180
+
181
+ const output = transform(input, { stylesIdentifier: "myStyles" });
182
+
183
+ // Should use custom identifier
184
+ expect(output).toContain("myStyles");
185
+ expect(output).toContain("myStyles._m_4");
186
+ expect(output).not.toContain("_twStyles");
187
+ });
188
+ });
189
+
190
+ describe("Babel plugin - twStyle function transformation", () => {
191
+ it("should transform twStyle function call", () => {
192
+ const input = `
193
+ import { twStyle } from '@mgcrea/react-native-tailwind';
194
+ const styles = twStyle('bg-blue-500 m-4');
195
+ `;
196
+
197
+ const output = transform(input);
198
+
199
+ // Should transform to object with style property
200
+ expect(output).toContain("style:");
201
+ expect(output).toContain("_twStyles._bg_blue_500_m_4");
202
+ });
203
+
204
+ it("should transform twStyle with modifiers", () => {
205
+ const input = `
206
+ import { twStyle } from '@mgcrea/react-native-tailwind';
207
+ const styles = twStyle('bg-blue-500 active:bg-blue-700');
208
+ `;
209
+
210
+ const output = transform(input);
211
+
212
+ expect(output).toContain("style:");
213
+ expect(output).toContain("activeStyle:");
214
+ });
215
+
216
+ it("should handle empty twStyle call", () => {
217
+ const input = `
218
+ import { twStyle } from '@mgcrea/react-native-tailwind';
219
+ const styles = twStyle('');
220
+ `;
221
+
222
+ const output = transform(input);
223
+
224
+ // Should replace with undefined
225
+ expect(output).toContain("undefined");
226
+ });
227
+ });
228
+
229
+ // Note: JSX tests require @babel/preset-react
230
+ describe("Babel plugin - className transformation (existing behavior)", () => {
231
+ it("should still transform className props", () => {
232
+ const input = `
233
+ import { View } from 'react-native';
234
+ export function Component() {
235
+ return <View className="m-4 p-2 bg-blue-500" />;
236
+ }
237
+ `;
238
+
239
+ const output = transform(input, undefined, true); // Enable JSX
240
+
241
+ // Should have StyleSheet
242
+ expect(output).toContain("StyleSheet.create");
243
+ expect(output).toContain("_twStyles");
244
+
245
+ // Should replace className with style
246
+ expect(output).not.toContain("className");
247
+ expect(output).toContain("style:");
248
+ });
249
+
250
+ it("should work with both tw and className in same file", () => {
251
+ const input = `
252
+ import { tw } from '@mgcrea/react-native-tailwind';
253
+ import { View } from 'react-native';
254
+
255
+ const styles = tw\`bg-red-500\`;
256
+
257
+ export function Component() {
258
+ return <View className="m-4 p-2" />;
259
+ }
260
+ `;
261
+
262
+ const output = transform(input, undefined, true); // Enable JSX
263
+
264
+ // Should have both styles in StyleSheet
265
+ expect(output).toContain("_bg_red_500");
266
+ expect(output).toContain("_m_4_p_2");
267
+ });
268
+ });