@mgcrea/react-native-tailwind 0.6.1 → 0.8.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 (100) hide show
  1. package/README.md +406 -1
  2. package/dist/babel/config-loader.ts +1 -23
  3. package/dist/babel/index.cjs +516 -211
  4. package/dist/babel/index.d.ts +4 -15
  5. package/dist/babel/index.test.ts +481 -0
  6. package/dist/babel/index.ts +4 -861
  7. package/dist/babel/plugin.d.ts +42 -0
  8. package/dist/babel/plugin.test.ts +482 -0
  9. package/dist/babel/plugin.ts +491 -0
  10. package/dist/babel/utils/attributeMatchers.d.ts +23 -0
  11. package/dist/babel/utils/attributeMatchers.ts +71 -0
  12. package/dist/babel/utils/componentSupport.d.ts +18 -0
  13. package/dist/babel/utils/componentSupport.ts +68 -0
  14. package/dist/babel/utils/dynamicProcessing.d.ts +32 -0
  15. package/dist/babel/utils/dynamicProcessing.ts +223 -0
  16. package/dist/babel/utils/modifierProcessing.d.ts +26 -0
  17. package/dist/babel/utils/modifierProcessing.ts +118 -0
  18. package/dist/babel/utils/styleInjection.d.ts +15 -0
  19. package/dist/babel/utils/styleInjection.ts +80 -0
  20. package/dist/babel/utils/styleTransforms.d.ts +39 -0
  21. package/dist/babel/utils/styleTransforms.test.ts +349 -0
  22. package/dist/babel/utils/styleTransforms.ts +258 -0
  23. package/dist/babel/utils/twProcessing.d.ts +28 -0
  24. package/dist/babel/utils/twProcessing.ts +124 -0
  25. package/dist/components/TextInput.d.ts +171 -14
  26. package/dist/config/tailwind.d.ts +302 -0
  27. package/dist/config/tailwind.js +1 -0
  28. package/dist/index.d.ts +6 -2
  29. package/dist/index.js +1 -1
  30. package/dist/parser/__snapshots__/colors.test.js.snap +242 -90
  31. package/dist/parser/__snapshots__/transforms.test.js.snap +58 -0
  32. package/dist/parser/colors.js +1 -1
  33. package/dist/parser/index.d.ts +1 -0
  34. package/dist/parser/index.js +1 -1
  35. package/dist/parser/modifiers.d.ts +2 -2
  36. package/dist/parser/modifiers.js +1 -1
  37. package/dist/parser/placeholder.d.ts +36 -0
  38. package/dist/parser/placeholder.js +1 -0
  39. package/dist/parser/placeholder.test.js +1 -0
  40. package/dist/parser/typography.d.ts +1 -0
  41. package/dist/parser/typography.js +1 -1
  42. package/dist/parser/typography.test.js +1 -1
  43. package/dist/runtime.cjs +2 -0
  44. package/dist/runtime.cjs.map +7 -0
  45. package/dist/runtime.d.ts +126 -0
  46. package/dist/runtime.js +2 -0
  47. package/dist/runtime.js.map +7 -0
  48. package/dist/runtime.test.js +1 -0
  49. package/dist/stubs/tw.d.ts +47 -0
  50. package/dist/stubs/tw.js +1 -0
  51. package/dist/types/core.d.ts +40 -0
  52. package/dist/types/core.js +0 -0
  53. package/dist/types/index.d.ts +2 -0
  54. package/dist/types/index.js +1 -0
  55. package/dist/types/runtime.d.ts +15 -0
  56. package/dist/types/runtime.js +1 -0
  57. package/dist/types/util.d.ts +3 -0
  58. package/dist/types/util.js +0 -0
  59. package/dist/utils/flattenColors.d.ts +16 -0
  60. package/dist/utils/flattenColors.js +1 -0
  61. package/dist/utils/flattenColors.test.js +1 -0
  62. package/dist/utils/modifiers.d.ts +29 -0
  63. package/dist/utils/modifiers.js +1 -0
  64. package/dist/utils/modifiers.test.js +1 -0
  65. package/dist/utils/styleKey.test.js +1 -0
  66. package/package.json +15 -3
  67. package/src/babel/config-loader.ts +1 -23
  68. package/src/babel/index.ts +4 -861
  69. package/src/babel/plugin.test.ts +482 -0
  70. package/src/babel/plugin.ts +491 -0
  71. package/src/babel/utils/attributeMatchers.ts +71 -0
  72. package/src/babel/utils/componentSupport.ts +68 -0
  73. package/src/babel/utils/dynamicProcessing.ts +223 -0
  74. package/src/babel/utils/modifierProcessing.ts +118 -0
  75. package/src/babel/utils/styleInjection.ts +80 -0
  76. package/src/babel/utils/styleTransforms.test.ts +349 -0
  77. package/src/babel/utils/styleTransforms.ts +258 -0
  78. package/src/babel/utils/twProcessing.ts +124 -0
  79. package/src/components/TextInput.tsx +17 -14
  80. package/src/config/{palettes.ts → tailwind.ts} +2 -2
  81. package/src/index.ts +9 -1
  82. package/src/parser/colors.ts +9 -23
  83. package/src/parser/index.ts +1 -0
  84. package/src/parser/modifiers.ts +10 -4
  85. package/src/parser/placeholder.test.ts +105 -0
  86. package/src/parser/placeholder.ts +78 -0
  87. package/src/parser/typography.test.ts +11 -0
  88. package/src/parser/typography.ts +20 -2
  89. package/src/runtime.test.ts +325 -0
  90. package/src/runtime.ts +265 -0
  91. package/src/stubs/tw.ts +65 -0
  92. package/src/{types.ts → types/core.ts} +0 -4
  93. package/src/types/index.ts +2 -0
  94. package/src/types/runtime.ts +17 -0
  95. package/src/types/util.ts +1 -0
  96. package/src/utils/flattenColors.test.ts +361 -0
  97. package/src/utils/flattenColors.ts +32 -0
  98. package/src/utils/modifiers.test.ts +286 -0
  99. package/src/utils/modifiers.ts +63 -0
  100. package/src/utils/styleKey.test.ts +168 -0
