@mgcrea/react-native-tailwind 0.3.0 → 0.5.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 (71) hide show
  1. package/README.md +459 -39
  2. package/dist/babel/index.cjs +810 -279
  3. package/dist/babel/index.d.ts +2 -1
  4. package/dist/babel/index.ts +328 -22
  5. package/dist/components/Pressable.d.ts +32 -0
  6. package/dist/components/Pressable.js +1 -0
  7. package/dist/components/TextInput.d.ts +56 -0
  8. package/dist/components/TextInput.js +1 -0
  9. package/dist/index.d.ts +9 -2
  10. package/dist/index.js +1 -1
  11. package/dist/parser/aspectRatio.d.ts +16 -0
  12. package/dist/parser/aspectRatio.js +1 -0
  13. package/dist/parser/aspectRatio.test.d.ts +1 -0
  14. package/dist/parser/aspectRatio.test.js +1 -0
  15. package/dist/parser/borders.js +1 -1
  16. package/dist/parser/borders.test.d.ts +1 -0
  17. package/dist/parser/borders.test.js +1 -0
  18. package/dist/parser/colors.d.ts +1 -0
  19. package/dist/parser/colors.js +1 -1
  20. package/dist/parser/colors.test.d.ts +1 -0
  21. package/dist/parser/colors.test.js +1 -0
  22. package/dist/parser/index.d.ts +4 -0
  23. package/dist/parser/index.js +1 -1
  24. package/dist/parser/layout.d.ts +2 -0
  25. package/dist/parser/layout.js +1 -1
  26. package/dist/parser/layout.test.d.ts +1 -0
  27. package/dist/parser/layout.test.js +1 -0
  28. package/dist/parser/modifiers.d.ts +47 -0
  29. package/dist/parser/modifiers.js +1 -0
  30. package/dist/parser/modifiers.test.d.ts +1 -0
  31. package/dist/parser/modifiers.test.js +1 -0
  32. package/dist/parser/shadows.d.ts +26 -0
  33. package/dist/parser/shadows.js +1 -0
  34. package/dist/parser/shadows.test.d.ts +1 -0
  35. package/dist/parser/shadows.test.js +1 -0
  36. package/dist/parser/sizing.test.d.ts +1 -0
  37. package/dist/parser/sizing.test.js +1 -0
  38. package/dist/parser/spacing.d.ts +1 -1
  39. package/dist/parser/spacing.js +1 -1
  40. package/dist/parser/spacing.test.d.ts +1 -0
  41. package/dist/parser/spacing.test.js +1 -0
  42. package/dist/parser/typography.d.ts +2 -1
  43. package/dist/parser/typography.js +1 -1
  44. package/dist/parser/typography.test.d.ts +1 -0
  45. package/dist/parser/typography.test.js +1 -0
  46. package/dist/types.d.ts +5 -2
  47. package/package.json +7 -6
  48. package/src/babel/index.ts +328 -22
  49. package/src/components/Pressable.tsx +46 -0
  50. package/src/components/TextInput.tsx +90 -0
  51. package/src/index.ts +20 -2
  52. package/src/parser/aspectRatio.test.ts +191 -0
  53. package/src/parser/aspectRatio.ts +73 -0
  54. package/src/parser/borders.test.ts +329 -0
  55. package/src/parser/borders.ts +187 -108
  56. package/src/parser/colors.test.ts +335 -0
  57. package/src/parser/colors.ts +117 -6
  58. package/src/parser/index.ts +13 -2
  59. package/src/parser/layout.test.ts +459 -0
  60. package/src/parser/layout.ts +128 -0
  61. package/src/parser/modifiers.test.ts +375 -0
  62. package/src/parser/modifiers.ts +104 -0
  63. package/src/parser/shadows.test.ts +201 -0
  64. package/src/parser/shadows.ts +133 -0
  65. package/src/parser/sizing.test.ts +256 -0
  66. package/src/parser/spacing.test.ts +226 -0
  67. package/src/parser/spacing.ts +93 -138
  68. package/src/parser/typography.test.ts +221 -0
  69. package/src/parser/typography.ts +143 -112
  70. package/src/types.ts +2 -2
  71. package/dist/react-native.d.js +0 -1
