Skip to content

Commit d1f066d

Browse files
Add support for colored box shadows (#5979)
* 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 <[email protected]>
1 parent c25fbc7 commit d1f066d

17 files changed

+426
-136
lines changed

src/corePlugins.js

+29-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import isPlainObject from './util/isPlainObject'
1111
import transformThemeValue from './util/transformThemeValue'
1212
import { version as tailwindVersion } from '../package.json'
1313
import log from './util/log'
14+
import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
1415

1516
export let variantPlugins = {
1617
pseudoElementVariants: ({ addVariant }) => {
@@ -1774,6 +1775,7 @@ export let corePlugins = {
17741775
'--tw-ring-offset-shadow': '0 0 #0000',
17751776
'--tw-ring-shadow': '0 0 #0000',
17761777
'--tw-shadow': '0 0 #0000',
1778+
'--tw-shadow-colored': '0 0 #0000',
17771779
},
17781780
})
17791781

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

1787+
let ast = parseBoxShadowValue(value)
1788+
for (let shadow of ast) {
1789+
// Don't override color if the whole shadow is a variable
1790+
if (!shadow.valid) {
1791+
continue
1792+
}
1793+
1794+
shadow.color = 'var(--tw-shadow-color)'
1795+
}
1796+
17851797
return {
17861798
'@defaults box-shadow': {},
17871799
'--tw-shadow': value === 'none' ? '0 0 #0000' : value,
1800+
'--tw-shadow-colored': value === 'none' ? '0 0 #0000' : formatBoxShadowValue(ast),
17881801
'box-shadow': defaultBoxShadow,
17891802
}
17901803
},
17911804
},
1792-
{ values: theme('boxShadow') }
1805+
{ values: theme('boxShadow'), type: ['shadow'] }
17931806
)
17941807
}
17951808
})(),
17961809