@@ -0,0 +1,325 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { clearCache, getCacheStats, getCustomColors, setConfig, tw, twStyle } from "./runtime";
3
+
4
+ describe("runtime", () => {
5
+ beforeEach(() => {
6
+ clearCache();
7
+ setConfig({}); // Reset config
8
+ });
9
+
10
+ describe("tw template tag", () => {
11
+ it("should parse static classes", () => {
12
+ const result = tw`m-4 p-2 bg-blue-500`;
13
+ expect(result?.style).toEqual({
14
+ margin: 16,
15
+ padding: 8,
16
+ backgroundColor: "#2b7fff",
17
+ });
18
+ expect(result?.activeStyle).toBeUndefined();
19
+ expect(result?.disabledStyle).toBeUndefined();
20
+ });
21
+
22
+ it("should handle interpolated values", () => {
23
+ const isActive = true;
24
+ const result = tw`m-4 ${isActive && "bg-blue-500"}`;
25
+ expect(result?.style).toEqual({
26
+ margin: 16,
27
+ backgroundColor: "#2b7fff",
28
+ });
29
+ });
30
+
31
+ it("should handle conditional classes", () => {
32
+ const isLarge = true;
33
+ const result = tw`p-4 ${isLarge ? "text-xl" : "text-sm"}`;
34
+ expect(result?.style).toEqual({
35
+ padding: 16,
36
+ fontSize: 20,
37
+ });
38
+ });
39
+
40
+ it("should handle falsy values", () => {
41
+ const result = tw`m-4 ${false} ${null} ${undefined} p-2`;
42
+ expect(result?.style).toEqual({
43
+ margin: 16,
44
+ padding: 8,
45
+ });
46
+ });
47
+
48
+ it("should return empty style object for empty className", () => {
49
+ const result = tw``;
50
+ expect(result).toEqual({ style: {} });
51
+ });
52
+
53
+ it("should normalize whitespace", () => {
54
+ const result = tw`m-4 p-2 bg-blue-500`;
55
+ expect(result?.style).toEqual({
56
+ margin: 16,
57
+ padding: 8,
58
+ backgroundColor: "#2b7fff",
59
+ });
60
+ });
61
+ });
62
+
63
+ describe("twStyle function", () => {
64
+ it("should parse className string", () => {
65
+ const result = twStyle("m-4 p-2 bg-blue-500");
66
+ expect(result?.style).toEqual({
67
+ margin: 16,
68
+ padding: 8,
69
+ backgroundColor: "#2b7fff",
70
+ });
71
+ expect(result?.activeStyle).toBeUndefined();
72
+ });
73
+
74
+ it("should return undefined for empty string", () => {
75
+ const result = twStyle("");
76
+ expect(result).toBeUndefined();
77
+ });
78
+
79
+ it("should normalize whitespace", () => {
80
+ const result = twStyle("m-4 p-2 bg-blue-500");
81
+ expect(result?.style).toEqual({
82
+ margin: 16,
83
+ padding: 8,
84
+ backgroundColor: "#2b7fff",
85
+ });
86
+ });
87
+ });
88
+
89
+ describe("setConfig", () => {
90
+ it("should set custom colors", () => {
91
+ setConfig({
92
+ theme: {
93
+ extend: {
94
+ colors: {
95
+ primary: "#007AFF",
96
+ secondary: "#5856D6",
97
+ },
98
+ },
99
+ },
100
+ });
101
+
102
+ const colors = getCustomColors();
103
+ expect(colors).toEqual({
104
+ primary: "#007AFF",
105
+ secondary: "#5856D6",
106
+ });
107
+ });
108
+
109
+ it("should flatten nested colors", () => {
110
+ setConfig({
111
+ theme: {
112
+ extend: {
113
+ colors: {
114
+ brand: {
115
+ light: "#FF6B6B",
116
+ dark: "#CC0000",
117
+ },
118
+ },
119
+ },
120
+ },
121
+ });
122
+
123
+ const colors = getCustomColors();
124
+ expect(colors).toEqual({
125
+ "brand-light": "#FF6B6B",
126
+ "brand-dark": "#CC0000",
127
+ });
128
+ });
129
+
130
+ it("should handle mixed flat and nested colors", () => {
131
+ setConfig({
132
+ theme: {
133
+ extend: {
134
+ colors: {
135
+ primary: "#007AFF",
136
+ brand: {
137
+ light: "#FF6B6B",
138
+ dark: "#CC0000",
139
+ },
140
+ },
141
+ },
142
+ },
143
+ });
144
+
145
+ const colors = getCustomColors();
146
+ expect(colors).toEqual({
147
+ primary: "#007AFF",
148
+ "brand-light": "#FF6B6B",
149
+ "brand-dark": "#CC0000",
150
+ });
151
+ });
152
+
153
+ it("should clear cache when config changes", () => {
154
+ const style = tw`bg-blue-500`;
155
+ expect(style).toBeDefined();
156
+ expect(getCacheStats().size).toBe(1);
157
+
158
+ setConfig({
159
+ theme: {
160
+ extend: {
161
+ colors: { primary: "#007AFF" },
162
+ },
163
+ },
164
+ });
165
+
166
+ expect(getCacheStats().size).toBe(0);
167
+ });
168
+
169
+ it("should use custom colors in parsing", () => {
170
+ setConfig({
171
+ theme: {
172
+ extend: {
173
+ colors: {
174
+ primary: "#007AFF",
175
+ },
176
+ },
177
+ },
178
+ });
179
+
180
+ const result = tw`bg-primary`;
181
+ expect(result?.style).toEqual({
182
+ backgroundColor: "#007AFF",
183
+ });
184
+ });
185
+ });
186
+
187
+ describe("cache", () => {
188
+ it("should cache parsed styles", () => {
189
+ const result1 = tw`m-4 p-2`;
190
+ const result2 = tw`m-4 p-2`;
191
+
192
+ // Should return the same reference (cached)
193
+ expect(result1).toBe(result2);
194
+ });
195
+
196
+ it("should track cache stats", () => {
197
+ const style1 = tw`m-4`;
198
+ const style2 = tw`p-2`;
199
+ const style3 = tw`bg-blue-500`;
200
+ expect(style1).toBeDefined();
201
+ expect(style2).toBeDefined();
202
+ expect(style3).toBeDefined();
203
+
204
+ const stats = getCacheStats();
205
+ expect(stats.size).toBe(3);
206
+ expect(stats.keys).toContain("m-4");
207
+ expect(stats.keys).toContain("p-2");
208
+ expect(stats.keys).toContain("bg-blue-500");
209
+ });
210
+
211
+ it("should clear cache", () => {
212
+ const style1 = tw`m-4`;
213
+ const style2 = tw`p-2`;
214
+ expect(style1).toBeDefined();
215
+ expect(style2).toBeDefined();
216
+ expect(getCacheStats().size).toBe(2);
217
+
218
+ clearCache();
219
+ expect(getCacheStats().size).toBe(0);
220
+ });
221
+ });
222
+
223
+ describe("state modifiers", () => {
224
+ it("should return activeStyle when active: modifier is used", () => {
225
+ const result = tw`bg-blue-500 active:bg-blue-700`;
226
+ expect(result?.style).toEqual({
227
+ backgroundColor: "#2b7fff",
228
+ });
229
+ expect(result?.activeStyle).toEqual({
230
+ backgroundColor: "#1447e6",
231
+ });
232
+ expect(result?.disabledStyle).toBeUndefined();
233
+ });
234
+
235
+ it("should return disabledStyle when disabled: modifier is used", () => {
236
+ const result = tw`bg-blue-500 disabled:bg-gray-300`;
237
+ expect(result?.style).toEqual({
238
+ backgroundColor: "#2b7fff",
239
+ });
240
+ expect(result?.disabledStyle).toEqual({
241
+ backgroundColor: "#d1d5dc",
242
+ });
243
+ expect(result?.activeStyle).toBeUndefined();
244
+ });
245
+
246
+ it("should return both activeStyle and disabledStyle when both modifiers are used", () => {
247
+ const result = tw`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300`;
248
+ expect(result?.style).toEqual({
249
+ backgroundColor: "#2b7fff",
250
+ });
251
+ expect(result?.activeStyle).toEqual({
252
+ backgroundColor: "#1447e6",
253
+ });
254
+ expect(result?.disabledStyle).toEqual({
255
+ backgroundColor: "#d1d5dc",
256
+ });
257
+ });
258
+
259
+ it("should merge base and active styles with multiple properties", () => {
260
+ const result = tw`p-4 m-2 bg-blue-500 active:bg-blue-700 active:p-6`;
261
+ expect(result?.style).toEqual({
262
+ padding: 16,
263
+ margin: 8,
264
+ backgroundColor: "#2b7fff",
265
+ });
266
+ expect(result?.activeStyle).toEqual({
267
+ backgroundColor: "#1447e6",
268
+ padding: 24,
269
+ });
270
+ });
271
+
272
+ it("should handle only modifier classes (no base)", () => {
273
+ const result = tw`active:bg-blue-700`;
274
+ expect(result?.style).toEqual({});
275
+ expect(result?.activeStyle).toEqual({
276
+ backgroundColor: "#1447e6",
277
+ });
278
+ });
279
+
280
+ it("should work with twStyle function", () => {
281
+ const result = twStyle("bg-blue-500 active:bg-blue-700");
282
+ expect(result?.style).toEqual({
283
+ backgroundColor: "#2b7fff",
284
+ });
285
+ expect(result?.activeStyle).toEqual({
286
+ backgroundColor: "#1447e6",
287
+ });
288
+ });
289
+
290
+ it("should provide raw hex values for animations", () => {
291
+ const result = tw`bg-blue-500 active:bg-blue-700`;
292
+ // Access raw backgroundColor value for use with reanimated
293
+ expect(result?.style.backgroundColor).toBe("#2b7fff");
294
+ expect(result?.activeStyle?.backgroundColor).toBe("#1447e6");
295
+ });
296
+
297
+ it("should return focusStyle when focus: modifier is used", () => {
298
+ const result = tw`bg-blue-500 focus:bg-blue-800`;
299
+ expect(result?.style).toEqual({
300
+ backgroundColor: "#2b7fff",
301
+ });
302
+ expect(result?.focusStyle).toEqual({
303
+ backgroundColor: "#193cb8",
304
+ });
305
+ expect(result?.activeStyle).toBeUndefined();
306
+ expect(result?.disabledStyle).toBeUndefined();
307
+ });
308
+
309
+ it("should return all three modifier styles when all are used", () => {
310
+ const result = tw`bg-blue-500 active:bg-blue-700 focus:bg-blue-800 disabled:bg-gray-300`;
311
+ expect(result?.style).toEqual({
312
+ backgroundColor: "#2b7fff",
313
+ });
314
+ expect(result?.activeStyle).toEqual({
315
+ backgroundColor: "#1447e6",
316
+ });
317
+ expect(result?.focusStyle).toEqual({
318
+ backgroundColor: "#193cb8",
319
+ });
320
+ expect(result?.disabledStyle).toEqual({
321
+ backgroundColor: "#d1d5dc",
322
+ });
323
+ });
324
+ });
325
+ });
package/src/runtime.ts ADDED
@@ -0,0 +1,265 @@
1
+ import { parseClassName } from "./parser/index.js";
2
+ import type { NativeStyle, TwStyle } from "./types/runtime.js";
3
+ import { flattenColors } from "./utils/flattenColors.js";
4
+ import { hasModifiers, splitModifierClasses } from "./utils/modifiers.js";
5
+
6
+ /**
7
+ * Runtime configuration type matching Tailwind config structure
8
+ */
9
+ export type RuntimeConfig = {
10
+ theme?: {
11
+ extend?: {
12
+ colors?: Record<string, string | Record<string, string>>;
13
+ // Future extensions can be added here:
14
+ // spacing?: Record<string, number | string>;
15
+ // fontFamily?: Record<string, string[]>;
16
+ };
17
+ };
18
+ };
19
+
20
+ // Global custom colors configuration
21
+ let globalCustomColors: Record<string, string> | undefined;
22
+
23
+ // Simple memoization cache
24
+ const styleCache = new Map<string, TwStyle>();
25
+
26
+ /**
27
+ * Configure runtime Tailwind settings
28
+ * Matches the structure of tailwind.config.mjs for consistency
29
+ *
30
+ * @param config - Runtime configuration object
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { setConfig } from '@mgcrea/react-native-tailwind/runtime';
35
+ *
36
+ * setConfig({
37
+ * theme: {
38
+ * extend: {
39
+ * colors: {
40
+ * primary: '#007AFF',
41
+ * secondary: '#5856D6',
42
+ * brand: {
43
+ * light: '#FF6B6B',
44
+ * dark: '#CC0000'
45
+ * }
46
+ * }
47
+ * }
48
+ * }
49
+ * });
50
+ * ```
51
+ */
52
+ export function setConfig(config: RuntimeConfig): void {
53
+ // Extract and flatten custom colors
54
+ if (config.theme?.extend?.colors) {
55
+ globalCustomColors = flattenColors(config.theme.extend.colors);
56
+ } else {
57
+ globalCustomColors = undefined;
58
+ }
59
+
60
+ // Clear cache when config changes
61
+ styleCache.clear();
62
+ }
63
+
64
+ /**
65
+ * Get currently configured custom colors
66
+ */
67
+ export function getCustomColors(): Record<string, string> | undefined {
68
+ return globalCustomColors;
69
+ }
70
+
71
+ /**
72
+ * Clear the memoization cache
73
+ * Useful for testing or when you want to force re-parsing
74
+ */
75
+ export function clearCache(): void {
76
+ styleCache.clear();
77
+ }
78
+
79
+ /**
80
+ * Get cache statistics (for debugging/monitoring)
81
+ */
82
+ export function getCacheStats(): { size: number; keys: string[] } {
83
+ return {
84
+ size: styleCache.size,
85
+ keys: Array.from(styleCache.keys()),
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Parse className string and return a TwStyle object with separate modifier properties
91
+ * Internal helper that handles caching and StyleSheet.create wrapping
92
+ */
93
+ function parseAndCache(className: string): TwStyle {
94
+ // Check cache first
95
+ const cached = styleCache.get(className);
96
+ if (cached) {
97
+ return cached;
98
+ }
99
+
100
+ // Check if className contains modifiers
101
+ if (!hasModifiers(className)) {
102
+ // No modifiers - simple case
103
+ const styleObject = parseClassName(className, globalCustomColors);
104
+
105
+ const result: TwStyle = {
106
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
107
+ style: styleObject,
108
+ };
109
+
110
+ // Cache the result
111
+ styleCache.set(className, result);
112
+
113
+ return result;
114
+ }
115
+
116
+ // Has modifiers - split and parse separately
117
+ const { base, modifiers } = splitModifierClasses(className);
118
+
119
+ // Parse base styles
120
+ const baseClassName = base.join(" ");
121
+ const baseStyle = baseClassName ? parseClassName(baseClassName, globalCustomColors) : {};
122
+
123
+ // Build result object
124
+ const result: TwStyle = {
125
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
126
+ style: baseStyle,
127
+ };
128
+
129
+ // Parse and add modifier styles
130
+ if (modifiers.has("active")) {
131
+ const activeClasses = modifiers.get("active");
132
+ if (activeClasses && activeClasses.length > 0) {
133
+ const activeClassName = activeClasses.join(" ");
134
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
135
+ result.activeStyle = parseClassName(activeClassName, globalCustomColors);
136
+ }
137
+ }
138
+
139
+ if (modifiers.has("focus")) {
140
+ const focusClasses = modifiers.get("focus");
141
+ if (focusClasses && focusClasses.length > 0) {
142
+ const focusClassName = focusClasses.join(" ");
143
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
144
+ result.focusStyle = parseClassName(focusClassName, globalCustomColors);
145
+ }
146
+ }
147
+
148
+ if (modifiers.has("disabled")) {
149
+ const disabledClasses = modifiers.get("disabled");
150
+ if (disabledClasses && disabledClasses.length > 0) {
151
+ const disabledClassName = disabledClasses.join(" ");
152
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
153
+ result.disabledStyle = parseClassName(disabledClassName, globalCustomColors);
154
+ }
155
+ }
156
+
157
+ // Cache the result
158
+ styleCache.set(className, result);
159
+
160
+ return result;
161
+ }
162
+
163
+ /**
164
+ * Runtime Tailwind CSS template tag for React Native
165
+ *
166
+ * Parses Tailwind class names at runtime and returns a TwStyle object with separate
167
+ * properties for base styles and modifier styles (active, focus, disabled).
168
+ * Results are memoized for performance.
169
+ *
170
+ * @param strings - Template string parts
171
+ * @param values - Interpolated values
172
+ * @returns TwStyle object with style, activeStyle, focusStyle, and disabledStyle properties
173
+ *
174
+ * @example
175
+ * ```tsx
176
+ * import { tw } from '@mgcrea/react-native-tailwind/runtime';
177
+ *
178
+ * // Simple usage - access .style property
179
+ * <View style={tw`m-4 p-2 bg-blue-500`.style} />
180
+ *
181
+ * // With interpolations
182
+ * <View style={tw`flex-1 ${isActive && 'bg-blue-500'} p-4`.style} />
183
+ *
184
+ * // With state modifiers - access activeStyle/focusStyle for animations
185
+ * const styles = tw`bg-blue-500 active:bg-blue-700 focus:bg-blue-800`;
186
+ * <Pressable style={(state) => [
187
+ * styles.style,
188
+ * state.pressed && styles.activeStyle,
189
+ * state.focused && styles.focusStyle
190
+ * ]}>
191
+ * <Text>Press me</Text>
192
+ * </Pressable>
193
+ *
194
+ * // Use with reanimated for animations with raw values
195
+ * const styles = tw`bg-blue-500 active:bg-blue-700`;
196
+ * const animatedStyles = useAnimatedStyle(() => ({
197
+ * ...styles.style,
198
+ * backgroundColor: interpolateColor(
199
+ * progress.value,
200
+ * [0, 1],
201
+ * [styles.style.backgroundColor, styles.activeStyle?.backgroundColor]
202
+ * )
203
+ * }));
204
+ * ```
205
+ */
206
+ export function tw<T extends NativeStyle = NativeStyle>(
207
+ strings: TemplateStringsArray,
208
+ ...values: unknown[]
209
+ ): TwStyle<T> {
210
+ // Combine template strings and values into a single className string
211
+ const className = strings.reduce((acc, str, i) => {
212
+ const value = values[i];
213
+ // Handle falsy values (false, null, undefined) - don't add them
214
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
215
+ const valueStr = value ? String(value) : "";
216
+ return acc + str + valueStr;
217
+ }, "");
218
+
219
+ // Trim and normalize whitespace
220
+ const normalizedClassName = className.trim().replace(/\s+/g, " ");
221
+
222
+ // Handle empty className
223
+ if (!normalizedClassName) {
224
+ return { style: {} as T };
225
+ }
226
+
227
+ return parseAndCache(normalizedClassName) as TwStyle<T>;
228
+ }
229
+
230
+ /**
231
+ * String version of tw for cases where template literals aren't needed
232
+ *
233
+ * Parses Tailwind class names at runtime and returns a TwStyle object with separate
234
+ * properties for base styles and modifier styles (active, focus, disabled).
235
+ *
236
+ * @param className - Space-separated Tailwind class names
237
+ * @returns TwStyle object with style, activeStyle, focusStyle, and disabledStyle properties
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * import { twStyle } from '@mgcrea/react-native-tailwind/runtime';
242
+ *
243
+ * // Simple usage - access .style property
244
+ * <View style={twStyle('m-4 p-2 bg-blue-500').style} />
245
+ *
246
+ * // With state modifiers
247
+ * const styles = twStyle('bg-blue-500 active:bg-blue-700 focus:bg-blue-800');
248
+ * <Pressable style={(state) => [
249
+ * styles.style,
250
+ * state.pressed && styles.activeStyle,
251
+ * state.focused && styles.focusStyle
252
+ * ]}>
253
+ * <Text>Press me</Text>
254
+ * </Pressable>
255
+ * ```
256
+ */
257
+ export function twStyle<T extends NativeStyle = NativeStyle>(className: string): TwStyle<T> | undefined {
258
+ const normalizedClassName = className.trim().replace(/\s+/g, " ");
259
+
260
+ if (!normalizedClassName) {
261
+ return undefined;
262
+ }
263
+
264
+ return parseAndCache(normalizedClassName) as TwStyle<T>;
265
+ }
@@ -0,0 +1,65 @@
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 { NativeStyle, TwStyle } from "../types/runtime.js";
11
+
12
+ /**
13
+ * Compile-time Tailwind CSS template tag (transformed by Babel plugin)
14
+ *
15
+ * This function is replaced at compile-time by the Babel plugin.
16
+ * The import is removed and calls are transformed to inline style objects.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * import { tw } from '@mgcrea/react-native-tailwind';
21
+ *
22
+ * const styles = tw`bg-blue-500 active:bg-blue-700`;
23
+ * // Transformed to:
24
+ * // const styles = {
25
+ * // style: styles._bg_blue_500,
26
+ * // activeStyle: styles._active_bg_blue_700
27
+ * // };
28
+ * ```
29
+ */
30
+ export function tw<T extends NativeStyle = NativeStyle>(
31
+ _strings: TemplateStringsArray,
32
+ ..._values: unknown[]
33
+ ): TwStyle<T> {
34
+ throw new Error(
35
+ "tw() must be transformed by the Babel plugin. " +
36
+ "Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. " +
37
+ "For runtime parsing, use: import { tw } from '@mgcrea/react-native-tailwind/runtime'",
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Compile-time Tailwind CSS string function (transformed by Babel plugin)
43
+ *
44
+ * This function is replaced at compile-time by the Babel plugin.
45
+ * The import is removed and calls are transformed to inline style objects.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * import { twStyle } from '@mgcrea/react-native-tailwind';
50
+ *
51
+ * const styles = twStyle('bg-blue-500 active:bg-blue-700');
52
+ * // Transformed to:
53
+ * // const styles = {
54
+ * // style: styles._bg_blue_500,
55
+ * // activeStyle: styles._active_bg_blue_700
56
+ * // };
57
+ * ```
58
+ */
59
+ export function twStyle<T extends NativeStyle = NativeStyle>(_className: string): TwStyle<T> | undefined {
60
+ throw new Error(
61
+ "twStyle() must be transformed by the Babel plugin. " +
62
+ "Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. " +
63
+ "For runtime parsing, use: import { twStyle } from '@mgcrea/react-native-tailwind/runtime'",
64
+ );
65
+ }
@@ -2,10 +2,6 @@
2
2
  * Core type definitions
3
3
  */
4
4
 
5
- import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
6
-
7
- export type RNStyle = ViewStyle | TextStyle | ImageStyle;
8
-
9
5
  // Transform types for React Native
10
6
  export type TransformStyle =
11
7
  | { scale?: number }
@@ -0,0 +1,2 @@
1
+ export * from "./core";
2
+ export * from "./util";
@@ -0,0 +1,17 @@
1
+ import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
2
+
3
+ /**
4
+ * Union type for all React Native style types
5
+ */
6
+ export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
7
+
8
+ /**
9
+ * Return type for tw/twStyle functions with separate style properties for modifiers
10
+ */
11
+ export type TwStyle<T extends NativeStyle = NativeStyle> = {
12
+ style: T;
13
+ activeStyle?: T;
14
+ focusStyle?: T;
15
+ disabledStyle?: T;
16
+ placeholderStyle?: TextStyle;
17
+ };
@@ -0,0 +1 @@
1
+ export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};