@mgcrea/react-native-tailwind 0.12.0 → 0.13.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 (97) hide show
  1. package/README.md +29 -2014
  2. package/dist/babel/config-loader.d.ts +3 -0
  3. package/dist/babel/config-loader.test.ts +2 -2
  4. package/dist/babel/config-loader.ts +37 -2
  5. package/dist/babel/index.cjs +2855 -2434
  6. package/dist/babel/plugin/componentScope.d.ts +26 -0
  7. package/dist/babel/plugin/componentScope.ts +87 -0
  8. package/dist/babel/plugin/state.d.ts +119 -0
  9. package/dist/babel/plugin/state.ts +177 -0
  10. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  11. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +74 -674
  12. package/dist/babel/plugin/visitors/className.ts +624 -0
  13. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  14. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  15. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  16. package/dist/babel/plugin/visitors/imports.ts +101 -0
  17. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  18. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  19. package/dist/babel/plugin/visitors/program.ts +99 -0
  20. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  21. package/dist/babel/plugin/visitors/tw.test.ts +620 -0
  22. package/dist/babel/plugin/visitors/tw.ts +148 -0
  23. package/dist/babel/plugin.d.ts +3 -96
  24. package/dist/babel/plugin.test.ts +470 -0
  25. package/dist/babel/plugin.ts +28 -953
  26. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  27. package/dist/babel/utils/componentSupport.test.ts +20 -7
  28. package/dist/babel/utils/componentSupport.ts +2 -0
  29. package/dist/babel/utils/modifierProcessing.ts +21 -0
  30. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  31. package/dist/babel/utils/styleInjection.d.ts +15 -0
  32. package/dist/babel/utils/styleInjection.ts +172 -17
  33. package/dist/babel/utils/twProcessing.ts +11 -0
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +1 -0
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/sizing.js +1 -1
  54. package/dist/parser/typography.d.ts +2 -1
  55. package/dist/parser/typography.js +1 -1
  56. package/dist/parser/typography.test.js +1 -1
  57. package/dist/runtime.cjs +1 -1
  58. package/dist/runtime.cjs.map +4 -4
  59. package/dist/runtime.js +1 -1
  60. package/dist/runtime.js.map +4 -4
  61. package/package.json +1 -1
  62. package/src/babel/config-loader.test.ts +2 -2
  63. package/src/babel/config-loader.ts +37 -2
  64. package/src/babel/plugin/componentScope.ts +87 -0
  65. package/src/babel/plugin/state.ts +177 -0
  66. package/src/babel/plugin/visitors/className.test.ts +1312 -0
  67. package/src/babel/plugin/visitors/className.ts +624 -0
  68. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  69. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  70. package/src/babel/plugin/visitors/imports.ts +101 -0
  71. package/src/babel/plugin/visitors/program.test.ts +325 -0
  72. package/src/babel/plugin/visitors/program.ts +99 -0
  73. package/src/babel/plugin/visitors/tw.test.ts +620 -0
  74. package/src/babel/plugin/visitors/tw.ts +148 -0
  75. package/src/babel/plugin.ts +28 -953
  76. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  77. package/src/babel/utils/componentSupport.test.ts +20 -7
  78. package/src/babel/utils/componentSupport.ts +2 -0
  79. package/src/babel/utils/modifierProcessing.ts +21 -0
  80. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  81. package/src/babel/utils/styleInjection.ts +172 -17
  82. package/src/babel/utils/twProcessing.ts +11 -0
  83. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  84. package/src/components/TouchableOpacity.tsx +71 -0
  85. package/src/components/index.ts +3 -0
  86. package/src/config/markers.ts +5 -0
  87. package/src/index.ts +4 -5
  88. package/src/parser/borders.test.ts +58 -0
  89. package/src/parser/borders.ts +18 -3
  90. package/src/parser/colors.test.ts +249 -0
  91. package/src/parser/colors.ts +38 -0
  92. package/src/parser/index.ts +4 -3
  93. package/src/parser/layout.test.ts +61 -0
  94. package/src/parser/layout.ts +55 -1
  95. package/src/parser/sizing.ts +11 -0
  96. package/src/parser/typography.test.ts +102 -0
  97. package/src/parser/typography.ts +61 -15
@@ -327,3 +327,61 @@ describe("parseBorder - comprehensive coverage", () => {
327
327
  });
328
328
  });
329
329
  });
