From db5749e71f0a67f06ed4349ad800c17e35ffce8e Mon Sep 17 00:00:00 2001
From: Evan Simpson <25159851+e-simpson@users.noreply.github.com>
Date: Mon, 27 Jul 2026 21:22:50 -0400
Subject: [PATCH] feat: support dynamic scale values
---
docs/src/content/docs/reference/transforms.md | 3 +
src/parser/transforms.test.ts | 17 +++-
src/parser/transforms.ts | 92 ++++++++++---------
3 files changed, 68 insertions(+), 44 deletions(-)
diff --git a/docs/src/content/docs/reference/transforms.md b/docs/src/content/docs/reference/transforms.md
index e7ca254..4309c9c 100644
--- a/docs/src/content/docs/reference/transforms.md
+++ b/docs/src/content/docs/reference/transforms.md
@@ -12,6 +12,7 @@ Apply 2D and 3D transformations to views with React Native's transform API. All
```tsx
// scale: 0
// scale: 0.5
+ // scale: 0.8
// scale: 1
// scale: 1.1
// scale: 1.5
@@ -22,12 +23,14 @@ Apply 2D and 3D transformations to views with React Native's transform API. All
```tsx
// scaleX: 1.1
// scaleY: 0.9
+ // scaleX: -0.8
```
### Arbitrary Values
```tsx
// scale: 1.23
+ // scale: 0.8
// scaleX: 0.5
// scaleY: 2.5
```
diff --git a/src/parser/transforms.test.ts b/src/parser/transforms.test.ts
index b87ae2b..ef3f420 100644
--- a/src/parser/transforms.test.ts
+++ b/src/parser/transforms.test.ts
@@ -32,6 +32,7 @@ describe("parseTransform - scale utilities", () => {
expect(parseTransform("scale-0")).toEqual({ transform: [{ scale: 0 }] });
expect(parseTransform("scale-50")).toEqual({ transform: [{ scale: 0.5 }] });
expect(parseTransform("scale-75")).toEqual({ transform: [{ scale: 0.75 }] });
+ expect(parseTransform("scale-80")).toEqual({ transform: [{ scale: 0.8 }] });
expect(parseTransform("scale-90")).toEqual({ transform: [{ scale: 0.9 }] });
expect(parseTransform("scale-95")).toEqual({ transform: [{ scale: 0.95 }] });
expect(parseTransform("scale-100")).toEqual({ transform: [{ scale: 1 }] });
@@ -40,11 +41,14 @@ describe("parseTransform - scale utilities", () => {
expect(parseTransform("scale-125")).toEqual({ transform: [{ scale: 1.25 }] });
expect(parseTransform("scale-150")).toEqual({ transform: [{ scale: 1.5 }] });
expect(parseTransform("scale-200")).toEqual({ transform: [{ scale: 2 }] });
+ expect(parseTransform("scale-102.5")).toEqual({ transform: [{ scale: 1.025 }] });
+ expect(parseTransform("scale-999")).toEqual({ transform: [{ scale: 9.99 }] });
});
it("should parse scaleX values", () => {
expect(parseTransform("scale-x-0")).toEqual({ transform: [{ scaleX: 0 }] });
expect(parseTransform("scale-x-50")).toEqual({ transform: [{ scaleX: 0.5 }] });
+ expect(parseTransform("scale-x-80")).toEqual({ transform: [{ scaleX: 0.8 }] });
expect(parseTransform("scale-x-100")).toEqual({ transform: [{ scaleX: 1 }] });
expect(parseTransform("scale-x-150")).toEqual({ transform: [{ scaleX: 1.5 }] });
});
@@ -52,16 +56,21 @@ describe("parseTransform - scale utilities", () => {
it("should parse scaleY values", () => {
expect(parseTransform("scale-y-0")).toEqual({ transform: [{ scaleY: 0 }] });
expect(parseTransform("scale-y-75")).toEqual({ transform: [{ scaleY: 0.75 }] });
+ expect(parseTransform("scale-y-80")).toEqual({ transform: [{ scaleY: 0.8 }] });
expect(parseTransform("scale-y-100")).toEqual({ transform: [{ scaleY: 1 }] });
expect(parseTransform("scale-y-125")).toEqual({ transform: [{ scaleY: 1.25 }] });
});
it("should parse arbitrary scale values", () => {
expect(parseTransform("scale-[1.23]")).toEqual({ transform: [{ scale: 1.23 }] });
+ expect(parseTransform("scale-[80]")).toEqual({ transform: [{ scale: 80 }] });
expect(parseTransform("scale-[0.5]")).toEqual({ transform: [{ scale: 0.5 }] });
expect(parseTransform("scale-[2.5]")).toEqual({ transform: [{ scale: 2.5 }] });
expect(parseTransform("scale-x-[1.5]")).toEqual({ transform: [{ scaleX: 1.5 }] });
expect(parseTransform("scale-y-[0.8]")).toEqual({ transform: [{ scaleY: 0.8 }] });
+ expect(parseTransform("scale-[80%]")).toEqual({ transform: [{ scale: 0.8 }] });
+ expect(parseTransform("scale-x-[125%]")).toEqual({ transform: [{ scaleX: 1.25 }] });
+ expect(parseTransform("scale-y-[80%]")).toEqual({ transform: [{ scaleY: 0.8 }] });
});
it("should parse negative arbitrary scale values", () => {
@@ -69,9 +78,15 @@ describe("parseTransform - scale utilities", () => {
expect(parseTransform("scale-x-[-0.5]")).toEqual({ transform: [{ scaleX: -0.5 }] });
});
+ it("should parse negative numeric scale values", () => {
+ expect(parseTransform("-scale-80")).toEqual({ transform: [{ scale: -0.8 }] });
+ expect(parseTransform("-scale-x-125")).toEqual({ transform: [{ scaleX: -1.25 }] });
+ expect(parseTransform("-scale-y-50")).toEqual({ transform: [{ scaleY: -0.5 }] });
+ });
+
it("should return null for invalid scale values", () => {
- expect(parseTransform("scale-999")).toBeNull();
expect(parseTransform("scale-invalid")).toBeNull();
+ expect(parseTransform("scale-10x")).toBeNull();
});
});
diff --git a/src/parser/transforms.ts b/src/parser/transforms.ts
index c75d813..b315e50 100644
--- a/src/parser/transforms.ts
+++ b/src/parser/transforms.ts
@@ -59,13 +59,14 @@ export const PERSPECTIVE_SCALE: Record = {
};
/**
- * Parse arbitrary scale value: [1.23], [0.5]
+ * Parse arbitrary scale value: [1.23], [0.5], [80%]
* Returns number for valid scale, null otherwise
*/
function parseArbitraryScale(value: string): number | null {
- const scaleMatch = value.match(/^\[(-?\d+(?:\.\d+)?)\]$/);
+ const scaleMatch = value.match(/^\[(-?\d+(?:\.\d+)?)(%)?\]$/);
if (scaleMatch) {
- return parseFloat(scaleMatch[1]);
+ const parsedValue = Number.parseFloat(scaleMatch[1]);
+ return scaleMatch[2] === "%" ? parsedValue / 100 : parsedValue;
}
// Unsupported format
@@ -73,7 +74,7 @@ function parseArbitraryScale(value: string): number | null {
/* v8 ignore next 5 */
if (process.env.NODE_ENV !== "production") {
console.warn(
- `[react-native-tailwind] Invalid arbitrary scale value: ${value}. Only numbers are supported (e.g., [1.5], [0.75]).`,
+ `[react-native-tailwind] Invalid arbitrary scale value: ${value}. Only numbers and percentages are supported (e.g., [1.5], [0.75], [80%]).`,
);
}
return null;
@@ -82,6 +83,29 @@ function parseArbitraryScale(value: string): number | null {
return null;
}
+/**
+ * Parse a Tailwind scale value.
+ * Named numeric utilities are percentages (scale-80 -> 0.8), while bracketed
+ * arbitrary numbers are raw React Native multipliers (scale-[1.25] -> 1.25).
+ */
+function parseScaleValue(value: string): number | null {
+ const arbitraryScale = parseArbitraryScale(value);
+ if (arbitraryScale !== null) {
+ return arbitraryScale;
+ }
+
+ const presetScale = SCALE_MAP[value];
+ if (presetScale !== undefined) {
+ return presetScale;
+ }
+
+ if (/^\d+(?:\.\d+)?$/.test(value)) {
+ return Number.parseFloat(value) / 100;
+ }
+
+ return null;
+}
+
/**
* Parse arbitrary rotation value: [37deg], [-15deg]
* Returns string for valid rotation, null otherwise
@@ -181,51 +205,33 @@ export function parseTransform(cls: string, customSpacing?: Record