Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for colored box shadows #5979

Merged
merged 6 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 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,42 @@ 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.x === undefined || shadow.y === undefined) {
continue
}
Copy link
Member

@RobinMalfait RobinMalfait Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we encode this information in each shadow itself? Like an isValid flag or something?
Not a requirement, but I noticed the same logic in multiple places.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree or even have the parse function return an error or something if it doesn’t parse a proper value. Moar tuples? Is this Elm?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, I think we can just encode it as a valid flag. When we re-print, then we can use the raw value in case the value is not valid.

E.g.:

// Input: 0 1px 2px -1px rgb(0 0 0 / 0.1)
// Parsed:
{
  raw: '0 1px 2px -1px rgb(0 0 0 / 0.1)',
  x: '0',
  y: '1px',
  blur: '2px',
  spread: '-1px',
  color: 'rgb(0 0 0 / 0.1)',
  valid: true
}
// Printed: 0 1px 2px -1px rgb(0 0 0 / 0.1)
// Input: var(--abc)
// Parsed:
{
  raw: 'var(--abc)',
  color: 'var(--abc)',
  valid: false
}
// Printed: var(--abc)

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 +1870,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.x === undefined || parsedShadow.y === undefined) {
return false
}
}

return true
}

export function color(value) {
let colors = 0

Expand Down
64 changes: 64 additions & 0 deletions src/util/parseBoxShadowValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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)
}
}
}

return result
})
}

export function formatBoxShadowValue(shadows) {
return shadows
.map((shadow) => {
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