@mgcrea/react-native-tailwind 0.7.0 → 0.8.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 (81) hide show
  1. package/README.md +2 -1
  2. package/dist/babel/index.cjs +333 -195
  3. package/dist/babel/index.d.ts +4 -40
  4. package/dist/babel/index.test.ts +214 -1
  5. package/dist/babel/index.ts +4 -1169
  6. package/dist/babel/plugin.d.ts +42 -0
  7. package/{src/babel/index.test.ts → dist/babel/plugin.test.ts} +216 -2
  8. package/dist/babel/plugin.ts +491 -0
  9. package/dist/babel/utils/attributeMatchers.d.ts +23 -0
  10. package/dist/babel/utils/attributeMatchers.ts +71 -0
  11. package/dist/babel/utils/componentSupport.d.ts +18 -0
  12. package/dist/babel/utils/componentSupport.ts +68 -0
  13. package/dist/babel/utils/dynamicProcessing.d.ts +32 -0
  14. package/dist/babel/utils/dynamicProcessing.ts +223 -0
  15. package/dist/babel/utils/modifierProcessing.d.ts +26 -0
  16. package/dist/babel/utils/modifierProcessing.ts +118 -0
  17. package/dist/babel/utils/styleInjection.d.ts +15 -0
  18. package/dist/babel/utils/styleInjection.ts +80 -0
  19. package/dist/babel/utils/styleTransforms.d.ts +39 -0
  20. package/dist/babel/utils/styleTransforms.test.ts +349 -0
  21. package/dist/babel/utils/styleTransforms.ts +258 -0
  22. package/dist/babel/utils/twProcessing.d.ts +28 -0
  23. package/dist/babel/utils/twProcessing.ts +124 -0
  24. package/dist/components/TextInput.d.ts +171 -14
  25. package/dist/config/tailwind.d.ts +302 -0
  26. package/dist/config/tailwind.js +1 -0
  27. package/dist/index.d.ts +5 -4
  28. package/dist/index.js +1 -1
  29. package/dist/parser/colors.js +1 -1
  30. package/dist/parser/index.d.ts +1 -0
  31. package/dist/parser/index.js +1 -1
  32. package/dist/parser/modifiers.d.ts +2 -2
  33. package/dist/parser/modifiers.js +1 -1
  34. package/dist/parser/placeholder.d.ts +36 -0
  35. package/dist/parser/placeholder.js +1 -0
  36. package/dist/parser/placeholder.test.js +1 -0
  37. package/dist/parser/typography.d.ts +1 -0
  38. package/dist/parser/typography.js +1 -1
  39. package/dist/parser/typography.test.js +1 -1
  40. package/dist/runtime.cjs +1 -1
  41. package/dist/runtime.cjs.map +4 -4
  42. package/dist/runtime.d.ts +1 -14
  43. package/dist/runtime.js +1 -1
  44. package/dist/runtime.js.map +4 -4
  45. package/dist/stubs/tw.d.ts +1 -14
  46. package/dist/types/core.d.ts +40 -0
  47. package/dist/types/core.js +0 -0
  48. package/dist/types/index.d.ts +2 -0
  49. package/dist/types/index.js +1 -0
  50. package/dist/types/runtime.d.ts +15 -0
  51. package/dist/types/runtime.js +1 -0
  52. package/dist/types/util.d.ts +3 -0
  53. package/dist/types/util.js +0 -0
  54. package/package.json +1 -1
  55. package/src/babel/index.ts +4 -1169
  56. package/src/babel/plugin.test.ts +482 -0
  57. package/src/babel/plugin.ts +491 -0
  58. package/src/babel/utils/attributeMatchers.ts +71 -0
  59. package/src/babel/utils/componentSupport.ts +68 -0
  60. package/src/babel/utils/dynamicProcessing.ts +223 -0
  61. package/src/babel/utils/modifierProcessing.ts +118 -0
  62. package/src/babel/utils/styleInjection.ts +80 -0
  63. package/src/babel/utils/styleTransforms.test.ts +349 -0
  64. package/src/babel/utils/styleTransforms.ts +258 -0
  65. package/src/babel/utils/twProcessing.ts +124 -0
  66. package/src/components/TextInput.tsx +17 -14
  67. package/src/config/{palettes.ts → tailwind.ts} +2 -2
  68. package/src/index.ts +6 -3
  69. package/src/parser/colors.ts +2 -2
  70. package/src/parser/index.ts +1 -0
  71. package/src/parser/modifiers.ts +10 -4
  72. package/src/parser/placeholder.test.ts +105 -0
  73. package/src/parser/placeholder.ts +78 -0
  74. package/src/parser/typography.test.ts +11 -0
  75. package/src/parser/typography.ts +20 -2
  76. package/src/runtime.ts +1 -16
  77. package/src/stubs/tw.ts +1 -16
  78. package/src/{types.ts → types/core.ts} +0 -4
  79. package/src/types/index.ts +2 -0
  80. package/src/types/runtime.ts +17 -0
  81. package/src/types/util.ts +1 -0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Utility functions for processing tw`...` and twStyle() calls