1810+
boxShadowColor: ({ matchUtilities, theme }) => {
1811+
matchUtilities(
1812+
{
1813+
shadow: (value) => {
1814+
return {
1815+
'--tw-shadow-color': toColorValue(value),
1816+
'--tw-shadow': 'var(--tw-shadow-colored)',
1817+
}
1818+
},
1819+
},
1820+
{ values: flattenColorPalette(theme('boxShadowColor')), type: ['color'] }
1821+
)
1822+
},
1823+
17971824
outlineStyle: ({ addUtilities }) => {
17981825
addUtilities({
17991826
'.outline-none': {
@@ -1844,6 +1871,7 @@ export let corePlugins = {
18441871
'--tw-ring-offset-shadow': '0 0 #0000',
18451872
'--tw-ring-shadow': '0 0 #0000',
18461873
'--tw-shadow': '0 0 #0000',
1874+
'--tw-shadow-colored': '0 0 #0000',
18471875
},
18481876
})
18491877

src/util/dataTypes.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { parseColor } from './color'
2+
import { parseBoxShadowValue } from './parseBoxShadowValue'
23

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

@@ -88,6 +89,18 @@ export function lineWidth(value) {
8889
return lineWidths.has(value)
8990
}
9091

92+
export function shadow(value) {
93+
let parsedShadows = parseBoxShadowValue(normalize(value))
94+
95+
for (let parsedShadow of parsedShadows) {
96+
if (!parsedShadow.valid) {
97+
return false
98+
}
99+
}
100+
101+
return true
102+
}
103+
91104
export function color(value) {
92105
let colors = 0
93106

src/util/parseBoxShadowValue.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
2+
let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
3+
let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
4+
let LENGTH = /^-?(\d+)(.*?)$/g
5+
6+
export function parseBoxShadowValue(input) {
7+
let shadows = input.split(COMMA)
8+
return shadows.map((shadow) => {
9+
let value = shadow.trim()
10+
let result = { raw: value }
11+
let parts = value.split(SPACE)
12+
let seen = new Set()
13+
14+
for (let part of parts) {
15+
// Reset index, since the regex is stateful.
16+
LENGTH.lastIndex = 0
17+
18+
// Keyword
19+
if (!seen.has('KEYWORD') && KEYWORDS.has(part)) {
20+
result.keyword = part
21+
seen.add('KEYWORD')
22+
}
23+
24+
// Length value
25+
else if (LENGTH.test(part)) {
26+
if (!seen.has('X')) {
27+
result.x = part
28+
seen.add('X')
29+
} else if (!seen.has('Y')) {
30+
result.y = part
31+
seen.add('Y')
32+
} else if (!seen.has('BLUR')) {
33+
result.blur = part
34+
seen.add('BLUR')
35+
} else if (!seen.has('SPREAD')) {
36+
result.spread = part
37+
seen.add('SPREAD')
38+
}
39+
}
40+
41+
// Color or unknown
42+
else {
43+
if (!result.color) {
44+
result.color = part
45+
} else {
46+
if (!result.unknown) result.unknown = []
47+
result.unknown.push(part)
48+
}
49+
}
50+
}
51+
52+
// Check if valid
53+
result.valid = result.x !== undefined && result.y !== undefined
54+
55+
return result
56+
})
57+
}
58+
59+
export function formatBoxShadowValue(shadows) {
60+
return shadows
61+
.map((shadow) => {
62+
if (!shadow.valid) {
63+
return shadow.raw
64+
}
65+
66+
return [shadow.keyword, shadow.x, shadow.y, shadow.blur, shadow.spread, shadow.color]
67+
.filter(Boolean)
68+
.join(' ')
69+
})
70+
.join(', ')
71+
}

src/util/pluginUtils.js

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
relativeSize,
1616
position,
1717
lineWidth,
18+
shadow,
1819
} from './dataTypes'
1920
import negateValue from './negateValue'
2021

@@ -148,6 +149,7 @@ let typeMap = {
148149
'line-width': guess(lineWidth),
149150
'absolute-size': guess(absoluteSize),
150151
'relative-size': guess(relativeSize),
152+
shadow: guess(shadow),
151153
}
152154

153155
let supportedTypes = Object.keys(typeMap)

stubs/defaultConfig.stub.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,15 @@ module.exports = {
203203
},
204204
boxShadow: {
205205
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
206-
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06)',
207-
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06)',
208-
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05)',
209-
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 10px 10px -5px rgb(0 0 0 / 0.04)',
206+
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
207+
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
208+
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
209+
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
210210
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
211-
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.06)',
211+
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
212212
none: 'none',
213213
},
214+
boxShadowColor: ({ theme }) => theme('colors'),
214215
caretColor: ({ theme }) => theme('colors'),
215216
accentColor: ({ theme }) => ({
216217
...theme('colors'),

tests/apply.test.css

+6-2
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,16 @@
127127
--tw-ordinal: ordinal;
128128
--tw-numeric-spacing: tabular-nums;
129129
font-variant-numeric: var(--tw-font-variant-numeric);
130-
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
130+
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
131+
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
132+
0 4px 6px -4px var(--tw-shadow-color);
131133
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
132134
var(--tw-shadow);
133135
}
134136
.complex-utilities:hover {
135-
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 10px 10px -5px rgb(0 0 0 / 0.04);
137+
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
138+
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color),
139+
0 8px 10px -6px var(--tw-shadow-color);
136140
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
137141
var(--tw-shadow);
138142
}

tests/arbitrary-values.test.css

