Skip to content

Commit

Permalink
Add support for colored box shadows (#5979)
Browse files Browse the repository at this point in the history
* WIP

* add box shadow parser

* use box shadow parser

* Update default shadows, add boxShadowColor key, add shadow datatype

* Update tests

* add `valid` flag to `boxShadow` parser

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
  • Loading branch information
adamwathan and RobinMalfait committed Nov 4, 2021
1 parent c25fbc7 commit d1f066d
Show file tree
Hide file tree
Showing 17 changed files with 426 additions and 136 deletions.
30 changes: 29 additions & 1 deletion src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import isPlainObject from './util/isPlainObject'
import transformThemeValue from './util/transformThemeValue'
import { version as tailwindVersion } from '../package.json'
import log from './util/log'
import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'

export let variantPlugins = {
pseudoElementVariants: ({ addVariant }) => {
Expand Down Expand Up @@ -1774,6 +1775,7 @@ export let corePlugins = {
'--tw-ring-offset-shadow': '0 0 #0000',
'--tw-ring-shadow': '0 0 #0000',
'--tw-shadow': '0 0 #0000',
'--tw-shadow-colored': '0 0 #0000',
},
})

Expand All @@ -1782,18 +1784,43 @@ export let corePlugins = {
shadow: (value) => {
value = transformValue(value)

let ast = parseBoxShadowValue(value)
for (let shadow of ast) {
// Don't override color if the whole shadow is a variable
if (!shadow.valid) {
continue
}

shadow.color = 'var(--tw-shadow-color)'
}

return {
'@defaults box-shadow': {},
'--tw-shadow': value === 'none' ? '0 0 #0000' : value,
'--tw-shadow-colored': value === 'none' ? '0 0 #0000' : formatBoxShadowValue(ast),
'box-shadow': defaultBoxShadow,
}
},
},
{ values: theme('boxShadow') }
{ values: theme('boxShadow'), type: ['shadow'] }
)
}
})(),

boxShadowColor: ({ matchUtilities, theme }) => {
matchUtilities(
{
shadow: (value) => {
return {
'--tw-shadow-color': toColorValue(value),
'--tw-shadow': 'var(--tw-shadow-colored)',
}
},
},
{ values: flattenColorPalette(theme('boxShadowColor')), type: ['color'] }
)
},