3
+ */
4
+
5
+ import type { NodePath } from "@babel/core";
6
+ import type * as BabelTypes from "@babel/types";
7
+ import type { ModifierType, ParsedModifier } from "../../parser/index.js";
8
+ import type { StyleObject } from "../../types/core.js";
9
+
10
+ /**
11
+ * Plugin state interface (subset needed for tw processing)
12
+ */
13
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
14
+ export interface TwProcessingState {
15
+ styleRegistry: Map<string, StyleObject>;
16
+ customColors: Record<string, string>;
17
+ stylesIdentifier: string;
18
+ }
19
+
20
+ /**
21
+ * Process tw`...` or twStyle('...') call and replace with TwStyle object
22
+ * Generates: { style: styles._base, activeStyle: styles._active, ... }
23
+ */
24
+ export function processTwCall(
25
+ className: string,
26
+ path: NodePath,
27
+ state: TwProcessingState,
28
+ parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
29
+ generateStyleKey: (className: string) => string,
30
+ splitModifierClasses: (className: string) => { baseClasses: string[]; modifierClasses: ParsedModifier[] },
31
+ t: typeof BabelTypes,
32
+ ): void {
33
+ const { baseClasses, modifierClasses } = splitModifierClasses(className);
34
+
35
+ // Build TwStyle object properties
36
+ const objectProperties: BabelTypes.ObjectProperty[] = [];
37
+
38
+ // Parse and add base styles
39
+ if (baseClasses.length > 0) {
40
+ const baseClassName = baseClasses.join(" ");
41
+ const baseStyleObject = parseClassName(baseClassName, state.customColors);
42
+ const baseStyleKey = generateStyleKey(baseClassName);
43
+ state.styleRegistry.set(baseStyleKey, baseStyleObject);
44
+
45
+ objectProperties.push(
46
+ t.objectProperty(
47
+ t.identifier("style"),
48
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey)),
49
+ ),
50
+ );
51
+ } else {
52
+ // No base classes - add empty style object
53
+ objectProperties.push(t.objectProperty(t.identifier("style"), t.objectExpression([])));
54
+ }
55
+
56
+ // Group modifiers by type
57
+ const modifiersByType = new Map<ModifierType, ParsedModifier[]>();
58
+ for (const mod of modifierClasses) {
59
+ if (!modifiersByType.has(mod.modifier)) {
60
+ modifiersByType.set(mod.modifier, []);
61
+ }
62
+ const modGroup = modifiersByType.get(mod.modifier);
63
+ if (modGroup) {
64
+ modGroup.push(mod);
65
+ }
66
+ }
67
+
68
+ // Add modifier styles
69
+ for (const [modifierType, modifiers] of modifiersByType) {
70
+ const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
71
+ const modifierStyleObject = parseClassName(modifierClassNames, state.customColors);
72
+ const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
73
+ state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
74
+
75
+ // Map modifier type to property name: active -> activeStyle
76
+ const propertyName = `${modifierType}Style`;
77
+
78
+ objectProperties.push(
79
+ t.objectProperty(
80
+ t.identifier(propertyName),
81
+ t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(modifierStyleKey)),
82
+ ),
83
+ );
84
+ }
85
+
86
+ // Replace the tw`...` or twStyle('...') with the object
87
+ const twStyleObject = t.objectExpression(objectProperties);
88
+ path.replaceWith(twStyleObject);
89
+ }
90
+
91
+ /**
92
+ * Remove tw/twStyle imports from @mgcrea/react-native-tailwind
93
+ * This is called after all tw calls have been transformed
94
+ */
95
+ export function removeTwImports(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
96
+ // Traverse the program to find and remove tw/twStyle imports
97
+ path.traverse({
98
+ ImportDeclaration(importPath) {
99
+ const node = importPath.node;
100
+
101
+ // Only process imports from main package
102
+ if (node.source.value !== "@mgcrea/react-native-tailwind") {
103
+ return;
104
+ }
105
+
106
+ // Filter out tw/twStyle specifiers
107
+ const remainingSpecifiers = node.specifiers.filter((spec) => {
108
+ if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
109
+ const importedName = spec.imported.name;
110
+ return importedName !== "tw" && importedName !== "twStyle";
111
+ }
112
+ return true;
113
+ });
114
+
115
+ if (remainingSpecifiers.length === 0) {
116
+ // Remove entire import if no specifiers remain
117
+ importPath.remove();
118
+ } else if (remainingSpecifiers.length < node.specifiers.length) {
119
+ // Update import with remaining specifiers
120
+ node.specifiers = remainingSpecifiers;
121
+ }
122
+ },
123
+ });
124
+ }
@@ -22,21 +22,24 @@ import {
22
22
  TextInput as RNTextInput,
23
23
  type TextInputProps as RNTextInputProps,
24
24
  } from "react-native";
