@mgcrea/react-native-tailwind 0.8.1 → 0.9.1
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 +205 -17
- package/dist/babel/plugin.d.ts +4 -1
- package/dist/babel/plugin.test.ts +327 -0
- package/dist/babel/plugin.ts +194 -14
- package/dist/babel/utils/platformModifierProcessing.d.ts +30 -0
- package/dist/babel/utils/platformModifierProcessing.ts +80 -0
- package/dist/babel/utils/styleInjection.d.ts +5 -1
- package/dist/babel/utils/styleInjection.ts +52 -7
- package/dist/babel/utils/styleTransforms.ts +1 -0
- package/dist/parser/aspectRatio.js +1 -1
- package/dist/parser/aspectRatio.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/parser/spacing.d.ts +1 -1
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.test.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/runtime.test.js +1 -1
- package/dist/stubs/tw.test.js +1 -0
- package/package.json +7 -7
- package/src/babel/config-loader.ts +2 -0
- package/src/babel/plugin.test.ts +327 -0
- package/src/babel/plugin.ts +194 -14
- package/src/babel/utils/platformModifierProcessing.ts +80 -0
- package/src/babel/utils/styleInjection.ts +52 -7
- package/src/babel/utils/styleTransforms.ts +1 -0
- package/src/parser/aspectRatio.test.ts +25 -2
- package/src/parser/aspectRatio.ts +4 -3
- package/src/parser/borders.ts +2 -0
- package/src/parser/colors.ts +2 -0
- package/src/parser/index.ts +9 -2
- 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.test.ts +63 -0
- package/src/parser/spacing.ts +11 -6
- package/src/parser/transforms.ts +5 -0
- package/src/parser/typography.ts +2 -0
- package/src/runtime.test.ts +27 -0
- package/src/runtime.ts +2 -1
- package/src/stubs/tw.test.ts +27 -0
- package/dist/babel/index.test.ts +0 -481
- package/dist/config/palettes.d.ts +0 -302
- package/dist/config/palettes.js +0 -1
- package/dist/parser/__snapshots__/aspectRatio.test.js.snap +0 -9
- package/dist/parser/__snapshots__/borders.test.js.snap +0 -23
- package/dist/parser/__snapshots__/colors.test.js.snap +0 -251
- package/dist/parser/__snapshots__/shadows.test.js.snap +0 -76
- package/dist/parser/__snapshots__/sizing.test.js.snap +0 -61
- package/dist/parser/__snapshots__/spacing.test.js.snap +0 -40
- package/dist/parser/__snapshots__/transforms.test.js.snap +0 -58
- package/dist/parser/__snapshots__/typography.test.js.snap +0 -30
- package/dist/parser/aspectRatio.test.d.ts +0 -1
- package/dist/parser/borders.test.d.ts +0 -1
- package/dist/parser/colors.test.d.ts +0 -1
- package/dist/parser/layout.test.d.ts +0 -1
- package/dist/parser/modifiers.test.d.ts +0 -1
- package/dist/parser/shadows.test.d.ts +0 -1
- package/dist/parser/sizing.test.d.ts +0 -1
- package/dist/parser/spacing.test.d.ts +0 -1
- package/dist/parser/typography.test.d.ts +0 -1
- package/dist/types.d.ts +0 -42
- package/dist/types.js +0 -1
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.`,
|
|
@@ -60,6 +60,69 @@ describe("parseSpacing - margin", () => {
|
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
describe("parseSpacing - negative margin", () => {
|
|
64
|
+
it("should parse negative margin all sides", () => {
|
|
65
|
+
expect(parseSpacing("-m-0")).toEqual({ margin: -0 }); // JavaScript -0 is distinct from +0
|
|
66
|
+
expect(parseSpacing("-m-4")).toEqual({ margin: -16 });
|
|
67
|
+
expect(parseSpacing("-m-8")).toEqual({ margin: -32 });
|
|
68
|
+
expect(parseSpacing("-m-96")).toEqual({ margin: -384 });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should parse negative margin with fractional values", () => {
|
|
72
|
+
expect(parseSpacing("-m-0.5")).toEqual({ margin: -2 });
|
|
73
|
+
expect(parseSpacing("-m-1.5")).toEqual({ margin: -6 });
|
|
74
|
+
expect(parseSpacing("-m-2.5")).toEqual({ margin: -10 });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should parse negative margin horizontal", () => {
|
|
78
|
+
expect(parseSpacing("-mx-4")).toEqual({ marginHorizontal: -16 });
|
|
79
|
+
expect(parseSpacing("-mx-8")).toEqual({ marginHorizontal: -32 });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should parse negative margin vertical", () => {
|
|
83
|
+
expect(parseSpacing("-my-4")).toEqual({ marginVertical: -16 });
|
|
84
|
+
expect(parseSpacing("-my-8")).toEqual({ marginVertical: -32 });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should parse negative margin directional", () => {
|
|
88
|
+
expect(parseSpacing("-mt-4")).toEqual({ marginTop: -16 });
|
|
89
|
+
expect(parseSpacing("-mr-4")).toEqual({ marginRight: -16 });
|
|
90
|
+
expect(parseSpacing("-mb-4")).toEqual({ marginBottom: -16 });
|
|
91
|
+
expect(parseSpacing("-ml-4")).toEqual({ marginLeft: -16 });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should parse negative margin with arbitrary values", () => {
|
|
95
|
+
expect(parseSpacing("-m-[16px]")).toEqual({ margin: -16 });
|
|
96
|
+
expect(parseSpacing("-m-[16]")).toEqual({ margin: -16 });
|
|
97
|
+
expect(parseSpacing("-m-[100px]")).toEqual({ margin: -100 });
|
|
98
|
+
expect(parseSpacing("-m-[100]")).toEqual({ margin: -100 });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should parse negative margin directional with arbitrary values", () => {
|
|
102
|
+
expect(parseSpacing("-mt-[24px]")).toEqual({ marginTop: -24 });
|
|
103
|
+
expect(parseSpacing("-mr-[32]")).toEqual({ marginRight: -32 });
|
|
104
|
+
expect(parseSpacing("-mb-[16px]")).toEqual({ marginBottom: -16 });
|
|
105
|
+
expect(parseSpacing("-ml-[48]")).toEqual({ marginLeft: -48 });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should parse negative margin horizontal/vertical with arbitrary values", () => {
|
|
109
|
+
expect(parseSpacing("-mx-[20px]")).toEqual({ marginHorizontal: -20 });
|
|
110
|
+
expect(parseSpacing("-my-[30]")).toEqual({ marginVertical: -30 });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should not parse negative padding (invalid)", () => {
|
|
114
|
+
expect(parseSpacing("-p-4")).toBeNull();
|
|
115
|
+
expect(parseSpacing("-px-4")).toBeNull();
|
|
116
|
+
expect(parseSpacing("-pt-4")).toBeNull();
|
|
117
|
+
expect(parseSpacing("-p-[16px]")).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should not parse negative gap (invalid)", () => {
|
|
121
|
+
expect(parseSpacing("-gap-4")).toBeNull();
|
|
122
|
+
expect(parseSpacing("-gap-[16px]")).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
63
126
|
describe("parseSpacing - padding", () => {
|
|
64
127
|
it("should parse padding all sides", () => {
|
|
65
128
|
expect(parseSpacing("p-0")).toEqual({ padding: 0 });
|
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]).`,
|
|
@@ -68,24 +69,28 @@ function parseArbitrarySpacing(value: string): number | null {
|
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* Parse spacing classes (margin, padding, gap)
|
|
71
|
-
* Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px]
|
|
72
|
+
* Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px], -m-4, -mt-[10px]
|
|
72
73
|
*/
|
|
73
74
|
export function parseSpacing(cls: string): StyleObject | null {
|
|
74
|
-
// Margin: m-4, mx-2, mt-8, m-[16px], etc.
|
|
75
|
-
|
|
75
|
+
// Margin: m-4, mx-2, mt-8, m-[16px], -m-4, -mt-2, etc.
|
|
76
|
+
// Supports negative values for margins (but not padding or gap)
|
|
77
|
+
const marginMatch = cls.match(/^(-?)m([xytrbls]?)-(.+)$/);
|
|
76
78
|
if (marginMatch) {
|
|
77
|
-
const [, dir, valueStr] = marginMatch;
|
|
79
|
+
const [, negativePrefix, dir, valueStr] = marginMatch;
|
|
80
|
+
const isNegative = negativePrefix === "-";
|
|
78
81
|
|
|
79
82
|
// Try arbitrary value first
|
|
80
83
|
const arbitraryValue = parseArbitrarySpacing(valueStr);
|
|
81
84
|
if (arbitraryValue !== null) {
|
|
82
|
-
|
|
85
|
+
const finalValue = isNegative ? -arbitraryValue : arbitraryValue;
|
|
86
|
+
return getMarginStyle(dir, finalValue);
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
// Try preset scale
|
|
86
90
|
const scaleValue = SPACING_SCALE[valueStr];
|
|
87
91
|
if (scaleValue !== undefined) {
|
|
88
|
-
|
|
92
|
+
const finalValue = isNegative ? -scaleValue : scaleValue;
|
|
93
|
+
return getMarginStyle(dir, finalValue);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
|
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]).`,
|
package/src/runtime.test.ts
CHANGED
|
@@ -45,6 +45,33 @@ describe("runtime", () => {
|
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
it("should preserve zero values in template literals", () => {
|
|
49
|
+
// Issue #1 fix: 0 should be treated as valid value, not filtered out
|
|
50
|
+
const result = tw`opacity-${0} m-4`;
|
|
51
|
+
expect(result?.style).toEqual({
|
|
52
|
+
opacity: 0,
|
|
53
|
+
margin: 16,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should preserve empty string values in template literals", () => {
|
|
58
|
+
// Empty strings should be preserved (even though they don't contribute to className)
|
|
59
|
+
const result = tw`m-4 ${""}p-2`;
|
|
60
|
+
expect(result?.style).toEqual({
|
|
61
|
+
margin: 16,
|
|
62
|
+
padding: 8,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should handle mixed falsy and truthy numeric values", () => {
|
|
67
|
+
const spacing = 0;
|
|
68
|
+
const result = tw`m-${spacing} p-4`;
|
|
69
|
+
expect(result?.style).toEqual({
|
|
70
|
+
margin: 0,
|
|
71
|
+
padding: 16,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
48
75
|
it("should return empty style object for empty className", () => {
|
|
49
76
|
const result = tw``;
|
|
50
77
|
expect(result).toEqual({ style: {} });
|
package/src/runtime.ts
CHANGED
|
@@ -211,8 +211,9 @@ export function tw<T extends NativeStyle = NativeStyle>(
|
|
|
211
211
|
const className = strings.reduce((acc, str, i) => {
|
|
212
212
|
const value = values[i];
|
|
213
213
|
// Handle falsy values (false, null, undefined) - don't add them
|
|
214
|
+
// Note: 0 and empty string are preserved as they may be valid values
|
|
214
215
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
215
|
-
const valueStr = value ? String(value) : "";
|
|
216
|
+
const valueStr = value != null && value !== false ? String(value) : "";
|
|
216
217
|
return acc + str + valueStr;
|
|
217
218
|
}, "");
|
|
218
219
|
|
|
@@ -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
|
+
});
|