outlineStyle: ({ addUtilities }) => {
addUtilities({
'.outline-none': {
Expand Down Expand Up @@ -1844,6 +1871,7 @@ export let corePlugins = {
'--tw-ring-offset-shadow': '0 0 #0000',
'--tw-ring-shadow': '0 0 #0000',
'--tw-shadow': '0 0 #0000',
'--tw-shadow-colored': '0 0 #0000',
},
})

Expand Down
13 changes: 13 additions & 0 deletions src/util/dataTypes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { parseColor } from './color'
import { parseBoxShadowValue } from './parseBoxShadowValue'

// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types

Expand Down Expand Up @@ -88,6 +89,18 @@ export function lineWidth(value) {
return lineWidths.has(value)
}

export function shadow(value) {
let parsedShadows = parseBoxShadowValue(normalize(value))

for (let parsedShadow of parsedShadows) {
if (!parsedShadow.valid) {
return false
}
}

return true
}

export function color(value) {
let colors = 0

Expand Down
71 changes: 71 additions & 0 deletions src/util/parseBoxShadowValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
let LENGTH = /^-?(\d+)(.*?)$/g

export function parseBoxShadowValue(input) {
let shadows = input.split(COMMA)
return shadows.map((shadow) => {
let value = shadow.trim()
let result = { raw: value }
let parts = value.split(SPACE)
let seen = new Set()

for (let part of parts) {
// Reset index, since the regex is stateful.
LENGTH.lastIndex = 0

// Keyword
if (!seen.has('KEYWORD') && KEYWORDS.has(part)) {
result.keyword = part
seen.add('KEYWORD')
}

// Length value
else if (LENGTH.test(part)) {
if (!seen.has('X')) {
result.x = part
seen.add('X')
} else if (!seen.has('Y')) {
result.y = part
seen.add('Y')
} else if (!seen.has('BLUR')) {
result.blur = part
seen.add('BLUR')
} else if (!seen.has('SPREAD')) {
result.spread = part
seen.add('SPREAD')
}
}

// Color or unknown
else {
if (!result.color) {
result.color = part
} else {
if (!result.unknown) result.unknown = []
result.unknown.push(part)
}
}
}

// Check if valid
result.valid = result.x !== undefined && result.y !== undefined

return result
})
}

export function formatBoxShadowValue(shadows) {
return shadows
.map((shadow) => {
if (!shadow.valid) {
return shadow.raw
}

return [shadow.keyword, shadow.x, shadow.y, shadow.blur, shadow.spread, shadow.color]
.filter(Boolean)
.join(' ')
})
.join(', ')
}
2 changes: 2 additions & 0 deletions src/util/pluginUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
relativeSize,
position,
lineWidth,
shadow,
} from './dataTypes'
import negateValue from './negateValue'

Expand Down Expand Up @@ -148,6 +149,7 @@ let typeMap = {
'line-width': guess(lineWidth),
'absolute-size': guess(absoluteSize),
'relative-size': guess(relativeSize),
shadow: guess(shadow),
}

let supportedTypes = Object.keys(typeMap)
Expand Down
11 changes: 6 additions & 5 deletions stubs/defaultConfig.stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,15 @@ module.exports = {
},
boxShadow: {
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06)',
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06)',
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05)',
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 10px 10px -5px rgb(0 0 0 / 0.04)',
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.06)',
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
none: 'none',
},
boxShadowColor: ({ theme }) => theme('colors'),
caretColor: ({ theme }) => theme('colors'),
accentColor: ({ theme }) => ({
...theme('colors'),
Expand Down
8 changes: 6 additions & 2 deletions tests/apply.test.css
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,16 @@
--tw-ordinal: ordinal;
--tw-numeric-spacing: tabular-nums;
font-variant-numeric: var(--tw-font-variant-numeric);
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.complex-utilities:hover {
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 10px 10px -5px rgb(0 0 0 / 0.04);
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color),
0 8px 10px -6px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand Down
4 changes: 3 additions & 1 deletion tests/arbitrary-values.test.css
Original file line number Diff line number Diff line change
Expand Up @@ -851,11 +851,13 @@
}
.shadow-\[0px_1px_2px_black\] {
--tw-shadow: 0px 1px 2px black;
--tw-shadow-colored: 0px 1px 2px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.shadow-\[var\(--value\)\] {
.shadow-\[shadow\:var\(--value\)\] {
--tw-shadow: var(--value);
--tw-shadow-colored: var(--value);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/arbitrary-values.test.html
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
<div class="opacity-[var(--opacity)]"></div>

<div class="shadow-[0px_1px_2px_black]"></div>
<div class="shadow-[var(--value)]"></div>
<div class="shadow-[shadow:var(--value)]"></div>

<div class="outline-[black]"></div>
<div class="outline-[10px]"></div>
Expand Down
1 change: 1 addition & 0 deletions tests/arbitrary-values.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ it('should convert _ to spaces', () => {
.shadow-\\[0px_0px_4px_black\\] {
--tw-shadow: 0px 0px 4px black;
--tw-shadow-colored: 0px 0px 4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand Down
24 changes: 21 additions & 3 deletions tests/basic-usage.test.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}

.ring,
Expand All @@ -54,6 +55,7 @@
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}

.blur-md,
Expand Down Expand Up @@ -807,20 +809,36 @@
mix-blend-mode: saturation;
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.shadow-md {
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06);
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.shadow-black {
--tw-shadow-color: #000;
--tw-shadow: var(--tw-shadow-colored);
}
.shadow-red-500\/25 {
--tw-shadow-color: rgb(239 68 68 / 0.25);
--tw-shadow: var(--tw-shadow-colored);
}
.shadow-blue-100\/10 {
--tw-shadow-color: rgb(219 234 254 / 0.1);
--tw-shadow: var(--tw-shadow-colored);
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
Expand Down
5 changes: 2 additions & 3 deletions tests/basic-usage.test.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
<div class="border-solid border-hidden"></div>
<div class="border"></div>
<div class="border-2 border-t border-b-4 border-x-4 border-y-4"></div>
<div class="shadow"></div>
<div class="shadow-md"></div>
<div class="shadow-lg"></div>
<div class="shadow shadow-md shadow-lg"></div>
<div class="shadow-black shadow-red-500/25 shadow-blue-100/10"></div>
<div class="decoration-clone decoration-slice"></div>
<div class="box-border"></div>
<div class="clear-left"></div>
Expand Down
20 changes: 16 additions & 4 deletions tests/experimental.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ test('experimental universal selector improvements (box-shadow)', () => {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand All @@ -51,14 +54,17 @@ test('experimental universal selector improvements (pseudo hover)', () => {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.hover\:shadow:hover {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand All @@ -84,14 +90,17 @@ test('experimental universal selector improvements (multiple classes: group)', (
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.group:hover .group-hover\:shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand Down Expand Up @@ -187,6 +196,7 @@ test('experimental universal selector improvements (#app important)', () => {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
#app .resize {
Expand All @@ -200,7 +210,9 @@ test('experimental universal selector improvements (#app important)', () => {
}
#app .shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
Expand Down
Loading

0 comments on commit d1f066d

Please sign in to comment.