330
+
331
+ describe("parseBorder - color pattern detection", () => {
332
+ it("should return null for directional border colors with preset values", () => {
333
+ // These should be handled by parseColor
334
+ expect(parseBorder("border-t-red-500")).toBeNull();
335
+ expect(parseBorder("border-r-blue-500")).toBeNull();
336
+ expect(parseBorder("border-b-green-500")).toBeNull();
337
+ expect(parseBorder("border-l-yellow-500")).toBeNull();
338
+ });
339
+
340
+ it("should return null for directional border colors with basic values", () => {
341
+ // These should be handled by parseColor
342
+ expect(parseBorder("border-t-white")).toBeNull();
343
+ expect(parseBorder("border-r-black")).toBeNull();
344
+ expect(parseBorder("border-b-transparent")).toBeNull();
345
+ expect(parseBorder("border-l-white")).toBeNull();
346
+ });
347
+
348
+ it("should return null for directional border colors with arbitrary hex values", () => {
349
+ // These should be handled by parseColor
350
+ expect(parseBorder("border-t-[#ff0000]")).toBeNull();
351
+ expect(parseBorder("border-r-[#3B82F6]")).toBeNull();
352
+ expect(parseBorder("border-b-[#abc]")).toBeNull();
353
+ expect(parseBorder("border-l-[#00FF00AA]")).toBeNull();
354
+ });
355
+
356
+ it("should return null for directional border colors with opacity", () => {
357
+ // These should be handled by parseColor
358
+ expect(parseBorder("border-t-red-500/50")).toBeNull();
359
+ expect(parseBorder("border-r-blue-500/80")).toBeNull();
360
+ expect(parseBorder("border-b-[#ff0000]/60")).toBeNull();
361
+ expect(parseBorder("border-l-black/25")).toBeNull();
362
+ });
363
+
364
+ it("should return null for directional border colors with custom colors", () => {
365
+ // These should be handled by parseColor (assuming brand-primary is a custom color)
366
+ expect(parseBorder("border-t-brand-primary")).toBeNull();
367
+ expect(parseBorder("border-r-accent")).toBeNull();
368
+ expect(parseBorder("border-b-brand-secondary")).toBeNull();
369
+ expect(parseBorder("border-l-custom")).toBeNull();
370
+ });
371
+
372
+ it("should still handle directional border widths correctly", () => {
373
+ // These should NOT be detected as color patterns
374
+ expect(parseBorder("border-t-2")).toEqual({ borderTopWidth: 2 });
375
+ expect(parseBorder("border-r-4")).toEqual({ borderRightWidth: 4 });
376
+ expect(parseBorder("border-b-8")).toEqual({ borderBottomWidth: 8 });
377
+ expect(parseBorder("border-l-0")).toEqual({ borderLeftWidth: 0 });
378
+ });
379
+
380
+ it("should still handle directional border width arbitrary values", () => {
381
+ // These should NOT be detected as color patterns
382
+ expect(parseBorder("border-t-[3px]")).toEqual({ borderTopWidth: 3 });
383
+ expect(parseBorder("border-r-[5px]")).toEqual({ borderRightWidth: 5 });
384
+ expect(parseBorder("border-b-[10]")).toEqual({ borderBottomWidth: 10 });
385
+ expect(parseBorder("border-l-[8px]")).toEqual({ borderLeftWidth: 8 });
386
+ });
387
+ });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { StyleObject } from "../types";
6
+ import { parseColor } from "./colors";
6
7
 
7
8
  // Border width scale
