@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
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Compile-time stub for tw/twStyle functions
3
+ *
4
+ * These functions are transformed by the Babel plugin at compile-time.
5
+ * If you see these errors at runtime, it means the Babel plugin is not configured correctly.
6
+ *
7
+ * For runtime parsing, use: import { tw } from '@mgcrea/react-native-tailwind/runtime'
8
+ */
9
+
10
+ import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
11
+
12
+ /**
13
+ * Union type for all React Native style types
14
+ */
15
+ export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
16
+
17
+ /**
18
+ * Return type for tw/twStyle functions with separate style properties for modifiers
19
+ */
20
+ export type TwStyle<T extends NativeStyle = NativeStyle> = {
21
+ style: T;
22
+ activeStyle?: T;
23
+ focusStyle?: T;
24
+ disabledStyle?: T;
25
+ };
26
+
27
+ /**
28
+ * Compile-time Tailwind CSS template tag (transformed by Babel plugin)
29
+ *
30
+ * This function is replaced at compile-time by the Babel plugin.
31
+ * The import is removed and calls are transformed to inline style objects.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * import { tw } from '@mgcrea/react-native-tailwind';
36
+ *
37
+ * const styles = tw`bg-blue-500 active:bg-blue-700`;
38
+ * // Transformed to:
39
+ * // const styles = {
40
+ * // style: styles._bg_blue_500,
41
+ * // activeStyle: styles._active_bg_blue_700
42
+ * // };
43
+ * ```
44
+ */
45
+ export function tw<T extends NativeStyle = NativeStyle>(
46
+ _strings: TemplateStringsArray,
47
+ ..._values: unknown[]
48
+ ): TwStyle<T> {
49
+ throw new Error(
50
+ "tw() must be transformed by the Babel plugin. " +
51
+ "Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. " +
52
+ "For runtime parsing, use: import { tw } from '@mgcrea/react-native-tailwind/runtime'",
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Compile-time Tailwind CSS string function (transformed by Babel plugin)
58
+ *
59
+ * This function is replaced at compile-time by the Babel plugin.
60
+ * The import is removed and calls are transformed to inline style objects.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * import { twStyle } from '@mgcrea/react-native-tailwind';
65
+ *
66
+ * const styles = twStyle('bg-blue-500 active:bg-blue-700');
67
+ * // Transformed to:
68
+ * // const styles = {
69
+ * // style: styles._bg_blue_500,
70
+ * // activeStyle: styles._active_bg_blue_700
71
+ * // };
72
+ * ```
73
+ */
74
+ export function twStyle<T extends NativeStyle = NativeStyle>(_className: string): TwStyle<T> | undefined {
75
+ throw new Error(
76
+ "twStyle() must be transformed by the Babel plugin. " +
77
+ "Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. " +
78
+ "For runtime parsing, use: import { twStyle } from '@mgcrea/react-native-tailwind/runtime'",
79
+ );
80
+ }
@@ -0,0 +1,361 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { flattenColors } from "./flattenColors";
3
+
4
+ describe("flattenColors", () => {
5
+ it("should handle flat color objects", () => {
6
+ const colors = {
7
+ red: "#ff0000",
8
+ blue: "#0000ff",
9
+ green: "#00ff00",
10
+ };
11
+
12
+ expect(flattenColors(colors)).toEqual({
13
+ red: "#ff0000",
14
+ blue: "#0000ff",
15
+ green: "#00ff00",
16
+ });
17
+ });
18
+
19
+ it("should flatten single-level nested objects", () => {
20
+ const colors = {
21
+ brand: {
22
+ primary: "#ff6b6b",
23
+ secondary: "#4ecdc4",
24
+ },
25
+ };
26
+
27
+ expect(flattenColors(colors)).toEqual({
28
+ "brand-primary": "#ff6b6b",
29
+ "brand-secondary": "#4ecdc4",
30
+ });
31
+ });
32
+
33
+ it("should flatten multi-level nested objects", () => {
34
+ const colors = {
35
+ brand: {
36
+ light: {
37
+ primary: "#ffcccc",
38
+ secondary: "#ccffff",
39
+ },
40
+ dark: {
41
+ primary: "#990000",
42
+ secondary: "#006666",
43
+ },
44
+ },
45
+ };
46
+
47
+ expect(flattenColors(colors)).toEqual({
48
+ "brand-light-primary": "#ffcccc",
49
+ "brand-light-secondary": "#ccffff",
50
+ "brand-dark-primary": "#990000",
51
+ "brand-dark-secondary": "#006666",
52
+ });
53
+ });
54
+
55
+ it("should handle mixed flat and nested objects", () => {
56
+ const colors = {
57
+ white: "#ffffff",
58
+ black: "#000000",
59
+ brand: {
60
+ primary: "#ff6b6b",
61
+ secondary: "#4ecdc4",
62
+ },
63
+ accent: "#ffe66d",
64
+ };
65
+
66
+ expect(flattenColors(colors)).toEqual({
67
+ white: "#ffffff",
68
+ black: "#000000",
69
+ "brand-primary": "#ff6b6b",
70
+ "brand-secondary": "#4ecdc4",
71
+ accent: "#ffe66d",
72
+ });
73
+ });
74
+
75
+ it("should handle Tailwind-style color scale objects", () => {
76
+ const colors = {
77
+ gray: {
78
+ "50": "#f9fafb",
79
+ "100": "#f3f4f6",
80
+ "200": "#e5e7eb",
81
+ "500": "#6b7280",
82
+ "900": "#111827",
83
+ },
84
+ };
85
+
86
+ expect(flattenColors(colors)).toEqual({
87
+ "gray-50": "#f9fafb",
88
+ "gray-100": "#f3f4f6",
89
+ "gray-200": "#e5e7eb",
90
+ "gray-500": "#6b7280",
91
+ "gray-900": "#111827",
92
+ });
93
+ });
94
+
95
+ it("should handle empty object", () => {
96
+ expect(flattenColors({})).toEqual({});
97
+ });
98
+
99
+ it("should handle single color", () => {
100
+ const colors = {
101
+ primary: "#ff0000",
102
+ };
103
+
104
+ expect(flattenColors(colors)).toEqual({
105
+ primary: "#ff0000",
106
+ });
107
+ });
108
+
109
+ it("should handle deeply nested objects (3+ levels)", () => {
110
+ const colors = {
111
+ theme: {
112
+ light: {
113
+ brand: {
114
+ primary: "#ff6b6b",
115
+ secondary: "#4ecdc4",
116
+ },
117
+ },
118
+ dark: {
119
+ brand: {
120
+ primary: "#990000",
121
+ secondary: "#006666",
122
+ },
123
+ },
124
+ },
125
+ };
126
+
127
+ expect(flattenColors(colors)).toEqual({
128
+ "theme-light-brand-primary": "#ff6b6b",
129
+ "theme-light-brand-secondary": "#4ecdc4",
130
+ "theme-dark-brand-primary": "#990000",
131
+ "theme-dark-brand-secondary": "#006666",
132
+ });
133
+ });
134
+
135
+ it("should handle numeric keys", () => {
136
+ const colors = {
137
+ blue: {
138
+ "100": "#dbeafe",
139
+ "500": "#3b82f6",
140
+ "900": "#1e3a8a",
141
+ },
142
+ };
143
+
144
+ expect(flattenColors(colors)).toEqual({
145
+ "blue-100": "#dbeafe",
146
+ "blue-500": "#3b82f6",
147
+ "blue-900": "#1e3a8a",
148
+ });
149
+ });
150
+
151
+ it("should handle keys with hyphens", () => {
152
+ const colors = {
153
+ "brand-primary": "#ff0000",
154
+ "brand-secondary": {
155
+ light: "#00ff00",
156
+ dark: "#006600",
157
+ },
158
+ };
159
+
160
+ expect(flattenColors(colors)).toEqual({
161
+ "brand-primary": "#ff0000",
162
+ "brand-secondary-light": "#00ff00",
163
+ "brand-secondary-dark": "#006600",
164
+ });
165
+ });
166
+
167
+ it("should handle uppercase and lowercase hex values", () => {
168
+ const colors = {
169
+ red: "#FF0000",
170
+ blue: "#0000ff",
171
+ green: "#00Ff00",
172
+ };
173
+
174
+ expect(flattenColors(colors)).toEqual({
175
+ red: "#FF0000",
176
+ blue: "#0000ff",
177
+ green: "#00Ff00",
178
+ });
179
+ });
180
+
181
+ it("should handle 3-digit hex values", () => {
182
+ const colors = {
183
+ red: "#f00",
184
+ blue: "#00f",
185
+ green: "#0f0",
186
+ };
187
+
188
+ expect(flattenColors(colors)).toEqual({
189
+ red: "#f00",
190
+ blue: "#00f",
191
+ green: "#0f0",
192
+ });
193
+ });
194
+
195
+ it("should handle 8-digit hex values (with alpha)", () => {
196
+ const colors = {
197
+ "red-50": "#ff000080",
198
+ "blue-50": "#0000ff80",
199
+ };
200
+
201
+ expect(flattenColors(colors)).toEqual({
202
+ "red-50": "#ff000080",
203
+ "blue-50": "#0000ff80",
204
+ });
205
+ });
206
+
207
+ it("should handle complex real-world Tailwind config", () => {
208
+ const colors = {
209
+ transparent: "transparent",
210
+ current: "currentColor",
211
+ white: "#ffffff",
212
+ black: "#000000",
213
+ gray: {
214
+ "50": "#f9fafb",
215
+ "100": "#f3f4f6",
216
+ "500": "#6b7280",
217
+ "900": "#111827",
218
+ },
219
+ brand: {
220
+ primary: "#ff6b6b",
221
+ secondary: "#4ecdc4",
222
+ accent: {
223
+ light: "#ffe66d",
224
+ dark: "#ffb900",
225
+ },
226
+ },
227
+ };
228
+
229
+ expect(flattenColors(colors)).toEqual({
230
+ transparent: "transparent",
231
+ current: "currentColor",
232
+ white: "#ffffff",
233
+ black: "#000000",
234
+ "gray-50": "#f9fafb",
235
+ "gray-100": "#f3f4f6",
236
+ "gray-500": "#6b7280",
237
+ "gray-900": "#111827",
238
+ "brand-primary": "#ff6b6b",
239
+ "brand-secondary": "#4ecdc4",
240
+ "brand-accent-light": "#ffe66d",
241
+ "brand-accent-dark": "#ffb900",
242
+ });
243
+ });
244
+
245
+ it("should not mutate input object", () => {
246
+ const colors = {
247
+ brand: {
248
+ primary: "#ff6b6b",
249
+ secondary: "#4ecdc4",
250
+ },
251
+ };
252
+
253
+ const original = JSON.parse(JSON.stringify(colors));
254
+ flattenColors(colors);
255
+
256
+ expect(colors).toEqual(original);
257
+ });
258
+
259
+ it("should handle undefined values gracefully", () => {
260
+ const colors = {
261
+ red: "#ff0000",
262
+ blue: undefined as unknown as string, // Testing runtime edge case
263
+ green: "#00ff00",
264
+ };
265
+
266
+ // undefined values are skipped (not objects or strings)
267
+ expect(flattenColors(colors)).toEqual({
268
+ red: "#ff0000",
269
+ green: "#00ff00",
270
+ });
271
+ });
272
+
273
+ it("should handle special color keywords", () => {
274
+ const colors = {
275
+ transparent: "transparent",
276
+ current: "currentColor",
277
+ inherit: "inherit",
278
+ };
279
+
280
+ expect(flattenColors(colors)).toEqual({
281
+ transparent: "transparent",
282
+ current: "currentColor",
283
+ inherit: "inherit",
284
+ });
285
+ });
286
+
287
+ it("should handle RGB/RGBA color values", () => {
288
+ const colors = {
289
+ primary: "rgb(255, 0, 0)",
290
+ secondary: "rgba(0, 255, 0, 0.5)",
291
+ };
292
+
293
+ expect(flattenColors(colors)).toEqual({
294
+ primary: "rgb(255, 0, 0)",
295
+ secondary: "rgba(0, 255, 0, 0.5)",
296
+ });
297
+ });
298
+
299
+ it("should handle very deeply nested structures (stress test)", () => {
300
+ const colors = {
301
+ level1: {
302
+ level2: {
303
+ level3: {
304
+ level4: {
305
+ level5: "#ff0000",
306
+ },
307
+ },
308
+ },
309
+ },
310
+ };
311
+
312
+ expect(flattenColors(colors)).toEqual({
313
+ "level1-level2-level3-level4-level5": "#ff0000",
314
+ });
315
+ });
316
+
317
+ it("should handle camelCase keys", () => {
318
+ const colors = {
319
+ brandPrimary: "#ff0000",
320
+ accentColor: {
321
+ lightShade: "#ffcccc",
322
+ darkShade: "#cc0000",
323
+ },
324
+ };
325
+
326
+ expect(flattenColors(colors)).toEqual({
327
+ brandPrimary: "#ff0000",
328
+ "accentColor-lightShade": "#ffcccc",
329
+ "accentColor-darkShade": "#cc0000",
330
+ });
331
+ });
332
+
333
+ it("should produce consistent output", () => {
334
+ const colors = {
335
+ brand: {
336
+ primary: "#ff6b6b",
337
+ secondary: "#4ecdc4",
338
+ },
339
+ };
340
+
341
+ const result1 = flattenColors(colors);
342
+ const result2 = flattenColors(colors);
343
+ const result3 = flattenColors(colors);
344
+
345
+ expect(result1).toEqual(result2);
346
+ expect(result2).toEqual(result3);
347
+ });
348
+
349
+ it("should maintain key order (insertion order)", () => {
350
+ const colors = {
351
+ z: "#000001",
352
+ a: "#000002",
353
+ m: "#000003",
354
+ };
355
+
356
+ const flattened = flattenColors(colors);
357
+ const keys = Object.keys(flattened);
358
+
359
+ expect(keys).toEqual(["z", "a", "m"]);
360
+ });
361
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Type representing a nested color structure that can be arbitrarily deep
3
+ */
4
+ type NestedColors = {
5
+ [key: string]: string | NestedColors;
6
+ };
7
+
8
+ /**
9
+ * Flatten nested color objects into flat key-value map
10
+ * Example: { brand: { light: '#fff', dark: '#000' } } => { 'brand-light': '#fff', 'brand-dark': '#000' }
11
+ *
12
+ * @param colors - Nested color object where values can be strings or objects
13
+ * @param prefix - Optional prefix for nested keys (used for recursion)
14
+ * @returns Flattened color map with dash-separated keys
15
+ */
16
+ export function flattenColors(colors: NestedColors, prefix = ""): Record<string, string> {
17
+ const result: Record<string, string> = {};
18
+
19
+ for (const [key, value] of Object.entries(colors)) {
20
+ const newKey = prefix ? `${prefix}-${key}` : key;
21
+
22
+ if (typeof value === "string") {
23
+ result[newKey] = value;
24
+ } else if (typeof value === "object" && value !== null) {
25
+ // Recursively flatten nested objects
26
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
27
+ Object.assign(result, flattenColors(value as NestedColors, newKey));
28
+ }
29
+ }
30
+
31
+ return result;
32
+ }