@mgcrea/react-native-tailwind 0.8.0 → 0.9.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 (50) hide show
  1. package/README.md +152 -0
  2. package/dist/babel/config-loader.ts +2 -0
  3. package/dist/babel/index.cjs +178 -5
  4. package/dist/babel/plugin.d.ts +2 -0
  5. package/dist/babel/plugin.test.ts +241 -0
  6. package/dist/babel/plugin.ts +187 -10
  7. package/dist/babel/utils/attributeMatchers.test.ts +294 -0
  8. package/dist/babel/utils/componentSupport.test.ts +426 -0
  9. package/dist/babel/utils/platformModifierProcessing.d.ts +30 -0
  10. package/dist/babel/utils/platformModifierProcessing.ts +80 -0
  11. package/dist/babel/utils/styleInjection.d.ts +4 -0
  12. package/dist/babel/utils/styleInjection.ts +28 -0
  13. package/dist/babel/utils/styleTransforms.ts +1 -0
  14. package/dist/parser/colors.test.js +1 -1
  15. package/dist/parser/index.d.ts +2 -2
  16. package/dist/parser/index.js +1 -1
  17. package/dist/parser/modifiers.d.ts +20 -2
  18. package/dist/parser/modifiers.js +1 -1
  19. package/dist/runtime.cjs +1 -1
  20. package/dist/runtime.cjs.map +4 -4
  21. package/dist/runtime.js +1 -1
  22. package/dist/runtime.js.map +4 -4
  23. package/dist/stubs/tw.test.js +1 -0
  24. package/dist/utils/flattenColors.d.ts +1 -0
  25. package/dist/utils/flattenColors.js +1 -1
  26. package/dist/utils/flattenColors.test.js +1 -1
  27. package/package.json +6 -5
  28. package/src/babel/config-loader.ts +2 -0
  29. package/src/babel/plugin.test.ts +241 -0
  30. package/src/babel/plugin.ts +187 -10
  31. package/src/babel/utils/attributeMatchers.test.ts +294 -0
  32. package/src/babel/utils/componentSupport.test.ts +426 -0
  33. package/src/babel/utils/platformModifierProcessing.ts +80 -0
  34. package/src/babel/utils/styleInjection.ts +28 -0
  35. package/src/babel/utils/styleTransforms.ts +1 -0
  36. package/src/parser/aspectRatio.ts +1 -0
  37. package/src/parser/borders.ts +2 -0
  38. package/src/parser/colors.test.ts +32 -0
  39. package/src/parser/colors.ts +2 -0
  40. package/src/parser/index.ts +10 -3
  41. package/src/parser/layout.ts +2 -0
  42. package/src/parser/modifiers.ts +38 -4
  43. package/src/parser/placeholder.ts +1 -0
  44. package/src/parser/sizing.ts +1 -0
  45. package/src/parser/spacing.ts +1 -0
  46. package/src/parser/transforms.ts +5 -0
  47. package/src/parser/typography.ts +2 -0
  48. package/src/stubs/tw.test.ts +27 -0
  49. package/src/utils/flattenColors.test.ts +100 -0
  50. package/src/utils/flattenColors.ts +3 -1