@@ -0,0 +1,335 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { COLORS, parseColor } from "./colors";
3
+
4
+ describe("COLORS", () => {
5
+ it("should export complete color palette", () => {
6
+ expect(COLORS).toMatchSnapshot();
7
+ });
8
+ });
9
+
10
+ describe("parseColor - background colors", () => {
11
+ it("should parse background colors with preset values", () => {
12
+ expect(parseColor("bg-blue-500")).toEqual({ backgroundColor: "#3B82F6" });
13
+ expect(parseColor("bg-red-500")).toEqual({ backgroundColor: "#EF4444" });
14
+ expect(parseColor("bg-green-500")).toEqual({ backgroundColor: "#22C55E" });
15
+ expect(parseColor("bg-gray-300")).toEqual({ backgroundColor: "#D1D5DB" });
16
+ });
17
+
18
+ it("should parse background colors with basic values", () => {
19
+ expect(parseColor("bg-white")).toEqual({ backgroundColor: "#FFFFFF" });
20
+ expect(parseColor("bg-black")).toEqual({ backgroundColor: "#000000" });
21
+ expect(parseColor("bg-transparent")).toEqual({ backgroundColor: "transparent" });
22
+ });
23
+
24
+ it("should parse background colors with arbitrary 6-digit hex values", () => {
25
+ expect(parseColor("bg-[#ff0000]")).toEqual({ backgroundColor: "#ff0000" });
26
+ expect(parseColor("bg-[#3B82F6]")).toEqual({ backgroundColor: "#3B82F6" });
27
+ expect(parseColor("bg-[#000000]")).toEqual({ backgroundColor: "#000000" });
28
+ expect(parseColor("bg-[#FFFFFF]")).toEqual({ backgroundColor: "#FFFFFF" });
29
+ });
30
+
31
+ it("should parse background colors with arbitrary 3-digit hex values", () => {
32
+ expect(parseColor("bg-[#f00]")).toEqual({ backgroundColor: "#ff0000" });
33
+ expect(parseColor("bg-[#abc]")).toEqual({ backgroundColor: "#aabbcc" });
34
+ expect(parseColor("bg-[#123]")).toEqual({ backgroundColor: "#112233" });
35
+ expect(parseColor("bg-[#FFF]")).toEqual({ backgroundColor: "#FFFFFF" });
36
+ });
37
+
38
+ it("should parse background colors with arbitrary 8-digit hex values (with alpha)", () => {
39
+ expect(parseColor("bg-[#ff0000aa]")).toEqual({ backgroundColor: "#ff0000aa" });
40
+ expect(parseColor("bg-[#3B82F680]")).toEqual({ backgroundColor: "#3B82F680" });
41
+ expect(parseColor("bg-[#00000000]")).toEqual({ backgroundColor: "#00000000" });
42
+ expect(parseColor("bg-[#FFFFFFFF]")).toEqual({ backgroundColor: "#FFFFFFFF" });
43
+ });
44
+
45
+ it("should handle case-insensitive hex values", () => {
46
+ expect(parseColor("bg-[#FF0000]")).toEqual({ backgroundColor: "#FF0000" });
47
+ expect(parseColor("bg-[#ff0000]")).toEqual({ backgroundColor: "#ff0000" });
48
+ expect(parseColor("bg-[#Ff0000]")).toEqual({ backgroundColor: "#Ff0000" });
49
+ });
50
+
51
+ it("should prefer arbitrary values over preset colors", () => {
52
+ // If someone creates a custom color named "[#ff0000]", the arbitrary value should take precedence
53
+ const customColors = { "[#ff0000]": "#00ff00" };
54
+ expect(parseColor("bg-[#ff0000]", customColors)).toEqual({ backgroundColor: "#ff0000" });
55
+ });
56
+ });
57
+
58
+ describe("parseColor - text colors", () => {
59
+ it("should parse text colors with preset values", () => {
60
+ expect(parseColor("text-blue-500")).toEqual({ color: "#3B82F6" });
61
+ expect(parseColor("text-red-500")).toEqual({ color: "#EF4444" });
62
+ expect(parseColor("text-green-500")).toEqual({ color: "#22C55E" });
63
+ expect(parseColor("text-gray-700")).toEqual({ color: "#374151" });
64
+ });
65
+
66
+ it("should parse text colors with basic values", () => {
67
+ expect(parseColor("text-white")).toEqual({ color: "#FFFFFF" });
68
+ expect(parseColor("text-black")).toEqual({ color: "#000000" });
69
+ });
70
+
71
+ it("should parse text colors with arbitrary 6-digit hex values", () => {
72
+ expect(parseColor("text-[#ff0000]")).toEqual({ color: "#ff0000" });
73
+ expect(parseColor("text-[#3B82F6]")).toEqual({ color: "#3B82F6" });
74
+ expect(parseColor("text-[#333333]")).toEqual({ color: "#333333" });
75
+ });
76
+
77
+ it("should parse text colors with arbitrary 3-digit hex values", () => {
78
+ expect(parseColor("text-[#f00]")).toEqual({ color: "#ff0000" });
79
+ expect(parseColor("text-[#abc]")).toEqual({ color: "#aabbcc" });
80
+ expect(parseColor("text-[#000]")).toEqual({ color: "#000000" });
81
+ });
82
+
83
+ it("should parse text colors with arbitrary 8-digit hex values (with alpha)", () => {
84
+ expect(parseColor("text-[#ff0000aa]")).toEqual({ color: "#ff0000aa" });
85
+ expect(parseColor("text-[#00000080]")).toEqual({ color: "#00000080" });
86
+ });
87
+ });
88
+
89
+ describe("parseColor - border colors", () => {
90
+ it("should parse border colors with preset values", () => {
91
+ expect(parseColor("border-blue-500")).toEqual({ borderColor: "#3B82F6" });
92
+ expect(parseColor("border-red-500")).toEqual({ borderColor: "#EF4444" });
93
+ expect(parseColor("border-green-500")).toEqual({ borderColor: "#22C55E" });
94
+ expect(parseColor("border-gray-200")).toEqual({ borderColor: "#E5E7EB" });
95
+ });
96
+
97
+ it("should parse border colors with basic values", () => {
98
+ expect(parseColor("border-white")).toEqual({ borderColor: "#FFFFFF" });
99
+ expect(parseColor("border-black")).toEqual({ borderColor: "#000000" });
100
+ expect(parseColor("border-transparent")).toEqual({ borderColor: "transparent" });
101
+ });
102
+
103
+ it("should parse border colors with arbitrary 6-digit hex values", () => {
104
+ expect(parseColor("border-[#ff0000]")).toEqual({ borderColor: "#ff0000" });
105
+ expect(parseColor("border-[#3B82F6]")).toEqual({ borderColor: "#3B82F6" });
106
+ expect(parseColor("border-[#cccccc]")).toEqual({ borderColor: "#cccccc" });
107
+ });
108
+
109
+ it("should parse border colors with arbitrary 3-digit hex values", () => {
110
+ expect(parseColor("border-[#f00]")).toEqual({ borderColor: "#ff0000" });
111
+ expect(parseColor("border-[#abc]")).toEqual({ borderColor: "#aabbcc" });
112
+ expect(parseColor("border-[#999]")).toEqual({ borderColor: "#999999" });
113
+ });
114
+
115
+ it("should parse border colors with arbitrary 8-digit hex values (with alpha)", () => {
116
+ expect(parseColor("border-[#ff0000aa]")).toEqual({ borderColor: "#ff0000aa" });
117
+ expect(parseColor("border-[#0000FF50]")).toEqual({ borderColor: "#0000FF50" });
118
+ });
119
+
120
+ it("should not match border width classes", () => {
121
+ expect(parseColor("border-0")).toBeNull();
122
+ expect(parseColor("border-2")).toBeNull();
123
+ expect(parseColor("border-4")).toBeNull();
124
+ });
125
+ });
126
+
127
+ describe("parseColor - custom colors", () => {
128
+ const customColors = {
129
+ "brand-primary": "#FF6B6B",
130
+ "brand-secondary": "#4ECDC4",
131
+ accent: "#FFE66D",
132
+ };
133
+
134
+ it("should support custom background colors", () => {
135
+ expect(parseColor("bg-brand-primary", customColors)).toEqual({ backgroundColor: "#FF6B6B" });
136
+ expect(parseColor("bg-brand-secondary", customColors)).toEqual({ backgroundColor: "#4ECDC4" });
137
+ expect(parseColor("bg-accent", customColors)).toEqual({ backgroundColor: "#FFE66D" });
138
+ });
139
+
140
+ it("should support custom text colors", () => {
141
+ expect(parseColor("text-brand-primary", customColors)).toEqual({ color: "#FF6B6B" });
142
+ expect(parseColor("text-brand-secondary", customColors)).toEqual({ color: "#4ECDC4" });
143
+ });
144
+
145
+ it("should support custom border colors", () => {
146
+ expect(parseColor("border-brand-primary", customColors)).toEqual({ borderColor: "#FF6B6B" });
147
+ expect(parseColor("border-accent", customColors)).toEqual({ borderColor: "#FFE66D" });
148
+ });
149
+
150
+ it("should allow custom colors to override preset colors", () => {
151
+ const overrideColors = { "blue-500": "#FF0000" };
152
+ expect(parseColor("bg-blue-500", overrideColors)).toEqual({ backgroundColor: "#FF0000" });
153
+ });
154
+
155
+ it("should fallback to preset colors when custom color not found", () => {
156
+ expect(parseColor("bg-red-500", customColors)).toEqual({ backgroundColor: "#EF4444" });
157
+ });
158
+ });
159
+
160
+ describe("parseColor - edge cases", () => {
161
+ it("should return null for invalid classes", () => {
162
+ expect(parseColor("invalid")).toBeNull();
163
+ expect(parseColor("bg")).toBeNull();
164
+ expect(parseColor("text")).toBeNull();
165
+ expect(parseColor("border")).toBeNull();
166
+ });
167
+
168
+ it("should return null for invalid color values", () => {
169
+ expect(parseColor("bg-invalid")).toBeNull();
170
+ expect(parseColor("text-notacolor")).toBeNull();
171
+ expect(parseColor("border-xyz")).toBeNull();
172
+ });
173
+
174
+ it("should return null for invalid arbitrary hex values", () => {
175
+ expect(parseColor("bg-[#ff]")).toBeNull(); // Too short (2 digits)
176
+ expect(parseColor("bg-[#ffff]")).toBeNull(); // Invalid length (4 digits)
177
+ expect(parseColor("bg-[#fffff]")).toBeNull(); // Invalid length (5 digits)
178
+ expect(parseColor("bg-[#fffffff]")).toBeNull(); // Invalid length (7 digits)
179
+ expect(parseColor("bg-[#fffffffff]")).toBeNull(); // Too long (9 digits)
180
+ });
181
+
182
+ it("should return null for malformed arbitrary values", () => {
183
+ expect(parseColor("bg-[#ff0000")).toBeNull(); // Missing closing bracket
184
+ expect(parseColor("bg-#ff0000]")).toBeNull(); // Missing opening bracket
185
+ expect(parseColor("bg-[]")).toBeNull(); // Empty brackets
186
+ expect(parseColor("bg-[ff0000]")).toBeNull(); // Missing # symbol
187
+ });
188
+
189
+ it("should return null for non-hex arbitrary values", () => {
190
+ expect(parseColor("bg-[#gggggg]")).toBeNull(); // Invalid hex characters
191
+ expect(parseColor("bg-[#zzzzzz]")).toBeNull(); // Invalid hex characters
192
+ expect(parseColor("bg-[rgb(255,0,0)]")).toBeNull(); // RGB format not supported
193
+ });
194
+
195
+ it("should not match partial class names", () => {
196
+ expect(parseColor("background-blue-500")).toBeNull();
197
+ expect(parseColor("textcolor-red-500")).toBeNull();
198
+ expect(parseColor("border-color-blue-500")).toBeNull();
199
+ });
200
+
201
+ it("should handle all color scale variants", () => {
202
+ const scales = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900"];
203
+ scales.forEach((scale) => {
204
+ expect(parseColor(`bg-blue-${scale}`)).toBeTruthy();
205
+ expect(parseColor(`text-red-${scale}`)).toBeTruthy();
206
+ expect(parseColor(`border-green-${scale}`)).toBeTruthy();
207
+ });
208
+ });
209
+ });
210
+
211
+ describe("parseColor - comprehensive coverage", () => {
212
+ it("should parse all color types with same preset color", () => {
213
+ expect(parseColor("bg-blue-500")).toEqual({ backgroundColor: "#3B82F6" });
214
+ expect(parseColor("text-blue-500")).toEqual({ color: "#3B82F6" });
215
+ expect(parseColor("border-blue-500")).toEqual({ borderColor: "#3B82F6" });
216
+ });
217
+
218
+ it("should parse all color types with same arbitrary hex", () => {
219
+ expect(parseColor("bg-[#ff0000]")).toEqual({ backgroundColor: "#ff0000" });
220
+ expect(parseColor("text-[#ff0000]")).toEqual({ color: "#ff0000" });
221
+ expect(parseColor("border-[#ff0000]")).toEqual({ borderColor: "#ff0000" });
222
+ });
223
+
224
+ it("should handle all color families", () => {
225
+ const families = ["gray", "red", "blue", "green", "yellow", "purple", "pink", "orange", "indigo"];
226
+ families.forEach((family) => {
227
+ expect(parseColor(`bg-${family}-500`)).toBeTruthy();
228
+ expect(parseColor(`text-${family}-500`)).toBeTruthy();
229
+ expect(parseColor(`border-${family}-500`)).toBeTruthy();
230
+ });
231
+ });
232
+
233
+ it("should handle arbitrary values with mixed case", () => {
234
+ expect(parseColor("bg-[#AbCdEf]")).toEqual({ backgroundColor: "#AbCdEf" });
235
+ expect(parseColor("text-[#aBcDeF]")).toEqual({ color: "#aBcDeF" });
236
+ expect(parseColor("border-[#ABCDEF]")).toEqual({ borderColor: "#ABCDEF" });
237
+ });
238
+
239
+ it("should expand 3-digit hex consistently across all color types", () => {
240
+ expect(parseColor("bg-[#abc]")).toEqual({ backgroundColor: "#aabbcc" });
241
+ expect(parseColor("text-[#abc]")).toEqual({ color: "#aabbcc" });
242
+ expect(parseColor("border-[#abc]")).toEqual({ borderColor: "#aabbcc" });
243
+ });
244
+
245
+ it("should handle alpha channel consistently across all color types", () => {
246
+ expect(parseColor("bg-[#ff0000aa]")).toEqual({ backgroundColor: "#ff0000aa" });
247
+ expect(parseColor("text-[#ff0000aa]")).toEqual({ color: "#ff0000aa" });
248
+ expect(parseColor("border-[#ff0000aa]")).toEqual({ borderColor: "#ff0000aa" });
249
+ });
250
+ });
251
+
252
+ describe("parseColor - opacity modifiers", () => {
253
+ it("should parse background colors with opacity modifiers", () => {
254
+ expect(parseColor("bg-black/50")).toEqual({ backgroundColor: "#00000080" });
255
+ expect(parseColor("bg-white/50")).toEqual({ backgroundColor: "#FFFFFF80" });
256
+ expect(parseColor("bg-blue-500/80")).toEqual({ backgroundColor: "#3B82F6CC" });
257
+ expect(parseColor("bg-red-500/30")).toEqual({ backgroundColor: "#EF44444D" });
258
+ });
259
+
260
+ it("should parse text colors with opacity modifiers", () => {
261
+ expect(parseColor("text-black/80")).toEqual({ color: "#000000CC" });
262
+ expect(parseColor("text-white/90")).toEqual({ color: "#FFFFFFE6" });
263
+ expect(parseColor("text-gray-900/70")).toEqual({ color: "#111827B3" });
264
+ expect(parseColor("text-blue-500/50")).toEqual({ color: "#3B82F680" });
265
+ });
266
+
267
+ it("should parse border colors with opacity modifiers", () => {
268
+ expect(parseColor("border-black/25")).toEqual({ borderColor: "#00000040" });
269
+ expect(parseColor("border-red-500/60")).toEqual({ borderColor: "#EF444499" });
270
+ expect(parseColor("border-gray-200/40")).toEqual({ borderColor: "#E5E7EB66" });
271
+ });
272
+
273
+ it("should handle opacity modifier with arbitrary hex colors", () => {
274
+ expect(parseColor("bg-[#ff0000]/50")).toEqual({ backgroundColor: "#FF000080" });
275
+ expect(parseColor("text-[#3B82F6]/80")).toEqual({ color: "#3B82F6CC" });
276
+ expect(parseColor("border-[#abc]/60")).toEqual({ borderColor: "#AABBCC99" });
277
+ });
278
+
279
+ it("should handle opacity modifier with custom colors", () => {
280
+ const customColors = { "brand-primary": "#FF6B6B" };
281
+ expect(parseColor("bg-brand-primary/50", customColors)).toEqual({ backgroundColor: "#FF6B6B80" });
282
+ expect(parseColor("text-brand-primary/75", customColors)).toEqual({ color: "#FF6B6BBF" });
283
+ });
284
+
285
+ it("should handle opacity 0 (fully transparent)", () => {
286
+ expect(parseColor("bg-black/0")).toEqual({ backgroundColor: "#00000000" });
287
+ expect(parseColor("text-red-500/0")).toEqual({ color: "#EF444400" });
288
+ });
289
+
290
+ it("should handle opacity 100 (fully opaque)", () => {
291
+ expect(parseColor("bg-black/100")).toEqual({ backgroundColor: "#000000FF" });
292
+ expect(parseColor("text-blue-500/100")).toEqual({ color: "#3B82F6FF" });
293
+ });
294
+
295
+ it("should handle transparent color with opacity modifier", () => {
296
+ // Transparent with opacity should remain transparent
297
+ expect(parseColor("bg-transparent/50")).toEqual({ backgroundColor: "transparent" });
298
+ });
299
+
300
+ it("should convert opacity percentage to correct hex values", () => {
301
+ // Test key opacity values
302
+ expect(parseColor("bg-black/0")).toEqual({ backgroundColor: "#00000000" }); // 0%
303
+ expect(parseColor("bg-black/10")).toEqual({ backgroundColor: "#0000001A" }); // ~10%
304
+ expect(parseColor("bg-black/25")).toEqual({ backgroundColor: "#00000040" }); // 25%
305
+ expect(parseColor("bg-black/50")).toEqual({ backgroundColor: "#00000080" }); // 50%
306
+ expect(parseColor("bg-black/75")).toEqual({ backgroundColor: "#000000BF" }); // 75%
307
+ expect(parseColor("bg-black/100")).toEqual({ backgroundColor: "#000000FF" }); // 100%
308
+ });
309
+
310
+ it("should return null for invalid opacity values", () => {
311
+ expect(parseColor("bg-black/101")).toBeNull(); // > 100
312
+ expect(parseColor("bg-black/-1")).toBeNull(); // < 0
313
+ expect(parseColor("bg-black/150")).toBeNull(); // Way over 100
314
+ });
315
+
316
+ it("should return null for malformed opacity syntax", () => {
317
+ expect(parseColor("bg-black/")).toBeNull(); // Missing opacity value
318
+ expect(parseColor("bg-black/abc")).toBeNull(); // Non-numeric opacity
319
+ expect(parseColor("bg-black/50/")).toBeNull(); // Extra slash
320
+ });
321
+
322
+ it("should handle opacity with 3-digit hex expansion", () => {
323
+ expect(parseColor("bg-[#f00]/50")).toEqual({ backgroundColor: "#FF000080" });
324
+ expect(parseColor("text-[#abc]/75")).toEqual({ color: "#AABBCCBF" });
325
+ });
326
+
327
+ it("should work with all color families", () => {
328
+ const families = ["gray", "red", "blue", "green", "yellow", "purple", "pink", "orange", "indigo"];
329
+ families.forEach((family) => {
330
+ expect(parseColor(`bg-${family}-500/50`)).toBeTruthy();
331
+ expect(parseColor(`text-${family}-500/50`)).toBeTruthy();
332
+ expect(parseColor(`border-${family}-500/50`)).toBeTruthy();
333
+ });
334
+ });
335
+ });
@@ -120,8 +120,75 @@ export const COLORS: Record<string, string> = {
120
120
  transparent: "transparent",
121
121
  };