25
+ import { type Simplify } from "../types/util";
25
26
 
26
- export type TextInputProps = Omit<RNTextInputProps, "style"> & {
27
- /**
28
- * Style can be a static style object/array or a function that receives focus and disabled state
29
- */
30
- style?:
31
- | RNTextInputProps["style"]
32
- | ((state: { focused: boolean; disabled: boolean }) => RNTextInputProps["style"]);
33
- className?: string; // compile-time only
34
- /**
35
- * Convenience prop for disabled state (overrides editable if provided)
36
- * When true, sets editable to false
37
- */
38
- disabled?: boolean;
39
- };
27
+ export type TextInputProps = Simplify<
28
+ Omit<RNTextInputProps, "style"> & {
29
+ /**
30
+ * Style can be a static style object/array or a function that receives focus and disabled state
31
+ */
32
+ style?:
33
+ | RNTextInputProps["style"]
34
+ | ((state: { focused: boolean; disabled: boolean }) => RNTextInputProps["style"]);
35
+ className?: string; // compile-time only
36
+ /**
37
+ * Convenience prop for disabled state (overrides editable if provided)
38
+ * When true, sets editable to false
39
+ */
40
+ disabled?: boolean;
41
+ }
42
+ >;
40
43
 
41
44
  /**
42
45
  * Enhanced TextInput with focus and disabled state support
@@ -12,7 +12,7 @@ export type TailwindPalette = {
12
12
  "950": string;
13
13
  };
14
14
 
15
- export const TAILWIND_PALETTES = {
15
+ export const TAILWIND_COLORS = {
16
16
  red: {
17
17
  "50": "#fef2f2",
18
18
  "100": "#ffe2e2",
@@ -301,4 +301,4 @@ export const TAILWIND_PALETTES = {
301
301
  },
302
302
  } satisfies Record<string, TailwindPalette>;
303
303
 
304
- export type TailwindColor = keyof typeof TAILWIND_PALETTES;
304
+ export type TailwindColor = keyof typeof TAILWIND_COLORS;
package/src/index.ts CHANGED
@@ -4,8 +4,7 @@
4
4
  */
5
5
 
6
6
  // Compile-time tw/twStyle functions (transformed by Babel plugin)
7
- export { tw, twStyle } from "./stubs/tw.js";
8
- export type { NativeStyle, TwStyle } from "./stubs/tw.js";
7
+ export { tw, twStyle } from "./stubs/tw";
9
8
 
10
9
  // Main parser functions
11
10
  export { parseClass, parseClassName } from "./parser";
@@ -13,7 +12,8 @@ export { flattenColors } from "./utils/flattenColors";
13
12
  export { generateStyleKey } from "./utils/styleKey";
14
13
 
15
14
  // Re-export types
16
- export type { RNStyle, StyleObject } from "./types";
15
+ export type { StyleObject } from "./types/core";
16
+ export type { NativeStyle, TwStyle } from "./types/runtime";
17
17
 
18
18
  // Re-export individual parsers for advanced usage
