diff --git a/CHANGELOG.md b/CHANGELOG.md index 104e4324b..3985ed6f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow default ring color to be a function ([#7587](https://github.com/tailwindlabs/tailwindcss/pull/7587)) +- Add `rgb` and `hsl` color helpers for CSS variables ([#7665](https://github.com/tailwindlabs/tailwindcss/pull/7665)) ## [3.0.23] - 2022-02-16 diff --git a/src/corePlugins.js b/src/corePlugins.js index e52f9d6c6..dbdea4b08 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1964,10 +1964,16 @@ export let corePlugins = { }) }, - ringColor: ({ matchUtilities, theme }) => { + ringColor: ({ matchUtilities, theme, corePlugins }) => { matchUtilities( { ring: (value) => { + if (!corePlugins('ringOpacity')) { + return { + '--tw-ring-color': toColorValue(value), + } + } + return withAlphaVariable({ color: value, property: '--tw-ring-color', diff --git a/src/util/resolveConfig.js b/src/util/resolveConfig.js index 15b876e88..ad9c22529 100644 --- a/src/util/resolveConfig.js +++ b/src/util/resolveConfig.js @@ -66,6 +66,36 @@ const configUtils = { {} ) }, + rgb(property) { + if (!property.startsWith('--')) { + throw new Error( + 'The rgb() helper requires a custom property name to be passed as the first argument.' + ) + } + + return ({ opacityValue }) => { + if (opacityValue === undefined || opacityValue === 1) { + return `rgb(var(${property}) / 1.0)` + } + + return `rgb(var(${property}) / ${opacityValue})` + } + }, + hsl(property) { + if (!property.startsWith('--')) { + throw new Error( + 'The hsl() helper requires a custom property name to be passed as the first argument.' + ) + } + + return ({ opacityValue }) => { + if (opacityValue === undefined || opacityValue === 1) { + return `hsl(var(${property}) / 1)` + } + + return `hsl(var(${property}) / ${opacityValue})` + } + }, } function value(valueToResolve, ...args) { diff --git a/tests/opacity.test.js b/tests/opacity.test.js index 6e270eb17..cd18c4796 100644 --- a/tests/opacity.test.js +++ b/tests/opacity.test.js @@ -95,3 +95,335 @@ test('colors defined as functions work when opacity plugins are disabled', () => `) }) }) + +it('can use rgb helper when defining custom properties for colors (opacity plugins enabled)', () => { + let config = { + content: [ + { + raw: html` +
+ + + + + + + + + + + + `, + }, + ], + theme: { + colors: ({ rgb }) => ({ + primary: rgb('--color-primary'), + }), + }, + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(var(--color-primary) / var(--tw-divide-opacity)); + } + .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 0.5; + } + .border-primary { + --tw-border-opacity: 1; + border-color: rgb(var(--color-primary) / var(--tw-border-opacity)); + } + .border-opacity-50 { + --tw-border-opacity: 0.5; + } + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(var(--color-primary) / var(--tw-bg-opacity)); + } + .bg-opacity-50 { + --tw-bg-opacity: 0.5; + } + .text-primary { + --tw-text-opacity: 1; + color: rgb(var(--color-primary) / var(--tw-text-opacity)); + } + .text-opacity-50 { + --tw-text-opacity: 0.5; + } + .placeholder-primary::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(var(--color-primary) / var(--tw-placeholder-opacity)); + } + .placeholder-opacity-50::placeholder { + --tw-placeholder-opacity: 0.5; + } + .ring-primary { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity)); + } + .ring-opacity-50 { + --tw-ring-opacity: 0.5; + } + `) + }) +}) + +it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => { + let config = { + content: [ + { + raw: html` + + + + + + + + + + + + + `, + }, + ], + theme: { + colors: ({ rgb }) => ({ + primary: rgb('--color-primary'), + }), + }, + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + textOpacity: false, + ringOpacity: false, + }, + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(var(--color-primary) / 1); + } + .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(var(--color-primary) / 0.5); + } + .border-primary { + border-color: rgb(var(--color-primary) / 1); + } + .border-primary\/50 { + border-color: rgb(var(--color-primary) / 0.5); + } + .bg-primary { + background-color: rgb(var(--color-primary) / 1); + } + .bg-primary\/50 { + background-color: rgb(var(--color-primary) / 0.5); + } + .text-primary { + color: rgb(var(--color-primary) / 1); + } + .text-primary\/50 { + color: rgb(var(--color-primary) / 0.5); + } + .placeholder-primary::placeholder { + color: rgb(var(--color-primary) / 1); + } + .placeholder-primary\/50::placeholder { + color: rgb(var(--color-primary) / 0.5); + } + .ring-primary { + --tw-ring-color: rgb(var(--color-primary) / 1); + } + .ring-primary\/50 { + --tw-ring-color: rgb(var(--color-primary) / 0.5); + } + `) + }) +}) + +it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => { + let config = { + content: [ + { + raw: html` + + + + + + + + + + + + + `, + }, + ], + theme: { + colors: ({ hsl }) => ({ + primary: hsl('--color-primary'), + }), + }, + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: hsl(var(--color-primary) / var(--tw-divide-opacity)); + } + .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 0.5; + } + .border-primary { + --tw-border-opacity: 1; + border-color: hsl(var(--color-primary) / var(--tw-border-opacity)); + } + .border-opacity-50 { + --tw-border-opacity: 0.5; + } + .bg-primary { + --tw-bg-opacity: 1; + background-color: hsl(var(--color-primary) / var(--tw-bg-opacity)); + } + .bg-opacity-50 { + --tw-bg-opacity: 0.5; + } + .text-primary { + --tw-text-opacity: 1; + color: hsl(var(--color-primary) / var(--tw-text-opacity)); + } + .text-opacity-50 { + --tw-text-opacity: 0.5; + } + .placeholder-primary::placeholder { + --tw-placeholder-opacity: 1; + color: hsl(var(--color-primary) / var(--tw-placeholder-opacity)); + } + .placeholder-opacity-50::placeholder { + --tw-placeholder-opacity: 0.5; + } + .ring-primary { + --tw-ring-opacity: 1; + --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity)); + } + .ring-opacity-50 { + --tw-ring-opacity: 0.5; + } + `) + }) +}) + +it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => { + let config = { + content: [ + { + raw: html` + + + + + + + + + + + + + `, + }, + ], + theme: { + colors: ({ hsl }) => ({ + primary: hsl('--color-primary'), + }), + }, + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + textOpacity: false, + ringOpacity: false, + }, + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + border-color: hsl(var(--color-primary) / 1); + } + .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) { + border-color: hsl(var(--color-primary) / 0.5); + } + .border-primary { + border-color: hsl(var(--color-primary) / 1); + } + .border-primary\/50 { + border-color: hsl(var(--color-primary) / 0.5); + } + .bg-primary { + background-color: hsl(var(--color-primary) / 1); + } + .bg-primary\/50 { + background-color: hsl(var(--color-primary) / 0.5); + } + .text-primary { + color: hsl(var(--color-primary) / 1); + } + .text-primary\/50 { + color: hsl(var(--color-primary) / 0.5); + } + .placeholder-primary::placeholder { + color: hsl(var(--color-primary) / 1); + } + .placeholder-primary\/50::placeholder { + color: hsl(var(--color-primary) / 0.5); + } + .ring-primary { + --tw-ring-color: hsl(var(--color-primary) / 1); + } + .ring-primary\/50 { + --tw-ring-color: hsl(var(--color-primary) / 0.5); + } + `) + }) +}) + +it('the rgb helper throws when not passing custom properties', () => { + let config = { + theme: { + colors: ({ rgb }) => ({ + primary: rgb('anything else'), + }), + }, + } + + return expect(run('@tailwind utilities', config)).rejects.toThrow( + 'The rgb() helper requires a custom property name to be passed as the first argument.' + ) +}) + +it('the hsl helper throws when not passing custom properties', () => { + let config = { + theme: { + colors: ({ hsl }) => ({ + primary: hsl('anything else'), + }), + }, + } + + return expect(run('@tailwind utilities', config)).rejects.toThrow( + 'The hsl() helper requires a custom property name to be passed as the first argument.' + ) +})