Repository: tailwindlabs/tailwindcss
Pull request #2185 (2020-08-18 20:48:44)

Fix color object closures with gradients
This PR applies two fixes, both related to #1676. 

## Color object flatening

#1676 actually won't work at all (my bad 😢) until this is fixed, because `lodash`'s `isObject` will return true if it is given a function. So, [the condition](https://github.com/tailwindlabs/tailwindcss/blob/master/src/util/flattenColorPalette.js#L6) had to be changed according to this information. 

## New gradient stop plugin

This one won't just not work, but will crash if it encounters a closure. This is because [the CSS is given the actual closure](https://github.com/tailwindlabs/tailwindcss/blob/master/src/plugins/gradientColorStops.js#L28), so PostCSS will complain. Like, it will complain by crashing.

To fix that, the `value` variable needs to be checked. If it is a function, it needs to be executed. But that function will take an object with an `opacityVariable` key as a parameter, and I thought it would be confusing to give it an empty value.

So I also introduced new opacity variables. I'm not sure how useful it can be, nor if it should be only one variable, though.

- For `.from-<color>` utilities, it is given `--gradient-from-opacity`
- For `.via-<color>` utilities, it is given `--gradient-via-opacity`
- For `.to-<color>` utilities, it is given `--gradient-to-opacity`

The variable name has been copied from the ones you used already, like `--gradient-from-color`.


Patch:
diff --git a/src/plugins/gradientColorStops.js b/src/plugins/gradientColorStops.js
index 01d3c934..4340f13c 100644
--- a/src/plugins/gradientColorStops.js
+++ b/src/plugins/gradientColorStops.js
@@ -12,6 +12,16 @@ export default function() {
 
     const utilities = _(colors)
       .map((value, modifier) => {
+        const getColorValue = (color, type) => {
+          if (_.isFunction(color)) {
+            return value({
+              opacityVariable: `--gradient-${type}-opacity`,
+            })
+          }
+
+          return color
+        }
+
         const transparentTo = (() => {
           try {
             const [r, g, b] = toRgba(value)
@@ -25,21 +35,21 @@ export default function() {
           [
             `.${e(`from-${modifier}`)}`,
             {
-              '--gradient-from-color': value,
+              '--gradient-from-color': getColorValue(value, 'from'),
               '--gradient-color-stops': `var(--gradient-from-color), var(--gradient-to-color, ${transparentTo})`,
             },
           ],
           [
             `.${e(`via-${modifier}`)}`,
             {
-              '--gradient-via-color': value,
+              '--gradient-via-color': getColorValue(value, 'via'),
               '--gradient-color-stops': `var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, ${transparentTo})`,
             },
           ],
           [
             `.${e(`to-${modifier}`)}`,
             {
-              '--gradient-to-color': value,
+              '--gradient-to-color': getColorValue(value, 'to'),
             },
           ],
         ]
diff --git a/src/util/flattenColorPalette.js b/src/util/flattenColorPalette.js
index 3214d58d..5110e988 100644
--- a/src/util/flattenColorPalette.js
+++ b/src/util/flattenColorPalette.js
@@ -3,7 +3,7 @@ import _ from 'lodash'
 export default function flattenColorPalette(colors) {
   const result = _(colors)
     .flatMap((color, name) => {
-      if (!_.isObject(color)) {
+      if (_.isFunction(color) || !_.isObject(color)) {
         return [[name, color]]
       }
 


Test patch:
diff --git a/__tests__/plugins/gradientColorStops.test.js b/__tests__/plugins/gradientColorStops.test.js
new file mode 100644
index 00000000..473ae777
--- /dev/null
+++ b/__tests__/plugins/gradientColorStops.test.js
@@ -0,0 +1,38 @@
+import postcss from 'postcss'
+import tailwind from '../../src/index'
+
+test('opacity variables are given to colors defined as closures', () => {
+  return postcss([
+    tailwind({
+      theme: {
+        colors: {
+          primary: ({ opacityVariable }) => `rgba(31,31,31,var(${opacityVariable},1))`,
+        },
+      },
+      variants: {
+        gradientColorStops: [],
+      },
+      corePlugins: ['gradientColorStops'],
+    }),
+  ])
+    .process('@tailwind utilities', { from: undefined })
+    .then(result => {
+      const expected = `
+				.from-primary {
+					--gradient-from-color: rgba(31,31,31,var(--gradient-from-opacity,1));
+					--gradient-color-stops: var(--gradient-from-color), var(--gradient-to-color, rgba(255, 255, 255, 0))
+				}
+		
+				.via-primary {
+					--gradient-via-color: rgba(31,31,31,var(--gradient-via-opacity,1));
+					--gradient-color-stops: var(--gradient-from-color), var(--gradient-via-color), var(--gradient-to-color, rgba(255, 255, 255, 0))
+				}
+		
+				.to-primary {
+					--gradient-to-color: rgba(31,31,31,var(--gradient-to-opacity,1))
+				}
+      `
+
+      expect(result.css).toMatchCss(expected)
+    })
+})