diff --git a/src/__tests__/vendor/tailwind/typography.test.tsx b/src/__tests__/vendor/tailwind/typography.test.tsx new file mode 100644 index 00000000..f4fe82e7 --- /dev/null +++ b/src/__tests__/vendor/tailwind/typography.test.tsx @@ -0,0 +1,710 @@ +import { renderCurrentTest, renderSimple } from "./_tailwind"; + +describe("Typography - Font Size", () => { + test("text-xs", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontSize: 10.5, lineHeight: 14 } }, + }); + }); + test("text-base", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontSize: 14, lineHeight: 21 } }, + }); + }); +}); + +describe("Typography - Font Smoothing", () => { + test("antialiased", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { + properties: ["-webkit-font-smoothing", "-moz-osx-font-smoothing"], + }, + }); + }); + test("subpixel-antialiased", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { + properties: ["-webkit-font-smoothing", "-moz-osx-font-smoothing"], + }, + }); + }); +}); + +describe("Typography - Font Style", () => { + test("italic", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontStyle: "italic" } }, + }); + }); + test("not-italic", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontStyle: "normal" } }, + }); + }); +}); + +describe("Typography - Font Weight", () => { + test("font-thin", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontWeight: 100 } }, + warnings: { + values: { + "font-weight": "initial", + }, + }, + }); + }); + test("font-normal", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontWeight: 400 } }, + warnings: { + values: { + "font-weight": "initial", + }, + }, + }); + }); + test("font-black", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { fontWeight: 900 } }, + warnings: { + values: { + "font-weight": "initial", + }, + }, + }); + }); +}); + +describe("Typography - Font Variant Numeric", () => { + test("normal-nums", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("ordinal", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("slashed-zero", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("lining-nums", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("oldstyle-nums", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("proportional-nums", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("tabular-nums", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("diagonal-fractions", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); + test("stacked-fractions", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["font-variant-numeric"] }, + }); + }); +}); + +describe("Typography - Letter Spacing", () => { + test("tracking-tighter", async () => { + // 14 * -0.05 + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { letterSpacing: -0.7 } }, + warnings: { + values: { + "letter-spacing": "initial", + }, + }, + }); + }); + test("tracking-tight", async () => { + // 14 * -0.025 + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { letterSpacing: -0.35 } }, + warnings: { + values: { + "letter-spacing": "initial", + }, + }, + }); + }); + test("tracking-normal", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { letterSpacing: 0 } }, + warnings: { + values: { + "letter-spacing": "initial", + }, + }, + }); + }); + test("tracking-wide", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + // 14 * 0.025 + props: { style: { letterSpacing: 0.35 } }, + warnings: { + values: { + "letter-spacing": "initial", + }, + }, + }); + }); + test("tracking-wider", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { letterSpacing: 0.7 } }, + warnings: { + values: { + "letter-spacing": "initial", + }, + }, + }); + }); + test("tracking-widest", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { letterSpacing: 1.4 } }, + warnings: { + values: { + "letter-spacing": "initial", + }, + }, + }); + }); +}); + +describe("Typography - Line Clamp", () => { + test("line-clamp-1", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { numberOfLines: 1, style: { overflow: "hidden" } }, + warnings: { + values: { + display: "box", + }, + }, + }); + }); + test("line-clamp-2", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { numberOfLines: 2, style: { overflow: "hidden" } }, + warnings: { + values: { + display: "box", + }, + }, + }); + }); + test("line-clamp-none", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { + style: { overflow: "visible" }, + }, + warnings: { + values: { + display: "block", + }, + }, + }); + }); +}); + +describe("Typography - Line Height", () => { + test("leading-3", async () => { + expect( + await renderCurrentTest({ + extraCss: `@utility leading-* { + line-height: calc(var(--spacing) / 1rem * --value(integer)); + }`, + }), + ).toStrictEqual({ + props: { style: { lineHeight: 10.5 } }, + warnings: { + values: { + "line-height": "initial", + }, + }, + }); + }); + test("leading-4", async () => { + expect( + await renderCurrentTest({ + extraCss: `@utility leading-* { + line-height: calc(var(--spacing) / 1rem * --value(integer)); + }`, + }), + ).toStrictEqual({ + props: { style: { lineHeight: 14 } }, + warnings: { + values: { + "line-height": "initial", + }, + }, + }); + }); +}); + +describe("Typography - List Style Image", () => { + test("list-image-none", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["list-style-image"] }, + }); + }); +}); + +describe("Typography - List Style Position", () => { + test("list-inside", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["list-style-position"] }, + }); + }); + test("list-outside", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["list-style-position"] }, + }); + }); +}); + +describe("Typography - List Style Type", () => { + test("list-none", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["list-style-type"] }, + }); + }); + test("list-disc", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["list-style-type"] }, + }); + }); + test("list-decimal", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["list-style-type"] }, + }); + }); +}); + +describe("Typography - Text Align", () => { + test("text-left", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textAlign: "left" } }, + }); + }); + test("text-center", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textAlign: "center" } }, + }); + }); + test("text-right", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textAlign: "right" } }, + }); + }); + test("text-justify", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textAlign: "justify" } }, + }); + }); +}); + +describe("Typography - Text Color", () => { + test("text-black", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { color: "#000" } }, + }); + }); + test("text-white", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { color: "#fff" } }, + }); + }); + test("text-transparent", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { color: "#0000" } }, + }); + }); + test("text-slate-50", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { color: "#f8fafc" } }, + }); + }); + test("text-white/50", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { color: "#ffffff80" } }, + }); + }); + test("text-current", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { + style: {}, + }, + }); + }); + test("text-inherit", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { color: "inherit" } }, + }); + }); +}); + +describe("Typography - Text Decoration", () => { + test("underline", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationLine: "underline" } }, + }); + }); + test("line-through", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationLine: "line-through" } }, + }); + }); + test("no-underline", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationLine: "none" } }, + }); + }); + test("overline", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "text-decoration-line": "overline" } }, + }); + }); +}); + +describe("Typography - Text Decoration Color", () => { + test("decoration-black", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationColor: "#000" } }, + }); + }); + test("decoration-white", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationColor: "#fff" } }, + }); + }); + test("decoration-transparent", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationColor: "#0000" } }, + }); + }); + test("decoration-slate-50", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationColor: "#f8fafc" } }, + }); + }); + test("decoration-current", async () => { + expect( + await renderSimple({ className: "decoration-current text-red-500" }), + ).toStrictEqual({ + props: { + style: { + color: "#fb2c36", + textDecorationColor: "#fb2c36", + }, + }, + }); + }); + test("decoration-inherit", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "text-decoration-color": "inherit" } }, + }); + }); +}); + +describe("Typography - Text Decoration Style", () => { + test("decoration-solid", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationStyle: "solid" } }, + }); + }); + test("decoration-double", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationStyle: "double" } }, + }); + }); + test("decoration-dotted", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationStyle: "dotted" } }, + }); + }); + test("decoration-dashed", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textDecorationStyle: "dashed" } }, + }); + }); + test("decoration-wavy", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "text-decoration-style": "wavy" } }, + }); + }); +}); + +describe("Typography - Text Decoration Thickness", () => { + test("decoration-auto", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-decoration-thickness"] }, + }); + }); + test("decoration-from-font", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-decoration-thickness"] }, + }); + }); + test("decoration-0", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-decoration-thickness"] }, + }); + }); + test("decoration-1", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-decoration-thickness"] }, + }); + }); +}); + +describe("Typography - Text Underline Offset", () => { + test("underline-offset-auto", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-underline-offset"] }, + }); + }); + test("underline-offset-0", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-underline-offset"] }, + }); + }); +}); + +describe("Typography - Text Transform", () => { + test("uppercase", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textTransform: "uppercase" } }, + }); + }); + test("lowercase", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textTransform: "lowercase" } }, + }); + }); + test("capitalize", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textTransform: "capitalize" } }, + }); + }); + test("normal-case", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { textTransform: "none" } }, + }); + }); +}); + +describe("Typography - Text Overflow", () => { + test("text-ellipsis", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-overflow"] }, + }); + }); + test("text-clip", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-overflow"] }, + }); + }); +}); + +describe("Typography - Text Indent", () => { + test("indent-px", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-indent"] }, + }); + }); + test("indent-0", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-indent"] }, + }); + }); + test("indent-1", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["text-indent"] }, + }); + }); +}); + +describe("Typography - Vertical Align", () => { + test("align-baseline", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "vertical-align": "baseline" } }, + }); + }); + test("align-top", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { verticalAlign: "top" } }, + }); + }); + test("align-middle", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { verticalAlign: "middle" } }, + }); + }); + test("align-bottom", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: { style: { verticalAlign: "bottom" } }, + }); + }); + test("align-text-top", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "vertical-align": "text-top" } }, + }); + }); + test("align-text-bottom", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "vertical-align": "text-bottom" } }, + }); + }); + test("align-sub", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "vertical-align": "sub" } }, + }); + }); + test("align-super", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { values: { "vertical-align": "super" } }, + }); + }); +}); + +describe("Typography - Whitespace", () => { + test("whitespace-normal", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["white-space"] }, + }); + }); + test("whitespace-nowrap", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["white-space"] }, + }); + }); + test("whitespace-pre", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["white-space"] }, + }); + }); + test("whitespace-pre-line", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["white-space"] }, + }); + }); + test("whitespace-pre-wrap", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["white-space"] }, + }); + }); +}); + +describe("Typography - Word Break", () => { + test("break-normal", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { + properties: ["overflow-wrap", "word-break"], + }, + }); + }); + test("break-words", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["overflow-wrap"] }, + }); + }); + test("break-all", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["word-break"] }, + }); + }); +}); + +describe("Typography - Hyphens", () => { + test("hyphens-none", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["hyphens"] }, + }); + }); + test("hyphens-manual", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["hyphens"] }, + }); + }); + test("hyphens-auto", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["hyphens"] }, + }); + }); +}); + +describe("Typography - Content", () => { + test("content-none", async () => { + expect(await renderCurrentTest()).toStrictEqual({ + props: {}, + warnings: { properties: ["content"] }, + }); + }); +}); diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts index 99e937b4..2e882c7f 100644 --- a/src/compiler/declarations.ts +++ b/src/compiler/declarations.ts @@ -63,15 +63,15 @@ const propertyRename: Record = { "background-image": "experimental_backgroundImage", }; -// TODO: We need a better way to handle this const unparsedRuntimeParsing = new Set([ "animation", "border", "box-shadow", + "line-height", + "rotate", + "scale", "text-shadow", "transform", - "scale", - "rotate", "translate", ]); @@ -2190,7 +2190,12 @@ export function parseLineHeightDeclaration( declaration: DeclarationType<"line-height">, builder: StylesheetBuilder, ) { - return parseLineHeight(declaration.value, builder); + builder.addDescriptor("line-height", [ + {}, + "lineHeight", + [parseLineHeight(declaration.value, builder)], + 1, + ]); } export function parseLineHeight( diff --git a/src/runtime/native/styles/line-height.ts b/src/runtime/native/styles/line-height.ts new file mode 100644 index 00000000..47d1739d --- /dev/null +++ b/src/runtime/native/styles/line-height.ts @@ -0,0 +1,25 @@ +import type { StyleFunctionResolver } from "./resolve"; + +export const lineHeight: StyleFunctionResolver = (resolve, func) => { + const value = resolve(func[2]); + + if (typeof value !== "number") { + return; + } + + let emValue = resolve([{}, "var", ["__rn-css-em"]]); + + if (typeof emValue !== "number") { + emValue = resolve([{}, "var", ["__rn-css-rem"]]); + } + + if (typeof emValue !== "number") { + return; + } + + return round(value * emValue); +}; + +function round(number: number) { + return Math.round((number + Number.EPSILON) * 100) / 100; +} diff --git a/src/runtime/native/styles/resolve.ts b/src/runtime/native/styles/resolve.ts index 7723a9e3..03ea0056 100644 --- a/src/runtime/native/styles/resolve.ts +++ b/src/runtime/native/styles/resolve.ts @@ -10,6 +10,7 @@ import { type Getter, type VariableContextValue } from "../reactivity"; import type { calculateProps } from "./calculate-props"; import { transformKeys } from "./defaults"; import * as functions from "./functions"; +import { lineHeight } from "./line-height"; import * as shorthands from "./shorthands"; import { em, rem, vh, vw } from "./units"; import { varResolver } from "./variables"; @@ -37,6 +38,7 @@ const functionResolvers = { ...shorthands, ...functions, animationName: shorthands.animation, + lineHeight, em, rem, vh,