@mgcrea/react-native-tailwind 0.6.1 → 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 (40) hide show
  1. package/README.md +404 -0
  2. package/dist/babel/config-loader.ts +1 -23
  3. package/dist/babel/index.cjs +227 -60
  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/index.d.ts +3 -0
  8. package/dist/index.js +1 -1
  9. package/dist/parser/__snapshots__/colors.test.js.snap +242 -90
  10. package/dist/parser/__snapshots__/transforms.test.js.snap +58 -0
  11. package/dist/parser/colors.js +1 -1
  12. package/dist/runtime.cjs +2 -0
  13. package/dist/runtime.cjs.map +7 -0
  14. package/dist/runtime.d.ts +139 -0
  15. package/dist/runtime.js +2 -0
  16. package/dist/runtime.js.map +7 -0
  17. package/dist/runtime.test.js +1 -0
  18. package/dist/stubs/tw.d.ts +60 -0
  19. package/dist/stubs/tw.js +1 -0
  20. package/dist/utils/flattenColors.d.ts +16 -0
  21. package/dist/utils/flattenColors.js +1 -0
  22. package/dist/utils/flattenColors.test.js +1 -0
  23. package/dist/utils/modifiers.d.ts +29 -0
  24. package/dist/utils/modifiers.js +1 -0
  25. package/dist/utils/modifiers.test.js +1 -0
  26. package/dist/utils/styleKey.test.js +1 -0
  27. package/package.json +15 -3
  28. package/src/babel/config-loader.ts +1 -23
  29. package/src/babel/index.test.ts +268 -0
  30. package/src/babel/index.ts +352 -44
  31. package/src/index.ts +5 -0
  32. package/src/parser/colors.ts +8 -22
  33. package/src/runtime.test.ts +325 -0
  34. package/src/runtime.ts +280 -0
  35. package/src/stubs/tw.ts +80 -0
  36. package/src/utils/flattenColors.test.ts +361 -0
  37. package/src/utils/flattenColors.ts +32 -0
  38. package/src/utils/modifiers.test.ts +286 -0
  39. package/src/utils/modifiers.ts +63 -0
  40. 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,280 @@