122
122
 
123
+ /**
124
+ * Apply opacity to hex color by appending alpha channel
125
+ * @param hex - Hex color string (e.g., "#ff0000", "#f00", or "transparent")
126
+ * @param opacity - Opacity value 0-100 (e.g., 50 for 50%)
127
+ * @returns 8-digit hex with alpha (e.g., "#FF000080") or rgba for special colors
128
+ */
129
+ function applyOpacity(hex: string, opacity: number): string {
130
+ // Handle transparent specially
131
+ if (hex === "transparent") {
132
+ return "transparent";
133
+ }
134
+
135
+ // Remove # if present
136
+ const cleanHex = hex.replace(/^#/, "");
137
+
138
+ // Expand 3-digit hex to 6-digit: #abc -> #aabbcc
139
+ const fullHex =
140
+ cleanHex.length === 3
141
+ ? cleanHex
142
+ .split("")
143
+ .map((char) => char + char)
144
+ .join("")
145
+ : cleanHex;
146
+
147
+ // Convert opacity percentage (0-100) to hex (00-FF)
148
+ const alpha = Math.round((opacity / 100) * 255);
149
+ const alphaHex = alpha.toString(16).padStart(2, "0").toUpperCase();
150
+
151
+ // Return 8-digit hex: #RRGGBBAA
152
+ return `#${fullHex.toUpperCase()}${alphaHex}`;
153
+ }
154
+
155
+ /**
156
+ * Parse arbitrary color value: [#ff0000], [#f00], [#FF0000AA]
157
+ * Supports 3-digit, 6-digit, and 8-digit (with alpha) hex colors
158
+ * Returns hex string if valid, null otherwise
159
+ */
160
+ function parseArbitraryColor(value: string): string | null {
161
+ // Match: [#rgb], [#rrggbb], or [#rrggbbaa]
162
+ const hexMatch = value.match(/^\[#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\]$/);
163
+ if (hexMatch) {
164
+ const hex = hexMatch[1];
165
+ // Expand 3-digit hex to 6-digit: #abc -> #aabbcc
166
+ if (hex.length === 3) {
167
+ const expanded = hex
168
+ .split("")
169
+ .map((char) => char + char)
170
+ .join("");
171
+ return `#${expanded}`;
172
+ }
173
+ return `#${hex}`;
174
+ }
175
+
176
+ // Warn about unsupported formats
177
+ if (value.startsWith("[") && value.endsWith("]")) {
178
+ if (process.env.NODE_ENV !== "production") {
179
+ console.warn(
180
+ `[react-native-tailwind] Unsupported arbitrary color value: ${value}. Only hex colors are supported (e.g., [#ff0000], [#f00], or [#ff0000aa]).`,
181
+ );
182
+ }
183
+ return null;
184
+ }
185
+
186
+ return null;
187
+ }
188
+
123
189
  /**
124
190
  * Parse color classes (background, text, border)
191
+ * Supports opacity modifier: bg-blue-500/50, text-black/80, border-red-500/30
125
192
  */
126
193
  export function parseColor(cls: string, customColors?: Record<string, string>): StyleObject | null {
127
194
  // Helper to get color with custom override (custom colors take precedence)
@@ -129,28 +196,72 @@ export function parseColor(cls: string, customColors?: Record<string, string>):
129
196
  return customColors?.[key] ?? COLORS[key];
130
197
  };
131
198
 
132
- // Background color: bg-blue-500
199
+ // Helper to parse color with optional opacity modifier
200
+ const parseColorWithOpacity = (colorKey: string): string | null => {
201
+ // Check for opacity modifier: blue-500/50
202
+ const opacityMatch = colorKey.match(/^(.+)\/(\d+)$/);
203
+ if (opacityMatch) {
204
+ const baseColorKey = opacityMatch[1];
205
+ const opacity = Number.parseInt(opacityMatch[2], 10);
206
+
207
+ // Validate opacity range (0-100)
208
+ if (opacity < 0 || opacity > 100) {
209
+ if (process.env.NODE_ENV !== "production") {
210
+ console.warn(
211
+ `[react-native-tailwind] Invalid opacity value: ${opacity}. Opacity must be between 0 and 100.`,
212
+ );
213
+ }
214
+ return null;
215
+ }
216
+
217
+ // Try arbitrary color first: bg-[#ff0000]/50
218
+ const arbitraryColor = parseArbitraryColor(baseColorKey);
219
+ if (arbitraryColor !== null) {
220
+ return applyOpacity(arbitraryColor, opacity);
221
+ }
222
+
223
+ // Try preset/custom colors: bg-blue-500/50
224
+ const color = getColor(baseColorKey);
225
+ if (color) {
226
+ return applyOpacity(color, opacity);
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ // No opacity modifier - try normal color parsing
233
+ // Try arbitrary value first
234
+ const arbitraryColor = parseArbitraryColor(colorKey);
235
+ if (arbitraryColor !== null) {
236
+ return arbitraryColor;
237
+ }
238
+
239
+ // Try preset/custom colors
240
+ return getColor(colorKey) ?? null;
241
+ };
242
+
243
+ // Background color: bg-blue-500, bg-blue-500/50, bg-[#ff0000]/80
133
244
  if (cls.startsWith("bg-")) {
134
245
  const colorKey = cls.substring(3);
135
- const color = getColor(colorKey);
246
+ const color = parseColorWithOpacity(colorKey);
136
247
  if (color) {
137
248
  return { backgroundColor: color };
138
249
  }
139
250
  }
140
251
 
141
- // Text color: text-blue-500
252
+ // Text color: text-blue-500, text-blue-500/50, text-[#ff0000]/80
142
253
  if (cls.startsWith("text-")) {
143
254
  const colorKey = cls.substring(5);
144
- const color = getColor(colorKey);
255
+ const color = parseColorWithOpacity(colorKey);
145
256
  if (color) {
146
257
  return { color: color };
147
258
  }
148
259
  }
149
260
 
150
- // Border color: border-blue-500
261
+ // Border color: border-blue-500, border-blue-500/50, border-[#ff0000]/80
151
262
  if (cls.startsWith("border-") && !cls.match(/^border-[0-9]/)) {
152
263
  const colorKey = cls.substring(7);
153
- const color = getColor(colorKey);
264
+ const color = parseColorWithOpacity(colorKey);
154
265
  if (color) {
155
266
  return { borderColor: color };
156
267
  }
@@ -4,9 +4,11 @@
4
4
  */
5
5
 
6
6
  import type { StyleObject } from "../types";
7
+ import { parseAspectRatio } from "./aspectRatio";
7
8
  import { parseBorder } from "./borders";
8
9
  import { parseColor } from "./colors";
9
10
  import { parseLayout } from "./layout";
11
+ import { parseShadow } from "./shadows";
10
12
  import { parseSizing } from "./sizing";
11
13
  import { parseSpacing } from "./spacing";
12
14
  import { parseTypography } from "./typography";
@@ -37,14 +39,17 @@ export function parseClassName(className: string, customColors?: Record<string,
37
39
  */
38
40
  export function parseClass(cls: string, customColors?: Record<string, string>): StyleObject {
39
41
  // Try each parser in order
42
+ // Note: parseBorder must come before parseColor to avoid border-[3px] being parsed as a color
40
43
  // parseColor gets custom colors, others don't need it
41
- const parsers: Array<(cls: string) => StyleObject | null> = [
44
+ const parsers: ((cls: string) => StyleObject | null)[] = [
42
45
  parseSpacing,
46
+ parseBorder,
43
47
  (cls: string) => parseColor(cls, customColors),
44
48
  parseLayout,
45
49
  parseTypography,
46
- parseBorder,
47
50
  parseSizing,
51
+ parseShadow,
52
+ parseAspectRatio,
48
53
  ];
49
54
 
50
55
  for (const parser of parsers) {
@@ -63,9 +68,15 @@ export function parseClass(cls: string, customColors?: Record<string, string>):
63
68
  }
64
69
 
65
70
  // Re-export parsers for testing/advanced usage
71
+ export { parseAspectRatio } from "./aspectRatio";
66
72
  export { parseBorder } from "./borders";
67
73
  export { parseColor } from "./colors";
68
74
  export { parseLayout } from "./layout";
75
+ export { parseShadow } from "./shadows";
69
76
  export { parseSizing } from "./sizing";
70
77
  export { parseSpacing } from "./spacing";
71
78
  export { parseTypography } from "./typography";
79
+
80
+ // Re-export modifier utilities
81
+ export { hasModifier, parseModifier, splitModifierClasses } from "./modifiers";
82
+ export type { ModifierType, ParsedModifier } from "./modifiers";