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