From a06d29e2d5201d5b78a2f4c0cb7fd8b687323bd0 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 20 May 2022 16:51:05 -0400 Subject: [PATCH] Support alpha modifier for theme color values --- src/lib/evaluateTailwindFunctions.js | 22 ++- src/lib/setupContextUtils.js | 19 ++- src/util/resolveConfig.js | 20 ++- src/util/transformThemeValue.js | 6 +- tests/evaluateTailwindFunctions.test.js | 178 ++++++++++++++++++++- tests/opacity.test.js | 200 ++++++++++++++++++++++++ 6 files changed, 429 insertions(+), 16 deletions(-) diff --git a/src/lib/evaluateTailwindFunctions.js b/src/lib/evaluateTailwindFunctions.js index fbe366a93197..bfeea5a52009 100644 --- a/src/lib/evaluateTailwindFunctions.js +++ b/src/lib/evaluateTailwindFunctions.js @@ -5,6 +5,7 @@ import parseValue from 'postcss-value-parser' import { normalizeScreens } from '../util/normalizeScreens' import buildMediaQuery from '../util/buildMediaQuery' import { toPath } from '../util/toPath' +import { withAlphaValue } from '../util/withAlphaVariable' function isObject(input) { return typeof input === 'object' && input !== null @@ -37,7 +38,7 @@ function listKeys(obj) { return list(Object.keys(obj)) } -function validatePath(config, path, defaultValue) { +function validatePath(config, path, defaultValue, themeOpts = {}) { const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+/g, '').replace(/['"]+$/g, '') @@ -114,7 +115,7 @@ function validatePath(config, path, defaultValue) { return { isValid: true, - value: transformThemeValue(themeSection)(value), + value: transformThemeValue(themeSection)(value, themeOpts), } } @@ -160,16 +161,29 @@ let nodeTypePropertyMap = { export default function ({ tailwindConfig: config }) { let functions = { theme: (node, path, ...defaultValue) => { - const { isValid, value, error } = validatePath( + let matches = path.match(/^([^\/\s]+)(?:\s*\/\s*([^\/\s]+))$/) + let alpha = undefined + + if (matches) { + path = matches[1] + alpha = matches[2] + } + + let { isValid, value, error } = validatePath( config, path, - defaultValue.length ? defaultValue : undefined + defaultValue.length ? defaultValue : undefined, + { opacityValue: alpha } ) if (!isValid) { throw node.error(error) } + if (alpha !== undefined) { + value = withAlphaValue(value, alpha, value) + } + return value }, screen: (node, screen) => { diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index f044a70d827b..5d1cb117d4bc 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -221,16 +221,25 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs return context.tailwindConfig.prefix + identifier } + function resolveThemeValue(path, defaultValue, opts = {}) { + const [pathRoot, ...subPaths] = toPath(path) + const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue) + return transformThemeValue(pathRoot)(value, opts) + } + + const theme = Object.assign( + (path, defaultValue = undefined) => resolveThemeValue(path, defaultValue), + { + withAlpha: (path, opacityValue) => resolveThemeValue(path, undefined, { opacityValue }), + } + ) + let api = { postcss, prefix: applyConfiguredPrefix, e: escapeClassName, config: getConfigValue, - theme(path, defaultValue) { - const [pathRoot, ...subPaths] = toPath(path) - const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue) - return transformThemeValue(pathRoot)(value) - }, + theme, corePlugins: (path) => { if (Array.isArray(tailwindConfig.corePlugins)) { return tailwindConfig.corePlugins.includes(path) diff --git a/src/util/resolveConfig.js b/src/util/resolveConfig.js index ad9c22529881..3a45552328f9 100644 --- a/src/util/resolveConfig.js +++ b/src/util/resolveConfig.js @@ -8,6 +8,7 @@ import { toPath } from './toPath' import { normalizeConfig } from './normalizeConfig' import isPlainObject from './isPlainObject' import { cloneDeep } from './cloneDeep' +import { withAlphaValue } from './withAlphaVariable' function isFunction(input) { return typeof input === 'function' @@ -187,11 +188,22 @@ function resolveFunctionKeys(object) { return val } - resolvePath.theme = resolvePath + Object.assign(resolvePath, { + theme: resolvePath, + ...configUtils, + withAlpha(key, opacityValue) { + // TODO: This is kinda iffy but it works + const path = toPath(key) + const lastSegment = path.pop() + let value = resolvePath(path)[lastSegment] + + if (value === undefined) { + return value + } - for (let key in configUtils) { - resolvePath[key] = configUtils[key] - } + return withAlphaValue(value, opacityValue) + }, + }) return Object.keys(object).reduce((resolved, key) => { return { diff --git a/src/util/transformThemeValue.js b/src/util/transformThemeValue.js index e6a8b8735317..30bd99cadfb0 100644 --- a/src/util/transformThemeValue.js +++ b/src/util/transformThemeValue.js @@ -44,8 +44,10 @@ export default function transformThemeValue(themeSection) { } } - return (value) => { - if (typeof value === 'function') value = value({}) + return (value, opts = {}) => { + if (typeof value === 'function') { + value = value(opts) + } return value } diff --git a/tests/evaluateTailwindFunctions.test.js b/tests/evaluateTailwindFunctions.test.js index 1cd07607b885..c7f31a416de7 100644 --- a/tests/evaluateTailwindFunctions.test.js +++ b/tests/evaluateTailwindFunctions.test.js @@ -1,11 +1,16 @@ import postcss from 'postcss' import plugin from '../src/lib/evaluateTailwindFunctions' -import { css } from './util/run' +import tailwind from '../src/index' +import { css, html } from './util/run' function run(input, opts = {}) { return postcss([plugin({ tailwindConfig: opts })]).process(input, { from: undefined }) } +function runFull(input, config) { + return postcss([tailwind(config)]).process(input, { from: undefined }) +} + test('it looks up values in the theme using dot notation', () => { let input = css` .banana { @@ -817,3 +822,174 @@ test('screen arguments can be quoted', () => { expect(result.warnings().length).toBe(0) }) }) + +test('Theme function can extract alpha values for colors (1)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 50%); + } + ` + + let output = css` + .foo { + color: rgb(59 130 246 / 50%); + } + ` + + return run(input, { + theme: { + colors: { blue: { 500: '#3b82f6' } }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (2)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 0.5); + } + ` + + let output = css` + .foo { + color: rgb(59 130 246 / 0.5); + } + ` + + return run(input, { + theme: { + colors: { blue: { 500: '#3b82f6' } }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (3)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / var(--my-alpha)); + } + ` + + let output = css` + .foo { + color: rgb(59 130 246 / var(--my-alpha)); + } + ` + + return run(input, { + theme: { + colors: { blue: { 500: '#3b82f6' } }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (4)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 50%); + } + ` + + let output = css` + .foo { + color: hsl(217 91% 60% / 50%); + } + ` + + return run(input, { + theme: { + colors: { + blue: { 500: 'hsl(217, 91%, 60%)' }, + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (5)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 0.5); + } + ` + + let output = css` + .foo { + color: hsl(217 91% 60% / 0.5); + } + ` + + return run(input, { + theme: { + colors: { + blue: { 500: 'hsl(217, 91%, 60%)' }, + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (6)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / var(--my-alpha)); + } + ` + + let output = css` + .foo { + color: hsl(217 91% 60% / var(--my-alpha)); + } + ` + + return run(input, { + theme: { + colors: { + blue: { 500: 'hsl(217, 91%, 60%)' }, + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (7)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / var(--my-alpha)); + } + ` + + let output = css` + .foo { + color: rgb(var(--foo) / var(--my-alpha)); + } + ` + + return runFull(input, { + theme: { + colors: ({ rgb }) => ({ + blue: { + 500: rgb('--foo'), + }, + }), + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) diff --git a/tests/opacity.test.js b/tests/opacity.test.js index cd18c479627a..36d9c60ccc7f 100644 --- a/tests/opacity.test.js +++ b/tests/opacity.test.js @@ -427,3 +427,203 @@ it('the hsl helper throws when not passing custom properties', () => { 'The hsl() helper requires a custom property name to be passed as the first argument.' ) }) + +test('Theme function in JS can apply alpha values to colors (1)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: rgb(59 130 246 / 50%); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: '#3b82f6' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', '50%'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('TTheme function in JS can apply alpha values to colors (2)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: rgb(59 130 246 / 0.5); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: '#3b82f6' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', '0.5'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('TTheme function in JS can apply alpha values to colors (3)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: rgb(59 130 246 / var(--my-alpha)); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: '#3b82f6' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', 'var(--my-alpha)'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('TTheme function in JS can apply alpha values to colors (4)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: hsl(217 91% 60% / 50%); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', '50%'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('TTheme function in JS can apply alpha values to colors (5)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: hsl(217 91% 60% / 0.5); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', '0.5'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('TTheme function in JS can apply alpha values to colors (6)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: hsl(217 91% 60% / var(--my-alpha)); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', 'var(--my-alpha)'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('TTheme function in JS can apply alpha values to colors (7)', () => { + let input = css` + @tailwind utilities; + ` + + let output = css` + .text-foo { + color: rgb(var(--foo) / var(--my-alpha)); + } + ` + + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: ({ rgb }) => ({ + blue: { + 500: rgb('--foo'), + }, + }), + extend: { + textColor: ({ theme }) => ({ + foo: theme.withAlpha('colors.blue.500', 'var(--my-alpha)'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +})