@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.
- package/README.md +152 -0
- package/dist/babel/config-loader.ts +2 -0
- package/dist/babel/index.cjs +178 -5
- package/dist/babel/plugin.d.ts +2 -0
- package/dist/babel/plugin.test.ts +241 -0
- package/dist/babel/plugin.ts +187 -10
- package/dist/babel/utils/attributeMatchers.test.ts +294 -0
- package/dist/babel/utils/componentSupport.test.ts +426 -0
- package/dist/babel/utils/platformModifierProcessing.d.ts +30 -0
- package/dist/babel/utils/platformModifierProcessing.ts +80 -0
- package/dist/babel/utils/styleInjection.d.ts +4 -0
- package/dist/babel/utils/styleInjection.ts +28 -0
- package/dist/babel/utils/styleTransforms.ts +1 -0
- package/dist/parser/colors.test.js +1 -1
- package/dist/parser/index.d.ts +2 -2
- package/dist/parser/index.js +1 -1
- package/dist/parser/modifiers.d.ts +20 -2
- package/dist/parser/modifiers.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +4 -4
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +4 -4
- package/dist/stubs/tw.test.js +1 -0
- package/dist/utils/flattenColors.d.ts +1 -0
- package/dist/utils/flattenColors.js +1 -1
- package/dist/utils/flattenColors.test.js +1 -1
- package/package.json +6 -5
- package/src/babel/config-loader.ts +2 -0
- package/src/babel/plugin.test.ts +241 -0
- package/src/babel/plugin.ts +187 -10
- package/src/babel/utils/attributeMatchers.test.ts +294 -0
- package/src/babel/utils/componentSupport.test.ts +426 -0
- package/src/babel/utils/platformModifierProcessing.ts +80 -0
- package/src/babel/utils/styleInjection.ts +28 -0
- package/src/babel/utils/styleTransforms.ts +1 -0
- package/src/parser/aspectRatio.ts +1 -0
- package/src/parser/borders.ts +2 -0
- package/src/parser/colors.test.ts +32 -0
- package/src/parser/colors.ts +2 -0
- package/src/parser/index.ts +10 -3
- package/src/parser/layout.ts +2 -0
- package/src/parser/modifiers.ts +38 -4
- package/src/parser/placeholder.ts +1 -0
- package/src/parser/sizing.ts +1 -0
- package/src/parser/spacing.ts +1 -0
- package/src/parser/transforms.ts +5 -0
- package/src/parser/typography.ts +2 -0
- package/src/stubs/tw.test.ts +27 -0
- package/src/utils/flattenColors.test.ts +100 -0
- package/src/utils/flattenColors.ts +3 -1
package/src/parser/modifiers.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Modifier parsing utilities for state-based class names
|
|
2
|
+
* Modifier parsing utilities for state-based and platform-specific class names
|
|
3
|
+
* - State modifiers: active:, hover:, focus:, disabled:, placeholder:
|
|
4
|
+
* - Platform modifiers: ios:, android:, web:
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
export type
|
|
7
|
+
export type StateModifierType = "active" | "hover" | "focus" | "disabled" | "placeholder";
|
|
8
|
+
export type PlatformModifierType = "ios" | "android" | "web";
|
|
9
|
+
export type ModifierType = StateModifierType | PlatformModifierType;
|
|
6
10
|
|
|
7
11
|
export type ParsedModifier = {
|
|
8
12
|
modifier: ModifierType;
|
|
@@ -10,9 +14,9 @@ export type ParsedModifier = {
|
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
|
-
* Supported modifiers that map to component states or pseudo-elements
|
|
17
|
+
* Supported state modifiers that map to component states or pseudo-elements
|
|
14
18
|
*/
|
|
15
|
-
const
|
|
19
|
+
const STATE_MODIFIERS: readonly StateModifierType[] = [
|
|
16
20
|
"active",
|
|
17
21
|
"hover",
|
|
18
22
|
"focus",
|
|
@@ -20,6 +24,16 @@ const SUPPORTED_MODIFIERS: readonly ModifierType[] = [
|
|
|
20
24
|
"placeholder",
|
|
21
25
|
] as const;
|
|
22
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Supported platform modifiers that map to Platform.OS values
|
|
29
|
+
*/
|
|
30
|
+
const PLATFORM_MODIFIERS: readonly PlatformModifierType[] = ["ios", "android", "web"] as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* All supported modifiers (state + platform)
|
|
34
|
+
*/
|
|
35
|
+
const SUPPORTED_MODIFIERS: readonly ModifierType[] = [...STATE_MODIFIERS, ...PLATFORM_MODIFIERS] as const;
|
|
36
|
+
|
|
23
37
|
/**
|
|
24
38
|
* Parse a class name to detect and extract modifiers
|
|
25
39
|
*
|
|
@@ -73,6 +87,26 @@ export function hasModifier(cls: string): boolean {
|
|
|
73
87
|
return parseModifier(cls) !== null;
|
|
74
88
|
}
|
|
75
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Check if a modifier is a state modifier (active, hover, focus, disabled, placeholder)
|
|
92
|
+
*
|
|
93
|
+
* @param modifier - Modifier type to check
|
|
94
|
+
* @returns true if modifier is a state modifier
|
|
95
|
+
*/
|
|
96
|
+
export function isStateModifier(modifier: ModifierType): modifier is StateModifierType {
|
|
97
|
+
return STATE_MODIFIERS.includes(modifier as StateModifierType);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if a modifier is a platform modifier (ios, android, web)
|
|
102
|
+
*
|
|
103
|
+
* @param modifier - Modifier type to check
|
|
104
|
+
* @returns true if modifier is a platform modifier
|
|
105
|
+
*/
|
|
106
|
+
export function isPlatformModifier(modifier: ModifierType): modifier is PlatformModifierType {
|
|
107
|
+
return PLATFORM_MODIFIERS.includes(modifier as PlatformModifierType);
|
|
108
|
+
}
|
|
109
|
+
|
|
76
110
|
/**
|
|
77
111
|
* Split a space-separated className string into base and modifier classes
|
|
78
112
|
*
|
|
@@ -27,6 +27,7 @@ export function parsePlaceholderClass(cls: string, customColors?: Record<string,
|
|
|
27
27
|
// Check if it's a text color class
|
|
28
28
|
if (!cls.startsWith("text-")) {
|
|
29
29
|
// Warn about unsupported utilities
|
|
30
|
+
/* v8 ignore next 5 */
|
|
30
31
|
if (process.env.NODE_ENV !== "production") {
|
|
31
32
|
console.warn(
|
|
32
33
|
`[react-native-tailwind] Only text color utilities are supported in placeholder: modifier. ` +
|
package/src/parser/sizing.ts
CHANGED
|
@@ -80,6 +80,7 @@ function parseArbitrarySize(value: string): number | string | null {
|
|
|
80
80
|
|
|
81
81
|
// Unsupported units (rem, em, vh, vw, etc.) - warn and reject
|
|
82
82
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
83
|
+
/* v8 ignore next 5 */
|
|
83
84
|
if (process.env.NODE_ENV !== "production") {
|
|
84
85
|
console.warn(
|
|
85
86
|
`[react-native-tailwind] Unsupported arbitrary size unit: ${value}. Only px and % are supported.`,
|
package/src/parser/spacing.ts
CHANGED
|
@@ -55,6 +55,7 @@ function parseArbitrarySpacing(value: string): number | null {
|
|
|
55
55
|
|
|
56
56
|
// Warn about unsupported formats
|
|
57
57
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
58
|
+
/* v8 ignore next 5 */
|
|
58
59
|
if (process.env.NODE_ENV !== "production") {
|
|
59
60
|
console.warn(
|
|
60
61
|
`[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px] or [16]).`,
|
package/src/parser/transforms.ts
CHANGED
|
@@ -70,6 +70,7 @@ function parseArbitraryScale(value: string): number | null {
|
|
|
70
70
|
|
|
71
71
|
// Unsupported format
|
|
72
72
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
73
|
+
/* v8 ignore next 5 */
|
|
73
74
|
if (process.env.NODE_ENV !== "production") {
|
|
74
75
|
console.warn(
|
|
75
76
|
`[react-native-tailwind] Invalid arbitrary scale value: ${value}. Only numbers are supported (e.g., [1.5], [0.75]).`,
|
|
@@ -93,6 +94,7 @@ function parseArbitraryRotation(value: string): string | null {
|
|
|
93
94
|
|
|
94
95
|
// Unsupported format
|
|
95
96
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
97
|
+
/* v8 ignore next 5 */
|
|
96
98
|
if (process.env.NODE_ENV !== "production") {
|
|
97
99
|
console.warn(
|
|
98
100
|
`[react-native-tailwind] Invalid arbitrary rotation value: ${value}. Only deg unit is supported (e.g., [45deg], [-15deg]).`,
|
|
@@ -123,6 +125,7 @@ function parseArbitraryTranslation(value: string): number | string | null {
|
|
|
123
125
|
|
|
124
126
|
// Unsupported units
|
|
125
127
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
128
|
+
/* v8 ignore next 5 */
|
|
126
129
|
if (process.env.NODE_ENV !== "production") {
|
|
127
130
|
console.warn(
|
|
128
131
|
`[react-native-tailwind] Unsupported arbitrary translation unit: ${value}. Only px and % are supported.`,
|
|
@@ -146,6 +149,7 @@ function parseArbitraryPerspective(value: string): number | null {
|
|
|
146
149
|
|
|
147
150
|
// Unsupported format
|
|
148
151
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
152
|
+
/* v8 ignore next 5 */
|
|
149
153
|
if (process.env.NODE_ENV !== "production") {
|
|
150
154
|
console.warn(
|
|
151
155
|
`[react-native-tailwind] Invalid arbitrary perspective value: ${value}. Only integers are supported (e.g., [1500]).`,
|
|
@@ -164,6 +168,7 @@ function parseArbitraryPerspective(value: string): number | null {
|
|
|
164
168
|
export function parseTransform(cls: string): StyleObject | null {
|
|
165
169
|
// Transform origin warning (not supported in React Native)
|
|
166
170
|
if (cls.startsWith("origin-")) {
|
|
171
|
+
/* v8 ignore next 5 */
|
|
167
172
|
if (process.env.NODE_ENV !== "production") {
|
|
168
173
|
console.warn(
|
|
169
174
|
`[react-native-tailwind] transform-origin is not supported in React Native. Class "${cls}" will be ignored.`,
|
package/src/parser/typography.ts
CHANGED
|
@@ -125,6 +125,7 @@ function parseArbitraryFontSize(value: string): number | null {
|
|
|
125
125
|
|
|
126
126
|
// Warn about unsupported formats
|
|
127
127
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
128
|
+
/* v8 ignore next 5 */
|
|
128
129
|
if (process.env.NODE_ENV !== "production") {
|
|
129
130
|
console.warn(
|
|
130
131
|
`[react-native-tailwind] Unsupported arbitrary font size value: ${value}. Only px values are supported (e.g., [18px] or [18]).`,
|
|
@@ -149,6 +150,7 @@ function parseArbitraryLineHeight(value: string): number | null {
|
|
|
149
150
|
|
|
150
151
|
// Warn about unsupported formats
|
|
151
152
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
153
|
+
/* v8 ignore next 5 */
|
|
152
154
|
if (process.env.NODE_ENV !== "production") {
|
|
153
155
|
console.warn(
|
|
154
156
|
`[react-native-tailwind] Unsupported arbitrary line height value: ${value}. Only px values are supported (e.g., [24px] or [24]).`,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { tw, twStyle } from "./tw";
|
|
3
|
+
|
|
4
|
+
describe("tw stub", () => {
|
|
5
|
+
it("should throw error when tw() is called without Babel transformation", () => {
|
|
6
|
+
expect(() => tw`bg-blue-500`).toThrow(
|
|
7
|
+
"tw() must be transformed by the Babel plugin. " +
|
|
8
|
+
"Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. " +
|
|
9
|
+
"For runtime parsing, use: import { tw } from '@mgcrea/react-native-tailwind/runtime'",
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should throw error when twStyle() is called without Babel transformation", () => {
|
|
14
|
+
expect(() => twStyle("bg-blue-500")).toThrow(
|
|
15
|
+
"twStyle() must be transformed by the Babel plugin. " +
|
|
16
|
+
"Ensure @mgcrea/react-native-tailwind/babel is configured in your babel.config.js. " +
|
|
17
|
+
"For runtime parsing, use: import { twStyle } from '@mgcrea/react-native-tailwind/runtime'",
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should throw error with template literal interpolation", () => {
|
|
22
|
+
const dynamic = "active";
|
|
23
|
+
expect(() => tw`bg-blue-500 ${dynamic}:bg-blue-700`).toThrow(
|
|
24
|
+
"tw() must be transformed by the Babel plugin",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -358,4 +358,104 @@ describe("flattenColors", () => {
|
|
|
358
358
|
|
|
359
359
|
expect(keys).toEqual(["z", "a", "m"]);
|
|
360
360
|
});
|
|
361
|
+
|
|
362
|
+
it("should handle DEFAULT key in color scale objects", () => {
|
|
363
|
+
const colors = {
|
|
364
|
+
primary: {
|
|
365
|
+
"50": "#eefdfd",
|
|
366
|
+
"100": "#d4f9f9",
|
|
367
|
+
"200": "#aef2f3",
|
|
368
|
+
"500": "#1bacb5",
|
|
369
|
+
"900": "#1e4f5b",
|
|
370
|
+
DEFAULT: "#1bacb5",
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
expect(flattenColors(colors)).toEqual({
|
|
375
|
+
primary: "#1bacb5", // DEFAULT becomes the parent key
|
|
376
|
+
"primary-50": "#eefdfd",
|
|
377
|
+
"primary-100": "#d4f9f9",
|
|
378
|
+
"primary-200": "#aef2f3",
|
|
379
|
+
"primary-500": "#1bacb5",
|
|
380
|
+
"primary-900": "#1e4f5b",
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it("should handle DEFAULT key with multiple color scales", () => {
|
|
385
|
+
const colors = {
|
|
386
|
+
primary: {
|
|
387
|
+
DEFAULT: "#1bacb5",
|
|
388
|
+
"500": "#1bacb5",
|
|
389
|
+
},
|
|
390
|
+
secondary: {
|
|
391
|
+
DEFAULT: "#ff6b6b",
|
|
392
|
+
"500": "#ff6b6b",
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
expect(flattenColors(colors)).toEqual({
|
|
397
|
+
primary: "#1bacb5",
|
|
398
|
+
"primary-500": "#1bacb5",
|
|
399
|
+
secondary: "#ff6b6b",
|
|
400
|
+
"secondary-500": "#ff6b6b",
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("should handle DEFAULT key in nested structures", () => {
|
|
405
|
+
const colors = {
|
|
406
|
+
brand: {
|
|
407
|
+
primary: {
|
|
408
|
+
DEFAULT: "#1bacb5",
|
|
409
|
+
light: "#d4f9f9",
|
|
410
|
+
dark: "#0e343e",
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
expect(flattenColors(colors)).toEqual({
|
|
416
|
+
"brand-primary": "#1bacb5", // DEFAULT uses parent key
|
|
417
|
+
"brand-primary-light": "#d4f9f9",
|
|
418
|
+
"brand-primary-dark": "#0e343e",
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("should handle DEFAULT at top level (edge case)", () => {
|
|
423
|
+
const colors = {
|
|
424
|
+
DEFAULT: "#000000",
|
|
425
|
+
primary: "#ff0000",
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
expect(flattenColors(colors)).toEqual({
|
|
429
|
+
DEFAULT: "#000000", // Top-level DEFAULT kept as-is (no parent)
|
|
430
|
+
primary: "#ff0000",
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("should handle mixed DEFAULT and regular keys", () => {
|
|
435
|
+
const colors = {
|
|
436
|
+
gray: {
|
|
437
|
+
"50": "#f9fafb",
|
|
438
|
+
"100": "#f3f4f6",
|
|
439
|
+
DEFAULT: "#6b7280",
|
|
440
|
+
"500": "#6b7280",
|
|
441
|
+
"900": "#111827",
|
|
442
|
+
},
|
|
443
|
+
white: "#ffffff",
|
|
444
|
+
brand: {
|
|
445
|
+
DEFAULT: "#ff6b6b",
|
|
446
|
+
accent: "#4ecdc4",
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
expect(flattenColors(colors)).toEqual({
|
|
451
|
+
"gray-50": "#f9fafb",
|
|
452
|
+
"gray-100": "#f3f4f6",
|
|
453
|
+
gray: "#6b7280", // DEFAULT becomes parent key
|
|
454
|
+
"gray-500": "#6b7280",
|
|
455
|
+
"gray-900": "#111827",
|
|
456
|
+
white: "#ffffff",
|
|
457
|
+
brand: "#ff6b6b", // DEFAULT becomes parent key
|
|
458
|
+
"brand-accent": "#4ecdc4",
|
|
459
|
+
});
|
|
460
|
+
});
|
|
361
461
|
});
|
|
@@ -8,6 +8,7 @@ type NestedColors = {
|
|
|
8
8
|
/**
|
|
9
9
|
* Flatten nested color objects into flat key-value map
|
|
10
10
|
* Example: { brand: { light: '#fff', dark: '#000' } } => { 'brand-light': '#fff', 'brand-dark': '#000' }
|
|
11
|
+
* Special handling for DEFAULT: { primary: { DEFAULT: '#000', 500: '#333' } } => { 'primary': '#000', 'primary-500': '#333' }
|
|
11
12
|
*
|
|
12
13
|
* @param colors - Nested color object where values can be strings or objects
|
|
13
14
|
* @param prefix - Optional prefix for nested keys (used for recursion)
|
|
@@ -17,7 +18,8 @@ export function flattenColors(colors: NestedColors, prefix = ""): Record<string,
|
|
|
17
18
|
const result: Record<string, string> = {};
|
|
18
19
|
|
|
19
20
|
for (const [key, value] of Object.entries(colors)) {
|
|
20
|
-
|
|
21
|
+
// Special handling for DEFAULT key - use parent key without suffix
|
|
22
|
+
const newKey = key === "DEFAULT" && prefix ? prefix : prefix ? `${prefix}-${key}` : key;
|
|
21
23
|
|
|
22
24
|
if (typeof value === "string") {
|
|
23
25
|
result[newKey] = value;
|