diff --git a/package.json b/package.json index f18ca7e..7657903 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-preset-mantine", - "version": "1.10.0", + "version": "1.11.0", "description": "PostCSS preset for Mantine (7.0+) applications", "main": "dist/preset.js", "types": "dist/index.d.ts", diff --git a/src/converters.ts b/src/converters.ts new file mode 100644 index 0000000..b9d1ef9 --- /dev/null +++ b/src/converters.ts @@ -0,0 +1,95 @@ +function scaleRem(remValue: string) { + return `calc(${remValue} * var(--mantine-scale))`; +} + +function createConverter(units: string, { shouldScale = false } = {}) { + function converter(value: unknown): string { + if (value === 0 || value === '0') { + return '0'; + } + + if (typeof value === 'number') { + const val = `${value / 16}${units}`; + return shouldScale ? scaleRem(val) : val; + } + + if (typeof value === 'string') { + if (value.startsWith('calc(') || value.startsWith('var(')) { + return value; + } + + if (value.includes(' ')) { + return value + .split(' ') + .map((val) => converter(val)) + .join(' '); + } + + if (value.includes(units)) { + return shouldScale ? scaleRem(value) : value; + } + + const replaced = value.replace('px', ''); + if (!Number.isNaN(Number(replaced))) { + const val = `${Number(replaced) / 16}${units}`; + return shouldScale ? scaleRem(val) : val; + } + } + + return value as string; + } + + return converter; +} + +const rem = createConverter('rem', { shouldScale: true }); +const remNoScale = createConverter('rem'); +const em = createConverter('em'); + +function getTransformedScaledValue(value: unknown) { + if (typeof value !== 'string' || !value.includes('var(--mantine-scale)')) { + return value; + } + + return value + .match(/^calc\((.*?)\)$/)?.[1] + .split('*')[0] + .trim(); +} + +function px(value: unknown) { + const transformedValue = getTransformedScaledValue(value); + + if (typeof transformedValue === 'number') { + return transformedValue; + } + + if (typeof transformedValue === 'string') { + if (transformedValue.includes('calc') || transformedValue.includes('var')) { + return transformedValue; + } + + if (transformedValue.includes('px')) { + return Number(transformedValue.replace('px', '')); + } + + if (transformedValue.includes('rem')) { + return Number(transformedValue.replace('rem', '')) * 16; + } + + if (transformedValue.includes('em')) { + return Number(transformedValue.replace('em', '')) * 16; + } + + return Number(transformedValue); + } + + return NaN; +} + +module.exports = { + px, + em, + rem, + remNoScale, +}; diff --git a/src/postcss-rem-em.ts b/src/postcss-rem-em.ts index 21ef27d..d33ecde 100644 --- a/src/postcss-rem-em.ts +++ b/src/postcss-rem-em.ts @@ -1,52 +1,5 @@ import type { AtRule, Root } from 'postcss'; - -function scaleRem(remValue: string) { - return `calc(${remValue} * var(--mantine-scale))`; -} - -function createConverter(units: string, { shouldScale = false } = {}) { - function converter(value: unknown): string { - if (value === 0 || value === '0') { - return '0'; - } - - if (typeof value === 'number') { - const val = `${value / 16}${units}`; - return shouldScale ? scaleRem(val) : val; - } - - if (typeof value === 'string') { - if (value.startsWith('calc(') || value.startsWith('var(')) { - return value; - } - - if (value.includes(' ')) { - return value - .split(' ') - .map((val) => converter(val)) - .join(' '); - } - - if (value.includes(units)) { - return shouldScale ? scaleRem(value) : value; - } - - const replaced = value.replace('px', ''); - if (!Number.isNaN(Number(replaced))) { - const val = `${Number(replaced) / 16}${units}`; - return shouldScale ? scaleRem(val) : val; - } - } - - return value as string; - } - - return converter; -} - -const rem = createConverter('rem', { shouldScale: true }); -const remNoScale = createConverter('rem'); -const em = createConverter('em'); +const converters = require('./converters'); const getRegExp = (units: 'rem' | 'em') => new RegExp('\\b' + units + '\\(([^()]+)\\)', 'g'); const emRegExp = getRegExp('em'); @@ -57,15 +10,17 @@ module.exports = () => { postcssPlugin: 'postcss-rem-em', Once(root: Root) { - root.replaceValues(remRegExp, { fast: `rem(` }, (_, values) => rem(values)); - root.replaceValues(emRegExp, { fast: `em(` }, (_, values) => em(values)); + root.replaceValues(remRegExp, { fast: `rem(` }, (_, values) => converters.rem(values)); + root.replaceValues(emRegExp, { fast: `em(` }, (_, values) => converters.em(values)); }, AtRule: { media: (atRule: AtRule) => { atRule.params = atRule.params - .replace(remRegExp, (value) => remNoScale(value.replace(/rem\((.*?)\)/g, '$1'))) - .replace(emRegExp, (value) => em(value.replace(/em\((.*?)\)/g, '$1'))); + .replace(remRegExp, (value) => + converters.remNoScale(value.replace(/rem\((.*?)\)/g, '$1')) + ) + .replace(emRegExp, (value) => converters.em(value.replace(/em\((.*?)\)/g, '$1'))); }, }, }; diff --git a/src/preset.ts b/src/preset.ts index 3d86510..a283a11 100644 --- a/src/preset.ts +++ b/src/preset.ts @@ -2,6 +2,7 @@ const nested = require('postcss-nested'); const mixins = require('postcss-mixins'); const remEm = require('./postcss-rem-em'); const lightDark = require('./postcss-light-dark'); +const converters = require('./converters'); function colorSchemeMixin(colorScheme: 'light' | 'dark') { return { @@ -56,6 +57,18 @@ const notLtrMixin = { }, }; +const smallerThanMixin = (_mixin: string, breakpoint: string) => ({ + [`@media (max-width: ${converters.em(converters.px(breakpoint) - 0.1)})`]: { + '@mixin-content': {}, + }, +}); + +const largerThanMixin = (_mixin: string, breakpoint: string) => ({ + [`@media (min-width: ${converters.em(breakpoint)})`]: { + '@mixin-content': {}, + }, +}); + module.exports = () => { return { postcssPlugin: 'postcss-preset-mantine', @@ -74,6 +87,8 @@ module.exports = () => { ltr: ltrMixin, 'not-rtl': notRtlMixin, 'not-ltr': notLtrMixin, + 'smaller-than': smallerThanMixin, + 'larger-than': largerThanMixin, }, }), ], diff --git a/src/tests/__snapshots__/smaller-larget-than.test.ts.snap b/src/tests/__snapshots__/smaller-larget-than.test.ts.snap new file mode 100644 index 0000000..393b112 --- /dev/null +++ b/src/tests/__snapshots__/smaller-larget-than.test.ts.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`larger-than it transforms larger-than mixin correctly 1`] = ` +" +@media (min-width: 20em) { +.a { + background: red +} +} +" +`; + +exports[`smaller-than it transforms smaller-than mixin correctly 1`] = ` +" +@media (max-width: 19.99375em) { +.a { + background: red +} +} +" +`; diff --git a/src/tests/smaller-larget-than.test.ts b/src/tests/smaller-larget-than.test.ts new file mode 100644 index 0000000..eae7922 --- /dev/null +++ b/src/tests/smaller-larget-than.test.ts @@ -0,0 +1,31 @@ +import { testTransform } from './utils'; + +const smallerThanInput = ` +.a { + @mixin smaller-than 320px { + background: red; + } +} +`; + +const largerThanInput = ` +.a { + @mixin larger-than 320px { + background: red; + } +} +`; + +describe('smaller-than', () => { + it('it transforms smaller-than mixin correctly', async () => { + const res = await testTransform(smallerThanInput); + expect(res.css).toMatchSnapshot(); + }); +}); + +describe('larger-than', () => { + it('it transforms larger-than mixin correctly', async () => { + const res = await testTransform(largerThanInput); + expect(res.css).toMatchSnapshot(); + }); +});