Add drop-shadow-* color support (#17434)
This PR adds support for two things:
- New `drop-shadow-{color}` utilities which can be used to control the
filder drop shadow color
- New `drop-shadow-*/{alpha}` utilities which can be used to control the
intensity of the drop shadow
Note that drop-shadow-* utilities without a modifier use variables from
your theme.
If you opt into using drop shadow colors or drop-shadow "intensity"
utilities we will inline these values into your CSS when using a drop
shadow color or drop shadow intensity utility and as such can't be
changed by changing the theme variables on a per-element/tree basis.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
55d7a65cfc
commit
9374647b86
5 changed files with 351 additions and 13 deletions
|
|
@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Added new `bg-{position,size}-*` utilities for arbitrary values ([#17432](https://github.com/tailwindlabs/tailwindcss/pull/17432))
|
||||
- Added new `shadow-*/{alpha}`, `inset-shadow-*/{alpha}`, and `text-shadow-*/{alpha}` utilities to control shadow opacity ([#17398](https://github.com/tailwindlabs/tailwindcss/pull/17398))
|
||||
- Added new `object-{top,bottom}-{left,right}` utilities ([#17437](https://github.com/tailwindlabs/tailwindcss/pull/17437))
|
||||
- Added new `drop-shadow-{color}` utilities ([#17434](https://github.com/tailwindlabs/tailwindcss/pull/17434))
|
||||
- Added new `drop-shadow-*/{alpha}` utilities to control drop shadow opacity ([#17434](https://github.com/tailwindlabs/tailwindcss/pull/17434))
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -3984,8 +3984,74 @@ exports[`getClassList 1`] = `
|
|||
"divide-y-4",
|
||||
"divide-y-8",
|
||||
"divide-y-reverse",
|
||||
"drop-shadow-current",
|
||||
"drop-shadow-current/0",
|
||||
"drop-shadow-current/5",
|
||||
"drop-shadow-current/10",
|
||||
"drop-shadow-current/15",
|
||||
"drop-shadow-current/20",
|
||||
"drop-shadow-current/25",
|
||||
"drop-shadow-current/30",
|
||||
"drop-shadow-current/35",
|
||||
"drop-shadow-current/40",
|
||||
"drop-shadow-current/45",
|
||||
"drop-shadow-current/50",
|
||||
"drop-shadow-current/55",
|
||||
"drop-shadow-current/60",
|
||||
"drop-shadow-current/65",
|
||||
"drop-shadow-current/70",
|
||||
"drop-shadow-current/75",
|
||||
"drop-shadow-current/80",
|
||||
"drop-shadow-current/85",
|
||||
"drop-shadow-current/90",
|
||||
"drop-shadow-current/95",
|
||||
"drop-shadow-current/100",
|
||||
"drop-shadow-inherit",
|
||||
"drop-shadow-inherit/0",
|
||||
"drop-shadow-inherit/5",
|
||||
"drop-shadow-inherit/10",
|
||||
"drop-shadow-inherit/15",
|
||||
"drop-shadow-inherit/20",
|
||||
"drop-shadow-inherit/25",
|
||||
"drop-shadow-inherit/30",
|
||||
"drop-shadow-inherit/35",
|
||||
"drop-shadow-inherit/40",
|
||||
"drop-shadow-inherit/45",
|
||||
"drop-shadow-inherit/50",
|
||||
"drop-shadow-inherit/55",
|
||||
"drop-shadow-inherit/60",
|
||||
"drop-shadow-inherit/65",
|
||||
"drop-shadow-inherit/70",
|
||||
"drop-shadow-inherit/75",
|
||||
"drop-shadow-inherit/80",
|
||||
"drop-shadow-inherit/85",
|
||||
"drop-shadow-inherit/90",
|
||||
"drop-shadow-inherit/95",
|
||||
"drop-shadow-inherit/100",
|
||||
"drop-shadow-none",
|
||||
"drop-shadow-sm",
|
||||
"drop-shadow-transparent",
|
||||
"drop-shadow-transparent/0",
|
||||
"drop-shadow-transparent/5",
|
||||
"drop-shadow-transparent/10",
|
||||
"drop-shadow-transparent/15",
|
||||
"drop-shadow-transparent/20",
|
||||
"drop-shadow-transparent/25",
|
||||
"drop-shadow-transparent/30",
|
||||
"drop-shadow-transparent/35",
|
||||
"drop-shadow-transparent/40",
|
||||
"drop-shadow-transparent/45",
|
||||
"drop-shadow-transparent/50",
|
||||
"drop-shadow-transparent/55",
|
||||
"drop-shadow-transparent/60",
|
||||
"drop-shadow-transparent/65",
|
||||
"drop-shadow-transparent/70",
|
||||
"drop-shadow-transparent/75",
|
||||
"drop-shadow-transparent/80",
|
||||
"drop-shadow-transparent/85",
|
||||
"drop-shadow-transparent/90",
|
||||
"drop-shadow-transparent/95",
|
||||
"drop-shadow-transparent/100",
|
||||
"duration-75",
|
||||
"duration-100",
|
||||
"duration-150",
|
||||
|
|
|
|||
|
|
@ -20362,6 +20362,7 @@ test('filter', async () => {
|
|||
css`
|
||||
@theme {
|
||||
--blur-xl: 24px;
|
||||
--color-red-500: #ef4444;
|
||||
--drop-shadow: 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
|
@ -20389,8 +20390,11 @@ test('filter', async () => {
|
|||
'invert-0',
|
||||
'invert-[var(--value)]',
|
||||
'drop-shadow',
|
||||
'drop-shadow/25',
|
||||
'drop-shadow-xl',
|
||||
'drop-shadow-[0_0_red]',
|
||||
'drop-shadow-red-500',
|
||||
'drop-shadow-red-500/50',
|
||||
'saturate-0',
|
||||
'saturate-[1.75]',
|
||||
'saturate-[var(--value)]',
|
||||
|
|
@ -20414,12 +20418,16 @@ test('filter', async () => {
|
|||
--tw-saturate: initial;
|
||||
--tw-sepia: initial;
|
||||
--tw-drop-shadow: initial;
|
||||
--tw-drop-shadow-color: initial;
|
||||
--tw-drop-shadow-alpha: 100%;
|
||||
--tw-drop-shadow-size: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root, :host {
|
||||
--blur-xl: 24px;
|
||||
--color-red-500: #ef4444;
|
||||
--drop-shadow: 0 1px 1px #0000000d;
|
||||
--drop-shadow-xl: 0 9px 7px #0000001a;
|
||||
}
|
||||
|
|
@ -20459,21 +20467,53 @@ test('filter', async () => {
|
|||
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
|
||||
}
|
||||
|
||||
.drop-shadow\\/25 {
|
||||
--tw-drop-shadow-alpha: 25%;
|
||||
--tw-drop-shadow-size: drop-shadow(0 1px 1px var(--tw-drop-shadow-color, oklab(0% 0 0 / .25)));
|
||||
--tw-drop-shadow: drop-shadow(var(--drop-shadow));
|
||||
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
|
||||
}
|
||||
|
||||
.drop-shadow {
|
||||
--tw-drop-shadow-size: drop-shadow(0 1px 1px var(--tw-drop-shadow-color, #0000000d));
|
||||
--tw-drop-shadow: drop-shadow(var(--drop-shadow));
|
||||
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
|
||||
}
|
||||
|
||||
.drop-shadow-\\[0_0_red\\] {
|
||||
--tw-drop-shadow: drop-shadow(0 0 red);
|
||||
--tw-drop-shadow-size: drop-shadow(0 0 var(--tw-drop-shadow-color, red));
|
||||
--tw-drop-shadow: var(--tw-drop-shadow-size);
|
||||
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
|
||||
}
|
||||
|
||||
.drop-shadow-xl {
|
||||
--tw-drop-shadow-size: drop-shadow(0 9px 7px var(--tw-drop-shadow-color, #0000001a));
|
||||
--tw-drop-shadow: drop-shadow(var(--drop-shadow-xl));
|
||||
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
|
||||
}
|
||||
|
||||
.drop-shadow-red-500 {
|
||||
--tw-drop-shadow-color: color-mix(in srgb, #ef4444 var(--tw-drop-shadow-alpha), transparent);
|
||||
--tw-drop-shadow: var(--tw-drop-shadow-size);
|
||||
}
|
||||
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
.drop-shadow-red-500 {
|
||||
--tw-drop-shadow-color: color-mix(in oklab, var(--color-red-500) var(--tw-drop-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.drop-shadow-red-500\\/50 {
|
||||
--tw-drop-shadow-color: color-mix(in srgb, #ef444480 var(--tw-drop-shadow-alpha), transparent);
|
||||
--tw-drop-shadow: var(--tw-drop-shadow-size);
|
||||
}
|
||||
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
.drop-shadow-red-500\\/50 {
|
||||
--tw-drop-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 50%, transparent) var(--tw-drop-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.grayscale {
|
||||
--tw-grayscale: grayscale(100%);
|
||||
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
|
||||
|
|
@ -20619,6 +20659,22 @@ test('filter', async () => {
|
|||
@property --tw-drop-shadow {
|
||||
syntax: "*";
|
||||
inherits: false
|
||||
}
|
||||
|
||||
@property --tw-drop-shadow-color {
|
||||
syntax: "*";
|
||||
inherits: false
|
||||
}
|
||||
|
||||
@property --tw-drop-shadow-alpha {
|
||||
syntax: "<percentage>";
|
||||
inherits: false;
|
||||
initial-value: 100%;
|
||||
}
|
||||
|
||||
@property --tw-drop-shadow-size {
|
||||
syntax: "*";
|
||||
inherits: false
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
|
|
@ -20650,6 +20706,15 @@ test('filter', async () => {
|
|||
'invert-unknown',
|
||||
'-drop-shadow-xl',
|
||||
'-drop-shadow-[0_0_red]',
|
||||
|
||||
'drop-shadow/foo',
|
||||
'-drop-shadow/foo',
|
||||
'-drop-shadow/25',
|
||||
'-drop-shadow-red-500',
|
||||
'drop-shadow-red-500/foo',
|
||||
'-drop-shadow-red-500/foo',
|
||||
'-drop-shadow-red-500/50',
|
||||
|
||||
'-saturate-0',
|
||||
'saturate--5',
|
||||
'-saturate-[1.75]',
|
||||
|
|
|
|||
|
|
@ -3926,6 +3926,9 @@ export function createUtilities(theme: Theme) {
|
|||
property('--tw-saturate'),
|
||||
property('--tw-sepia'),
|
||||
property('--tw-drop-shadow'),
|
||||
property('--tw-drop-shadow-color'),
|
||||
property('--tw-drop-shadow-alpha', '100%', '<percentage>'),
|
||||
property('--tw-drop-shadow-size'),
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -4318,20 +4321,134 @@ export function createUtilities(theme: Theme) {
|
|||
['--tw-drop-shadow', ' '],
|
||||
['filter', cssFilterValue],
|
||||
])
|
||||
functionalUtility('drop-shadow', {
|
||||
themeKeys: ['--drop-shadow'],
|
||||
handle: (value) => [
|
||||
filterProperties(),
|
||||
decl(
|
||||
'--tw-drop-shadow',
|
||||
segment(value, ',')
|
||||
.map((v) => `drop-shadow(${v})`)
|
||||
.join(' '),
|
||||
),
|
||||
decl('filter', cssFilterValue),
|
||||
],
|
||||
|
||||
utilities.functional('drop-shadow', (candidate) => {
|
||||
let alpha: string | undefined
|
||||
|
||||
if (candidate.modifier) {
|
||||
if (candidate.modifier.kind === 'arbitrary') {
|
||||
alpha = candidate.modifier.value
|
||||
} else {
|
||||
if (isPositiveInteger(candidate.modifier.value)) {
|
||||
alpha = `${candidate.modifier.value}%`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidate.value) {
|
||||
let value = theme.get(['--drop-shadow'])
|
||||
if (value === null) return
|
||||
|
||||
return [
|
||||
filterProperties(),
|
||||
decl('--tw-drop-shadow-alpha', alpha),
|
||||
...alphaReplacedDropShadowProperties(
|
||||
'--tw-drop-shadow-size',
|
||||
value,
|
||||
alpha,
|
||||
(color) => `var(--tw-drop-shadow-color, ${color})`,
|
||||
),
|
||||
decl('--tw-drop-shadow', `drop-shadow(${theme.resolve(null, ['--drop-shadow'])})`),
|
||||
decl('filter', cssFilterValue),
|
||||
]
|
||||
}
|
||||
|
||||
if (candidate.value.kind === 'arbitrary') {
|
||||
let value: string | null = candidate.value.value
|
||||
let type = candidate.value.dataType ?? inferDataType(value, ['color'])
|
||||
|
||||
switch (type) {
|
||||
case 'color': {
|
||||
value = asColor(value, candidate.modifier, theme)
|
||||
if (value === null) return
|
||||
return [
|
||||
filterProperties(),
|
||||
decl('--tw-drop-shadow-color', withAlpha(value, 'var(--tw-drop-shadow-alpha)')),
|
||||
decl('--tw-drop-shadow', `var(--tw-drop-shadow-size)`),
|
||||
]
|
||||
}
|
||||
default: {
|
||||
if (candidate.modifier && !alpha) return
|
||||
|
||||
return [
|
||||
filterProperties(),
|
||||
decl('--tw-drop-shadow-alpha', alpha),
|
||||
...alphaReplacedDropShadowProperties(
|
||||
'--tw-drop-shadow-size',
|
||||
value,
|
||||
alpha,
|
||||
(color) => `var(--tw-drop-shadow-color, ${color})`,
|
||||
),
|
||||
decl('--tw-drop-shadow', `var(--tw-drop-shadow-size)`),
|
||||
decl('filter', cssFilterValue),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shadow size
|
||||
{
|
||||
let value = theme.get([`--drop-shadow-${candidate.value.value}`])
|
||||
if (value) {
|
||||
if (candidate.modifier && !alpha) return
|
||||
|
||||
if (alpha) {
|
||||
return [
|
||||
filterProperties(),
|
||||
decl('--tw-drop-shadow-alpha', alpha),
|
||||
...alphaReplacedDropShadowProperties(
|
||||
'--tw-drop-shadow-size',
|
||||
value,
|
||||
alpha,
|
||||
(color) => `var(--tw-drop-shadow-color, ${color})`,
|
||||
),
|
||||
decl('--tw-drop-shadow', `var(--tw-drop-shadow-size)`),
|
||||
decl('filter', cssFilterValue),
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
filterProperties(),
|
||||
decl('--tw-drop-shadow-alpha', alpha),
|
||||
...alphaReplacedDropShadowProperties(
|
||||
'--tw-drop-shadow-size',
|
||||
value,
|
||||
alpha,
|
||||
(color) => `var(--tw-drop-shadow-color, ${color})`,
|
||||
),
|
||||
decl(
|
||||
'--tw-drop-shadow',
|
||||
`drop-shadow(${theme.resolve(candidate.value.value, ['--drop-shadow'])})`,
|
||||
),
|
||||
decl('filter', cssFilterValue),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Shadow color
|
||||
{
|
||||
let value = resolveThemeColor(candidate, theme, ['--drop-shadow-color', '--color'])
|
||||
if (value) {
|
||||
return [
|
||||
filterProperties(),
|
||||
decl('--tw-drop-shadow-color', withAlpha(value, 'var(--tw-drop-shadow-alpha)')),
|
||||
decl('--tw-drop-shadow', `var(--tw-drop-shadow-size)`),
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suggest('drop-shadow', () => [
|
||||
{
|
||||
values: ['current', 'inherit', 'transparent'],
|
||||
valueThemeKeys: ['--drop-shadow-color', '--color'],
|
||||
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
|
||||
},
|
||||
{
|
||||
valueThemeKeys: ['--drop-shadow'],
|
||||
},
|
||||
])
|
||||
|
||||
functionalUtility('backdrop-opacity', {
|
||||
themeKeys: ['--backdrop-opacity', '--opacity'],
|
||||
handleBareValue: ({ value }) => {
|
||||
|
|
@ -6116,3 +6233,54 @@ function alphaReplacedShadowProperties(
|
|||
return [decl(property, prefix + replacedValue)]
|
||||
}
|
||||
}
|
||||
|
||||
function alphaReplacedDropShadowProperties(
|
||||
property: string,
|
||||
value: string,
|
||||
alpha: string | null | undefined,
|
||||
varInjector: (color: string) => string,
|
||||
prefix: string = '',
|
||||
): AstNode[] {
|
||||
let requiresFallback = false
|
||||
|
||||
let replacedValue = segment(value, ',')
|
||||
.map((value) =>
|
||||
replaceShadowColors(value, (color) => {
|
||||
if (alpha == null) {
|
||||
return varInjector(color)
|
||||
}
|
||||
|
||||
// When the input is currentcolor, we use our existing `color-mix(…)` approach to increase
|
||||
// browser support. Note that the fallback of this is handled more generically in
|
||||
// post-processing.
|
||||
if (color.startsWith('current')) {
|
||||
return varInjector(withAlpha(color, alpha))
|
||||
}
|
||||
|
||||
// If any dynamic values are needed for the relative color syntax, we need to insert a
|
||||
// replacement as lightningcss won't be able to resolve them statically.
|
||||
if (color.startsWith('var(') || alpha.startsWith('var(')) {
|
||||
requiresFallback = true
|
||||
}
|
||||
|
||||
return varInjector(replaceAlpha(color, alpha))
|
||||
}),
|
||||
)
|
||||
.map((value) => `drop-shadow(${value})`)
|
||||
.join(' ')
|
||||
|
||||
if (requiresFallback) {
|
||||
return [
|
||||
decl(
|
||||
property,
|
||||
prefix +
|
||||
segment(value, ',')
|
||||
.map((value) => `drop-shadow(${replaceShadowColors(value, varInjector)})`)
|
||||
.join(' '),
|
||||
),
|
||||
rule('@supports (color: lab(from red l a b))', [decl(property, prefix + replacedValue)]),
|
||||
]
|
||||
} else {
|
||||
return [decl(property, prefix + replacedValue)]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1826,6 +1826,43 @@ test('filter', async ({ page }) => {
|
|||
expect(await getPropertyValue('#b', 'filter')).toEqual('contrast(1)')
|
||||
})
|
||||
|
||||
test('drop shadow colors', async ({ page }) => {
|
||||
let { getPropertyList } = await render(
|
||||
page,
|
||||
html`
|
||||
<div id="a" class="drop-shadow-md"></div>
|
||||
<div id="b" class="drop-shadow-md drop-shadow-red"></div>
|
||||
<div id="c" class="drop-shadow-md/50"></div>
|
||||
<div id="d" class="drop-shadow-md/50 drop-shadow-red"></div>
|
||||
<div id="e" class="drop-shadow-md/50 drop-shadow-red/50"></div>
|
||||
`,
|
||||
)
|
||||
|
||||
expect(await getPropertyList('#a', 'filter')).toEqual([
|
||||
'drop-shadow(rgba(0, 0, 0, 0.12) 0px 3px 3px)',
|
||||
])
|
||||
|
||||
expect(await getPropertyList('#b', 'filter')).toEqual([
|
||||
expect.stringMatching(/drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+\) 0px 3px 3px\)/),
|
||||
])
|
||||
|
||||
expect(await getPropertyList('#c', 'filter')).toEqual([
|
||||
'drop-shadow(oklab(0 0 0 / 0.5) 0px 3px 3px)',
|
||||
])
|
||||
|
||||
expect(await getPropertyList('#d', 'filter')).toEqual([
|
||||
expect.stringMatching(
|
||||
/drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+ \/ 0\.5\) 0px 3px 3px\)/,
|
||||
),
|
||||
])
|
||||
|
||||
expect(await getPropertyList('#e', 'filter')).toEqual([
|
||||
expect.stringMatching(
|
||||
/drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+ \/ 0\.25\) 0px 3px 3px\)/,
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
test('outline style is optional', async ({ page }) => {
|
||||
let { getPropertyValue } = await render(
|
||||
page,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue