@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
@@ -1910,3 +1910,473 @@ describe("Babel plugin - color scheme modifiers in tw/twStyle", () => {
1910
1910
  expect(output).toContain("_twStyles._ios_p_6");
1911
1911
  });
1912
1912
  });
1913
+
1914
+ describe("Babel plugin - window dimensions (w-screen/h-screen)", () => {
1915
+ it("should inject useWindowDimensions hook for w-screen in function component", () => {
1916
+ const input = `
1917
+ import React from 'react';
1918
+ import { View } from 'react-native';
1919
+
1920
+ export function MyComponent() {
1921
+ return <View className="w-screen bg-white" />;
1922
+ }
1923
+ `;
1924
+
1925
+ const output = transform(input, undefined, true);
1926
+
1927
+ // Should import useWindowDimensions
1928
+ expect(output).toContain("useWindowDimensions");
1929
+ expect(output).toMatch(/import.*useWindowDimensions.*from ['"]react-native['"]/);
1930
+
1931
+ // Should inject hook call
1932
+ expect(output).toContain("_twDimensions");
1933
+ expect(output).toContain("useWindowDimensions()");
1934
+
1935
+ // Should generate inline style with width
1936
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
1937
+
1938
+ // Should have StyleSheet for static styles
1939
+ expect(output).toContain("_twStyles");
1940
+ expect(output).toContain("backgroundColor:");
1941
+ });
1942
+
1943
+ it("should inject useWindowDimensions hook for h-screen in function component", () => {
1944
+ const input = `
1945
+ import React from 'react';
1946
+ import { View } from 'react-native';
1947
+
1948
+ export function MyComponent() {
1949
+ return <View className="h-screen bg-blue-500" />;
1950
+ }
1951
+ `;
1952
+
1953
+ const output = transform(input, undefined, true);
1954
+
1955
+ // Should import useWindowDimensions
1956
+ expect(output).toContain("useWindowDimensions");
1957
+
1958
+ // Should inject hook call
1959
+ expect(output).toContain("_twDimensions");
1960
+ expect(output).toContain("useWindowDimensions()");
1961
+
1962
+ // Should generate inline style with height
1963
+ expect(output).toMatch(/height:\s*_twDimensions\.height/);
1964
+ });
1965
+
1966
+ it("should handle both w-screen and h-screen together", () => {
1967
+ const input = `
1968
+ import React from 'react';
1969
+ import { View } from 'react-native';
1970
+
1971
+ function FullScreenView() {
1972
+ return <View className="w-screen h-screen bg-gray-100" />;
1973
+ }
1974
+ `;
1975
+
1976
+ const output = transform(input, undefined, true);
1977
+
1978
+ // Should inject hook
1979
+ expect(output).toContain("useWindowDimensions()");
1980
+
1981
+ // Should generate inline style with both dimensions
1982
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
1983
+ expect(output).toMatch(/height:\s*_twDimensions\.height/);
1984
+
1985
+ // Should have StyleSheet for static styles
1986
+ expect(output).toContain("backgroundColor:");
1987
+ });
1988
+
1989
+ it("should handle w-screen/h-screen with arrow function component", () => {
1990
+ const input = `
1991
+ import React from 'react';
1992
+ import { View } from 'react-native';
1993
+
1994
+ const MyComponent = () => {
1995
+ return <View className="w-screen" />;
1996
+ };
1997
+ `;
1998
+
1999
+ const output = transform(input, undefined, true);
2000
+
2001
+ // Should inject hook
2002
+ expect(output).toContain("useWindowDimensions()");
2003
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
2004
+ });
2005
+
2006
+ it("should handle concise arrow function and inject hook", () => {
2007
+ const input = `
2008
+ import React from 'react';
2009
+ import { View } from 'react-native';
2010
+
2011
+ const MyComponent = () => <View className="w-screen" />;
2012
+ `;
2013
+
2014
+ const output = transform(input, undefined, true);
2015
+
2016
+ // Should convert concise arrow to block statement and inject hook
2017
+ expect(output).toContain("useWindowDimensions()");
2018
+ expect(output).toContain("return");
2019
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
2020
+ });
2021
+
2022
+ it("should merge useWindowDimensions with existing react-native import", () => {
2023
+ const input = `
2024
+ import React from 'react';
2025
+ import { View, Text } from 'react-native';
2026
+
2027
+ function MyComponent() {
2028
+ return <View className="w-screen" />;
2029
+ }
2030
+ `;
2031
+
2032
+ const output = transform(input, undefined, true);
2033
+
2034
+ // Should merge useWindowDimensions into existing import (not create separate import)
2035
+ // The key is that useWindowDimensions should be imported
2036
+ expect(output).toContain("useWindowDimensions");
2037
+ });
2038
+
2039
+ it("should warn when w-screen/h-screen used outside function component", () => {
2040
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
2041
+
2042
+ const input = `
2043
+ import React from 'react';
2044
+ import { View } from 'react-native';
2045
+
2046
+ class MyComponent extends React.Component {
2047
+ render() {
2048
+ return <View className="w-screen" />;
2049
+ }
2050
+ }
2051
+ `;
2052
+
2053
+ transform(input, undefined, true);
2054
+
2055
+ // Should warn about usage in class component
2056
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
2057
+ expect.stringContaining("w-screen/h-screen classes require a function component scope"),
2058
+ );
2059
+
2060
+ consoleWarnSpy.mockRestore();
2061
+ });
2062
+
2063
+ it("should combine w-screen with other static classes", () => {
2064
+ const input = `
2065
+ import React from 'react';
2066
+ import { View } from 'react-native';
2067
+
2068
+ function MyComponent() {
2069
+ return <View className="w-screen p-4 bg-white rounded-lg" />;
2070
+ }
2071
+ `;
2072
+
2073
+ const output = transform(input, undefined, true);
2074
+
2075
+ // Should have static styles in StyleSheet
2076
+ expect(output).toContain("padding:");
2077
+ expect(output).toContain("backgroundColor:");
2078
+ expect(output).toContain("borderRadius:");
2079
+
2080
+ // Should have runtime dimension
2081
+ expect(output).toMatch(/width:\s*_twDimensions\.width/);
2082
+
2083
+ // Should combine as array
2084
+ expect(output).toContain("_twStyles");
2085
+ expect(output).toContain("_twDimensions");
2086
+ });
2087
+
2088
+ it("should inject hook only once even with multiple w-screen/h-screen uses", () => {
2089
+ const input = `
2090
+ import React from 'react';
2091
+ import { View } from 'react-native';
2092
+
2093
+ function MyComponent() {
2094
+ return (
2095
+ <>
2096
+ <View className="w-screen" />
2097
+ <View className="h-screen" />
2098
+ <View className="w-screen h-screen" />
2099
+ </>
2100
+ );
2101
+ }
2102
+ `;
2103
+
2104
+ const output = transform(input, undefined, true);
2105
+
2106
+ // Count occurrences of hook injection (should be exactly 1)
2107
+ const hookMatches = output.match(/_twDimensions\s*=\s*useWindowDimensions\(\)/g) ?? [];
2108
+ expect(hookMatches.length).toBe(1);
2109
+ });
2110
+
2111
+ it("should handle aliased useWindowDimensions import", () => {
2112
+ const input = `
2113
+ import React from 'react';
2114
+ import { View, useWindowDimensions as useDims } from 'react-native';
2115
+
2116
+ function MyComponent() {
2117
+ const dims = useDims();
2118
+ return <View className="w-screen" />;
2119
+ }
2120
+ `;
2121
+
2122
+ const output = transform(input, undefined, true);
2123
+
2124
+ // Should use the aliased name
2125
+ expect(output).toContain("useDims()");
2126
+ // Should still generate _twDimensions variable for our use
2127
+ expect(output).toContain("_twDimensions");
2128
+ });
2129
+ });
2130
+
2131
+ describe("Babel plugin - w-screen/h-screen error handling", () => {
2132
+ it("should error when w-screen is combined with dark: modifier", () => {
2133
+ const input = `
2134
+ import { View } from 'react-native';
2135
+ export function MyComponent() {
2136
+ return <View className="dark:w-screen bg-white" />;
2137
+ }
2138
+ `;
2139
+
2140
+ expect(() => transform(input, undefined, true)).toThrow(
2141
+ /w-screen and h-screen cannot be combined with color scheme modifiers/,
2142
+ );
2143
+ });
2144
+
2145
+ it("should error when h-screen is combined with light: modifier", () => {
2146
+ const input = `
2147
+ import { View } from 'react-native';
2148
+ export function MyComponent() {
2149
+ return <View className="light:h-screen p-4" />;
2150
+ }
2151
+ `;
2152
+
2153
+ expect(() => transform(input, undefined, true)).toThrow(
2154
+ /w-screen and h-screen cannot be combined with color scheme modifiers/,
2155
+ );
2156
+ });
2157
+
2158
+ it("should error when w-screen is combined with active: modifier", () => {
2159
+ const input = `
2160
+ import { Pressable } from 'react-native';
2161
+ export function MyComponent() {
2162
+ return <Pressable className="active:w-screen bg-blue-500" />;
2163
+ }
2164
+ `;
2165
+
2166
+ expect(() => transform(input, undefined, true)).toThrow(
2167
+ /w-screen and h-screen cannot be combined with state modifiers/,
2168
+ );
2169
+ });
2170
+
2171
+ it("should error when h-screen is combined with hover: modifier", () => {
2172
+ const input = `
2173
+ import { Pressable } from 'react-native';
2174
+ export function MyComponent() {
2175
+ return <Pressable className="hover:h-screen p-4" />;
2176
+ }
2177
+ `;
2178
+
2179
+ expect(() => transform(input, undefined, true)).toThrow(
2180
+ /w-screen and h-screen cannot be combined with state modifiers/,
2181
+ );
2182
+ });
2183
+
2184
+ it("should error when w-screen is combined with ios: modifier", () => {
2185
+ const input = `
2186
+ import { View } from 'react-native';
2187
+ export function MyComponent() {
2188
+ return <View className="ios:w-screen bg-white" />;
2189
+ }
2190
+ `;
2191
+
2192
+ expect(() => transform(input, undefined, true)).toThrow(
2193
+ /w-screen and h-screen cannot be combined with.*platform modifiers/,
2194
+ );
2195
+ });
2196
+
2197
+ it("should error when h-screen is combined with android: modifier", () => {
2198
+ const input = `
2199
+ import { View } from 'react-native';
2200
+ export function MyComponent() {
2201
+ return <View className="android:h-screen p-4" />;
2202
+ }
2203
+ `;
2204
+
2205
+ expect(() => transform(input, undefined, true)).toThrow(
2206
+ /w-screen and h-screen cannot be combined with.*platform modifiers/,
2207
+ );
2208
+ });
2209
+
2210
+ it("should error when w-screen is used in tw`` call", () => {
2211
+ const input = `
2212
+ import { tw } from '@mgcrea/react-native-tailwind';
2213
+ export function MyComponent() {
2214
+ const styles = tw\`w-screen bg-white\`;
2215
+ return <View style={styles.style} />;
2216
+ }
2217
+ `;
2218
+
2219
+ expect(() => transform(input, undefined, true)).toThrow(
2220
+ /w-screen and h-screen are not supported in tw.*or twStyle/,
2221
+ );
2222
+ });
2223
+
2224
+ it("should error when h-screen is used in twStyle() call", () => {
2225
+ const input = `
2226
+ import { twStyle } from '@mgcrea/react-native-tailwind';
2227
+ export function MyComponent() {
2228
+ const styles = twStyle('h-screen p-4');
2229
+ return <View style={styles.style} />;
2230
+ }
2231
+ `;
2232
+
2233
+ expect(() => transform(input, undefined, true)).toThrow(
2234
+ /w-screen and h-screen are not supported in tw.*or twStyle/,
2235
+ );
2236
+ });
2237
+
2238
+ it("should properly merge w-screen with Pressable style function", () => {
2239
+ const input = `
2240
+ import { Pressable } from 'react-native';
2241
+ export function MyComponent() {
2242
+ return (
2243
+ <Pressable
2244
+ className="w-screen bg-white"
2245
+ style={({ pressed }) => [pressed && { opacity: 0.5 }]}
2246
+ />
2247
+ );
2248
+ }
2249
+ `;
2250
+
2251
+ const output = transform(input, undefined, true);
2252
+
2253
+ // Should inject useWindowDimensions hook
2254
+ expect(output).toContain("useWindowDimensions");
2255
+ expect(output).toContain("_twDimensions");
2256
+
2257
+ // Should wrap the style function properly
2258
+ expect(output).toContain("_state");
2259
+ // Should contain the runtime dimension access
2260
+ expect(output).toContain("_twDimensions.width");
2261
+ // Should call the existing function
2262
+ expect(output).toContain("pressed");
2263
+ });
2264
+
2265
+ it("should properly merge h-screen with arrow function style", () => {
2266
+ const input = `
2267
+ import { Pressable } from 'react-native';
2268
+ export function MyComponent() {
2269
+ return (
2270
+ <Pressable
2271
+ className="h-screen p-4"
2272
+ style={(state) => state.pressed ? { opacity: 0.8 } : null}
2273
+ />
2274
+ );
2275
+ }
2276
+ `;
2277
+
2278
+ const output = transform(input, undefined, true);
2279
+
2280
+ // Should inject useWindowDimensions hook
2281
+ expect(output).toContain("useWindowDimensions");
2282
+ expect(output).toContain("_twDimensions");
2283
+
2284
+ // Should wrap the style function
2285
+ expect(output).toContain("_state");
2286
+ // Should contain the runtime dimension access
2287
+ expect(output).toContain("_twDimensions.height");
2288
+ // Should call the original function
2289
+ expect(output).toContain("state.pressed");
2290
+ });
2291
+
2292
+ it("should error when w-screen is in base classes with dark: modifier", () => {
2293
+ const input = `
2294
+ import { View } from 'react-native';
2295
+ export function MyComponent() {
2296
+ return <View className="w-screen dark:bg-black" />;
2297
+ }
2298
+ `;
2299
+
2300
+ expect(() => transform(input, undefined, true)).toThrow(
2301
+ /w-screen and h-screen cannot be combined with modifiers/,
2302
+ );
2303
+ });
2304
+
2305
+ it("should error when w-screen is in base classes with ios: modifier", () => {
2306
+ const input = `
2307
+ import { View } from 'react-native';
2308
+ export function MyComponent() {
2309
+ return <View className="w-screen ios:p-4" />;
2310
+ }
2311
+ `;
2312
+
2313
+ expect(() => transform(input, undefined, true)).toThrow(
2314
+ /w-screen and h-screen cannot be combined with modifiers/,
2315
+ );
2316
+ });
2317
+
2318
+ it("should error when h-screen is in base classes with active: modifier", () => {
2319
+ const input = `
2320
+ import { Pressable } from 'react-native';
2321
+ export function MyComponent() {
2322
+ return <Pressable className="h-screen active:bg-blue-500" />;
2323
+ }
2324
+ `;
2325
+
2326
+ expect(() => transform(input, undefined, true)).toThrow(
2327
+ /w-screen and h-screen cannot be combined with.*modifiers/,
2328
+ );
2329
+ });
2330
+
2331
+ it("should error when w-screen is in base classes with multiple modifiers", () => {
2332
+ const input = `
2333
+ import { View } from 'react-native';
2334
+ export function MyComponent() {
2335
+ return <View className="w-screen bg-white dark:bg-black ios:p-4" />;
2336
+ }
2337
+ `;
2338
+
2339
+ expect(() => transform(input, undefined, true)).toThrow(
2340
+ /w-screen and h-screen cannot be combined with modifiers/,
2341
+ );
2342
+ });
2343
+
2344
+ it("should error when h-screen is in base classes with light: modifier", () => {
2345
+ const input = `
2346
+ import { View } from 'react-native';
2347
+ export function MyComponent() {
2348
+ return <View className="h-screen light:bg-white" />;
2349
+ }
2350
+ `;
2351
+
2352
+ expect(() => transform(input, undefined, true)).toThrow(
2353
+ /w-screen and h-screen cannot be combined with modifiers/,
2354
+ );
2355
+ });
2356
+
2357
+ it("should error when w-screen is in base classes with android: modifier", () => {
2358
+ const input = `
2359
+ import { View } from 'react-native';
2360
+ export function MyComponent() {
2361
+ return <View className="w-screen android:p-4" />;
2362
+ }
2363
+ `;
2364
+
2365
+ expect(() => transform(input, undefined, true)).toThrow(
2366
+ /w-screen and h-screen cannot be combined with modifiers/,
2367
+ );
2368
+ });
2369
+
2370
+ it("should error when h-screen is in base classes with web: modifier", () => {
2371
+ const input = `
2372
+ import { View } from 'react-native';
2373
+ export function MyComponent() {
2374
+ return <View className="h-screen web:shadow-lg" />;
2375
+ }
2376
+ `;
2377
+
2378
+ expect(() => transform(input, undefined, true)).toThrow(
2379
+ /w-screen and h-screen cannot be combined with modifiers/,
2380
+ );
2381
+ });
2382
+ });