+3-1
Original file line numberDiff line numberDiff line change
@@ -851,11 +851,13 @@
851851
}
852852
.shadow-\[0px_1px_2px_black\] {
853853
--tw-shadow: 0px 1px 2px black;
854+
--tw-shadow-colored: 0px 1px 2px var(--tw-shadow-color);
854855
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
855856
var(--tw-shadow);
856857
}
857-
.shadow-\[var\(--value\)\] {
858+
.shadow-\[shadow\:var\(--value\)\] {
858859
--tw-shadow: var(--value);
860+
--tw-shadow-colored: var(--value);
859861
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
860862
var(--tw-shadow);
861863
}

tests/arbitrary-values.test.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@
319319
<div class="opacity-[var(--opacity)]"></div>
320320

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

324324
<div class="outline-[black]"></div>
325325
<div class="outline-[10px]"></div>

tests/arbitrary-values.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ it('should convert _ to spaces', () => {
166166
167167
.shadow-\\[0px_0px_4px_black\\] {
168168
--tw-shadow: 0px 0px 4px black;
169+
--tw-shadow-colored: 0px 0px 4px var(--tw-shadow-color);
169170
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
170171
var(--tw-shadow);
171172
}

tests/basic-usage.test.css

+21-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
--tw-ring-offset-shadow: 0 0 #0000;
4444
--tw-ring-shadow: 0 0 #0000;
4545
--tw-shadow: 0 0 #0000;
46+
--tw-shadow-colored: 0 0 #0000;
4647
}
4748

4849
.ring,
@@ -54,6 +55,7 @@
5455
--tw-ring-offset-shadow: 0 0 #0000;
5556
--tw-ring-shadow: 0 0 #0000;
5657
--tw-shadow: 0 0 #0000;
58+
--tw-shadow-colored: 0 0 #0000;
5759
}
5860

5961
.blur-md,
@@ -807,20 +809,36 @@
807809
mix-blend-mode: saturation;
808810
}
809811
.shadow {
810-
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
812+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
813+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
811814
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
812815
var(--tw-shadow);
813816
}
814817
.shadow-md {
815-
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06);
818+
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
819+
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
816820
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
817821
var(--tw-shadow);
818822
}
819823
.shadow-lg {
820-
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
824+
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
825+
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
826+
0 4px 6px -4px var(--tw-shadow-color);
821827
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
822828
var(--tw-shadow);
823829
}
830+
.shadow-black {
831+
--tw-shadow-color: #000;
832+
--tw-shadow: var(--tw-shadow-colored);
833+
}
834+
.shadow-red-500\/25 {
835+
--tw-shadow-color: rgb(239 68 68 / 0.25);
836+
--tw-shadow: var(--tw-shadow-colored);
837+
}
838+
.shadow-blue-100\/10 {
839+
--tw-shadow-color: rgb(219 234 254 / 0.1);
840+
--tw-shadow: var(--tw-shadow-colored);
841+
}
824842
.outline-none {
825843
outline: 2px solid transparent;
826844
outline-offset: 2px;

tests/basic-usage.test.html

+2-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@
3131
<div class="border-solid border-hidden"></div>
3232
<div class="border"></div>
3333
<div class="border-2 border-t border-b-4 border-x-4 border-y-4"></div>
34-
<div class="shadow"></div>
35-
<div class="shadow-md"></div>
36-
<div class="shadow-lg"></div>
34+
<div class="shadow shadow-md shadow-lg"></div>
35+
<div class="shadow-black shadow-red-500/25 shadow-blue-100/10"></div>
3736
<div class="decoration-clone decoration-slice"></div>
3837
<div class="box-border"></div>
3938
<div class="clear-left"></div>

tests/experimental.test.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ test('experimental universal selector improvements (box-shadow)', () => {
1818
--tw-ring-offset-shadow: 0 0 #0000;
1919
--tw-ring-shadow: 0 0 #0000;
2020
--tw-shadow: 0 0 #0000;
21+
--tw-shadow-colored: 0 0 #0000;
2122
}
2223
2324
.resize {
2425
resize: both;
2526
}
2627
2728
.shadow {
28-
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
29+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
30+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
31+
0 1px 2px -1px var(--tw-shadow-color);
2932
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
3033
var(--tw-shadow);
3134
}
@@ -51,14 +54,17 @@ test('experimental universal selector improvements (pseudo hover)', () => {
5154
--tw-ring-offset-shadow: 0 0 #0000;
5255
--tw-ring-shadow: 0 0 #0000;
5356
--tw-shadow: 0 0 #0000;
57+
--tw-shadow-colored: 0 0 #0000;
5458
}
5559
5660
.resize {
5761
resize: both;
5862
}
5963
6064
.hover\:shadow:hover {
61-
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
65+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
66+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
67+
0 1px 2px -1px var(--tw-shadow-color);
6268
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
6369
var(--tw-shadow);
6470
}
@@ -84,14 +90,17 @@ test('experimental universal selector improvements (multiple classes: group)', (
8490
--tw-ring-offset-shadow: 0 0 #0000;
8591
--tw-ring-shadow: 0 0 #0000;
8692
--tw-shadow: 0 0 #0000;
93+
--tw-shadow-colored: 0 0 #0000;
8794
}
8895
8996
.resize {
9097
resize: both;
9198
}
9299
93100
.group:hover .group-hover\:shadow {
94-
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
101+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
102+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
103+
0 1px 2px -1px var(--tw-shadow-color);
95104
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
96105
var(--tw-shadow);
97106
}
@@ -187,6 +196,7 @@ test('experimental universal selector improvements (#app important)', () => {
187196
--tw-ring-offset-shadow: 0 0 #0000;
188197
--tw-ring-shadow: 0 0 #0000;
189198
--tw-shadow: 0 0 #0000;
199+
--tw-shadow-colored: 0 0 #0000;
190200
}
191201
192202
#app .resize {
@@ -200,7 +210,9 @@ test('experimental universal selector improvements (#app important)', () => {
200210
}
201211
202212
#app .shadow {
203-
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06);
213+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
214+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
215+
0 1px 2px -1px var(--tw-shadow-color);
204216
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
205217
var(--tw-shadow);
206218
}

0 commit comments

Comments
 (0)