8
9
  export const BORDER_WIDTH_SCALE: Record<string, number> = {
@@ -108,8 +109,10 @@ function parseArbitraryBorderRadius(value: string): number | null {
108
109
 
109
110
  /**
110
111
  * Parse border classes
112
+ * @param cls - The class name to parse
113
+ * @param customColors - Optional custom colors from tailwind.config (used to detect color patterns)
111
114
  */
112
- export function parseBorder(cls: string): StyleObject | null {
115
+ export function parseBorder(cls: string, customColors?: Record<string, string>): StyleObject | null {
113
116
  // Border style (must come before parseBorderWidth)
114
117
  if (cls === "border-solid") return { borderStyle: "solid" };
115
118
  if (cls === "border-dotted") return { borderStyle: "dotted" };
@@ -117,7 +120,7 @@ export function parseBorder(cls: string): StyleObject | null {
117
120
 
118
121
  // Border width (border-0, border-t, border-[8px], etc.)
119
122
  if (cls.startsWith("border-")) {
120
- return parseBorderWidth(cls);
123
+ return parseBorderWidth(cls, customColors);
121
124
  }
122
125
 
123
126
  if (cls === "border") {
@@ -134,14 +137,26 @@ export function parseBorder(cls: string): StyleObject | null {
134
137
 
135
138
  /**
136
139
  * Parse border width classes
140
+ * @param cls - The class name to parse
141
+ * @param customColors - Optional custom colors (passed to parseColor for pattern detection)
137
142
  */
138
- function parseBorderWidth(cls: string): StyleObject | null {
143
+ function parseBorderWidth(cls: string, customColors?: Record<string, string>): StyleObject | null {
139
144
  // Directional borders: border-t, border-t-2, border-t-[8px]
145
+ // Note: border-x and border-y are handled by parseColor for colors only
140
146
  const dirMatch = cls.match(/^border-([trbl])(?:-(.+))?$/);
141
147
  if (dirMatch) {
142
148
  const dir = dirMatch[1];
143
149
  const valueStr = dirMatch[2] || ""; // empty string for border-t
144
150
 
151
+ // If it's a color pattern, let parseColor handle it
152
+ // Try to parse as color - if it succeeds, return null (let parseColor handle it)
153
+ if (valueStr) {
154
+ const colorResult = parseColor(cls, customColors);
155
+ if (colorResult !== null) {
156
+ return null; // It's a color, let parseColor handle it
157
+ }
158
+ }
159
+
145
160
  // Try arbitrary value first (if it starts with [)
146
161
  if (valueStr.startsWith("[")) {
147
162
  const arbitraryValue = parseArbitraryBorderWidth(valueStr);
@@ -391,3 +391,252 @@ describe("parseColor - opacity modifiers", () => {
391
391
  });
392
392
  });
393
393
  });
394
+
395
+ describe("parseColor - directional border colors", () => {
396
+ it("should parse directional border colors with preset values", () => {
397
+ expect(parseColor("border-t-red-500")).toEqual({ borderTopColor: COLORS["red-500"] });
398
+ expect(parseColor("border-r-blue-500")).toEqual({ borderRightColor: COLORS["blue-500"] });
399
+ expect(parseColor("border-b-green-500")).toEqual({ borderBottomColor: COLORS["green-500"] });
400
+ expect(parseColor("border-l-yellow-500")).toEqual({ borderLeftColor: COLORS["yellow-500"] });
401
+ });
402
+
403
+ it("should parse directional border colors with basic values", () => {
404
+ expect(parseColor("border-t-white")).toEqual({ borderTopColor: "#FFFFFF" });
405
+ expect(parseColor("border-r-black")).toEqual({ borderRightColor: "#000000" });
406
+ expect(parseColor("border-b-transparent")).toEqual({ borderBottomColor: "transparent" });
407
+ expect(parseColor("border-l-white")).toEqual({ borderLeftColor: "#FFFFFF" });
408
+ });
409
+
410
+ it("should parse directional border colors with arbitrary 6-digit hex values", () => {
411
+ expect(parseColor("border-t-[#ff0000]")).toEqual({ borderTopColor: "#ff0000" });
412
+ expect(parseColor("border-r-[#3B82F6]")).toEqual({ borderRightColor: "#3B82F6" });
413
+ expect(parseColor("border-b-[#00ff00]")).toEqual({ borderBottomColor: "#00ff00" });
414
+ expect(parseColor("border-l-[#cccccc]")).toEqual({ borderLeftColor: "#cccccc" });
415
+ });
416
+
417
+ it("should parse directional border colors with arbitrary 3-digit hex values", () => {
418
+ expect(parseColor("border-t-[#f00]")).toEqual({ borderTopColor: "#ff0000" });
419
+ expect(parseColor("border-r-[#abc]")).toEqual({ borderRightColor: "#aabbcc" });
420
+ expect(parseColor("border-b-[#123]")).toEqual({ borderBottomColor: "#112233" });
421
+ expect(parseColor("border-l-[#999]")).toEqual({ borderLeftColor: "#999999" });
422
+ });
423
+
424
+ it("should parse directional border colors with arbitrary 8-digit hex values (with alpha)", () => {
425
+ expect(parseColor("border-t-[#ff0000aa]")).toEqual({ borderTopColor: "#ff0000aa" });
426
+ expect(parseColor("border-r-[#0000FF50]")).toEqual({ borderRightColor: "#0000FF50" });
427
+ expect(parseColor("border-b-[#00FF0080]")).toEqual({ borderBottomColor: "#00FF0080" });
428
+ expect(parseColor("border-l-[#FFFFFF00]")).toEqual({ borderLeftColor: "#FFFFFF00" });
429
+ });
430
+
431
+ it("should parse directional border colors with opacity modifiers", () => {
432
+ expect(parseColor("border-t-red-500/50")).toEqual({
433
+ borderTopColor: applyOpacity(COLORS["red-500"], 50),
434
+ });
435
+ expect(parseColor("border-r-blue-500/80")).toEqual({
436
+ borderRightColor: applyOpacity(COLORS["blue-500"], 80),
437
+ });
438
+ expect(parseColor("border-b-green-500/30")).toEqual({
439
+ borderBottomColor: applyOpacity(COLORS["green-500"], 30),
440
+ });
441
+ expect(parseColor("border-l-black/25")).toEqual({
442
+ borderLeftColor: applyOpacity(COLORS.black, 25),
443
+ });
444
+ });
445
+
446
+ it("should parse directional border colors with arbitrary hex and opacity", () => {
447
+ expect(parseColor("border-t-[#ff0000]/50")).toEqual({ borderTopColor: "#FF000080" });
448
+ expect(parseColor("border-r-[#3B82F6]/80")).toEqual({ borderRightColor: "#3B82F6CC" });
449
+ expect(parseColor("border-b-[#abc]/60")).toEqual({ borderBottomColor: "#AABBCC99" });
450
+ expect(parseColor("border-l-[#000]/100")).toEqual({ borderLeftColor: "#000000FF" });
451
+ });
452
+
453
+ it("should parse directional border colors with custom colors", () => {
454
+ const customColors = {
455
+ "brand-primary": "#FF6B6B",
456
+ "brand-secondary": "#4ECDC4",
457
+ accent: "#FFE66D",
458
+ };
459
+
460
+ expect(parseColor("border-t-brand-primary", customColors)).toEqual({
461
+ borderTopColor: "#FF6B6B",
462
+ });
463
+ expect(parseColor("border-r-brand-secondary", customColors)).toEqual({
464
+ borderRightColor: "#4ECDC4",
465
+ });
466
+ expect(parseColor("border-b-accent", customColors)).toEqual({ borderBottomColor: "#FFE66D" });
467
+ expect(parseColor("border-l-brand-primary", customColors)).toEqual({
468
+ borderLeftColor: "#FF6B6B",
469
+ });
470
+ });
471
+
472
+ it("should parse directional border colors with custom colors and opacity", () => {
473
+ const customColors = { "brand-primary": "#FF6B6B" };
474
+ expect(parseColor("border-t-brand-primary/50", customColors)).toEqual({
475
+ borderTopColor: "#FF6B6B80",
476
+ });
477
+ expect(parseColor("border-l-brand-primary/75", customColors)).toEqual({
478
+ borderLeftColor: "#FF6B6BBF",
479
+ });
480
+ });
481
+
482
+ it("should not match border width classes", () => {
483
+ // These should be handled by parseBorder
484
+ expect(parseColor("border-t-2")).toBeNull();
485
+ expect(parseColor("border-r-4")).toBeNull();
486
+ expect(parseColor("border-b-8")).toBeNull();
487
+ expect(parseColor("border-l-0")).toBeNull();
488
+ });
489
+
490
+ it("should not match border width arbitrary values", () => {
491
+ // These should be handled by parseBorder
492
+ expect(parseColor("border-t-[3px]")).toBeNull();
493
+ expect(parseColor("border-r-[8px]")).toBeNull();
494
+ expect(parseColor("border-b-[5]")).toBeNull();
495
+ expect(parseColor("border-l-[10px]")).toBeNull();
496
+ });
497
+
498
+ it("should return null for invalid directional border color values", () => {
499
+ expect(parseColor("border-t-invalid")).toBeNull();
500
+ expect(parseColor("border-r-notacolor")).toBeNull();
501
+ expect(parseColor("border-b-xyz")).toBeNull();
502
+ expect(parseColor("border-l-wrongcolor")).toBeNull();
503
+ });
504
+
505
+ it("should handle all directions with same color", () => {
506
+ const color = COLORS["blue-500"];
507
+ expect(parseColor("border-t-blue-500")).toEqual({ borderTopColor: color });
508
+ expect(parseColor("border-r-blue-500")).toEqual({ borderRightColor: color });
509
+ expect(parseColor("border-b-blue-500")).toEqual({ borderBottomColor: color });
510
+ expect(parseColor("border-l-blue-500")).toEqual({ borderLeftColor: color });
511
+ });
512
+
513
+ it("should handle all color families for directional borders", () => {
514
+ const families = ["gray", "red", "blue", "green", "yellow", "purple", "pink", "orange", "indigo"];
515
+ families.forEach((family) => {
516
+ expect(parseColor(`border-t-${family}-500`)).toBeTruthy();
517
+ expect(parseColor(`border-r-${family}-500`)).toBeTruthy();
518
+ expect(parseColor(`border-b-${family}-500`)).toBeTruthy();
519
+ expect(parseColor(`border-l-${family}-500`)).toBeTruthy();
520
+ });
521
+ });
522
+
523
+ it("should handle directional borders with all opacity levels", () => {
524
+ expect(parseColor("border-t-black/0")).toEqual({ borderTopColor: "#00000000" });
525
+ expect(parseColor("border-t-black/25")).toEqual({ borderTopColor: "#00000040" });
526
+ expect(parseColor("border-t-black/50")).toEqual({ borderTopColor: "#00000080" });
527
+ expect(parseColor("border-t-black/75")).toEqual({ borderTopColor: "#000000BF" });
528
+ expect(parseColor("border-t-black/100")).toEqual({ borderTopColor: "#000000FF" });
529
+ });
530
+ });
531
+
532
+ describe("parseColor - border-x and border-y colors", () => {
533
+ it("should parse border-x colors (horizontal: left + right)", () => {
534
+ expect(parseColor("border-x-red-500")).toEqual({
535
+ borderLeftColor: COLORS["red-500"],
536
+ borderRightColor: COLORS["red-500"],
537
+ });
538
+ expect(parseColor("border-x-blue-500")).toEqual({
539
+ borderLeftColor: COLORS["blue-500"],
540
+ borderRightColor: COLORS["blue-500"],
541
+ });
542
+ });
543
+
544
+ it("should parse border-y colors (vertical: top + bottom)", () => {
545
+ expect(parseColor("border-y-green-500")).toEqual({
546
+ borderTopColor: COLORS["green-500"],
547
+ borderBottomColor: COLORS["green-500"],
548
+ });
549
+ expect(parseColor("border-y-yellow-500")).toEqual({
550
+ borderTopColor: COLORS["yellow-500"],
551
+ borderBottomColor: COLORS["yellow-500"],
552
+ });
553
+ });
554
+
555
+ it("should parse border-x with basic colors", () => {
556
+ expect(parseColor("border-x-white")).toEqual({
557
+ borderLeftColor: "#FFFFFF",
558
+ borderRightColor: "#FFFFFF",
559
+ });
560
+ expect(parseColor("border-x-black")).toEqual({
561
+ borderLeftColor: "#000000",
562
+ borderRightColor: "#000000",
563
+ });
564
+ });
565
+
566
+ it("should parse border-y with basic colors", () => {
567
+ expect(parseColor("border-y-white")).toEqual({
568
+ borderTopColor: "#FFFFFF",
569
+ borderBottomColor: "#FFFFFF",
570
+ });
571
+ expect(parseColor("border-y-transparent")).toEqual({
572
+ borderTopColor: "transparent",
573
+ borderBottomColor: "transparent",
574
+ });
575
+ });
576
+
577
+ it("should parse border-x with opacity", () => {
578
+ expect(parseColor("border-x-red-500/50")).toEqual({
579
+ borderLeftColor: applyOpacity(COLORS["red-500"], 50),
580
+ borderRightColor: applyOpacity(COLORS["red-500"], 50),
581
+ });
582
+ });
583
+
584
+ it("should parse border-y with opacity", () => {
585
+ expect(parseColor("border-y-blue-500/80")).toEqual({
586
+ borderTopColor: applyOpacity(COLORS["blue-500"], 80),
587
+ borderBottomColor: applyOpacity(COLORS["blue-500"], 80),
588
+ });
589
+ });
590
+
591
+ it("should parse border-x with arbitrary hex colors", () => {
592
+ expect(parseColor("border-x-[#ff0000]")).toEqual({
593
+ borderLeftColor: "#ff0000",
594
+ borderRightColor: "#ff0000",
595
+ });
596
+ expect(parseColor("border-x-[#abc]")).toEqual({
597
+ borderLeftColor: "#aabbcc",
598
+ borderRightColor: "#aabbcc",
599
+ });
600
+ });
601
+
602
+ it("should parse border-y with arbitrary hex colors", () => {
603
+ expect(parseColor("border-y-[#00ff00]")).toEqual({
604
+ borderTopColor: "#00ff00",
605
+ borderBottomColor: "#00ff00",
606
+ });
607
+ expect(parseColor("border-y-[#123]")).toEqual({
608
+ borderTopColor: "#112233",
609
+ borderBottomColor: "#112233",
610
+ });
611
+ });
612
+
613
+ it("should parse border-x with custom colors", () => {
614
+ const customColors = { "brand-primary": "#FF6B6B" };
615
+ expect(parseColor("border-x-brand-primary", customColors)).toEqual({
616
+ borderLeftColor: "#FF6B6B",
617
+ borderRightColor: "#FF6B6B",
618
+ });
619
+ });
620
+
621
+ it("should parse border-y with custom colors", () => {
622
+ const customColors = { accent: "#FFE66D" };
623
+ expect(parseColor("border-y-accent", customColors)).toEqual({
624
+ borderTopColor: "#FFE66D",
625
+ borderBottomColor: "#FFE66D",
626
+ });
627
+ });
628
+
629
+ it("should not match border-x/border-y width classes", () => {
630
+ expect(parseColor("border-x-2")).toBeNull();
631
+ expect(parseColor("border-y-4")).toBeNull();
632
+ expect(parseColor("border-x-0")).toBeNull();
633
+ expect(parseColor("border-y-8")).toBeNull();
634
+ });
635
+
636
+ it("should not match border-x/border-y arbitrary width values", () => {
637
+ expect(parseColor("border-x-[3px]")).toBeNull();
638
+ expect(parseColor("border-y-[5px]")).toBeNull();
639
+ expect(parseColor("border-x-[10]")).toBeNull();
640
+ expect(parseColor("border-y-[8px]")).toBeNull();
641
+ });
642
+ });
@@ -178,5 +178,43 @@ export function parseColor(cls: string, customColors?: Record<string, string>):
178
178
  }
179
179
  }
180
180
 
181
+ // Directional border colors: border-t-red-500, border-l-blue-500/50, border-r-[#ff0000]
182
+ const dirBorderMatch = cls.match(/^border-([trblxy])-(.+)$/);
183
+ if (dirBorderMatch) {
184
+ const dir = dirBorderMatch[1];
185
+ const colorKey = dirBorderMatch[2];
186
+
187
+ // Skip arbitrary values that don't look like colors (e.g., border-t-[3px] is width)
188
+ if (colorKey.startsWith("[") && !colorKey.startsWith("[#")) {
189
+ return null;
190
+ }
191
+
192
+ const color = parseColorWithOpacity(colorKey);
193
+ if (color) {
194
+ // Map direction to React Native property/properties
195
+ // x and y apply to multiple sides (RN doesn't have borderHorizontalColor/borderVerticalColor)
196
+ if (dir === "x") {
197
+ return {
198
+ borderLeftColor: color,
199
+ borderRightColor: color,
200
+ };
201
+ }
202
+ if (dir === "y") {
203
+ return {
204
+ borderTopColor: color,
205
+ borderBottomColor: color,
206
+ };
207
+ }
208
+
209
+ const propMap: Record<string, string> = {
210
+ t: "borderTopColor",
211
+ r: "borderRightColor",
212
+ b: "borderBottomColor",
213
+ l: "borderLeftColor",
214
+ };
215
+ return { [propMap[dir]]: color };
216
+ }
217
+ }
218
+
181
219
  return null;
182
220
  }
@@ -20,6 +20,7 @@ import { parseTypography } from "./typography";
20
20
  export type CustomTheme = {
21
21
  colors?: Record<string, string>;
22
22
  fontFamily?: Record<string, string>;
23
+ fontSize?: Record<string, number>;
23
24
  };
24
25
 
25
26
  /**
@@ -49,13 +50,13 @@ export function parseClassName(className: string, customTheme?: CustomTheme): St
49
50
  export function parseClass(cls: string, customTheme?: CustomTheme): StyleObject {
50
51
  // Try each parser in order
51
52
  // Note: parseBorder must come before parseColor to avoid border-[3px] being parsed as a color
52
- // parseColor and parseTypography get custom theme, others don't need it
53
+ // parseBorder, parseColor and parseTypography get custom theme
53
54
  const parsers: Array<(cls: string) => StyleObject | null> = [
54
55
  parseSpacing,
55
- parseBorder,
56
+ (cls: string) => parseBorder(cls, customTheme?.colors),
56
57
  (cls: string) => parseColor(cls, customTheme?.colors),
57
58
  parseLayout,
58
- (cls: string) => parseTypography(cls, customTheme?.fontFamily),
59
+ (cls: string) => parseTypography(cls, customTheme?.fontFamily, customTheme?.fontSize),
59
60
  parseSizing,
60
61
  parseShadow,
61
62
  parseAspectRatio,
@@ -73,6 +73,67 @@ describe("parseLayout - flex grow/shrink utilities", () => {
73
73
  it("should parse shrink-0", () => {
74
74
  expect(parseLayout("shrink-0")).toEqual({ flexShrink: 0 });
75
75
  });
76
+
77
+ it("should parse grow with arbitrary values", () => {
78
+ expect(parseLayout("grow-[1.5]")).toEqual({ flexGrow: 1.5 });
79
+ expect(parseLayout("grow-[2]")).toEqual({ flexGrow: 2 });
80
+ expect(parseLayout("grow-[0.5]")).toEqual({ flexGrow: 0.5 });
81
+ expect(parseLayout("grow-[3]")).toEqual({ flexGrow: 3 });
82
+ expect(parseLayout("grow-[0]")).toEqual({ flexGrow: 0 });
83
+ });
84
+
85
+ it("should parse shrink with arbitrary values", () => {
86
+ expect(parseLayout("shrink-[0.5]")).toEqual({ flexShrink: 0.5 });
87
+ expect(parseLayout("shrink-[2]")).toEqual({ flexShrink: 2 });
88
+ expect(parseLayout("shrink-[1.5]")).toEqual({ flexShrink: 1.5 });
89
+ expect(parseLayout("shrink-[3]")).toEqual({ flexShrink: 3 });
90
+ expect(parseLayout("shrink-[0]")).toEqual({ flexShrink: 0 });
91
+ });
92
+
93
+ it("should parse CSS-style flex-grow aliases", () => {
94
+ expect(parseLayout("flex-grow")).toEqual({ flexGrow: 1 });
95
+ expect(parseLayout("flex-grow-0")).toEqual({ flexGrow: 0 });
96
+ });
97
+
98
+ it("should parse CSS-style flex-shrink aliases", () => {
99
+ expect(parseLayout("flex-shrink")).toEqual({ flexShrink: 1 });
100
+ expect(parseLayout("flex-shrink-0")).toEqual({ flexShrink: 0 });
101
+ });
102
+
103
+ it("should parse CSS-style flex-grow with arbitrary values", () => {
104
+ expect(parseLayout("flex-grow-[1.5]")).toEqual({ flexGrow: 1.5 });
105
+ expect(parseLayout("flex-grow-[2]")).toEqual({ flexGrow: 2 });
106
+ expect(parseLayout("flex-grow-[0.5]")).toEqual({ flexGrow: 0.5 });
107
+ });
108
+
109
+ it("should parse CSS-style flex-shrink with arbitrary values", () => {
110
+ expect(parseLayout("flex-shrink-[0.5]")).toEqual({ flexShrink: 0.5 });
111
+ expect(parseLayout("flex-shrink-[2]")).toEqual({ flexShrink: 2 });
112
+ expect(parseLayout("flex-shrink-[1.5]")).toEqual({ flexShrink: 1.5 });
113
+ });
114
+
115
+ it("should handle edge case values", () => {
116
+ expect(parseLayout("grow-[0.1]")).toEqual({ flexGrow: 0.1 });
117
+ expect(parseLayout("grow-[10]")).toEqual({ flexGrow: 10 });
118
+ expect(parseLayout("shrink-[0.01]")).toEqual({ flexShrink: 0.01 });
119
+ expect(parseLayout("shrink-[100]")).toEqual({ flexShrink: 100 });
120
+ });
121
+
122
+ it("should parse Tailwind shorthand decimals (no leading zero)", () => {
123
+ expect(parseLayout("grow-[.5]")).toEqual({ flexGrow: 0.5 });
124
+ expect(parseLayout("grow-[.75]")).toEqual({ flexGrow: 0.75 });
125
+ expect(parseLayout("shrink-[.5]")).toEqual({ flexShrink: 0.5 });
126
+ expect(parseLayout("shrink-[.25]")).toEqual({ flexShrink: 0.25 });
127
+ expect(parseLayout("flex-grow-[.5]")).toEqual({ flexGrow: 0.5 });
128
+ expect(parseLayout("flex-shrink-[.5]")).toEqual({ flexShrink: 0.5 });
129
+ });
130
+
131
+ it("should reject negative values", () => {
132
+ expect(parseLayout("grow-[-1]")).toBeNull();
133
+ expect(parseLayout("shrink-[-1]")).toBeNull();
134
+ expect(parseLayout("flex-grow-[-2]")).toBeNull();
135
+ expect(parseLayout("flex-shrink-[-0.5]")).toBeNull();
136
+ });
76
137
  });
77
138
 
78
139
  describe("parseLayout - justify content utilities", () => {
@@ -60,6 +60,31 @@ function parseArbitraryZIndex(value: string): number | null {
60
60
  return null;
61
61
  }
62
62
 
63
+ /**
64
+ * Parse arbitrary grow/shrink value: [1.5], [2], [0.5], [.5]
65
+ * Returns number for valid non-negative values, null otherwise
66
+ */
67
+ function parseArbitraryGrowShrink(value: string): number | null {
68
+ // Match: [1.5], [2], [0], [0.5], [.5] (non-negative decimals, optional leading digit)
69
+ const match = value.match(/^\[(\d+(?:\.\d+)?|\.\d+)\]$/);
70
+ if (match) {
71
+ return parseFloat(match[1]);
72
+ }
73
+
74
+ // Warn about invalid formats (negative values, unsupported formats)
75
+ if (value.startsWith("[") && value.endsWith("]")) {
76
+ /* v8 ignore next 5 */
77
+ if (process.env.NODE_ENV !== "production") {
78
+ console.warn(
79
+ `[react-native-tailwind] Invalid arbitrary grow/shrink value: ${value}. Only non-negative numbers are supported (e.g., [1.5], [2], [0.5], [.5]).`,
80
+ );
81
+ }
82
+ return null;
83
+ }
84
+
85
+ return null;
86
+ }
87
+
63
88
  // Display utilities
64
89
  const DISPLAY_MAP: Record<string, StyleObject> = {
65
90
  flex: { display: "flex" },
@@ -88,12 +113,17 @@ const FLEX_MAP: Record<string, StyleObject> = {
88
113
  "flex-none": { flex: 0 },
89
114
  };
90
115
 
91
- // Flex grow/shrink utilities
116
+ // Flex grow/shrink utilities (includes CSS-style aliases)
92
117
  const GROW_SHRINK_MAP: Record<string, StyleObject> = {
93
118
  grow: { flexGrow: 1 },
94
119
  "grow-0": { flexGrow: 0 },
95
120
  shrink: { flexShrink: 1 },
96
121
  "shrink-0": { flexShrink: 0 },
122
+ // CSS-style aliases
123
+ "flex-grow": { flexGrow: 1 },
124
+ "flex-grow-0": { flexGrow: 0 },
125
+ "flex-shrink": { flexShrink: 1 },
126
+ "flex-shrink-0": { flexShrink: 0 },
97
127
  };
98
128
 
99
129
  // Justify content utilities
@@ -357,6 +387,30 @@ export function parseLayout(cls: string): StyleObject | null {
357
387
  }
358
388
  }
359
389
 
390
+ // Flex grow: grow-[1.5], flex-grow-[2], etc. (arbitrary values)
391
+ if (cls.startsWith("grow-") || cls.startsWith("flex-grow-")) {
392
+ const prefix = cls.startsWith("flex-grow-") ? "flex-grow-" : "grow-";
393
+ const growKey = cls.substring(prefix.length);
394
+
395
+ // Arbitrary values: grow-[1.5], flex-grow-[2]
396
+ const arbitraryGrow = parseArbitraryGrowShrink(growKey);
397
+ if (arbitraryGrow !== null) {
398
+ return { flexGrow: arbitraryGrow };
399
+ }
400
+ }
401
+
402
+ // Flex shrink: shrink-[0.5], flex-shrink-[1], etc. (arbitrary values)
403
+ if (cls.startsWith("shrink-") || cls.startsWith("flex-shrink-")) {
404
+ const prefix = cls.startsWith("flex-shrink-") ? "flex-shrink-" : "shrink-";
405
+ const shrinkKey = cls.substring(prefix.length);
406
+
407
+ // Arbitrary values: shrink-[0.5], flex-shrink-[1]
408
+ const arbitraryShrink = parseArbitraryGrowShrink(shrinkKey);
409
+ if (arbitraryShrink !== null) {
410
+ return { flexShrink: arbitraryShrink };
411
+ }
412
+ }
413
+
360
414
  // Try each lookup table in order
361
415
  return (
362
416
  DISPLAY_MAP[cls] ??
@@ -2,6 +2,7 @@
2
2
  * Sizing utilities (width, height, min/max)
3
3
  */
4
4
 
5
+ import { RUNTIME_DIMENSIONS_MARKER } from "../config/markers";
5
6
  import type { StyleObject } from "../types";
6
7
 
7
8
  // Size scale (in pixels/percentages)
@@ -100,6 +101,11 @@ export function parseSizing(cls: string): StyleObject | null {
100
101
  if (cls.startsWith("w-")) {
101
102
  const sizeKey = cls.substring(2);
102
103
 
104
+ // Screen width (requires runtime hook)
105
+ if (sizeKey === "screen") {
106
+ return { width: `${RUNTIME_DIMENSIONS_MARKER}width}}` } as StyleObject;
107
+ }
108
+
103
109
  // Arbitrary values: w-[123px], w-[50%]
104
110
  const arbitrarySize = parseArbitrarySize(sizeKey);
105
111
  if (arbitrarySize !== null) {
@@ -128,6 +134,11 @@ export function parseSizing(cls: string): StyleObject | null {
128
134
  if (cls.startsWith("h-")) {
129
135
  const sizeKey = cls.substring(2);
130
136
 
137
+ // Screen height (requires runtime hook)
138
+ if (sizeKey === "screen") {
139
+ return { height: `${RUNTIME_DIMENSIONS_MARKER}height}}` } as StyleObject;
140
+ }
141
+
131
142
  // Arbitrary values: h-[123px], h-[50%]
132
143
  const arbitrarySize = parseArbitrarySize(sizeKey);
133
144
  if (arbitrarySize !== null) {