1
+ import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
2
+ import { parseClassName } from "./parser/index.js";
3
+ import { flattenColors } from "./utils/flattenColors.js";
4
+ import { hasModifiers, splitModifierClasses } from "./utils/modifiers.js";
5
+
6
+ /**
7
+ * Union type for all React Native style types
8
+ */
9
+ export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
10
+
11
+ /**
12
+ * Return type for tw/twStyle functions with separate style properties for modifiers
13
+ */
14
+ export type TwStyle<T extends NativeStyle = NativeStyle> = {
15
+ style: T;
16
+ activeStyle?: T;
17
+ focusStyle?: T;
18
+ disabledStyle?: T;
19
+ };
20
+
21
+ /**
22
+ * Runtime configuration type matching Tailwind config structure
23
+ */
24
+ export type RuntimeConfig = {
25
+ theme?: {
26
+ extend?: {
27
+ colors?: Record<string, string | Record<string, string>>;
28
+ // Future extensions can be added here:
29
+ // spacing?: Record<string, number | string>;
30
+ // fontFamily?: Record<string, string[]>;
31
+ };
32
+ };
33
+ };
34
+
35
+ // Global custom colors configuration
36
+ let globalCustomColors: Record<string, string> | undefined;
37
+
38
+ // Simple memoization cache
39
+ const styleCache = new Map<string, TwStyle>();
40
+
41
+ /**
42
+ * Configure runtime Tailwind settings
43
+ * Matches the structure of tailwind.config.mjs for consistency
44
+ *
45
+ * @param config - Runtime configuration object
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * import { setConfig } from '@mgcrea/react-native-tailwind/runtime';
50
+ *
51
+ * setConfig({
52
+ * theme: {
53
+ * extend: {
54
+ * colors: {
55
+ * primary: '#007AFF',
56
+ * secondary: '#5856D6',
57
+ * brand: {
58
+ * light: '#FF6B6B',
59
+ * dark: '#CC0000'
60
+ * }
61
+ * }
62
+ * }
63
+ * }
64
+ * });
65
+ * ```
66
+ */
67
+ export function setConfig(config: RuntimeConfig): void {
68
+ // Extract and flatten custom colors
69
+ if (config.theme?.extend?.colors) {
70
+ globalCustomColors = flattenColors(config.theme.extend.colors);
71
+ } else {
72
+ globalCustomColors = undefined;
73
+ }
74
+
75
+ // Clear cache when config changes
76
+ styleCache.clear();
77
+ }
78
+
79
+ /**
80
+ * Get currently configured custom colors
81
+ */
82
+ export function getCustomColors(): Record<string, string> | undefined {
83
+ return globalCustomColors;
84
+ }
85
+
86
+ /**
87
+ * Clear the memoization cache
88
+ * Useful for testing or when you want to force re-parsing
89
+ */
90
+ export function clearCache(): void {
91
+ styleCache.clear();
92
+ }
93
+
94
+ /**
95
+ * Get cache statistics (for debugging/monitoring)
96
+ */
97
+ export function getCacheStats(): { size: number; keys: string[] } {
98
+ return {
99
+ size: styleCache.size,
100
+ keys: Array.from(styleCache.keys()),
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Parse className string and return a TwStyle object with separate modifier properties
106
+ * Internal helper that handles caching and StyleSheet.create wrapping
107
+ */
108
+ function parseAndCache(className: string): TwStyle {
109
+ // Check cache first
110
+ const cached = styleCache.get(className);
111
+ if (cached) {
112
+ return cached;
113
+ }
114
+
115
+ // Check if className contains modifiers
116
+ if (!hasModifiers(className)) {
117
+ // No modifiers - simple case
118
+ const styleObject = parseClassName(className, globalCustomColors);
119
+
120
+ const result: TwStyle = {
121
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
122
+ style: styleObject,
123
+ };
124
+
125
+ // Cache the result
126
+ styleCache.set(className, result);
127
+
128
+ return result;
129
+ }
130
+
131
+ // Has modifiers - split and parse separately
132
+ const { base, modifiers } = splitModifierClasses(className);
133
+
134
+ // Parse base styles
135
+ const baseClassName = base.join(" ");
136
+ const baseStyle = baseClassName ? parseClassName(baseClassName, globalCustomColors) : {};
137
+
138
+ // Build result object
139
+ const result: TwStyle = {
140
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
141
+ style: baseStyle,
142
+ };
143
+
144
+ // Parse and add modifier styles
145
+ if (modifiers.has("active")) {
146
+ const activeClasses = modifiers.get("active");
147
+ if (activeClasses && activeClasses.length > 0) {
148
+ const activeClassName = activeClasses.join(" ");
149
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
150
+ result.activeStyle = parseClassName(activeClassName, globalCustomColors);
151
+ }
152
+ }
153
+
154
+ if (modifiers.has("focus")) {
155
+ const focusClasses = modifiers.get("focus");
156
+ if (focusClasses && focusClasses.length > 0) {
157
+ const focusClassName = focusClasses.join(" ");
158
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
159
+ result.focusStyle = parseClassName(focusClassName, globalCustomColors);
160
+ }
161
+ }
162
+
163
+ if (modifiers.has("disabled")) {
164
+ const disabledClasses = modifiers.get("disabled");
165
+ if (disabledClasses && disabledClasses.length > 0) {
166
+ const disabledClassName = disabledClasses.join(" ");
167
+ // @ts-expect-error - StyleObject transform types are broader than React Native's strict types
168
+ result.disabledStyle = parseClassName(disabledClassName, globalCustomColors);
169
+ }
170
+ }
171
+
172
+ // Cache the result
173
+ styleCache.set(className, result);
174
+
175
+ return result;
176
+ }
177
+
178
+ /**
179
+ * Runtime Tailwind CSS template tag for React Native
180
+ *
181
+ * Parses Tailwind class names at runtime and returns a TwStyle object with separate
182
+ * properties for base styles and modifier styles (active, focus, disabled).
183
+ * Results are memoized for performance.
184
+ *
185
+ * @param strings - Template string parts
186
+ * @param values - Interpolated values
187
+ * @returns TwStyle object with style, activeStyle, focusStyle, and disabledStyle properties
188
+ *
189
+ * @example
190
+ * ```tsx
191
+ * import { tw } from '@mgcrea/react-native-tailwind/runtime';
192
+ *
193
+ * // Simple usage - access .style property
194
+ * <View style={tw`m-4 p-2 bg-blue-500`.style} />
195
+ *
196
+ * // With interpolations
197
+ * <View style={tw`flex-1 ${isActive && 'bg-blue-500'} p-4`.style} />
198
+ *
199
+ * // With state modifiers - access activeStyle/focusStyle for animations
200
+ * const styles = tw`bg-blue-500 active:bg-blue-700 focus:bg-blue-800`;
201
+ * <Pressable style={(state) => [
202
+ * styles.style,
203
+ * state.pressed && styles.activeStyle,
204
+ * state.focused && styles.focusStyle
205
+ * ]}>
206
+ * <Text>Press me</Text>
207
+ * </Pressable>
208
+ *
209
+ * // Use with reanimated for animations with raw values
210
+ * const styles = tw`bg-blue-500 active:bg-blue-700`;
211
+ * const animatedStyles = useAnimatedStyle(() => ({
212
+ * ...styles.style,
213
+ * backgroundColor: interpolateColor(
214
+ * progress.value,
215
+ * [0, 1],
216
+ * [styles.style.backgroundColor, styles.activeStyle?.backgroundColor]
217
+ * )
218
+ * }));
219
+ * ```
220
+ */
221
+ export function tw<T extends NativeStyle = NativeStyle>(
222
+ strings: TemplateStringsArray,
223
+ ...values: unknown[]
224
+ ): TwStyle<T> {
225
+ // Combine template strings and values into a single className string
226
+ const className = strings.reduce((acc, str, i) => {
227
+ const value = values[i];
228
+ // Handle falsy values (false, null, undefined) - don't add them
229
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
230
+ const valueStr = value ? String(value) : "";
231
+ return acc + str + valueStr;
232
+ }, "");
233
+
234
+ // Trim and normalize whitespace
235
+ const normalizedClassName = className.trim().replace(/\s+/g, " ");
236
+
237
+ // Handle empty className
238
+ if (!normalizedClassName) {
239
+ return { style: {} as T };
240
+ }
241
+
242
+ return parseAndCache(normalizedClassName) as TwStyle<T>;
243
+ }
244
+
245
+ /**
246
+ * String version of tw for cases where template literals aren't needed
247
+ *
248
+ * Parses Tailwind class names at runtime and returns a TwStyle object with separate
249
+ * properties for base styles and modifier styles (active, focus, disabled).
250
+ *
251
+ * @param className - Space-separated Tailwind class names
252
+ * @returns TwStyle object with style, activeStyle, focusStyle, and disabledStyle properties
253
+ *
254
+ * @example
255
+ * ```tsx
256
+ * import { twStyle } from '@mgcrea/react-native-tailwind/runtime';
257
+ *
258
+ * // Simple usage - access .style property
259
+ * <View style={twStyle('m-4 p-2 bg-blue-500').style} />
260
+ *
261
+ * // With state modifiers
262
+ * const styles = twStyle('bg-blue-500 active:bg-blue-700 focus:bg-blue-800');
263
+ * <Pressable style={(state) => [
264
+ * styles.style,
265
+ * state.pressed && styles.activeStyle,
266
+ * state.focused && styles.focusStyle
267
+ * ]}>
268
+ * <Text>Press me</Text>
269
+ * </Pressable>
270
+ * ```
271
+ */
272
+ export function twStyle<T extends NativeStyle = NativeStyle>(className: string): TwStyle<T> | undefined {
273
+ const normalizedClassName = className.trim().replace(/\s+/g, " ");
274
+
275
+ if (!normalizedClassName) {
276
+ return undefined;
277
+ }
278
+
279
+ return parseAndCache(normalizedClassName) as TwStyle<T>;
280
+ }
@@ -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
+ }