@mgcrea/react-native-tailwind 0.11.1 → 0.12.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.
@@ -1 +1 @@
1
- var _vitest=require("vitest");var _runtime=require("./runtime");(0,_vitest.describe)("runtime",function(){(0,_vitest.beforeEach)(function(){(0,_runtime.clearCache)();(0,_runtime.setConfig)({});});(0,_vitest.describe)("tw template tag",function(){(0,_vitest.it)("should parse static classes",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should handle interpolated values",function(){var isActive=true;var result=(0,_runtime.tw)`m-4 ${isActive&&"bg-blue-500"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,backgroundColor:"#2b7fff"});});(0,_vitest.it)("should handle conditional classes",function(){var isLarge=true;var result=(0,_runtime.tw)`p-4 ${isLarge?"text-xl":"text-sm"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,fontSize:20});});(0,_vitest.it)("should handle falsy values",function(){var result=(0,_runtime.tw)`m-4 ${false} ${null} ${undefined} p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should preserve zero values in template literals",function(){var result=(0,_runtime.tw)`opacity-${0} m-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({opacity:0,margin:16});});(0,_vitest.it)("should preserve empty string values in template literals",function(){var result=(0,_runtime.tw)`m-4 ${""}p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should handle mixed falsy and truthy numeric values",function(){var spacing=0;var result=(0,_runtime.tw)`m-${spacing} p-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:0,padding:16});});(0,_vitest.it)("should return empty style object for empty className",function(){var result=(0,_runtime.tw)``;(0,_vitest.expect)(result).toEqual({style:{}});});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("twStyle function",function(){(0,_vitest.it)("should parse className string",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return undefined for empty string",function(){var result=(0,_runtime.twStyle)("");(0,_vitest.expect)(result).toBeUndefined();});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("setConfig",function(){(0,_vitest.it)("should set custom colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",secondary:"#5856D6"}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF",secondary:"#5856D6"});});(0,_vitest.it)("should flatten nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({"brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should handle mixed flat and nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF","brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should clear cache when config changes",function(){var style=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(1);(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});(0,_vitest.it)("should use custom colors in parsing",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});var result=(0,_runtime.tw)`bg-primary`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#007AFF"});});});(0,_vitest.describe)("cache",function(){(0,_vitest.it)("should cache parsed styles",function(){var result1=(0,_runtime.tw)`m-4 p-2`;var result2=(0,_runtime.tw)`m-4 p-2`;(0,_vitest.expect)(result1).toBe(result2);});(0,_vitest.it)("should track cache stats",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;var style3=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)(style3).toBeDefined();var stats=(0,_runtime.getCacheStats)();(0,_vitest.expect)(stats.size).toBe(3);(0,_vitest.expect)(stats.keys).toContain("m-4");(0,_vitest.expect)(stats.keys).toContain("p-2");(0,_vitest.expect)(stats.keys).toContain("bg-blue-500");});(0,_vitest.it)("should clear cache",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(2);(0,_runtime.clearCache)();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});});(0,_vitest.describe)("state modifiers",function(){(0,_vitest.it)("should return activeStyle when active: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return disabledStyle when disabled: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return both activeStyle and disabledStyle when both modifiers are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});(0,_vitest.it)("should merge base and active styles with multiple properties",function(){var result=(0,_runtime.tw)`p-4 m-2 bg-blue-500 active:bg-blue-700 active:p-6`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,margin:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6",padding:24});});(0,_vitest.it)("should handle only modifier classes (no base)",function(){var result=(0,_runtime.tw)`active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should work with twStyle function",function(){var result=(0,_runtime.twStyle)("bg-blue-500 active:bg-blue-700");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should provide raw hex values for animations",function(){var _result$activeStyle;var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style.backgroundColor).toBe("#2b7fff");(0,_vitest.expect)(result==null||(_result$activeStyle=result.activeStyle)==null?void 0:_result$activeStyle.backgroundColor).toBe("#1447e6");});(0,_vitest.it)("should return focusStyle when focus: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 focus:bg-blue-800`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return all three modifier styles when all are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 focus:bg-blue-800 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});});});
1
+ var _vitest=require("vitest");var _runtime=require("./runtime");(0,_vitest.describe)("runtime",function(){(0,_vitest.beforeEach)(function(){(0,_runtime.clearCache)();(0,_runtime.setConfig)({});});(0,_vitest.describe)("tw template tag",function(){(0,_vitest.it)("should parse static classes",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should handle interpolated values",function(){var isActive=true;var result=(0,_runtime.tw)`m-4 ${isActive&&"bg-blue-500"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,backgroundColor:"#2b7fff"});});(0,_vitest.it)("should handle conditional classes",function(){var isLarge=true;var result=(0,_runtime.tw)`p-4 ${isLarge?"text-xl":"text-sm"}`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,fontSize:20});});(0,_vitest.it)("should handle falsy values",function(){var result=(0,_runtime.tw)`m-4 ${false} ${null} ${undefined} p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should preserve zero values in template literals",function(){var result=(0,_runtime.tw)`opacity-${0} m-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({opacity:0,margin:16});});(0,_vitest.it)("should preserve empty string values in template literals",function(){var result=(0,_runtime.tw)`m-4 ${""}p-2`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8});});(0,_vitest.it)("should handle mixed falsy and truthy numeric values",function(){var spacing=0;var result=(0,_runtime.tw)`m-${spacing} p-4`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:0,padding:16});});(0,_vitest.it)("should return empty style object for empty className",function(){var result=(0,_runtime.tw)``;(0,_vitest.expect)(result).toEqual({style:{}});});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.tw)`m-4 p-2 bg-blue-500`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("twStyle function",function(){(0,_vitest.it)("should parse className string",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return undefined for empty string",function(){var result=(0,_runtime.twStyle)("");(0,_vitest.expect)(result).toBeUndefined();});(0,_vitest.it)("should normalize whitespace",function(){var result=(0,_runtime.twStyle)("m-4 p-2 bg-blue-500");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({margin:16,padding:8,backgroundColor:"#2b7fff"});});});(0,_vitest.describe)("setConfig",function(){(0,_vitest.it)("should set custom colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",secondary:"#5856D6"}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF",secondary:"#5856D6"});});(0,_vitest.it)("should flatten nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({"brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should handle mixed flat and nested colors",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF",brand:{light:"#FF6B6B",dark:"#CC0000"}}}}});var theme=(0,_runtime.getCustomTheme)();(0,_vitest.expect)(theme.colors).toEqual({primary:"#007AFF","brand-light":"#FF6B6B","brand-dark":"#CC0000"});});(0,_vitest.it)("should clear cache when config changes",function(){var style=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(1);(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});(0,_vitest.it)("should use custom colors in parsing",function(){(0,_runtime.setConfig)({theme:{extend:{colors:{primary:"#007AFF"}}}});var result=(0,_runtime.tw)`bg-primary`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#007AFF"});});});(0,_vitest.describe)("cache",function(){(0,_vitest.it)("should cache parsed styles",function(){var result1=(0,_runtime.tw)`m-4 p-2`;var result2=(0,_runtime.tw)`m-4 p-2`;(0,_vitest.expect)(result1).toBe(result2);});(0,_vitest.it)("should track cache stats",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;var style3=(0,_runtime.tw)`bg-blue-500`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)(style3).toBeDefined();var stats=(0,_runtime.getCacheStats)();(0,_vitest.expect)(stats.size).toBe(3);(0,_vitest.expect)(stats.keys).toContain("m-4");(0,_vitest.expect)(stats.keys).toContain("p-2");(0,_vitest.expect)(stats.keys).toContain("bg-blue-500");});(0,_vitest.it)("should clear cache",function(){var style1=(0,_runtime.tw)`m-4`;var style2=(0,_runtime.tw)`p-2`;(0,_vitest.expect)(style1).toBeDefined();(0,_vitest.expect)(style2).toBeDefined();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(2);(0,_runtime.clearCache)();(0,_vitest.expect)((0,_runtime.getCacheStats)().size).toBe(0);});});(0,_vitest.describe)("state modifiers",function(){(0,_vitest.it)("should return activeStyle when active: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return disabledStyle when disabled: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();});(0,_vitest.it)("should return both activeStyle and disabledStyle when both modifiers are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});(0,_vitest.it)("should merge base and active styles with multiple properties",function(){var result=(0,_runtime.tw)`p-4 m-2 bg-blue-500 active:bg-blue-700 active:p-6`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({padding:16,margin:8,backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6",padding:24});});(0,_vitest.it)("should handle only modifier classes (no base)",function(){var result=(0,_runtime.tw)`active:bg-blue-700`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should work with twStyle function",function(){var result=(0,_runtime.twStyle)("bg-blue-500 active:bg-blue-700");(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});});(0,_vitest.it)("should provide raw hex values for animations",function(){var _result$activeStyle;var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700`;var style=Array.isArray(result==null?void 0:result.style)?result.style.find(function(s){return s!==false;}):result==null?void 0:result.style;(0,_vitest.expect)(style&&typeof style==="object"&&"backgroundColor"in style?style.backgroundColor:undefined).toBe("#2b7fff");(0,_vitest.expect)(result==null||(_result$activeStyle=result.activeStyle)==null?void 0:_result$activeStyle.backgroundColor).toBe("#1447e6");});(0,_vitest.it)("should return focusStyle when focus: modifier is used",function(){var result=(0,_runtime.tw)`bg-blue-500 focus:bg-blue-800`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toBeUndefined();(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toBeUndefined();});(0,_vitest.it)("should return all three modifier styles when all are used",function(){var result=(0,_runtime.tw)`bg-blue-500 active:bg-blue-700 focus:bg-blue-800 disabled:bg-gray-300`;(0,_vitest.expect)(result==null?void 0:result.style).toEqual({backgroundColor:"#2b7fff"});(0,_vitest.expect)(result==null?void 0:result.activeStyle).toEqual({backgroundColor:"#1447e6"});(0,_vitest.expect)(result==null?void 0:result.focusStyle).toEqual({backgroundColor:"#193cb8"});(0,_vitest.expect)(result==null?void 0:result.disabledStyle).toEqual({backgroundColor:"#d1d5dc"});});});});
@@ -5,11 +5,18 @@ import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
5
5
  export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
6
6
  /**
7
7
  * Return type for tw/twStyle functions with separate style properties for modifiers
8
+ * When color-scheme modifiers (dark:, light:) are present, style becomes an array with runtime conditionals
9
+ * When platform modifiers (ios:, android:, web:) are present, style becomes an array with Platform.select()
8
10
  */
9
11
  export type TwStyle<T extends NativeStyle = NativeStyle> = {
10
- style: T;
12
+ style: T | Array<T | false>;
11
13
  activeStyle?: T;
12
14
  focusStyle?: T;
13
15
  disabledStyle?: T;
14
16
  placeholderStyle?: TextStyle;
17
+ lightStyle?: T;
18
+ darkStyle?: T;
19
+ iosStyle?: T;
20
+ androidStyle?: T;
21
+ webStyle?: T;
15
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Compile-time Tailwind CSS for React Native with zero runtime overhead",
5
5
  "author": "Olivier Louvignes <olivier@mgcrea.io> (https://github.com/mgcrea)",
6
6
  "homepage": "https://github.com/mgcrea/react-native-tailwind#readme",
@@ -334,6 +334,89 @@ describe("Babel plugin - className transformation (existing behavior)", () => {
334
334
  // Should not have className in output
335
335
  expect(output).not.toContain("className");
336
336
  });
337
+
338
+ it('should transform className={"..."} (string literal in expression container)', () => {
339
+ const input = `
340
+ import { View } from 'react-native';
341
+ export function Component() {
342
+ return <View className={"flex-row items-center justify-start"} />;
343
+ }
344
+ `;
345
+
346
+ const output = transform(input, undefined, true);
347
+
348
+ // Should have StyleSheet
349
+ expect(output).toContain("StyleSheet.create");
350
+ expect(output).toContain("_twStyles");
351
+
352
+ // Should replace className with style
353
+ expect(output).not.toContain("className");
354
+ expect(output).toContain("style:");
355
+
356
+ // Should have the expected style keys
357
+ expect(output).toContain("_flex_row_items_center_justify_start");
358
+ });
359
+
360
+ it('should transform className={"..."} with modifiers', () => {
361
+ const input = `
362
+ import { Pressable } from 'react-native';
363
+ export function Component() {
364
+ return <Pressable className={"bg-blue-500 active:bg-blue-700 p-4"} />;
365
+ }
366
+ `;
367
+
368
+ const output = transform(input, undefined, true);
369
+
370
+ // Should have StyleSheet with both base and active styles
371
+ expect(output).toContain("_bg_blue_500_p_4");
372
+ expect(output).toContain("_active_bg_blue_700");
373
+
374
+ // Should have style function for active modifier (Pressable uses 'pressed' parameter)
375
+ expect(output).toMatch(/(pressed|_state)/);
376
+
377
+ // Should not have className in output
378
+ expect(output).not.toContain("className");
379
+ });
380
+
381
+ it('should transform className={"..."} with platform modifiers', () => {
382
+ const input = `
383
+ import { View } from 'react-native';
384
+ export function Component() {
385
+ return <View className={"p-4 ios:p-6 android:p-8"} />;
386
+ }
387
+ `;
388
+
389
+ const output = transform(input, undefined, true);
390
+
391
+ // Should have Platform import
392
+ expect(output).toContain("Platform");
393
+ expect(output).toMatch(/from ['"]react-native['"]/); // Match both single and double quotes
394
+
395
+ // Should have Platform.select
396
+ expect(output).toContain("Platform.select");
397
+
398
+ // Should have platform-specific styles
399
+ expect(output).toContain("_ios_p_6");
400
+ expect(output).toContain("_android_p_8");
401
+
402
+ // Should not have className in output
403
+ expect(output).not.toContain("className");
404
+ });
405
+
406
+ it('should handle empty className={""}', () => {
407
+ const input = `
408
+ import { View } from 'react-native';
409
+ export function Component() {
410
+ return <View className={""} />;
411
+ }
412
+ `;
413
+
414
+ const output = transform(input, undefined, true);
415
+
416
+ // Should remove empty className attribute entirely
417
+ expect(output).not.toContain("className");
418
+ expect(output).not.toContain("style=");
419
+ });
337
420
  });
338
421
 
339
422
  describe("Babel plugin - placeholder: modifier transformation", () => {
@@ -1250,6 +1333,34 @@ describe("Babel plugin - custom color scheme hook import", () => {
1250
1333
  expect(output).not.toContain("_twColorScheme = useTheme()");
1251
1334
  });
1252
1335
 
1336
+ it("should not treat type-only imports as having the hook", () => {
1337
+ const input = `
1338
+ import React from 'react';
1339
+ import { View } from 'react-native';
1340
+ import type { useColorScheme } from 'react-native';
1341
+
1342
+ export function Component() {
1343
+ return <View className="dark:bg-gray-900" />;
1344
+ }
1345
+ `;
1346
+
1347
+ const output = transform(input, undefined, true);
1348
+
1349
+ // Should add a VALUE import for useColorScheme (type import doesn't count)
1350
+ expect(output).toMatch(/import\s+\{[^}]*useColorScheme[^}]*\}\s+from\s+['"]react-native['"]/);
1351
+
1352
+ // Should inject the hook
1353
+ expect(output).toContain("_twColorScheme = useColorScheme()");
1354
+
1355
+ // Should have both type-only and value imports in output
1356
+ // (TypeScript preset keeps type imports for type checking)
1357
+ const colorSchemeMatches = output.match(/useColorScheme/g);
1358
+ expect(colorSchemeMatches).toBeTruthy();
1359
+ if (colorSchemeMatches) {
1360
+ expect(colorSchemeMatches.length).toBeGreaterThanOrEqual(2); // At least in import and hook call
1361
+ }
1362
+ });
1363
+
1253
1364
  it("should handle both type-only and aliased imports together", () => {
1254
1365
  const input = `
1255
1366
  import React from 'react';
@@ -1412,3 +1523,390 @@ describe("Babel plugin - scheme: modifier", () => {
1412
1523
  expect(output).toContain("_twColorScheme === 'light'");
1413
1524
  });
1414
1525
  });
1526
+
1527
+ describe("Babel plugin - color scheme modifiers in tw/twStyle", () => {
1528
+ it("should transform tw with dark: modifier inside component", () => {
1529
+ const input = `
1530
+ import { tw } from '@mgcrea/react-native-tailwind';
1531
+
1532
+ function MyComponent() {
1533
+ const styles = tw\`bg-white dark:bg-gray-900\`;
1534
+ return null;
1535
+ }
1536
+ `;
1537
+
1538
+ const output = transform(input);
1539
+
1540
+ // Should inject useColorScheme hook
1541
+ expect(output).toContain("useColorScheme");
1542
+ expect(output).toContain("_twColorScheme");
1543
+
1544
+ // Should generate style array with conditionals
1545
+ expect(output).toContain("style: [");
1546
+ expect(output).toContain('_twColorScheme === "dark"');
1547
+ expect(output).toContain("_twStyles._dark_bg_gray_900");
1548
+ expect(output).toContain("_twStyles._bg_white");
1549
+
1550
+ // Should have StyleSheet.create
1551
+ expect(output).toContain("StyleSheet.create");
1552
+ });
1553
+
1554
+ it("should transform twStyle with light: modifier inside component", () => {
1555
+ const input = `
1556
+ import { twStyle } from '@mgcrea/react-native-tailwind';
1557
+
1558
+ export const MyComponent = () => {
1559
+ const buttonStyles = twStyle('text-gray-900 light:text-gray-100');
1560
+ return null;
1561
+ };
1562
+ `;
1563
+
1564
+ const output = transform(input);
1565
+
1566
+ // Should inject useColorScheme hook
1567
+ expect(output).toContain("useColorScheme");
1568
+ expect(output).toContain("_twColorScheme");
1569
+
1570
+ // Should generate style array with conditionals
1571
+ expect(output).toContain("style: [");
1572
+ expect(output).toContain('_twColorScheme === "light"');
1573
+ expect(output).toContain("_twStyles._light_text_gray_100");
1574
+ expect(output).toContain("_twStyles._text_gray_900");
1575
+ });
1576
+
1577
+ it("should transform tw with both dark: and light: modifiers", () => {
1578
+ const input = `
1579
+ import { tw } from '@mgcrea/react-native-tailwind';
1580
+
1581
+ function MyComponent() {
1582
+ const styles = tw\`bg-blue-500 dark:bg-blue-900 light:bg-blue-100\`;
1583
+ return null;
1584
+ }
1585
+ `;
1586
+
1587
+ const output = transform(input);
1588
+
1589
+ // Should have both conditionals
1590
+ expect(output).toContain('_twColorScheme === "dark"');
1591
+ expect(output).toContain('_twColorScheme === "light"');
1592
+ expect(output).toContain("_twStyles._dark_bg_blue_900");
1593
+ expect(output).toContain("_twStyles._light_bg_blue_100");
1594
+ expect(output).toContain("_twStyles._bg_blue_500");
1595
+ });
1596
+
1597
+ it("should combine color scheme modifiers with state modifiers", () => {
1598
+ const input = `
1599
+ import { tw } from '@mgcrea/react-native-tailwind';
1600
+
1601
+ function MyComponent() {
1602
+ const styles = tw\`bg-white dark:bg-gray-900 active:bg-blue-500\`;
1603
+ return null;
1604
+ }
1605
+ `;
1606
+
1607
+ const output = transform(input);
1608
+
1609
+ // Should have color scheme conditionals in style array
1610
+ expect(output).toContain("style: [");
1611
+ expect(output).toContain('_twColorScheme === "dark"');
1612
+
1613
+ // Should have activeStyle property (separate from color scheme)
1614
+ expect(output).toContain("activeStyle:");
1615
+ expect(output).toContain("_twStyles._active_bg_blue_500");
1616
+ });
1617
+
1618
+ it("should warn if tw with color scheme modifiers used outside component", () => {
1619
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
1620
+
1621
+ const input = `
1622
+ import { tw } from '@mgcrea/react-native-tailwind';
1623
+
1624
+ const globalStyles = tw\`bg-white dark:bg-gray-900\`;
1625
+ `;
1626
+
1627
+ const output = transform(input);
1628
+
1629
+ // Should warn about usage outside component
1630
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
1631
+ expect.stringContaining("Color scheme modifiers (dark:, light:) in tw/twStyle calls"),
1632
+ );
1633
+
1634
+ // Should not inject hook (no component scope)
1635
+ expect(output).not.toContain("useColorScheme");
1636
+
1637
+ // Should still generate styles but without runtime conditionals
1638
+ expect(output).toContain("_twStyles");
1639
+
1640
+ consoleWarnSpy.mockRestore();
1641
+ });
1642
+
1643
+ it("should handle tw with only dark: modifier (no base class)", () => {
1644
+ const input = `
1645
+ import { tw } from '@mgcrea/react-native-tailwind';
1646
+
1647
+ function MyComponent() {
1648
+ const styles = tw\`dark:bg-gray-900\`;
1649
+ return null;
1650
+ }
1651
+ `;
1652
+
1653
+ const output = transform(input);
1654
+
1655
+ // Should still generate style array
1656
+ expect(output).toContain("style: [");
1657
+ expect(output).toContain('_twColorScheme === "dark"');
1658
+ expect(output).toContain("_twStyles._dark_bg_gray_900");
1659
+ });
1660
+
1661
+ it("should work with custom color scheme hook import", () => {
1662
+ const input = `
1663
+ import { tw } from '@mgcrea/react-native-tailwind';
1664
+ import { useTheme } from '@react-navigation/native';
1665
+
1666
+ function MyComponent() {
1667
+ const styles = tw\`bg-white dark:bg-gray-900\`;
1668
+ return null;
1669
+ }
1670
+ `;
1671
+
1672
+ const options: PluginOptions = {
1673
+ colorScheme: {
1674
+ importFrom: "@react-navigation/native",
1675
+ importName: "useTheme",
1676
+ },
1677
+ };
1678
+
1679
+ const output = transform(input, options);
1680
+
1681
+ // Should use existing import (not duplicate)
1682
+ const themeImportCount = (output.match(/useTheme/g) ?? []).length;
1683
+ // Should appear in import statement and hook call
1684
+ expect(themeImportCount).toBeGreaterThanOrEqual(2);
1685
+
1686
+ // Should call the custom hook
1687
+ expect(output).toContain("useTheme()");
1688
+ });
1689
+
1690
+ it("should generate both style array and darkStyle/lightStyle properties", () => {
1691
+ const input = `
1692
+ import { tw } from '@mgcrea/react-native-tailwind';
1693
+
1694
+ function MyComponent() {
1695
+ const styles = tw\`bg-white dark:bg-gray-900 light:bg-gray-50\`;
1696
+ return null;
1697
+ }
1698
+ `;
1699
+
1700
+ const output = transform(input);
1701
+
1702
+ // Should have runtime conditional in style array
1703
+ expect(output).toContain("style: [");
1704
+ expect(output).toContain('_twColorScheme === "dark"');
1705
+ expect(output).toContain('_twColorScheme === "light"');
1706
+
1707
+ // Should ALSO have darkStyle and lightStyle properties for manual access
1708
+ expect(output).toContain("darkStyle:");
1709
+ expect(output).toContain("lightStyle:");
1710
+ expect(output).toContain("_twStyles._dark_bg_gray_900");
1711
+ expect(output).toContain("_twStyles._light_bg_gray_50");
1712
+ });
1713
+
1714
+ it("should allow accessing raw color values from darkStyle/lightStyle", () => {
1715
+ const input = `
1716
+ import { tw } from '@mgcrea/react-native-tailwind';
1717
+
1718
+ function MyComponent() {
1719
+ const btnStyles = tw\`bg-blue-500 dark:bg-blue-900\`;
1720
+ // User can access raw hex for Reanimated
1721
+ const darkBgColor = btnStyles.darkStyle?.backgroundColor;
1722
+ return null;
1723
+ }
1724
+ `;
1725
+
1726
+ const output = transform(input);
1727
+
1728
+ // Should have darkStyle property available
1729
+ expect(output).toContain("darkStyle:");
1730
+ expect(output).toContain("_twStyles._dark_bg_blue_900");
1731
+
1732
+ // The actual usage line should be preserved (TypeScript/Babel doesn't remove it)
1733
+ expect(output).toContain("btnStyles.darkStyle");
1734
+ });
1735
+
1736
+ // Platform modifier tests for tw/twStyle
1737
+ it("should transform tw with ios: modifier", () => {
1738
+ const input = `
1739
+ import { tw } from '@mgcrea/react-native-tailwind';
1740
+
1741
+ function MyComponent() {
1742
+ const styles = tw\`bg-white ios:p-6\`;
1743
+ return null;
1744
+ }
1745
+ `;
1746
+
1747
+ const output = transform(input);
1748
+
1749
+ // Should add Platform import
1750
+ expect(output).toContain("Platform");
1751
+ expect(output).toContain('from "react-native"');
1752
+
1753
+ // Should generate style array with Platform.select()
1754
+ expect(output).toContain("style: [");
1755
+ expect(output).toContain("Platform.select");
1756
+ expect(output).toContain("ios:");
1757
+ expect(output).toContain("_twStyles._ios_p_6");
1758
+ expect(output).toContain("_twStyles._bg_white");
1759
+
1760
+ // Should have StyleSheet.create
1761
+ expect(output).toContain("StyleSheet.create");
1762
+ });
1763
+
1764
+ it("should transform twStyle with android: modifier", () => {
1765
+ const input = `
1766
+ import { twStyle } from '@mgcrea/react-native-tailwind';
1767
+
1768
+ export const MyComponent = () => {
1769
+ const buttonStyles = twStyle('bg-blue-500 android:p-8');
1770
+ return null;
1771
+ };
1772
+ `;
1773
+
1774
+ const output = transform(input);
1775
+
1776
+ // Should add Platform import
1777
+ expect(output).toContain("Platform");
1778
+
1779
+ // Should generate style array with Platform.select()
1780
+ expect(output).toContain("style: [");
1781
+ expect(output).toContain("Platform.select");
1782
+ expect(output).toContain("android:");
1783
+ expect(output).toContain("_twStyles._android_p_8");
1784
+ expect(output).toContain("_twStyles._bg_blue_500");
1785
+ });
1786
+
1787
+ it("should transform tw with multiple platform modifiers", () => {
1788
+ const input = `
1789
+ import { tw } from '@mgcrea/react-native-tailwind';
1790
+
1791
+ function MyComponent() {
1792
+ const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
1793
+ return null;
1794
+ }
1795
+ `;
1796
+
1797
+ const output = transform(input);
1798
+
1799
+ // Should generate Platform.select() with all platforms
1800
+ expect(output).toContain("Platform.select");
1801
+ expect(output).toContain("ios:");
1802
+ expect(output).toContain("android:");
1803
+ expect(output).toContain("web:");
1804
+ expect(output).toContain("_twStyles._ios_p_6");
1805
+ expect(output).toContain("_twStyles._android_p_8");
1806
+ expect(output).toContain("_twStyles._web_p_4");
1807
+ });
1808
+
1809
+ it("should combine platform modifiers with color-scheme modifiers", () => {
1810
+ const input = `
1811
+ import { tw } from '@mgcrea/react-native-tailwind';
1812
+
1813
+ function MyComponent() {
1814
+ const styles = tw\`bg-white ios:p-6 dark:bg-gray-900\`;
1815
+ return null;
1816
+ }
1817
+ `;
1818
+
1819
+ const output = transform(input);
1820
+
1821
+ // Should have both Platform and useColorScheme
1822
+ expect(output).toContain("Platform");
1823
+ expect(output).toContain("useColorScheme");
1824
+ expect(output).toContain("_twColorScheme");
1825
+
1826
+ // Should have both conditionals in style array
1827
+ expect(output).toContain("Platform.select");
1828
+ expect(output).toContain('_twColorScheme === "dark"');
1829
+ });
1830
+
1831
+ it("should generate iosStyle/androidStyle/webStyle properties for manual access", () => {
1832
+ const input = `
1833
+ import { tw } from '@mgcrea/react-native-tailwind';
1834
+
1835
+ function MyComponent() {
1836
+ const styles = tw\`bg-white ios:p-6 android:p-8 web:p-4\`;
1837
+ return null;
1838
+ }
1839
+ `;
1840
+
1841
+ const output = transform(input);
1842
+
1843
+ // Should have separate platform style properties
1844
+ expect(output).toContain("iosStyle:");
1845
+ expect(output).toContain("_twStyles._ios_p_6");
1846
+ expect(output).toContain("androidStyle:");
1847
+ expect(output).toContain("_twStyles._android_p_8");
1848
+ expect(output).toContain("webStyle:");
1849
+ expect(output).toContain("_twStyles._web_p_4");
1850
+
1851
+ // Should also have runtime Platform.select() in style array
1852
+ expect(output).toContain("Platform.select");
1853
+ });
1854
+
1855
+ it("should work with only platform modifiers (no base class)", () => {
1856
+ const input = `
1857
+ import { tw } from '@mgcrea/react-native-tailwind';
1858
+
1859
+ function MyComponent() {
1860
+ const styles = tw\`ios:p-6 android:p-8\`;
1861
+ return null;
1862
+ }
1863
+ `;
1864
+
1865
+ const output = transform(input);
1866
+
1867
+ // Should generate Platform.select() even without base classes
1868
+ expect(output).toContain("Platform.select");
1869
+ expect(output).toContain("_twStyles._ios_p_6");
1870
+ expect(output).toContain("_twStyles._android_p_8");
1871
+ });
1872
+
1873
+ it("should allow accessing platform-specific styles manually", () => {
1874
+ const input = `
1875
+ import { tw } from '@mgcrea/react-native-tailwind';
1876
+
1877
+ function MyComponent() {
1878
+ const btnStyles = tw\`bg-blue-500 ios:p-6\`;
1879
+ const iosPadding = btnStyles.iosStyle;
1880
+ return null;
1881
+ }
1882
+ `;
1883
+
1884
+ const output = transform(input);
1885
+
1886
+ // Should have iosStyle property available
1887
+ expect(output).toContain("iosStyle:");
1888
+ expect(output).toContain("_twStyles._ios_p_6");
1889
+
1890
+ // The actual usage line should be preserved
1891
+ expect(output).toContain("btnStyles.iosStyle");
1892
+ });
1893
+
1894
+ it("should combine state modifiers with platform modifiers", () => {
1895
+ const input = `
1896
+ import { tw } from '@mgcrea/react-native-tailwind';
1897
+
1898
+ function MyComponent() {
1899
+ const styles = tw\`bg-white active:bg-blue-500 ios:p-6\`;
1900
+ return null;
1901
+ }
1902
+ `;
1903
+
1904
+ const output = transform(input);
1905
+
1906
+ // Should have both activeStyle and platform modifiers
1907
+ expect(output).toContain("activeStyle:");
1908
+ expect(output).toContain("_twStyles._active_bg_blue_500");
1909
+ expect(output).toContain("Platform.select");
1910
+ expect(output).toContain("_twStyles._ios_p_6");
1911
+ });
1912
+ });