@@ -0,0 +1 @@
1
+ var _vitest=require("vitest");var _tw=require("./tw");(0,_vitest.describe)("tw stub",function(){(0,_vitest.it)("should throw error when tw() is called without Babel transformation",function(){(0,_vitest.expect)(function(){return(0,_tw.tw)`bg-blue-500`;}).toThrow("tw() must be transformed by the Babel plugin. "+"Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. "+"For runtime parsing, use: import { tw } from '@mgcrea/react-native-tailwind/runtime'");});(0,_vitest.it)("should throw error when twStyle() is called without Babel transformation",function(){(0,_vitest.expect)(function(){return(0,_tw.twStyle)("bg-blue-500");}).toThrow("twStyle() must be transformed by the Babel plugin. "+"Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. "+"For runtime parsing, use: import { twStyle } from '@mgcrea/react-native-tailwind/runtime'");});(0,_vitest.it)("should throw error with template literal interpolation",function(){var dynamic="active";(0,_vitest.expect)(function(){return(0,_tw.tw)`bg-blue-500 ${dynamic}:bg-blue-700`;}).toThrow("tw() must be transformed by the Babel plugin");});});
@@ -7,6 +7,7 @@ type NestedColors = {
7
7
  /**
8
8
  * Flatten nested color objects into flat key-value map
9
9
  * Example: { brand: { light: '#fff', dark: '#000' } } => { 'brand-light': '#fff', 'brand-dark': '#000' }
10
+ * Special handling for DEFAULT: { primary: { DEFAULT: '#000', 500: '#333' } } => { 'primary': '#000', 'primary-500': '#333' }
10
11
  *
11
12
  * @param colors - Nested color object where values can be strings or objects
12
13
  * @param prefix - Optional prefix for nested keys (used for recursion)
@@ -1 +1 @@
1
- var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.flattenColors=flattenColors;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));function flattenColors(colors){var prefix=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"";var result={};for(var _ref of Object.entries(colors)){var _ref2=(0,_slicedToArray2.default)(_ref,2);var _key=_ref2[0];var value=_ref2[1];var newKey=prefix?`${prefix}-${_key}`:_key;if(typeof value==="string"){result[newKey]=value;}else if(typeof value==="object"&&value!==null){Object.assign(result,flattenColors(value,newKey));}}return result;}
1
+ var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.flattenColors=flattenColors;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));function flattenColors(colors){var prefix=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"";var result={};for(var _ref of Object.entries(colors)){var _ref2=(0,_slicedToArray2.default)(_ref,2);var _key=_ref2[0];var value=_ref2[1];var newKey=_key==="DEFAULT"&&prefix?prefix:prefix?`${prefix}-${_key}`:_key;if(typeof value==="string"){result[newKey]=value;}else if(typeof value==="object"&&value!==null){Object.assign(result,flattenColors(value,newKey));}}return result;}
@@ -1 +1 @@
1
- var _vitest=require("vitest");var _flattenColors=require("./flattenColors");(0,_vitest.describe)("flattenColors",function(){(0,_vitest.it)("should handle flat color objects",function(){var colors={red:"#ff0000",blue:"#0000ff",green:"#00ff00"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#ff0000",blue:"#0000ff",green:"#00ff00"});});(0,_vitest.it)("should flatten single-level nested objects",function(){var colors={brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-primary":"#ff6b6b","brand-secondary":"#4ecdc4"});});(0,_vitest.it)("should flatten multi-level nested objects",function(){var colors={brand:{light:{primary:"#ffcccc",secondary:"#ccffff"},dark:{primary:"#990000",secondary:"#006666"}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-light-primary":"#ffcccc","brand-light-secondary":"#ccffff","brand-dark-primary":"#990000","brand-dark-secondary":"#006666"});});(0,_vitest.it)("should handle mixed flat and nested objects",function(){var colors={white:"#ffffff",black:"#000000",brand:{primary:"#ff6b6b",secondary:"#4ecdc4"},accent:"#ffe66d"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({white:"#ffffff",black:"#000000","brand-primary":"#ff6b6b","brand-secondary":"#4ecdc4",accent:"#ffe66d"});});(0,_vitest.it)("should handle Tailwind-style color scale objects",function(){var colors={gray:{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","500":"#6b7280","900":"#111827"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"gray-50":"#f9fafb","gray-100":"#f3f4f6","gray-200":"#e5e7eb","gray-500":"#6b7280","gray-900":"#111827"});});(0,_vitest.it)("should handle empty object",function(){(0,_vitest.expect)((0,_flattenColors.flattenColors)({})).toEqual({});});(0,_vitest.it)("should handle single color",function(){var colors={primary:"#ff0000"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({primary:"#ff0000"});});(0,_vitest.it)("should handle deeply nested objects (3+ levels)",function(){var colors={theme:{light:{brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}},dark:{brand:{primary:"#990000",secondary:"#006666"}}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"theme-light-brand-primary":"#ff6b6b","theme-light-brand-secondary":"#4ecdc4","theme-dark-brand-primary":"#990000","theme-dark-brand-secondary":"#006666"});});(0,_vitest.it)("should handle numeric keys",function(){var colors={blue:{"100":"#dbeafe","500":"#3b82f6","900":"#1e3a8a"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"blue-100":"#dbeafe","blue-500":"#3b82f6","blue-900":"#1e3a8a"});});(0,_vitest.it)("should handle keys with hyphens",function(){var colors={"brand-primary":"#ff0000","brand-secondary":{light:"#00ff00",dark:"#006600"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-primary":"#ff0000","brand-secondary-light":"#00ff00","brand-secondary-dark":"#006600"});});(0,_vitest.it)("should handle uppercase and lowercase hex values",function(){var colors={red:"#FF0000",blue:"#0000ff",green:"#00Ff00"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#FF0000",blue:"#0000ff",green:"#00Ff00"});});(0,_vitest.it)("should handle 3-digit hex values",function(){var colors={red:"#f00",blue:"#00f",green:"#0f0"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#f00",blue:"#00f",green:"#0f0"});});(0,_vitest.it)("should handle 8-digit hex values (with alpha)",function(){var colors={"red-50":"#ff000080","blue-50":"#0000ff80"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"red-50":"#ff000080","blue-50":"#0000ff80"});});(0,_vitest.it)("should handle complex real-world Tailwind config",function(){var colors={transparent:"transparent",current:"currentColor",white:"#ffffff",black:"#000000",gray:{"50":"#f9fafb","100":"#f3f4f6","500":"#6b7280","900":"#111827"},brand:{primary:"#ff6b6b",secondary:"#4ecdc4",accent:{light:"#ffe66d",dark:"#ffb900"}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({transparent:"transparent",current:"currentColor",white:"#ffffff",black:"#000000","gray-50":"#f9fafb","gray-100":"#f3f4f6","gray-500":"#6b7280","gray-900":"#111827","brand-primary":"#ff6b6b","brand-secondary":"#4ecdc4","brand-accent-light":"#ffe66d","brand-accent-dark":"#ffb900"});});(0,_vitest.it)("should not mutate input object",function(){var colors={brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}};var original=JSON.parse(JSON.stringify(colors));(0,_flattenColors.flattenColors)(colors);(0,_vitest.expect)(colors).toEqual(original);});(0,_vitest.it)("should handle undefined values gracefully",function(){var colors={red:"#ff0000",blue:undefined,green:"#00ff00"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#ff0000",green:"#00ff00"});});(0,_vitest.it)("should handle special color keywords",function(){var colors={transparent:"transparent",current:"currentColor",inherit:"inherit"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({transparent:"transparent",current:"currentColor",inherit:"inherit"});});(0,_vitest.it)("should handle RGB/RGBA color values",function(){var colors={primary:"rgb(255, 0, 0)",secondary:"rgba(0, 255, 0, 0.5)"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({primary:"rgb(255, 0, 0)",secondary:"rgba(0, 255, 0, 0.5)"});});(0,_vitest.it)("should handle very deeply nested structures (stress test)",function(){var colors={level1:{level2:{level3:{level4:{level5:"#ff0000"}}}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"level1-level2-level3-level4-level5":"#ff0000"});});(0,_vitest.it)("should handle camelCase keys",function(){var colors={brandPrimary:"#ff0000",accentColor:{lightShade:"#ffcccc",darkShade:"#cc0000"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({brandPrimary:"#ff0000","accentColor-lightShade":"#ffcccc","accentColor-darkShade":"#cc0000"});});(0,_vitest.it)("should produce consistent output",function(){var colors={brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}};var result1=(0,_flattenColors.flattenColors)(colors);var result2=(0,_flattenColors.flattenColors)(colors);var result3=(0,_flattenColors.flattenColors)(colors);(0,_vitest.expect)(result1).toEqual(result2);(0,_vitest.expect)(result2).toEqual(result3);});(0,_vitest.it)("should maintain key order (insertion order)",function(){var colors={z:"#000001",a:"#000002",m:"#000003"};var flattened=(0,_flattenColors.flattenColors)(colors);var keys=Object.keys(flattened);(0,_vitest.expect)(keys).toEqual(["z","a","m"]);});});
1
+ var _vitest=require("vitest");var _flattenColors=require("./flattenColors");(0,_vitest.describe)("flattenColors",function(){(0,_vitest.it)("should handle flat color objects",function(){var colors={red:"#ff0000",blue:"#0000ff",green:"#00ff00"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#ff0000",blue:"#0000ff",green:"#00ff00"});});(0,_vitest.it)("should flatten single-level nested objects",function(){var colors={brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-primary":"#ff6b6b","brand-secondary":"#4ecdc4"});});(0,_vitest.it)("should flatten multi-level nested objects",function(){var colors={brand:{light:{primary:"#ffcccc",secondary:"#ccffff"},dark:{primary:"#990000",secondary:"#006666"}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-light-primary":"#ffcccc","brand-light-secondary":"#ccffff","brand-dark-primary":"#990000","brand-dark-secondary":"#006666"});});(0,_vitest.it)("should handle mixed flat and nested objects",function(){var colors={white:"#ffffff",black:"#000000",brand:{primary:"#ff6b6b",secondary:"#4ecdc4"},accent:"#ffe66d"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({white:"#ffffff",black:"#000000","brand-primary":"#ff6b6b","brand-secondary":"#4ecdc4",accent:"#ffe66d"});});(0,_vitest.it)("should handle Tailwind-style color scale objects",function(){var colors={gray:{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","500":"#6b7280","900":"#111827"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"gray-50":"#f9fafb","gray-100":"#f3f4f6","gray-200":"#e5e7eb","gray-500":"#6b7280","gray-900":"#111827"});});(0,_vitest.it)("should handle empty object",function(){(0,_vitest.expect)((0,_flattenColors.flattenColors)({})).toEqual({});});(0,_vitest.it)("should handle single color",function(){var colors={primary:"#ff0000"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({primary:"#ff0000"});});(0,_vitest.it)("should handle deeply nested objects (3+ levels)",function(){var colors={theme:{light:{brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}},dark:{brand:{primary:"#990000",secondary:"#006666"}}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"theme-light-brand-primary":"#ff6b6b","theme-light-brand-secondary":"#4ecdc4","theme-dark-brand-primary":"#990000","theme-dark-brand-secondary":"#006666"});});(0,_vitest.it)("should handle numeric keys",function(){var colors={blue:{"100":"#dbeafe","500":"#3b82f6","900":"#1e3a8a"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"blue-100":"#dbeafe","blue-500":"#3b82f6","blue-900":"#1e3a8a"});});(0,_vitest.it)("should handle keys with hyphens",function(){var colors={"brand-primary":"#ff0000","brand-secondary":{light:"#00ff00",dark:"#006600"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-primary":"#ff0000","brand-secondary-light":"#00ff00","brand-secondary-dark":"#006600"});});(0,_vitest.it)("should handle uppercase and lowercase hex values",function(){var colors={red:"#FF0000",blue:"#0000ff",green:"#00Ff00"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#FF0000",blue:"#0000ff",green:"#00Ff00"});});(0,_vitest.it)("should handle 3-digit hex values",function(){var colors={red:"#f00",blue:"#00f",green:"#0f0"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#f00",blue:"#00f",green:"#0f0"});});(0,_vitest.it)("should handle 8-digit hex values (with alpha)",function(){var colors={"red-50":"#ff000080","blue-50":"#0000ff80"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"red-50":"#ff000080","blue-50":"#0000ff80"});});(0,_vitest.it)("should handle complex real-world Tailwind config",function(){var colors={transparent:"transparent",current:"currentColor",white:"#ffffff",black:"#000000",gray:{"50":"#f9fafb","100":"#f3f4f6","500":"#6b7280","900":"#111827"},brand:{primary:"#ff6b6b",secondary:"#4ecdc4",accent:{light:"#ffe66d",dark:"#ffb900"}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({transparent:"transparent",current:"currentColor",white:"#ffffff",black:"#000000","gray-50":"#f9fafb","gray-100":"#f3f4f6","gray-500":"#6b7280","gray-900":"#111827","brand-primary":"#ff6b6b","brand-secondary":"#4ecdc4","brand-accent-light":"#ffe66d","brand-accent-dark":"#ffb900"});});(0,_vitest.it)("should not mutate input object",function(){var colors={brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}};var original=JSON.parse(JSON.stringify(colors));(0,_flattenColors.flattenColors)(colors);(0,_vitest.expect)(colors).toEqual(original);});(0,_vitest.it)("should handle undefined values gracefully",function(){var colors={red:"#ff0000",blue:undefined,green:"#00ff00"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({red:"#ff0000",green:"#00ff00"});});(0,_vitest.it)("should handle special color keywords",function(){var colors={transparent:"transparent",current:"currentColor",inherit:"inherit"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({transparent:"transparent",current:"currentColor",inherit:"inherit"});});(0,_vitest.it)("should handle RGB/RGBA color values",function(){var colors={primary:"rgb(255, 0, 0)",secondary:"rgba(0, 255, 0, 0.5)"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({primary:"rgb(255, 0, 0)",secondary:"rgba(0, 255, 0, 0.5)"});});(0,_vitest.it)("should handle very deeply nested structures (stress test)",function(){var colors={level1:{level2:{level3:{level4:{level5:"#ff0000"}}}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"level1-level2-level3-level4-level5":"#ff0000"});});(0,_vitest.it)("should handle camelCase keys",function(){var colors={brandPrimary:"#ff0000",accentColor:{lightShade:"#ffcccc",darkShade:"#cc0000"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({brandPrimary:"#ff0000","accentColor-lightShade":"#ffcccc","accentColor-darkShade":"#cc0000"});});(0,_vitest.it)("should produce consistent output",function(){var colors={brand:{primary:"#ff6b6b",secondary:"#4ecdc4"}};var result1=(0,_flattenColors.flattenColors)(colors);var result2=(0,_flattenColors.flattenColors)(colors);var result3=(0,_flattenColors.flattenColors)(colors);(0,_vitest.expect)(result1).toEqual(result2);(0,_vitest.expect)(result2).toEqual(result3);});(0,_vitest.it)("should maintain key order (insertion order)",function(){var colors={z:"#000001",a:"#000002",m:"#000003"};var flattened=(0,_flattenColors.flattenColors)(colors);var keys=Object.keys(flattened);(0,_vitest.expect)(keys).toEqual(["z","a","m"]);});(0,_vitest.it)("should handle DEFAULT key in color scale objects",function(){var colors={primary:{"50":"#eefdfd","100":"#d4f9f9","200":"#aef2f3","500":"#1bacb5","900":"#1e4f5b",DEFAULT:"#1bacb5"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({primary:"#1bacb5","primary-50":"#eefdfd","primary-100":"#d4f9f9","primary-200":"#aef2f3","primary-500":"#1bacb5","primary-900":"#1e4f5b"});});(0,_vitest.it)("should handle DEFAULT key with multiple color scales",function(){var colors={primary:{DEFAULT:"#1bacb5","500":"#1bacb5"},secondary:{DEFAULT:"#ff6b6b","500":"#ff6b6b"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({primary:"#1bacb5","primary-500":"#1bacb5",secondary:"#ff6b6b","secondary-500":"#ff6b6b"});});(0,_vitest.it)("should handle DEFAULT key in nested structures",function(){var colors={brand:{primary:{DEFAULT:"#1bacb5",light:"#d4f9f9",dark:"#0e343e"}}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"brand-primary":"#1bacb5","brand-primary-light":"#d4f9f9","brand-primary-dark":"#0e343e"});});(0,_vitest.it)("should handle DEFAULT at top level (edge case)",function(){var colors={DEFAULT:"#000000",primary:"#ff0000"};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({DEFAULT:"#000000",primary:"#ff0000"});});(0,_vitest.it)("should handle mixed DEFAULT and regular keys",function(){var colors={gray:{"50":"#f9fafb","100":"#f3f4f6",DEFAULT:"#6b7280","500":"#6b7280","900":"#111827"},white:"#ffffff",brand:{DEFAULT:"#ff6b6b",accent:"#4ecdc4"}};(0,_vitest.expect)((0,_flattenColors.flattenColors)(colors)).toEqual({"gray-50":"#f9fafb","gray-100":"#f3f4f6",gray:"#6b7280","gray-500":"#6b7280","gray-900":"#111827",white:"#ffffff",brand:"#ff6b6b","brand-accent":"#4ecdc4"});});});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.8.0",
3
+ "version": "0.9.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",
@@ -46,7 +46,7 @@
46
46
  "lint": "eslint src/",
47
47
  "prettify": "prettier --write src/",
48
48
  "check": "tsc --noEmit",
49
- "spec": "vitest --run",
49
+ "spec": "vitest --run --coverage",
50
50
  "test": "npm run lint && npm run check && npm run spec",
51
51
  "prepare": "npm run build"
52
52
  },
@@ -69,7 +69,8 @@
69
69
  "@testing-library/react-native": "^13.3.3",
70
70
  "@types/babel__core": "^7.20.5",
71
71
  "@types/babel__traverse": "^7.28.0",
72
- "@types/react": "^19.2.5",
72
+ "@types/react": "^19.2.6",
73
+ "@vitest/coverage-v8": "^4.0.13",
73
74
  "babel-plugin-module-resolver": "^5.0.2",
74
75
  "esbuild": "^0.27.0",
75
76
  "eslint": "^9.39.1",
@@ -79,12 +80,12 @@
79
80
  "react": "^19.2.0",
80
81
  "react-native": "0.82.1",
81
82
  "typescript": "^5.9.3",
82
- "vitest": "^4.0.10"
83
+ "vitest": "^4.0.13"
83
84
  },
84
85
  "engines": {
85
86
  "node": ">=18"
86
87
  },
87
- "packageManager": "pnpm@10.22.0",
88
+ "packageManager": "pnpm@10.23.0",
88
89
  "publishConfig": {
89
90
  "access": "public"
90
91
  },
@@ -72,6 +72,7 @@ export function loadTailwindConfig(configPath: string): TailwindConfig | null {
72
72
  configCache.set(configPath, resolved);
73
73
  return resolved;
74
74
  } catch (error) {
75
+ /* v8 ignore next 3 */
75
76
  if (process.env.NODE_ENV !== "production") {
76
77
  console.warn(`[react-native-tailwind] Failed to load config from ${configPath}:`, error);
77
78
  }
@@ -98,6 +99,7 @@ export function extractCustomColors(filename: string): Record<string, string> {
98
99
  }
99
100
 
100
101
  // Warn if using theme.colors instead of theme.extend.colors
102
+ /* v8 ignore next 5 */
101
103
  if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {
102
104
  console.warn(
103
105
  "[react-native-tailwind] Using theme.colors will override all default colors. " +
@@ -480,3 +480,244 @@ describe("Babel plugin - placeholder: modifier transformation", () => {
480
480
  consoleSpy.mockRestore();
481
481
  });
482
482
  });
483
+
484
+ describe("Babel plugin - platform modifier transformation", () => {
485
+ it("should transform platform modifiers to Platform.select()", () => {
486
+ const input = `
487
+ import React from 'react';
488
+ import { View } from 'react-native';
489
+
490
+ export function Component() {
491
+ return (
492
+ <View className="p-4 ios:p-6 android:p-8" />
493
+ );
494
+ }
495
+ `;
496
+
497
+ const output = transform(input, undefined, true);
498
+
499
+ // Should import Platform from react-native
500
+ expect(output).toContain("Platform");
501
+ expect(output).toMatch(/import.*Platform.*from ['"]react-native['"]/);
502
+
503
+ // Should generate Platform.select()
504
+ expect(output).toContain("Platform.select");
505
+
506
+ // Should have base padding style
507
+ expect(output).toContain("_p_4");
508
+
509
+ // Should have iOS and Android specific styles
510
+ expect(output).toContain("_ios_p_6");
511
+ expect(output).toContain("_android_p_8");
512
+
513
+ // Should have correct style values in StyleSheet.create
514
+ expect(output).toMatch(/padding:\s*16/); // p-4
515
+ expect(output).toMatch(/padding:\s*24/); // p-6 (ios)
516
+ expect(output).toMatch(/padding:\s*32/); // p-8 (android)
517
+ });
518
+
519
+ it("should support multiple platform modifiers on same element", () => {
520
+ const input = `
521
+ import React from 'react';
522
+ import { View } from 'react-native';
523
+
524
+ export function Component() {
525
+ return (
526
+ <View className="bg-white ios:bg-blue-50 android:bg-green-50 p-4 ios:p-6 android:p-8" />
527
+ );
528
+ }
529
+ `;
530
+
531
+ const output = transform(input, undefined, true);
532
+
533
+ // Should have Platform import
534
+ expect(output).toContain("Platform");
535
+
536
+ // Should have base styles (combined key)
537
+ expect(output).toContain("_bg_white_p_4");
538
+
539
+ // Should have iOS specific styles (combined key for multiple ios: modifiers)
540
+ expect(output).toContain("_ios_bg_blue_50_p_6");
541
+
542
+ // Should have Android specific styles (combined key for multiple android: modifiers)
543
+ expect(output).toContain("_android_bg_green_50_p_8");
544
+
545
+ // Should contain Platform.select with both platforms
546
+ expect(output).toMatch(/Platform\.select\s*\(\s*\{[\s\S]*ios:/);
547
+ expect(output).toMatch(/Platform\.select\s*\(\s*\{[\s\S]*android:/);
548
+ });
549
+
550
+ it("should support web platform modifier", () => {
551
+ const input = `
552
+ import React from 'react';
553
+ import { View } from 'react-native';
554
+
555
+ export function Component() {
556
+ return (
557
+ <View className="p-4 web:p-2" />
558
+ );
559
+ }
560
+ `;
561
+
562
+ const output = transform(input, undefined, true);
563
+
564
+ // Should have Platform.select with web
565
+ expect(output).toContain("Platform.select");
566
+ expect(output).toContain("web:");
567
+ expect(output).toContain("_web_p_2");
568
+ });
569
+
570
+ it("should work with platform modifiers on all components", () => {
571
+ const input = `
572
+ import React from 'react';
573
+ import { View, Text, ScrollView } from 'react-native';
574
+
575
+ export function Component() {
576
+ return (
577
+ <View className="ios:bg-blue-500 android:bg-green-500">
578
+ <Text className="ios:text-lg android:text-xl">Platform text</Text>
579
+ <ScrollView contentContainerClassName="ios:p-4 android:p-8" />
580
+ </View>
581
+ );
582
+ }
583
+ `;
584
+
585
+ const output = transform(input, undefined, true);
586
+
587
+ // Should work on View - check for Platform.select separately (not checking style= format)
588
+ expect(output).toContain("Platform.select");
589
+
590
+ // Should work on Text
591
+ expect(output).toContain("_ios_text_lg");
592
+ expect(output).toContain("_android_text_xl");
593
+
594
+ // Should work on ScrollView contentContainerStyle
595
+ expect(output).toContain("contentContainerStyle");
596
+ });
597
+
598
+ it("should combine platform modifiers with state modifiers", () => {
599
+ const input = `
600
+ import React from 'react';
601
+ import { Pressable, Text } from 'react-native';
602
+
603
+ export function Component() {
604
+ return (
605
+ <Pressable className="bg-blue-500 active:bg-blue-700 ios:shadow-md android:shadow-sm p-4">
606
+ <Text className="text-white">Button</Text>
607
+ </Pressable>
608
+ );
609
+ }
610
+ `;
611
+
612
+ const output = transform(input, undefined, true);
613
+
614
+ // Should have Platform.select for platform modifiers
615
+ expect(output).toContain("Platform.select");
616
+ expect(output).toContain("_ios_shadow_md");
617
+ expect(output).toContain("_android_shadow_sm");
618
+
619
+ // Should have state modifier function for active
620
+ expect(output).toMatch(/\(\s*\{\s*pressed\s*\}\s*\)\s*=>/);
621
+ expect(output).toContain("pressed");
622
+ expect(output).toContain("_active_bg_blue_700");
623
+
624
+ // Should have base styles
625
+ expect(output).toContain("_bg_blue_500");
626
+ expect(output).toContain("_p_4");
627
+ });
628
+
629
+ it("should handle platform-specific colors", () => {
630
+ const input = `
631
+ import React from 'react';
632
+ import { View, Text } from 'react-native';
633
+
634
+ export function Component() {
635
+ return (
636
+ <View className="bg-gray-100 ios:bg-blue-50 android:bg-green-50">
637
+ <Text className="text-gray-900 ios:text-blue-900 android:text-green-900">
638
+ Platform colors
639
+ </Text>
640
+ </View>
641
+ );
642
+ }
643
+ `;
644
+
645
+ const output = transform(input, undefined, true);
646
+
647
+ // Should have color values in StyleSheet
648
+ expect(output).toMatch(/#[0-9A-F]{6}/i); // Hex color format
649
+
650
+ // Should have platform-specific color classes
651
+ expect(output).toContain("_ios_text_blue_900");
652
+ expect(output).toContain("_android_text_green_900");
653
+ });
654
+
655
+ it("should only add Platform import once when needed", () => {
656
+ const input = `
657
+ import React from 'react';
658
+ import { View } from 'react-native';
659
+
660
+ export function Component() {
661
+ return (
662
+ <>
663
+ <View className="ios:p-4" />
664
+ <View className="android:p-8" />
665
+ <View className="ios:bg-blue-500" />
666
+ </>
667
+ );
668
+ }
669
+ `;
670
+
671
+ const output = transform(input, undefined, true);
672
+
673
+ // Should have Platform import
674
+ expect(output).toContain("Platform");
675
+
676
+ // Count how many times Platform is imported (should be once)
677
+ const platformImports = output.match(/import.*Platform.*from ['"]react-native['"]/g);
678
+ expect(platformImports).toHaveLength(1);
679
+ });
680
+
681
+ it("should merge with existing Platform import", () => {
682
+ const input = `
683
+ import React from 'react';
684
+ import { View, Platform } from 'react-native';
685
+
686
+ export function Component() {
687
+ return <View className="ios:p-4 android:p-8" />;
688
+ }
689
+ `;
690
+
691
+ const output = transform(input, undefined, true);
692
+
693
+ // Should still use Platform.select
694
+ expect(output).toContain("Platform.select");
695
+
696
+ // Should not duplicate Platform import - Platform appears in import and Platform.select calls
697
+ expect(output).toMatch(/Platform.*react-native/);
698
+ });
699
+
700
+ it("should handle platform modifiers without base classes", () => {
701
+ const input = `
702
+ import React from 'react';
703
+ import { View } from 'react-native';
704
+
705
+ export function Component() {
706
+ return <View className="ios:p-6 android:p-8" />;
707
+ }
708
+ `;
709
+
710
+ const output = transform(input, undefined, true);
711
+
712
+ // Should only have Platform.select, no base style
713
+ expect(output).toContain("Platform.select");
714
+ expect(output).toContain("_ios_p_6");
715
+ expect(output).toContain("_android_p_8");
716
+
717
+ // Should not have generic padding without platform prefix
718
+ // Check that non-platform-prefixed style keys don't exist
719
+ expect(output).not.toMatch(/(?<!_ios|_android|_web)_p_4:/);
720
+ expect(output).not.toMatch(/(?<!_ios|_android|_web)_p_6:/);
721
+ expect(output).not.toMatch(/(?<!_ios|_android|_web)_p_8:/);
722
+ });
723
+ });
@@ -5,7 +5,14 @@
5
5
 
6
6
  import type { NodePath, PluginObj, PluginPass } from "@babel/core";
7
7
  import * as BabelTypes from "@babel/types";
8
- import { parseClassName, parsePlaceholderClasses, splitModifierClasses } from "../parser/index.js";
8
+ import type { ParsedModifier, StateModifierType } from "../parser/index.js";
9
+ import {
10
+ isPlatformModifier,
11
+ isStateModifier,
12
+ parseClassName,
13
+ parsePlaceholderClasses,
14
+ splitModifierClasses,
15
+ } from "../parser/index.js";
9
16
  import type { StyleObject } from "../types/core.js";
10
17
  import { generateStyleKey } from "../utils/styleKey.js";
11
18
  import { extractCustomColors } from "./config-loader.js";
@@ -17,10 +24,11 @@ import {
17
24
  getTargetStyleProp,
18
25
  isAttributeSupported,
19
26
  } from "./utils/attributeMatchers.js";
20
- import { getComponentModifierSupport } from "./utils/componentSupport.js";
27
+ import { getComponentModifierSupport, getStatePropertyForModifier } from "./utils/componentSupport.js";
21
28
  import { processDynamicExpression } from "./utils/dynamicProcessing.js";
22
29
  import { createStyleFunction, processStaticClassNameWithModifiers } from "./utils/modifierProcessing.js";
23
- import { addStyleSheetImport, injectStylesAtTop } from "./utils/styleInjection.js";
30
+ import { processPlatformModifiers } from "./utils/platformModifierProcessing.js";
31
+ import { addPlatformImport, addStyleSheetImport, injectStylesAtTop } from "./utils/styleInjection.js";
24
32
  import {
25
33
  addOrMergePlaceholderTextColorProp,
26
34
  findStyleAttribute,
@@ -59,6 +67,8 @@ type PluginState = PluginPass & {
59
67
  styleRegistry: Map<string, StyleObject>;
60
68
  hasClassNames: boolean;
61
69
  hasStyleSheetImport: boolean;
70
+ hasPlatformImport: boolean;
71
+ needsPlatformImport: boolean;
62
72
  customColors: Record<string, string>;
63
73
  supportedAttributes: Set<string>;
64
74
  attributePatterns: RegExp[];
@@ -90,6 +100,8 @@ export default function reactNativeTailwindBabelPlugin(
90
100
  state.styleRegistry = new Map();
91
101
  state.hasClassNames = false;
92
102
  state.hasStyleSheetImport = false;
103
+ state.hasPlatformImport = false;
104
+ state.needsPlatformImport = false;
93
105
  state.supportedAttributes = exactMatches;
94
106
  state.attributePatterns = patterns;
95
107
  state.stylesIdentifier = stylesIdentifier;
@@ -116,19 +128,25 @@ export default function reactNativeTailwindBabelPlugin(
116
128
  addStyleSheetImport(path, t);
117
129
  }
118
130
 
131
+ // Add Platform import if platform modifiers were used and not already present
132
+ if (state.needsPlatformImport && !state.hasPlatformImport) {
133
+ addPlatformImport(path, t);
134
+ }
135
+
119
136
  // Generate and inject StyleSheet.create at the beginning of the file (after imports)
120
137
  // This ensures _twStyles is defined before any code that references it
121
138
  injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
122
139
  },
123
140
  },
124
141
 
125
- // Check if StyleSheet is already imported and track tw/twStyle imports
142
+ // Check if StyleSheet/Platform are already imported and track tw/twStyle imports
126
143
  ImportDeclaration(path, state) {
127
144
  const node = path.node;
128
145
 
129
- // Track react-native StyleSheet import
146
+ // Track react-native StyleSheet and Platform imports
130
147
  if (node.source.value === "react-native") {
131
148
  const specifiers = node.specifiers;
149
+
132
150
  const hasStyleSheet = specifiers.some((spec) => {
133
151
  if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
134
152
  return spec.imported.name === "StyleSheet";
@@ -136,6 +154,13 @@ export default function reactNativeTailwindBabelPlugin(
136
154
  return false;
137
155
  });
138
156
 
157
+ const hasPlatform = specifiers.some((spec) => {
158
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
159
+ return spec.imported.name === "Platform";
160
+ }
161
+ return false;
162
+ });
163
+
139
164
  if (hasStyleSheet) {
140
165
  state.hasStyleSheetImport = true;
141
166
  } else {
@@ -143,6 +168,10 @@ export default function reactNativeTailwindBabelPlugin(
143
168
  node.specifiers.push(t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")));
144
169
  state.hasStyleSheetImport = true;
145
170
  }
171
+
172
+ if (hasPlatform) {
173
+ state.hasPlatformImport = true;
174
+ }
146
175
  }
147
176
 
148
177
  // Track tw/twStyle imports from main package (for compile-time transformation)
@@ -291,12 +320,15 @@ export default function reactNativeTailwindBabelPlugin(
291
320
 
292
321
  state.hasClassNames = true;
293
322
 
294
- // Check if className contains modifiers (active:, hover:, focus:, placeholder:)
323
+ // Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
295
324
  const { baseClasses, modifierClasses } = splitModifierClasses(className);
296
325
 
297
- // Separate placeholder modifiers from state modifiers
326
+ // Separate modifiers by type
298
327
  const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
299
- const stateModifiers = modifierClasses.filter((m) => m.modifier !== "placeholder");
328
+ const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
329
+ const stateModifiers = modifierClasses.filter(
330
+ (m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
331
+ );
300
332
 
301
333
  // Handle placeholder modifiers first (they generate placeholderTextColor prop, not style)
302
334
  if (placeholderModifiers.length > 0) {
@@ -322,8 +354,153 @@ export default function reactNativeTailwindBabelPlugin(
322
354
  }
323
355
  }
324
356
 
325
- // If there are state modifiers, check if this component supports them
326
- if (stateModifiers.length > 0) {
357
+ // Handle combination of modifiers
358
+ const hasPlatformModifiers = platformModifiers.length > 0;
359
+ const hasStateModifiers = stateModifiers.length > 0;
360
+ const hasBaseClasses = baseClasses.length > 0;
361
+
362
+ // If we have both state and platform modifiers, or platform modifiers with complex state,
363
+ // we need to combine them in an array expression wrapped in an arrow function
364
+ if (hasStateModifiers && hasPlatformModifiers) {
365
+ // Get the JSX opening element for component support checking
366
+ const jsxOpeningElement = path.parent;
367
+ const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
368
+
369
+ if (componentSupport) {
370
+ // Build style array: [baseStyle, Platform.select(...), stateConditionals]
371
+ const styleArrayElements: BabelTypes.Expression[] = [];
372
+
373
+ // Add base classes
374
+ if (hasBaseClasses) {
375
+ const baseClassName = baseClasses.join(" ");
376
+ const baseStyleObject = parseClassName(baseClassName, state.customColors);
377
+ const baseStyleKey = generateStyleKey(baseClassName);
378
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
379
+ styleArrayElements.push(
380
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
381
+ );
382
+ }
383
+
384
+ // Add platform modifiers as Platform.select()
385
+ const platformSelectExpression = processPlatformModifiers(
386
+ platformModifiers,
387
+ state,
388
+ parseClassName,
389
+ generateStyleKey,
390
+ t,
391
+ );
392
+ styleArrayElements.push(platformSelectExpression);
393
+
394
+ // Add state modifiers as conditionals
395
+ // Group by modifier type
396
+ const modifiersByType = new Map<StateModifierType, ParsedModifier[]>();
397
+ for (const mod of stateModifiers) {
398
+ const modType = mod.modifier as StateModifierType;
399
+ if (!modifiersByType.has(modType)) {
400
+ modifiersByType.set(modType, []);
401
+ }
402
+ modifiersByType.get(modType)?.push(mod);
403
+ }
404
+
405
+ // Build conditionals for each state modifier type
406
+ for (const [modifierType, modifiers] of modifiersByType) {
407
+ if (!componentSupport.supportedModifiers.includes(modifierType)) {
408
+ continue; // Skip unsupported modifiers
409
+ }
410
+
411
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
412
+ const modifierStyleObject = parseClassName(modifierClassNames, state.customColors);
413
+ const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
414
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
415
+
416
+ const stateProperty = getStatePropertyForModifier(modifierType);
417
+ const conditionalExpression = t.logicalExpression(
418
+ "&&",
419
+ t.identifier(stateProperty),
420
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey)),
421
+ );
422
+
423
+ styleArrayElements.push(conditionalExpression);
424
+ }
425
+
426
+ // Wrap in arrow function for state support
427
+ const usedModifiers = Array.from(new Set(stateModifiers.map((m) => m.modifier))).filter((mod) =>
428
+ componentSupport.supportedModifiers.includes(mod),
429
+ );
430
+ const styleArrayExpression = t.arrayExpression(styleArrayElements);
431
+ const styleFunctionExpression = createStyleFunction(styleArrayExpression, usedModifiers, t);
432
+
433
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
434
+ if (styleAttribute) {
435
+ mergeStyleFunctionAttribute(path, styleAttribute, styleFunctionExpression, t);
436
+ } else {
437
+ replaceWithStyleFunctionAttribute(path, styleFunctionExpression, targetStyleProp, t);
438
+ }
439
+ return;
440
+ } else {
441
+ // Component doesn't support state modifiers, but we can still use platform modifiers
442
+ // Fall through to platform-only handling
443
+ }
444
+ }
445
+
446
+ // Handle platform-only modifiers (no state modifiers)
447
+ if (hasPlatformModifiers && !hasStateModifiers) {
448
+ // Build style array/expression: [baseStyle, Platform.select(...)]
449
+ const styleExpressions: BabelTypes.Expression[] = [];
450
+
451
+ // Add base classes
452
+ if (hasBaseClasses) {
453
+ const baseClassName = baseClasses.join(" ");
454
+ const baseStyleObject = parseClassName(baseClassName, state.customColors);
455
+ const baseStyleKey = generateStyleKey(baseClassName);
456
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
457
+ styleExpressions.push(
458
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
459
+ );
460
+ }
461
+
462
+ // Add platform modifiers as Platform.select()
463
+ const platformSelectExpression = processPlatformModifiers(
464
+ platformModifiers,
465
+ state,
466
+ parseClassName,
467
+ generateStyleKey,
468
+ t,
469
+ );
470
+ styleExpressions.push(platformSelectExpression);
471
+
472
+ // Generate style attribute
473
+ const styleExpression =
474
+ styleExpressions.length === 1 ? styleExpressions[0] : t.arrayExpression(styleExpressions);
475
+
476
+ const styleAttribute = findStyleAttribute(path, targetStyleProp, t);
477
+ if (styleAttribute) {
478
+ // Merge with existing style attribute
479
+ const existingStyle = styleAttribute.value;
480
+ if (
481
+ t.isJSXExpressionContainer(existingStyle) &&
482
+ !t.isJSXEmptyExpression(existingStyle.expression)
483
+ ) {
484
+ const existing = existingStyle.expression;
485
+ // Merge as array: [ourStyles, existingStyles]
486
+ const mergedArray = t.isArrayExpression(existing)
487
+ ? t.arrayExpression([styleExpression, ...existing.elements])
488
+ : t.arrayExpression([styleExpression, existing]);
489
+ styleAttribute.value = t.jsxExpressionContainer(mergedArray);
490
+ } else {
491
+ styleAttribute.value = t.jsxExpressionContainer(styleExpression);
492
+ }
493
+ path.remove();
494
+ } else {
495
+ // Replace className with style prop containing our expression
496
+ path.node.name = t.jsxIdentifier(targetStyleProp);
497
+ path.node.value = t.jsxExpressionContainer(styleExpression);
498
+ }
499
+ return;
500
+ }
501
+
502
+ // If there are state modifiers (and no platform modifiers), check if this component supports them
503
+ if (hasStateModifiers) {
327
504
  // Get the JSX opening element (the direct parent of the attribute)
328
505
  const jsxOpeningElement = path.parent;
329
506
  const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);