diff --git a/.editorconfig b/.editorconfig index 696f400..2ee043a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ root = true charset = utf-8 end_of_line = lf indent_size = 4 -indent_style = space +indent_style = tab insert_final_newline = true trim_trailing_whitespace = true @@ -14,3 +14,4 @@ trim_trailing_whitespace = false [*.{json,yml}] indent_size = 2 +indent_style = space diff --git a/.eslintrc.js b/.eslintrc.js index 088134b..77b112f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,24 +1,24 @@ module.exports = { - extends: ['@zazen/eslint-config/typescript'], - env: { - node: true, - }, - rules: { - '@typescript-eslint/indent': 'off', - '@typescript-eslint/space-before-function-paren': 'off', + extends: ['@zazen/eslint-config/typescript'], + env: { + node: true, + }, + rules: { + '@typescript-eslint/indent': 'off', + '@typescript-eslint/space-before-function-paren': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - }, - overrides: [ - { - // Jest config - files: [ - '**/__tests__/**/*.{js,ts,tsx}', - '**/*.@(spec|test).{js,ts,tsx}', - ], - env: { - jest: true, - }, - }, - ], + '@typescript-eslint/explicit-function-return-type': 'off', + }, + overrides: [ + { + // Jest config + files: [ + '**/__tests__/**/*.{js,ts,tsx}', + '**/*.@(spec|test).{js,ts,tsx}', + ], + env: { + jest: true, + }, + }, + ], } diff --git a/README.md b/README.md index aa49b8e..88cc11b 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ This plugin requires a `fontMetrics` key added to your Tailwind theme. It should ```ts { - ascent: number - descent: number - lineGap: number - unitsPerEm: number - capHeight: number + ascent: number + descent: number + lineGap: number + unitsPerEm: number + capHeight: number } ``` @@ -33,24 +33,24 @@ These values can be determined by using the [Capsize website](https://seek-oss.g ```js // tailwind.config.js module.exports = { - theme: { - fontFamily: { - sans: ['Inter', 'sans-serif'], - }, - fontMetrics: { - sans: { - capHeight: 2048, - ascent: 2728, - descent: -680, - lineGap: 0, - unitsPerEm: 2816, - }, - }, - fontSize: { ... }, - lineHeight: { ... }, - ... - }, - plugins: [require('tailwindcss-capsize')], + theme: { + fontFamily: { + sans: ['Inter', 'sans-serif'], + }, + fontMetrics: { + sans: { + capHeight: 2048, + ascent: 2728, + descent: -680, + lineGap: 0, + unitsPerEm: 2816, + }, + }, + fontSize: { ... }, + lineHeight: { ... }, + ... + }, + plugins: [require('tailwindcss-capsize')], } ``` @@ -94,12 +94,12 @@ By default the plugin replaces the `fontFamily`, `fontSize`, and `lineHeight` co ```diff .font-sans { -+ --ascent-scale: 0.9688; -+ --descent-scale: 0.2415; -+ --cap-height-scale: 0.7273; -+ --line-gap-scale: 0; -+ --line-height-scale: 1.2102; - font-family: Inter, sans-serif; ++ --ascent-scale: 0.9688; ++ --descent-scale: 0.2415; ++ --cap-height-scale: 0.7273; ++ --line-gap-scale: 0; ++ --line-height-scale: 1.2102; + font-family: Inter, sans-serif; } ``` @@ -119,7 +119,7 @@ Sometimes an interface calls for truncating text to a single line or clamping te
- Text to be truncated to a single line + Text to be truncated to a single line
``` @@ -129,7 +129,7 @@ To solve this, a child element to the element with the `.capsize` class should w- Text to be truncated to a single line + Text to be truncated to a single line
``` diff --git a/package-lock.json b/package-lock.json index 2b4de3b..6611a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tailwindcss-capsize", - "version": "3.0.1", + "version": "3.0.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tailwindcss-capsize", - "version": "3.0.1", + "version": "3.0.4", "license": "ISC", "dependencies": { "@capsizecss/core": "^3.0.0" diff --git a/src/__tests__/plugin.test.ts b/src/__tests__/plugin.test.ts index 0358a56..24ee825 100644 --- a/src/__tests__/plugin.test.ts +++ b/src/__tests__/plugin.test.ts @@ -8,814 +8,814 @@ import tailwindcss from 'tailwindcss' const capsizePlugin = require('../../dist') const THEME_CONFIG = { - screens: { - sm: '640px', - }, - fontFamily: { - sans: ['Inter', 'sans-serif'], - }, - fontMetrics: { - sans: { - capHeight: 2048, - ascent: 2728, - descent: -680, - lineGap: 0, - unitsPerEm: 2816, - }, - }, + screens: { + sm: '640px', + }, + fontFamily: { + sans: ['Inter', 'sans-serif'], + }, + fontMetrics: { + sans: { + capHeight: 2048, + ascent: 2728, + descent: -680, + lineGap: 0, + unitsPerEm: 2816, + }, + }, } expect.extend({ - toMatchCss: (receivedCss: string, expectedCss: string) => { - let strip = (str: string) => str.replace(/[;\s]/g, '') - - if (strip(receivedCss) === strip(expectedCss)) { - return { - message: () => - `expected ${receivedCss} not to match CSS ${expectedCss}`, - pass: true, - } - } else { - let receivedCssFormatted = format(receivedCss, { parser: 'css' }) - let expectedCssFormatted = format(expectedCss, { parser: 'css' }) - let diff = diffStringsUnified( - receivedCssFormatted, - expectedCssFormatted, - ) - - return { - message: () => `expected CSS to match:\n${diff}`, - pass: false, - } - } - }, + toMatchCss: (receivedCss: string, expectedCss: string) => { + let strip = (str: string) => str.replace(/[;\s]/g, '') + + if (strip(receivedCss) === strip(expectedCss)) { + return { + message: () => + `expected ${receivedCss} not to match CSS ${expectedCss}`, + pass: true, + } + } else { + let receivedCssFormatted = format(receivedCss, { parser: 'css' }) + let expectedCssFormatted = format(expectedCss, { parser: 'css' }) + let diff = diffStringsUnified( + receivedCssFormatted, + expectedCssFormatted, + ) + + return { + message: () => `expected CSS to match:\n${diff}`, + pass: false, + } + } + }, }) describe('Plugin', () => { - describe('in "modern" mode', () => { - it('generates utility classes with a default root size', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - fontSize: { - sm: '14px', - md: '1.5rem', - }, - lineHeight: { - sm: '20px', - md: '2.5rem', - }, - }, - corePlugins: false, - plugins: [capsizePlugin], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans { - --ascent-scale: 0.9688; - --descent-scale: 0.2415; - --cap-height-scale: 0.7273; - --line-gap-scale: 0; - --line-height-scale: 1.2102; - font-family: Inter, sans-serif; - } - - .text-sm { - --font-size-px: 14; - font-size: 14px; - } - - .text-md { - --font-size-px: 24; - font-size: 1.5rem; - } - - .leading-sm { - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 20) / 2) / - var(--font-size-px) - ); - line-height: 20px; - } - - .leading-md { - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 40) / 2) / - var(--font-size-px) - ); - line-height: 2.5rem; - } - - .capsize::before { - display: table; - content: ""; - margin-bottom: calc( - ( - ( - var(--ascent-scale) - var(--cap-height-scale) + - var(--line-gap-scale) / 2 - ) - var(--line-height-offset) - ) * -1em - ); - } - - .capsize::after { - display: table; - content: ""; - margin-top: calc( - ( - (var(--descent-scale) + var(--line-gap-scale) / 2) - - var(--line-height-offset) - ) * -1em - ); - } - - @media (min-width: 640px) { - .sm\\:font-sans { - --ascent-scale: 0.9688; - --descent-scale: 0.2415; - --cap-height-scale: 0.7273; - --line-gap-scale: 0; - --line-height-scale: 1.2102; - font-family: Inter, sans-serif; - } - - .sm\\:text-sm { - --font-size-px: 14; - font-size: 14px; - } - - .sm\\:text-md { - --font-size-px: 24; - font-size: 1.5rem; - } - - .sm\\:leading-sm { - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 20) / 2) / - var(--font-size-px) - ); - line-height: 20px; - } - - .sm\\:leading-md { - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 40) / 2) / - var(--font-size-px) - ); - line-height: 2.5rem; - } - } - `) - }) - }) - - it('generates utility classes with a custom root size', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - screens: {}, - fontSize: { - sm: '14px', - md: '1.5rem', - }, - lineHeight: { - sm: '20px', - md: '2.5rem', - }, - }, - corePlugins: false, - plugins: [capsizePlugin({ rootSize: 12 })], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans { - --ascent-scale: 0.9688; - --descent-scale: 0.2415; - --cap-height-scale: 0.7273; - --line-gap-scale: 0; - --line-height-scale: 1.2102; - font-family: Inter, sans-serif; - } - - .text-sm { - --font-size-px: 14; - font-size: 14px; - } - - .text-md { - --font-size-px: 18; - font-size: 1.5rem; - } - - .leading-sm { - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 20) / 2) / - var(--font-size-px) - ); - line-height: 20px; - } - - .leading-md { - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 30) / 2) / - var(--font-size-px) - ); - line-height: 2.5rem; - } - - .capsize::before { - display: table; - content: ""; - margin-bottom: calc( - ( - ( - var(--ascent-scale) - var(--cap-height-scale) + - var(--line-gap-scale) / 2 - ) - var(--line-height-offset) - ) * -1em - ); - } - - .capsize::after { - display: table; - content: ""; - margin-top: calc( - ( - (var(--descent-scale) + var(--line-gap-scale) / 2) - - var(--line-height-offset) - ) * -1em - ); - } - `) - }) - }) - - it('works with unitless or percentage line-height values', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - screens: {}, - fontSize: { - sm: '1rem', - }, - lineHeight: { - sm: '100%', - md: '1.5', - }, - }, - corePlugins: false, - plugins: [capsizePlugin], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans { - --ascent-scale: 0.9688; - --descent-scale: 0.2415; - --cap-height-scale: 0.7273; - --line-gap-scale: 0; - --line-height-scale: 1.2102; - font-family: Inter, sans-serif; - } - - .text-sm { - --font-size-px: 16; - font-size: 1rem; - } - - .leading-sm { - --line-height-offset: calc( - ( - ( - (var(--line-height-scale) * var(--font-size-px)) - - calc(1 * var(--font-size-px)) - ) / 2 - ) / var(--font-size-px) - ); - line-height: 100%; - } - - .leading-md { - --line-height-offset: calc( - ( - ( - (var(--line-height-scale) * var(--font-size-px)) - - calc(1.5 * var(--font-size-px)) - ) / 2 - ) / var(--font-size-px) - ); - line-height: 1.5; - } - - .capsize::before { - display: table; - content: ""; - margin-bottom: calc( - ( - ( - var(--ascent-scale) - var(--cap-height-scale) + - var(--line-gap-scale) / 2 - ) - var(--line-height-offset) - ) * -1em - ); - } - - .capsize::after { - display: table; - content: ""; - margin-top: calc( - ( - (var(--descent-scale) + var(--line-gap-scale) / 2) - - var(--line-height-offset) - ) * -1em - ); - } - `) - }) - }) - - it('works with default line-height values', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - screens: {}, - fontSize: { - base: ['1rem', '1.5rem'], - }, - lineHeight: {}, - }, - corePlugins: false, - plugins: [capsizePlugin], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans { - --ascent-scale: 0.9688; - --descent-scale: 0.2415; - --cap-height-scale: 0.7273; - --line-gap-scale: 0; - --line-height-scale: 1.2102; - font-family: Inter, sans-serif; - } - - .text-base { - --font-size-px: 16; - font-size: 1rem; - --line-height-offset: calc( - (((var(--line-height-scale) * var(--font-size-px)) - 24) / 2) / - var(--font-size-px) - ); - line-height: 1.5rem; - } - - .capsize::before { - display: table; - content: ""; - margin-bottom: calc( - ( - ( - var(--ascent-scale) - var(--cap-height-scale) + - var(--line-gap-scale) / 2 - ) - var(--line-height-offset) - ) * -1em - ); - } - - .capsize::after { - display: table; - content: ""; - margin-top: calc( - ( - (var(--descent-scale) + var(--line-gap-scale) / 2) - - var(--line-height-offset) - ) * -1em - ); - } - `) - }) - }) - - it('generates utility classes with a custom activation class', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - screens: {}, - fontSize: { - sm: '1rem', - }, - lineHeight: { - md: '1.5', - }, - }, - corePlugins: false, - plugins: [capsizePlugin({ className: 'leading-trim' })], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans { - --ascent-scale: 0.9688; - --descent-scale: 0.2415; - --cap-height-scale: 0.7273; - --line-gap-scale: 0; - --line-height-scale: 1.2102; - font-family: Inter, sans-serif; - } - - .text-sm { - --font-size-px: 16; - font-size: 1rem; - } - - .leading-md { - --line-height-offset: calc( - ( - ( - (var(--line-height-scale) * var(--font-size-px)) - - calc(1.5 * var(--font-size-px)) - ) / 2 - ) / var(--font-size-px) - ); - line-height: 1.5; - } - - .leading-trim::before { - display: table; - content: ""; - margin-bottom: calc( - ( - ( - var(--ascent-scale) - var(--cap-height-scale) + - var(--line-gap-scale) / 2 - ) - var(--line-height-offset) - ) * -1em - ); - } - - .leading-trim::after { - display: table; - content: ""; - margin-top: calc( - ( - (var(--descent-scale) + var(--line-gap-scale) / 2) - - var(--line-height-offset) - ) * -1em - ); - } - `) - }) - }) - }) - - describe('in "classic" mode', () => { - it('generates utility classes with a default root size', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - fontSize: { - sm: '14px', - md: '1.5rem', - }, - lineHeight: { - sm: '20px', - md: '2.5rem', - }, - }, - corePlugins: false, - plugins: [capsizePlugin({ mode: 'classic' })], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans.text-sm.leading-sm.capsize::before, - .font-sans .text-sm.leading-sm.capsize::before, - .font-sans .text-sm .leading-sm.capsize::before, - .text-sm .font-sans.leading-sm.capsize::before, - .text-sm .font-sans .leading-sm.capsize::before { - content: ''; - margin-bottom: -0.3506em; - display: table; - } - - .font-sans.text-sm.leading-sm.capsize::after, - .font-sans .text-sm.leading-sm.capsize::after, - .font-sans .text-sm .leading-sm.capsize::after, - .text-sm .font-sans.leading-sm.capsize::after, - .text-sm .font-sans .leading-sm.capsize::after { - content: ''; - margin-top: -0.3506em; - display: table; - } - - .font-sans.text-sm.leading-md.capsize::before, - .font-sans .text-sm.leading-md.capsize::before, - .font-sans .text-sm .leading-md.capsize::before, - .text-sm .font-sans.leading-md.capsize::before, - .text-sm .font-sans .leading-md.capsize::before { - content: ''; - margin-bottom: -1.0649em; - display: table; - } - - .font-sans.text-sm.leading-md.capsize::after, - .font-sans .text-sm.leading-md.capsize::after, - .font-sans .text-sm .leading-md.capsize::after, - .text-sm .font-sans.leading-md.capsize::after, - .text-sm .font-sans .leading-md.capsize::after { - content: ''; - margin-top: -1.0649em; - display: table; - } - - .font-sans.text-md.leading-sm.capsize::before, - .font-sans .text-md.leading-sm.capsize::before, - .font-sans .text-md .leading-sm.capsize::before, - .text-md .font-sans.leading-sm.capsize::before, - .text-md .font-sans .leading-sm.capsize::before { - content: ''; - margin-bottom: -0.053em; - display: table; - } - - .font-sans.text-md.leading-sm.capsize::after, - .font-sans .text-md.leading-sm.capsize::after, - .font-sans .text-md .leading-sm.capsize::after, - .text-md .font-sans.leading-sm.capsize::after, - .text-md .font-sans .leading-sm.capsize::after { - content: ''; - margin-top: -0.053em; - display: table; - } - - .font-sans.text-md.leading-md.capsize::before, - .font-sans .text-md.leading-md.capsize::before, - .font-sans .text-md .leading-md.capsize::before, - .text-md .font-sans.leading-md.capsize::before, - .text-md .font-sans .leading-md.capsize::before { - content: ''; - margin-bottom: -0.4697em; - display: table; - } - - .font-sans.text-md.leading-md.capsize::after, - .font-sans .text-md.leading-md.capsize::after, - .font-sans .text-md .leading-md.capsize::after, - .text-md .font-sans.leading-md.capsize::after, - .text-md .font-sans .leading-md.capsize::after { - content: ''; - margin-top: -0.4697em; - display: table; - } - `) - }) - }) - - it('generates utility classes with a custom root size', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - fontSize: { - sm: '14px', - md: '1.5rem', - }, - lineHeight: { - sm: '20px', - md: '2.5rem', - }, - }, - corePlugins: false, - plugins: [capsizePlugin({ rootSize: 12, mode: 'classic' })], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans.text-sm.leading-sm.capsize::before, - .font-sans .text-sm.leading-sm.capsize::before, - .font-sans .text-sm .leading-sm.capsize::before, - .text-sm .font-sans.leading-sm.capsize::before, - .text-sm .font-sans .leading-sm.capsize::before { - content: ''; - margin-bottom: -0.3506em; - display: table; - } - - .font-sans.text-sm.leading-sm.capsize::after, - .font-sans .text-sm.leading-sm.capsize::after, - .font-sans .text-sm .leading-sm.capsize::after, - .text-sm .font-sans.leading-sm.capsize::after, - .text-sm .font-sans .leading-sm.capsize::after { - content: ''; - margin-top: -0.3506em; - display: table; - } - - .font-sans.text-sm.leading-md.capsize::before, - .font-sans .text-sm.leading-md.capsize::before, - .font-sans .text-sm .leading-md.capsize::before, - .text-sm .font-sans.leading-md.capsize::before, - .text-sm .font-sans .leading-md.capsize::before { - content: ''; - margin-bottom: -0.7078em; - display: table; - } - - .font-sans.text-sm.leading-md.capsize::after, - .font-sans .text-sm.leading-md.capsize::after, - .font-sans .text-sm .leading-md.capsize::after, - .text-sm .font-sans.leading-md.capsize::after, - .text-sm .font-sans .leading-md.capsize::after { - content: ''; - margin-top: -0.7078em; - display: table; - } - - .font-sans.text-md.leading-sm.capsize::before, - .font-sans .text-md.leading-sm.capsize::before, - .font-sans .text-md .leading-sm.capsize::before, - .text-md .font-sans.leading-sm.capsize::before, - .text-md .font-sans .leading-sm.capsize::before { - content: ''; - margin-bottom: -0.1919em; - display: table; - } - - .font-sans.text-md.leading-sm.capsize::after, - .font-sans .text-md.leading-sm.capsize::after, - .font-sans .text-md .leading-sm.capsize::after, - .text-md .font-sans.leading-sm.capsize::after, - .text-md .font-sans .leading-sm.capsize::after { - content: ''; - margin-top: -0.1919em; - display: table; - } - - .font-sans.text-md.leading-md.capsize::before, - .font-sans .text-md.leading-md.capsize::before, - .font-sans .text-md .leading-md.capsize::before, - .text-md .font-sans.leading-md.capsize::before, - .text-md .font-sans .leading-md.capsize::before { - content: ''; - margin-bottom: -0.4697em; - display: table; - } - - .font-sans.text-md.leading-md.capsize::after, - .font-sans .text-md.leading-md.capsize::after, - .font-sans .text-md .leading-md.capsize::after, - .text-md .font-sans.leading-md.capsize::after, - .text-md .font-sans .leading-md.capsize::after { - content: ''; - margin-top: -0.4697em; - display: table; - } - `) - }) - }) - - it('works with unitless or percentage line-height values', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - fontSize: { - sm: '1rem', - }, - lineHeight: { - sm: '100%', - md: '1.5', - }, - }, - corePlugins: false, - plugins: [capsizePlugin({ mode: 'classic' })], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans.text-sm.leading-sm.capsize::before, - .font-sans .text-sm.leading-sm.capsize::before, - .font-sans .text-sm .leading-sm.capsize::before, - .text-sm .font-sans.leading-sm.capsize::before, - .text-sm .font-sans .leading-sm.capsize::before { - content: ''; - margin-bottom: -0.1364em; - display: table; - } - - .font-sans.text-sm.leading-sm.capsize::after, - .font-sans .text-sm.leading-sm.capsize::after, - .font-sans .text-sm .leading-sm.capsize::after, - .text-sm .font-sans.leading-sm.capsize::after, - .text-sm .font-sans .leading-sm.capsize::after { - content: ''; - margin-top: -0.1364em; - display: table; - } - - .font-sans.text-sm.leading-md.capsize::before, - .font-sans .text-sm.leading-md.capsize::before, - .font-sans .text-sm .leading-md.capsize::before, - .text-sm .font-sans.leading-md.capsize::before, - .text-sm .font-sans .leading-md.capsize::before { - content: ''; - margin-bottom: -0.3864em; - display: table; - } - - .font-sans.text-sm.leading-md.capsize::after, - .font-sans .text-sm.leading-md.capsize::after, - .font-sans .text-sm .leading-md.capsize::after, - .text-sm .font-sans.leading-md.capsize::after, - .text-sm .font-sans .leading-md.capsize::after { - content: ''; - margin-top: -0.3864em; - display: table; - } - `) - }) - }) - - it('generates utility classes with a custom activation class', async () => { - return await postcss( - tailwindcss({ - theme: { - ...THEME_CONFIG, - fontSize: { - sm: '1rem', - }, - lineHeight: { - md: '1.5', - }, - }, - corePlugins: false, - plugins: [ - capsizePlugin({ - className: 'leading-trim', - mode: 'classic', - }), - ], - }), - ) - .process('@tailwind utilities', { - from: undefined, - }) - .then((result) => { - expect(result.css).toMatchCss(` - .font-sans.text-sm.leading-md.leading-trim::before, - .font-sans .text-sm.leading-md.leading-trim::before, - .font-sans .text-sm .leading-md.leading-trim::before, - .text-sm .font-sans.leading-md.leading-trim::before, - .text-sm .font-sans .leading-md.leading-trim::before { - content: ''; - margin-bottom: -0.3864em; - display: table; - } - - .font-sans.text-sm.leading-md.leading-trim::after, - .font-sans .text-sm.leading-md.leading-trim::after, - .font-sans .text-sm .leading-md.leading-trim::after, - .text-sm .font-sans.leading-md.leading-trim::after, - .text-sm .font-sans .leading-md.leading-trim::after { - content: ''; - margin-top: -0.3864em; - display: table; - } - `) - }) - }) - }) + describe('in "modern" mode', () => { + it('generates utility classes with a default root size', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + fontSize: { + sm: '14px', + md: '1.5rem', + }, + lineHeight: { + sm: '20px', + md: '2.5rem', + }, + }, + corePlugins: false, + plugins: [capsizePlugin], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans { + --ascent-scale: 0.9688; + --descent-scale: 0.2415; + --cap-height-scale: 0.7273; + --line-gap-scale: 0; + --line-height-scale: 1.2102; + font-family: Inter, sans-serif; + } + + .text-sm { + --font-size-px: 14; + font-size: 14px; + } + + .text-md { + --font-size-px: 24; + font-size: 1.5rem; + } + + .leading-sm { + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 20) / 2) / + var(--font-size-px) + ); + line-height: 20px; + } + + .leading-md { + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 40) / 2) / + var(--font-size-px) + ); + line-height: 2.5rem; + } + + .capsize::before { + display: table; + content: ""; + margin-bottom: calc( + ( + ( + var(--ascent-scale) - var(--cap-height-scale) + + var(--line-gap-scale) / 2 + ) - var(--line-height-offset) + ) * -1em + ); + } + + .capsize::after { + display: table; + content: ""; + margin-top: calc( + ( + (var(--descent-scale) + var(--line-gap-scale) / 2) - + var(--line-height-offset) + ) * -1em + ); + } + + @media (min-width: 640px) { + .sm\\:font-sans { + --ascent-scale: 0.9688; + --descent-scale: 0.2415; + --cap-height-scale: 0.7273; + --line-gap-scale: 0; + --line-height-scale: 1.2102; + font-family: Inter, sans-serif; + } + + .sm\\:text-sm { + --font-size-px: 14; + font-size: 14px; + } + + .sm\\:text-md { + --font-size-px: 24; + font-size: 1.5rem; + } + + .sm\\:leading-sm { + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 20) / 2) / + var(--font-size-px) + ); + line-height: 20px; + } + + .sm\\:leading-md { + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 40) / 2) / + var(--font-size-px) + ); + line-height: 2.5rem; + } + } + `) + }) + }) + + it('generates utility classes with a custom root size', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + screens: {}, + fontSize: { + sm: '14px', + md: '1.5rem', + }, + lineHeight: { + sm: '20px', + md: '2.5rem', + }, + }, + corePlugins: false, + plugins: [capsizePlugin({ rootSize: 12 })], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans { + --ascent-scale: 0.9688; + --descent-scale: 0.2415; + --cap-height-scale: 0.7273; + --line-gap-scale: 0; + --line-height-scale: 1.2102; + font-family: Inter, sans-serif; + } + + .text-sm { + --font-size-px: 14; + font-size: 14px; + } + + .text-md { + --font-size-px: 18; + font-size: 1.5rem; + } + + .leading-sm { + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 20) / 2) / + var(--font-size-px) + ); + line-height: 20px; + } + + .leading-md { + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 30) / 2) / + var(--font-size-px) + ); + line-height: 2.5rem; + } + + .capsize::before { + display: table; + content: ""; + margin-bottom: calc( + ( + ( + var(--ascent-scale) - var(--cap-height-scale) + + var(--line-gap-scale) / 2 + ) - var(--line-height-offset) + ) * -1em + ); + } + + .capsize::after { + display: table; + content: ""; + margin-top: calc( + ( + (var(--descent-scale) + var(--line-gap-scale) / 2) - + var(--line-height-offset) + ) * -1em + ); + } + `) + }) + }) + + it('works with unitless or percentage line-height values', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + screens: {}, + fontSize: { + sm: '1rem', + }, + lineHeight: { + sm: '100%', + md: '1.5', + }, + }, + corePlugins: false, + plugins: [capsizePlugin], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans { + --ascent-scale: 0.9688; + --descent-scale: 0.2415; + --cap-height-scale: 0.7273; + --line-gap-scale: 0; + --line-height-scale: 1.2102; + font-family: Inter, sans-serif; + } + + .text-sm { + --font-size-px: 16; + font-size: 1rem; + } + + .leading-sm { + --line-height-offset: calc( + ( + ( + (var(--line-height-scale) * var(--font-size-px)) - + calc(1 * var(--font-size-px)) + ) / 2 + ) / var(--font-size-px) + ); + line-height: 100%; + } + + .leading-md { + --line-height-offset: calc( + ( + ( + (var(--line-height-scale) * var(--font-size-px)) - + calc(1.5 * var(--font-size-px)) + ) / 2 + ) / var(--font-size-px) + ); + line-height: 1.5; + } + + .capsize::before { + display: table; + content: ""; + margin-bottom: calc( + ( + ( + var(--ascent-scale) - var(--cap-height-scale) + + var(--line-gap-scale) / 2 + ) - var(--line-height-offset) + ) * -1em + ); + } + + .capsize::after { + display: table; + content: ""; + margin-top: calc( + ( + (var(--descent-scale) + var(--line-gap-scale) / 2) - + var(--line-height-offset) + ) * -1em + ); + } + `) + }) + }) + + it('works with default line-height values', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + screens: {}, + fontSize: { + base: ['1rem', '1.5rem'], + }, + lineHeight: {}, + }, + corePlugins: false, + plugins: [capsizePlugin], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans { + --ascent-scale: 0.9688; + --descent-scale: 0.2415; + --cap-height-scale: 0.7273; + --line-gap-scale: 0; + --line-height-scale: 1.2102; + font-family: Inter, sans-serif; + } + + .text-base { + --font-size-px: 16; + font-size: 1rem; + --line-height-offset: calc( + (((var(--line-height-scale) * var(--font-size-px)) - 24) / 2) / + var(--font-size-px) + ); + line-height: 1.5rem; + } + + .capsize::before { + display: table; + content: ""; + margin-bottom: calc( + ( + ( + var(--ascent-scale) - var(--cap-height-scale) + + var(--line-gap-scale) / 2 + ) - var(--line-height-offset) + ) * -1em + ); + } + + .capsize::after { + display: table; + content: ""; + margin-top: calc( + ( + (var(--descent-scale) + var(--line-gap-scale) / 2) - + var(--line-height-offset) + ) * -1em + ); + } + `) + }) + }) + + it('generates utility classes with a custom activation class', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + screens: {}, + fontSize: { + sm: '1rem', + }, + lineHeight: { + md: '1.5', + }, + }, + corePlugins: false, + plugins: [capsizePlugin({ className: 'leading-trim' })], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans { + --ascent-scale: 0.9688; + --descent-scale: 0.2415; + --cap-height-scale: 0.7273; + --line-gap-scale: 0; + --line-height-scale: 1.2102; + font-family: Inter, sans-serif; + } + + .text-sm { + --font-size-px: 16; + font-size: 1rem; + } + + .leading-md { + --line-height-offset: calc( + ( + ( + (var(--line-height-scale) * var(--font-size-px)) - + calc(1.5 * var(--font-size-px)) + ) / 2 + ) / var(--font-size-px) + ); + line-height: 1.5; + } + + .leading-trim::before { + display: table; + content: ""; + margin-bottom: calc( + ( + ( + var(--ascent-scale) - var(--cap-height-scale) + + var(--line-gap-scale) / 2 + ) - var(--line-height-offset) + ) * -1em + ); + } + + .leading-trim::after { + display: table; + content: ""; + margin-top: calc( + ( + (var(--descent-scale) + var(--line-gap-scale) / 2) - + var(--line-height-offset) + ) * -1em + ); + } + `) + }) + }) + }) + + describe('in "classic" mode', () => { + it('generates utility classes with a default root size', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + fontSize: { + sm: '14px', + md: '1.5rem', + }, + lineHeight: { + sm: '20px', + md: '2.5rem', + }, + }, + corePlugins: false, + plugins: [capsizePlugin({ mode: 'classic' })], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans.text-sm.leading-sm.capsize::before, + .font-sans .text-sm.leading-sm.capsize::before, + .font-sans .text-sm .leading-sm.capsize::before, + .text-sm .font-sans.leading-sm.capsize::before, + .text-sm .font-sans .leading-sm.capsize::before { + content: ''; + margin-bottom: -0.3506em; + display: table; + } + + .font-sans.text-sm.leading-sm.capsize::after, + .font-sans .text-sm.leading-sm.capsize::after, + .font-sans .text-sm .leading-sm.capsize::after, + .text-sm .font-sans.leading-sm.capsize::after, + .text-sm .font-sans .leading-sm.capsize::after { + content: ''; + margin-top: -0.3506em; + display: table; + } + + .font-sans.text-sm.leading-md.capsize::before, + .font-sans .text-sm.leading-md.capsize::before, + .font-sans .text-sm .leading-md.capsize::before, + .text-sm .font-sans.leading-md.capsize::before, + .text-sm .font-sans .leading-md.capsize::before { + content: ''; + margin-bottom: -1.0649em; + display: table; + } + + .font-sans.text-sm.leading-md.capsize::after, + .font-sans .text-sm.leading-md.capsize::after, + .font-sans .text-sm .leading-md.capsize::after, + .text-sm .font-sans.leading-md.capsize::after, + .text-sm .font-sans .leading-md.capsize::after { + content: ''; + margin-top: -1.0649em; + display: table; + } + + .font-sans.text-md.leading-sm.capsize::before, + .font-sans .text-md.leading-sm.capsize::before, + .font-sans .text-md .leading-sm.capsize::before, + .text-md .font-sans.leading-sm.capsize::before, + .text-md .font-sans .leading-sm.capsize::before { + content: ''; + margin-bottom: -0.053em; + display: table; + } + + .font-sans.text-md.leading-sm.capsize::after, + .font-sans .text-md.leading-sm.capsize::after, + .font-sans .text-md .leading-sm.capsize::after, + .text-md .font-sans.leading-sm.capsize::after, + .text-md .font-sans .leading-sm.capsize::after { + content: ''; + margin-top: -0.053em; + display: table; + } + + .font-sans.text-md.leading-md.capsize::before, + .font-sans .text-md.leading-md.capsize::before, + .font-sans .text-md .leading-md.capsize::before, + .text-md .font-sans.leading-md.capsize::before, + .text-md .font-sans .leading-md.capsize::before { + content: ''; + margin-bottom: -0.4697em; + display: table; + } + + .font-sans.text-md.leading-md.capsize::after, + .font-sans .text-md.leading-md.capsize::after, + .font-sans .text-md .leading-md.capsize::after, + .text-md .font-sans.leading-md.capsize::after, + .text-md .font-sans .leading-md.capsize::after { + content: ''; + margin-top: -0.4697em; + display: table; + } + `) + }) + }) + + it('generates utility classes with a custom root size', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + fontSize: { + sm: '14px', + md: '1.5rem', + }, + lineHeight: { + sm: '20px', + md: '2.5rem', + }, + }, + corePlugins: false, + plugins: [capsizePlugin({ rootSize: 12, mode: 'classic' })], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans.text-sm.leading-sm.capsize::before, + .font-sans .text-sm.leading-sm.capsize::before, + .font-sans .text-sm .leading-sm.capsize::before, + .text-sm .font-sans.leading-sm.capsize::before, + .text-sm .font-sans .leading-sm.capsize::before { + content: ''; + margin-bottom: -0.3506em; + display: table; + } + + .font-sans.text-sm.leading-sm.capsize::after, + .font-sans .text-sm.leading-sm.capsize::after, + .font-sans .text-sm .leading-sm.capsize::after, + .text-sm .font-sans.leading-sm.capsize::after, + .text-sm .font-sans .leading-sm.capsize::after { + content: ''; + margin-top: -0.3506em; + display: table; + } + + .font-sans.text-sm.leading-md.capsize::before, + .font-sans .text-sm.leading-md.capsize::before, + .font-sans .text-sm .leading-md.capsize::before, + .text-sm .font-sans.leading-md.capsize::before, + .text-sm .font-sans .leading-md.capsize::before { + content: ''; + margin-bottom: -0.7078em; + display: table; + } + + .font-sans.text-sm.leading-md.capsize::after, + .font-sans .text-sm.leading-md.capsize::after, + .font-sans .text-sm .leading-md.capsize::after, + .text-sm .font-sans.leading-md.capsize::after, + .text-sm .font-sans .leading-md.capsize::after { + content: ''; + margin-top: -0.7078em; + display: table; + } + + .font-sans.text-md.leading-sm.capsize::before, + .font-sans .text-md.leading-sm.capsize::before, + .font-sans .text-md .leading-sm.capsize::before, + .text-md .font-sans.leading-sm.capsize::before, + .text-md .font-sans .leading-sm.capsize::before { + content: ''; + margin-bottom: -0.1919em; + display: table; + } + + .font-sans.text-md.leading-sm.capsize::after, + .font-sans .text-md.leading-sm.capsize::after, + .font-sans .text-md .leading-sm.capsize::after, + .text-md .font-sans.leading-sm.capsize::after, + .text-md .font-sans .leading-sm.capsize::after { + content: ''; + margin-top: -0.1919em; + display: table; + } + + .font-sans.text-md.leading-md.capsize::before, + .font-sans .text-md.leading-md.capsize::before, + .font-sans .text-md .leading-md.capsize::before, + .text-md .font-sans.leading-md.capsize::before, + .text-md .font-sans .leading-md.capsize::before { + content: ''; + margin-bottom: -0.4697em; + display: table; + } + + .font-sans.text-md.leading-md.capsize::after, + .font-sans .text-md.leading-md.capsize::after, + .font-sans .text-md .leading-md.capsize::after, + .text-md .font-sans.leading-md.capsize::after, + .text-md .font-sans .leading-md.capsize::after { + content: ''; + margin-top: -0.4697em; + display: table; + } + `) + }) + }) + + it('works with unitless or percentage line-height values', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + fontSize: { + sm: '1rem', + }, + lineHeight: { + sm: '100%', + md: '1.5', + }, + }, + corePlugins: false, + plugins: [capsizePlugin({ mode: 'classic' })], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans.text-sm.leading-sm.capsize::before, + .font-sans .text-sm.leading-sm.capsize::before, + .font-sans .text-sm .leading-sm.capsize::before, + .text-sm .font-sans.leading-sm.capsize::before, + .text-sm .font-sans .leading-sm.capsize::before { + content: ''; + margin-bottom: -0.1364em; + display: table; + } + + .font-sans.text-sm.leading-sm.capsize::after, + .font-sans .text-sm.leading-sm.capsize::after, + .font-sans .text-sm .leading-sm.capsize::after, + .text-sm .font-sans.leading-sm.capsize::after, + .text-sm .font-sans .leading-sm.capsize::after { + content: ''; + margin-top: -0.1364em; + display: table; + } + + .font-sans.text-sm.leading-md.capsize::before, + .font-sans .text-sm.leading-md.capsize::before, + .font-sans .text-sm .leading-md.capsize::before, + .text-sm .font-sans.leading-md.capsize::before, + .text-sm .font-sans .leading-md.capsize::before { + content: ''; + margin-bottom: -0.3864em; + display: table; + } + + .font-sans.text-sm.leading-md.capsize::after, + .font-sans .text-sm.leading-md.capsize::after, + .font-sans .text-sm .leading-md.capsize::after, + .text-sm .font-sans.leading-md.capsize::after, + .text-sm .font-sans .leading-md.capsize::after { + content: ''; + margin-top: -0.3864em; + display: table; + } + `) + }) + }) + + it('generates utility classes with a custom activation class', async () => { + return await postcss( + tailwindcss({ + theme: { + ...THEME_CONFIG, + fontSize: { + sm: '1rem', + }, + lineHeight: { + md: '1.5', + }, + }, + corePlugins: false, + plugins: [ + capsizePlugin({ + className: 'leading-trim', + mode: 'classic', + }), + ], + }), + ) + .process('@tailwind utilities', { + from: undefined, + }) + .then((result) => { + expect(result.css).toMatchCss(` + .font-sans.text-sm.leading-md.leading-trim::before, + .font-sans .text-sm.leading-md.leading-trim::before, + .font-sans .text-sm .leading-md.leading-trim::before, + .text-sm .font-sans.leading-md.leading-trim::before, + .text-sm .font-sans .leading-md.leading-trim::before { + content: ''; + margin-bottom: -0.3864em; + display: table; + } + + .font-sans.text-sm.leading-md.leading-trim::after, + .font-sans .text-sm.leading-md.leading-trim::after, + .font-sans .text-sm .leading-md.leading-trim::after, + .text-sm .font-sans.leading-md.leading-trim::after, + .text-sm .font-sans .leading-md.leading-trim::after { + content: ''; + margin-top: -0.3864em; + display: table; + } + `) + }) + }) + }) }) diff --git a/src/plugin.ts b/src/plugin.ts index f4d4c46..9f5f0c5 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -4,267 +4,267 @@ import type { NestedObject } from '@navith/tailwindcss-plugin-author-types' import plugin from 'tailwindcss/plugin' import { - isPlainObject, - lineHeightProperties, - makeCssSelectors, - normalizeThemeValue, - normalizeValue, - round, + isPlainObject, + lineHeightProperties, + makeCssSelectors, + normalizeThemeValue, + normalizeValue, + round, } from './utils' export interface PluginOptions { - /** The root font-size, in pixels */ - rootSize?: number - /** Custom utility classname */ - className?: string - /** CSS Output strategy */ - mode?: 'modern' | 'classic' + /** The root font-size, in pixels */ + rootSize?: number + /** Custom utility classname */ + className?: string + /** CSS Output strategy */ + mode?: 'modern' | 'classic' } interface FontSizeOptions { - lineHeight: string - letterSpacing?: string + lineHeight: string + letterSpacing?: string } export default plugin.withOptions