19
19
  export {
@@ -21,6 +21,8 @@ export {
21
21
  parseBorder,
22
22
  parseColor,
23
23
  parseLayout,
24
+ parsePlaceholderClass,
25
+ parsePlaceholderClasses,
24
26
  parseShadow,
25
27
  parseSizing,
26
28
  parseSpacing,
@@ -41,3 +43,4 @@ export { Pressable } from "./components/Pressable";
41
43
  export type { PressableProps } from "./components/Pressable";
42
44
  export { TextInput } from "./components/TextInput";
43
45
  export type { TextInputProps } from "./components/TextInput";
46
+ export { TAILWIND_COLORS } from "./config/tailwind";
@@ -2,13 +2,13 @@
2
2
  * Color utilities (background, text, border colors)
3
3
  */
4
4
 
5
- import { TAILWIND_PALETTES } from "../config/palettes";
5
+ import { TAILWIND_COLORS } from "../config/tailwind";
6
6
  import type { StyleObject } from "../types";
7
7
  import { flattenColors } from "../utils/flattenColors";
8
8
 
9
9
  // Tailwind color palette (flattened from config)
10
10
  export const COLORS: Record<string, string> = {
11
- ...flattenColors(TAILWIND_PALETTES),
11
+ ...flattenColors(TAILWIND_COLORS),
12
12
  // Add basic colors
13
13
  white: "#FFFFFF",
14
14
  black: "#000000",
@@ -74,6 +74,7 @@ export { parseAspectRatio } from "./aspectRatio";
74
74
  export { parseBorder } from "./borders";
75
75
  export { parseColor } from "./colors";
76
76
  export { parseLayout } from "./layout";
77
+ export { parsePlaceholderClass, parsePlaceholderClasses } from "./placeholder";
77
78
  export { parseShadow } from "./shadows";
78
79
  export { parseSizing } from "./sizing";
79
80
  export { parseSpacing } from "./spacing";
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Modifier parsing utilities for state-based class names (active:, hover:, focus:)
2
+ * Modifier parsing utilities for state-based class names (active:, hover:, focus:, placeholder:)
3
3
  */
4
4
 
5
- export type ModifierType = "active" | "hover" | "focus" | "disabled";
5
+ export type ModifierType = "active" | "hover" | "focus" | "disabled" | "placeholder";
6
6
 
7
7
  export type ParsedModifier = {
8
8
  modifier: ModifierType;
@@ -10,9 +10,15 @@ export type ParsedModifier = {
10
10
  };
11
11
 
12
12
  /**
13
- * Supported modifiers that map to component states
13
+ * Supported modifiers that map to component states or pseudo-elements
14
14
  */
15
- const SUPPORTED_MODIFIERS: readonly ModifierType[] = ["active", "hover", "focus", "disabled"] as const;
15
+ const SUPPORTED_MODIFIERS: readonly ModifierType[] = [
16
+ "active",
17
+ "hover",
18
+ "focus",
19
+ "disabled",
20
+ "placeholder",
21
+ ] as const;
16
22
 
17
23
  /**
18
24
  * Parse a class name to detect and extract modifiers
@@ -0,0 +1,105 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { parsePlaceholderClass, parsePlaceholderClasses } from "./placeholder";
4
+
5
+ describe("parsePlaceholderClass", () => {
6
+ it("should parse text color classes", () => {
7
+ // Using actual colors from this project's custom palette (src/config/tailwind.ts)
8
+ expect(parsePlaceholderClass("text-red-500")).toBe("#fb2c36");
9
+ expect(parsePlaceholderClass("text-blue-500")).toBe("#2b7fff");
10
+ expect(parsePlaceholderClass("text-gray-400")).toBe("#99a1af");
11
+ });
12
+
13
+ it("should parse text colors with opacity", () => {
14
+ // Using actual colors from custom palette
15
+ // Note: opacity conversion uppercases hex values
16
+ expect(parsePlaceholderClass("text-red-500/50")).toBe("#FB2C3680"); // 50% opacity
17
+ expect(parsePlaceholderClass("text-blue-500/75")).toBe("#2B7FFFBF"); // 75% opacity
18
+ expect(parsePlaceholderClass("text-gray-400/25")).toBe("#99A1AF40"); // 25% opacity
19
+ });
20
+
21
+ it("should parse arbitrary color values", () => {
22
+ expect(parsePlaceholderClass("text-[#ff0000]")).toBe("#ff0000");
23
+ expect(parsePlaceholderClass("text-[#ff0000aa]")).toBe("#ff0000aa");
24
+ });
25
+
26
+ it("should parse custom colors", () => {
27
+ const customColors = {
28
+ "brand-primary": "#123456",
29
+ "brand-secondary": "#654321",
30
+ };
31
+ expect(parsePlaceholderClass("text-brand-primary", customColors)).toBe("#123456");
32
+ expect(parsePlaceholderClass("text-brand-secondary", customColors)).toBe("#654321");
33
+ });
34
+
35
+ it("should return null for non-text color classes", () => {
36
+ expect(parsePlaceholderClass("font-bold")).toBeNull();
37
+ expect(parsePlaceholderClass("text-lg")).toBeNull();
38
+ expect(parsePlaceholderClass("text-center")).toBeNull();
39
+ expect(parsePlaceholderClass("italic")).toBeNull();
40
+ });
41
+
42
+ it("should warn about unsupported utilities in development", () => {
43
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
44
+ const originalEnv = process.env.NODE_ENV;
45
+ process.env.NODE_ENV = "development";
46
+
47
+ parsePlaceholderClass("font-bold");
48
+ expect(consoleSpy).toHaveBeenCalledWith(
49
+ expect.stringContaining("Only text color utilities are supported in placeholder: modifier"),
50
+ );
51
+
52
+ parsePlaceholderClass("text-lg");
53
+ expect(consoleSpy).toHaveBeenCalledWith(
54
+ expect.stringContaining("Only text color utilities are supported in placeholder: modifier"),
55
+ );
56
+
57
+ process.env.NODE_ENV = originalEnv;
58
+ consoleSpy.mockRestore();
59
+ });
60
+
61
+ it("should not warn in production", () => {
62
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
63
+ const originalEnv = process.env.NODE_ENV;
64
+ process.env.NODE_ENV = "production";
65
+
66
+ parsePlaceholderClass("font-bold");
67
+ expect(consoleSpy).not.toHaveBeenCalled();
68
+
69
+ process.env.NODE_ENV = originalEnv;
70
+ consoleSpy.mockRestore();
71
+ });
72
+
73
+ it("should return null for invalid color values", () => {
74
+ expect(parsePlaceholderClass("text-invalid-color")).toBeNull();
75
+ expect(parsePlaceholderClass("text-[invalid]")).toBeNull();
76
+ });
77
+ });
78
+
79
+ describe("parsePlaceholderClasses", () => {
80
+ it("should parse multiple color classes (last wins)", () => {
81
+ expect(parsePlaceholderClasses("text-red-500 text-blue-500")).toBe("#2b7fff"); // blue-500
82
+ expect(parsePlaceholderClasses("text-gray-400 text-green-500")).toBe("#00c950"); // green-500
83
+ });
84
+
85
+ it("should ignore non-color classes and return last valid color", () => {
86
+ expect(parsePlaceholderClasses("text-red-500 font-bold text-blue-500")).toBe("#2b7fff");
87
+ expect(parsePlaceholderClasses("font-bold text-red-500 text-lg")).toBe("#fb2c36");
88
+ });
89
+
90
+ it("should return null if no valid colors found", () => {
91
+ expect(parsePlaceholderClasses("font-bold italic text-lg")).toBeNull();
92
+ expect(parsePlaceholderClasses("")).toBeNull();
93
+ });
94
+
95
+ it("should handle opacity modifiers", () => {
96
+ // Note: opacity conversion uppercases hex values
97
+ expect(parsePlaceholderClasses("text-red-500/50")).toBe("#FB2C3680");
98
+ expect(parsePlaceholderClasses("text-red-500 text-blue-500/75")).toBe("#2B7FFFBF");
99
+ });
100
+
101
+ it("should work with custom colors", () => {
102
+ const customColors = { "brand-primary": "#123456" };
103
+ expect(parsePlaceholderClasses("text-red-500 text-brand-primary", customColors)).toBe("#123456");
104
+ });
105
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Placeholder text color utilities (placeholder:text-{color})
3
+ *
4
+ * React Native only supports styling the COLOR of placeholder text via placeholderTextColor prop.
5
+ * Other text styling properties (font-size, font-weight, etc.) are not supported.
6
+ */
7
+
8
+ import { parseColor } from "./colors.js";
9
+
10
+ /**
11
+ * Parse placeholder modifier classes to extract color
12
+ *
13
+ * Only text-{color} classes are supported inside placeholder: modifier.
14
+ * Other utilities (font-*, text-lg, etc.) will log warnings.
15
+ *
16
+ * @param cls - Class name inside placeholder: modifier (e.g., "text-red-500")
17
+ * @param customColors - Optional custom color palette from tailwind.config
18
+ * @returns Color string (hex with optional alpha) or null if invalid
19
+ *
20
+ * @example
21
+ * parsePlaceholderClass("text-red-500") // "#ef4444"
22
+ * parsePlaceholderClass("text-red-500/50", {}) // "#ef444480"
23
+ * parsePlaceholderClass("text-brand-primary", { "brand-primary": "#123456" }) // "#123456"
24
+ * parsePlaceholderClass("font-bold") // null (+ warning)
25
+ */
26
+ export function parsePlaceholderClass(cls: string, customColors?: Record<string, string>): string | null {
27
+ // Check if it's a text color class
28
+ if (!cls.startsWith("text-")) {
29
+ // Warn about unsupported utilities
30
+ if (process.env.NODE_ENV !== "production") {
31
+ console.warn(
32
+ `[react-native-tailwind] Only text color utilities are supported in placeholder: modifier. ` +
33
+ `Class "${cls}" will be ignored. React Native only supports placeholderTextColor prop.`,
34
+ );
35
+ }
36
+ return null;
37
+ }
38
+
39
+ // Parse as color using existing color parser
40
+ // This handles text-red-500, text-red-500/50, text-[#ff0000], etc.
41
+ const styleObject = parseColor(cls, customColors);
42
+
43
+ if (!styleObject?.color) {
44
+ return null;
45
+ }
46
+
47
+ // Return the color value
48
+ return styleObject.color as string;
49
+ }
50
+
51
+ /**
52
+ * Parse multiple placeholder classes and return the last valid color
53
+ * (Later classes override earlier ones, matching CSS cascade behavior)
54
+ *
55
+ * @param classes - Space-separated class names
56
+ * @param customColors - Optional custom color palette
57
+ * @returns Color string or null
58
+ *
59
+ * @example
60
+ * parsePlaceholderClasses("text-red-500 text-blue-500") // "#3b82f6" (blue wins)
61
+ * parsePlaceholderClasses("text-red-500 font-bold") // "#ef4444" (ignores font-bold)
62
+ */
63
+ export function parsePlaceholderClasses(
64
+ classes: string,
65
+ customColors?: Record<string, string>,
66
+ ): string | null {
67
+ const classList = classes.trim().split(/\s+/).filter(Boolean);
68
+ let finalColor: string | null = null;
69
+
70
+ for (const cls of classList) {
71
+ const color = parsePlaceholderClass(cls, customColors);
72
+ if (color) {
73
+ finalColor = color; // Later colors override earlier ones
74
+ }
75
+ }
76
+
77
+ return finalColor;
78
+ }
@@ -118,6 +118,17 @@ describe("parseTypography - line height", () => {
118
118
  expect(parseTypography("leading-loose")).toEqual({ lineHeight: 32 });
119
119
  });
120
120
 
121
+ it("should parse line height with numeric scale", () => {
122
+ expect(parseTypography("leading-3")).toEqual({ lineHeight: 12 });
123
+ expect(parseTypography("leading-4")).toEqual({ lineHeight: 16 });
124
+ expect(parseTypography("leading-5")).toEqual({ lineHeight: 20 });
125
+ expect(parseTypography("leading-6")).toEqual({ lineHeight: 24 });
126
+ expect(parseTypography("leading-7")).toEqual({ lineHeight: 28 });
127
+ expect(parseTypography("leading-8")).toEqual({ lineHeight: 32 });
128
+ expect(parseTypography("leading-9")).toEqual({ lineHeight: 36 });
129
+ expect(parseTypography("leading-10")).toEqual({ lineHeight: 40 });
130
+ });
131
+
121
132
  it("should parse line height with arbitrary pixel values", () => {
122
133
  expect(parseTypography("leading-[24px]")).toEqual({ lineHeight: 24 });
123
134
  expect(parseTypography("leading-[24]")).toEqual({ lineHeight: 24 });
@@ -80,7 +80,19 @@ const TEXT_TRANSFORM_MAP: Record<string, StyleObject> = {
80
80
  "normal-case": { textTransform: "none" },
81
81
  };
82
82
 
83
- // Line height utilities
83
+ // Line height scale (numeric)
84
+ export const LINE_HEIGHT_SCALE: Record<string, number> = {
85
+ 3: 12,
86
+ 4: 16,
87
+ 5: 20,
88
+ 6: 24,
89
+ 7: 28,
90
+ 8: 32,
91
+ 9: 36,
92
+ 10: 40,
93
+ };
94
+
95
+ // Line height utilities (named)
84
96
  const LINE_HEIGHT_MAP: Record<string, StyleObject> = {
85
97
  "leading-none": { lineHeight: 16 },
86
98
  "leading-tight": { lineHeight: 20 },
@@ -169,7 +181,7 @@ export function parseTypography(cls: string): StyleObject | null {
169
181
  }
170
182
  }
171
183
 
172
- // Line height: leading-normal, leading-[24px], etc.
184
+ // Line height: leading-normal, leading-6, leading-[24px], etc.
173
185
  if (cls.startsWith("leading-")) {
174
186
  const heightKey = cls.substring(8);
175
187
 
@@ -178,6 +190,12 @@ export function parseTypography(cls: string): StyleObject | null {
178
190
  if (arbitraryValue !== null) {
179
191
  return { lineHeight: arbitraryValue };
180
192
  }
193
+
194
+ // Try numeric scale (leading-3, leading-6, etc.)
195
+ const lineHeight = LINE_HEIGHT_SCALE[heightKey];
196
+ if (lineHeight !== undefined) {
197
+ return { lineHeight };
198
+ }
181
199
  }
182
200
 
183
201
  // Try each lookup table in order
package/src/runtime.ts CHANGED
@@ -1,23 +1,8 @@
1
- import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
2
1
  import { parseClassName } from "./parser/index.js";
2
+ import type { NativeStyle, TwStyle } from "./types/runtime.js";
3
3
  import { flattenColors } from "./utils/flattenColors.js";
4
4
  import { hasModifiers, splitModifierClasses } from "./utils/modifiers.js";
5
5
 
6
- /**
7
- * Union type for all React Native style types
8
- */
9
- export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
10
-
11
- /**
12
- * Return type for tw/twStyle functions with separate style properties for modifiers
13
- */
14
- export type TwStyle<T extends NativeStyle = NativeStyle> = {
15
- style: T;
16
- activeStyle?: T;
17
- focusStyle?: T;
18
- disabledStyle?: T;
19
- };
20
-
21
6
  /**
22
7
  * Runtime configuration type matching Tailwind config structure
23
8
  */
package/src/stubs/tw.ts CHANGED
@@ -7,22 +7,7 @@
7
7
  * For runtime parsing, use: import { tw } from '@mgcrea/react-native-tailwind/runtime'
8
8
  */
9
9
 
10
- import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
11
-
12
- /**
13
- * Union type for all React Native style types
14
- */
15
- export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
16
-
17
- /**
18
- * Return type for tw/twStyle functions with separate style properties for modifiers
19
- */
20
- export type TwStyle<T extends NativeStyle = NativeStyle> = {
21
- style: T;
22
- activeStyle?: T;
23
- focusStyle?: T;
24
- disabledStyle?: T;
25
- };
10
+ import type { NativeStyle, TwStyle } from "../types/runtime.js";
26
11
 
27
12
  /**
28
13
  * Compile-time Tailwind CSS template tag (transformed by Babel plugin)
@@ -2,10 +2,6 @@
2
2
  * Core type definitions
3
3
  */
4
4
 
5
- import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
6
-
7
- export type RNStyle = ViewStyle | TextStyle | ImageStyle;
8
-
9
5
  // Transform types for React Native
10
6
  export type TransformStyle =
11
7
  | { scale?: number }
@@ -0,0 +1,2 @@
1
+ export * from "./core";
2
+ export * from "./util";
@@ -0,0 +1,17 @@
1
+ import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
2
+
3
+ /**
4
+ * Union type for all React Native style types
5
+ */
6
+ export type NativeStyle = ViewStyle | TextStyle | ImageStyle;
7
+
8
+ /**
9
+ * Return type for tw/twStyle functions with separate style properties for modifiers
10
+ */
11
+ export type TwStyle<T extends NativeStyle = NativeStyle> = {
12
+ style: T;
13
+ activeStyle?: T;
14
+ focusStyle?: T;
15
+ disabledStyle?: T;
16
+ placeholderStyle?: TextStyle;
17
+ };
@@ -0,0 +1 @@
1
+ export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};