Skip to content

Commit 5115771

Browse files
committed
Add gradient color stop position utilities (#10886)
* add gradient color stop positions * update tests to include gradient position color stop reset values * add dedicated color stop position tests * use `%` sign in the name of the uility * update changelog * ensure `length` values and css variables work
1 parent 0bfee99 commit 5115771

14 files changed

+359
-95
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Add `content-normal` and `content-stretch` utilities ([#10645](https://github.com/tailwindlabs/tailwindcss/pull/10645))
2424
- Add `whitespace-break-spaces` utility ([#10729](https://github.com/tailwindlabs/tailwindcss/pull/10729))
2525
- Add support for configuring default `font-variation-settings` for a `font-family` ([#10034](https://github.com/tailwindlabs/tailwindcss/pull/10034), [#10515](https://github.com/tailwindlabs/tailwindcss/pull/10515))
26+
- Add gradient color stop position utilities ([#10886](https://github.com/tailwindlabs/tailwindcss/pull/10886))
2627

2728
### Fixed
2829

oxide/crates/core/src/parser.rs

+17-8
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ impl<'a> Extractor<'a> {
4343

4444
#[cfg(test)]
4545
pub fn unique_ord(input: &'a [u8], opts: ExtractorOptions) -> Vec<&'a [u8]> {
46-
// This is an inefficient way to get an ordered, unique
47-
// list as a Vec but it is only meant for testing.
48-
let mut candidates = Self::all(input, opts);
49-
let mut unique_list = FxHashSet::default();
50-
unique_list.reserve(candidates.len());
51-
candidates.retain(|c| unique_list.insert(*c));
46+
// This is an inefficient way to get an ordered, unique
47+
// list as a Vec but it is only meant for testing.
48+
let mut candidates = Self::all(input, opts);
49+
let mut unique_list = FxHashSet::default();
50+
unique_list.reserve(candidates.len());
51+
candidates.retain(|c| unique_list.insert(*c));
5252

53-
candidates
53+
candidates
5454
}
5555
}
5656

@@ -319,7 +319,16 @@ impl<'a> Extractor<'a> {
319319

320320
// Allowed characters in the candidate itself
321321
// None of these can come after a closing bracket `]`
322-
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'(' | b')' | b'!' | b'@'
322+
b'a'..=b'z'
323+
| b'A'..=b'Z'
324+
| b'0'..=b'9'
325+
| b'-'
326+
| b'_'
327+
| b'('
328+
| b')'
329+
| b'!'
330+
| b'@'
331+
| b'%'
323332
if prev != b']' =>
324333
{
325334
/* TODO: The `b'@'` is necessary for custom separators like _@, maybe we can handle this in a better way... */

src/corePlugins.js

+57-5
Original file line numberDiff line numberDiff line change
@@ -1756,40 +1756,92 @@ export let corePlugins = {
17561756
type: ['color', 'any'],
17571757
}
17581758

1759+
let positionOptions = {
1760+
values: theme('gradientColorStopPositions'),
1761+
type: ['length', 'percentage'],
1762+
}
1763+
17591764
matchUtilities(
17601765
{
17611766
from: (value) => {
17621767
let transparentToValue = transparentTo(value)
17631768

17641769
return {
1765-
'--tw-gradient-from': toColorValue(value, 'from'),
1766-
'--tw-gradient-to': transparentToValue,
1770+
'--tw-gradient-from': `${toColorValue(
1771+
value,
1772+
'from'
1773+
)} var(--tw-gradient-from-position)`,
1774+
'--tw-gradient-from-position': ' ',
1775+
'--tw-gradient-to': `${transparentToValue} var(--tw-gradient-from-position)`,
1776+
'--tw-gradient-to-position': ' ',
17671777
'--tw-gradient-stops': `var(--tw-gradient-from), var(--tw-gradient-to)`,
17681778
}
17691779
},
17701780
},
17711781
options
17721782
)
1783+
1784+
matchUtilities(
1785+
{
1786+
from: (value) => {
1787+
return {
1788+
'--tw-gradient-from-position': value,
1789+
}
1790+
},
1791+
},
1792+
positionOptions
1793+
)
1794+
17731795
matchUtilities(
17741796
{
17751797
via: (value) => {
17761798
let transparentToValue = transparentTo(value)
17771799

17781800
return {
1779-
'--tw-gradient-to': transparentToValue,
1801+
'--tw-gradient-via-position': ' ',
1802+
'--tw-gradient-to': `${transparentToValue} var(--tw-gradient-to-position)`,
1803+
'--tw-gradient-to-position': ' ',
17801804
'--tw-gradient-stops': `var(--tw-gradient-from), ${toColorValue(
17811805
value,
17821806
'via'
1783-
)}, var(--tw-gradient-to)`,
1807+
)} var(--tw-gradient-via-position), var(--tw-gradient-to)`,
17841808
}
17851809
},
17861810
},
17871811
options
17881812
)
1813+
17891814
matchUtilities(
1790-
{ to: (value) => ({ '--tw-gradient-to': toColorValue(value, 'to') }) },
1815+
{
1816+
via: (value) => {
1817+
return {
1818+
'--tw-gradient-via-position': value,
1819+
}
1820+
},
1821+
},
1822+
positionOptions
1823+
)
1824+
1825+
matchUtilities(
1826+
{
1827+
to: (value) => ({
1828+
'--tw-gradient-to': `${toColorValue(value, 'to')} var(--tw-gradient-to-position)`,
1829+
'--tw-gradient-to-position': ' ',
1830+
}),
1831+
},
17911832
options
17921833
)
1834+
1835+
matchUtilities(
1836+
{
1837+
to: (value) => {
1838+
return {
1839+
'--tw-gradient-to-position': value,
1840+
}
1841+
},
1842+
},
1843+
positionOptions
1844+
)
17931845
}
17941846
})(),
17951847

stubs/config.full.js

+23
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,29 @@ module.exports = {
355355
},
356356
gap: ({ theme }) => theme('spacing'),
357357
gradientColorStops: ({ theme }) => theme('colors'),
358+
gradientColorStopPositions: {
359+
'0%': '0%',
360+
'5%': '5%',
361+
'10%': '10%',
362+
'15%': '15%',
363+
'20%': '20%',
364+
'25%': '25%',
365+
'30%': '30%',
366+
'35%': '35%',
367+
'40%': '40%',
368+
'45%': '45%',
369+
'50%': '50%',
370+
'55%': '55%',
371+
'60%': '60%',
372+
'65%': '65%',
373+
'70%': '70%',
374+
'75%': '75%',
375+
'80%': '80%',
376+
'85%': '85%',
377+
'90%': '90%',
378+
'95%': '95%',
379+
'100%': '100%',
380+
},
358381
grayscale: {
359382
0: '0',
360383
DEFAULT: '100%',

tests/any-type.test.js

+26-16
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ crosscheck(({ stable, oxide }) => {
185185
`
186186

187187
return run(input, config).then((result) => {
188-
let oxideExpected = css`
188+
oxide.expect(result.css).toMatchFormattedCss(css`
189189
.inset-\[var\(--any-value\)\] {
190190
inset: var(--any-value);
191191
}
@@ -499,16 +499,22 @@ crosscheck(({ stable, oxide }) => {
499499
background-color: var(--any-value);
500500
}
501501
.from-\[var\(--any-value\)\] {
502-
--tw-gradient-from: var(--any-value);
503-
--tw-gradient-to: #fff0;
502+
--tw-gradient-from: var(--any-value) var(--tw-gradient-from-position);
503+
--tw-gradient-from-position: ;
504+
--tw-gradient-to: #fff0 var(--tw-gradient-from-position);
505+
--tw-gradient-to-position: ;
504506
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
505507
}
506508
.via-\[var\(--any-value\)\] {
507-
--tw-gradient-to: #fff0;
508-
--tw-gradient-stops: var(--tw-gradient-from), var(--any-value), var(--tw-gradient-to);
509+
--tw-gradient-via-position: ;
510+
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
511+
--tw-gradient-to-position: ;
512+
--tw-gradient-stops: var(--tw-gradient-from),
513+
var(--any-value) var(--tw-gradient-via-position), var(--tw-gradient-to);
509514
}
510515
.to-\[var\(--any-value\)\] {
511-
--tw-gradient-to: var(--any-value);
516+
--tw-gradient-to: var(--any-value) var(--tw-gradient-to-position);
517+
--tw-gradient-to-position: ;
512518
}
513519
.fill-\[var\(--any-value\)\] {
514520
fill: var(--any-value);
@@ -732,8 +738,8 @@ crosscheck(({ stable, oxide }) => {
732738
--tw-content: var(--any-value);
733739
content: var(--tw-content);
734740
}
735-
`
736-
let stableExpected = css`
741+
`)
742+
stable.expect(result.css).toMatchFormattedCss(css`
737743
.inset-\[var\(--any-value\)\] {
738744
inset: var(--any-value);
739745
}
@@ -1056,16 +1062,22 @@ crosscheck(({ stable, oxide }) => {
10561062
--tw-bg-opacity: var(--any-value);
10571063
}
10581064
.from-\[var\(--any-value\)\] {
1059-
--tw-gradient-from: var(--any-value);
1060-
--tw-gradient-to: #fff0;
1065+
--tw-gradient-from: var(--any-value) var(--tw-gradient-from-position);
1066+
--tw-gradient-from-position: ;
1067+
--tw-gradient-to: #fff0 var(--tw-gradient-from-position);
1068+
--tw-gradient-to-position: ;
10611069
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
10621070
}
10631071
.via-\[var\(--any-value\)\] {
1064-
--tw-gradient-to: #fff0;
1065-
--tw-gradient-stops: var(--tw-gradient-from), var(--any-value), var(--tw-gradient-to);
1072+
--tw-gradient-via-position: ;
1073+
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
1074+
--tw-gradient-to-position: ;
1075+
--tw-gradient-stops: var(--tw-gradient-from),
1076+
var(--any-value) var(--tw-gradient-via-position), var(--tw-gradient-to);
10661077
}
10671078
.to-\[var\(--any-value\)\] {
1068-
--tw-gradient-to: var(--any-value);
1079+
--tw-gradient-to: var(--any-value) var(--tw-gradient-to-position);
1080+
--tw-gradient-to-position: ;
10691081
}
10701082
.fill-\[var\(--any-value\)\] {
10711083
fill: var(--any-value);
@@ -1298,9 +1310,7 @@ crosscheck(({ stable, oxide }) => {
12981310
--tw-content: var(--any-value);
12991311
content: var(--tw-content);
13001312
}
1301-
`
1302-
oxide.expect(result.css).toMatchFormattedCss(oxideExpected)
1303-
stable.expect(result.css).toMatchFormattedCss(stableExpected)
1313+
`)
13041314
})
13051315
})
13061316
test.todo('rewrite the any test to be easier to understand or break it up into multiple tests')

tests/arbitrary-values.oxide.test.css

+22-10
Original file line numberDiff line numberDiff line change
@@ -659,28 +659,40 @@
659659
background-image: var(--url);
660660
}
661661
.from-\[\#da5b66\] {
662-
--tw-gradient-from: #da5b66;
663-
--tw-gradient-to: #da5b6600;
662+
--tw-gradient-from: #da5b66 var(--tw-gradient-from-position);
663+
--tw-gradient-from-position: ;
664+
--tw-gradient-to: #da5b6600 var(--tw-gradient-from-position);
665+
--tw-gradient-to-position: ;
664666
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
665667
}
666668
.from-\[var\(--color\)\] {
667-
--tw-gradient-from: var(--color);
668-
--tw-gradient-to: #fff0;
669+
--tw-gradient-from: var(--color) var(--tw-gradient-from-position);
670+
--tw-gradient-from-position: ;
671+
--tw-gradient-to: #fff0 var(--tw-gradient-from-position);
672+
--tw-gradient-to-position: ;
669673
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
670674
}
671675
.via-\[\#da5b66\] {
672-
--tw-gradient-to: #da5b6600;
673-
--tw-gradient-stops: var(--tw-gradient-from), #da5b66, var(--tw-gradient-to);
676+
--tw-gradient-via-position: ;
677+
--tw-gradient-to: #da5b6600 var(--tw-gradient-to-position);
678+
--tw-gradient-to-position: ;
679+
--tw-gradient-stops: var(--tw-gradient-from), #da5b66 var(--tw-gradient-via-position),
680+
var(--tw-gradient-to);
674681
}
675682
.via-\[var\(--color\)\] {
676-
--tw-gradient-to: #fff0;
677-
--tw-gradient-stops: var(--tw-gradient-from), var(--color), var(--tw-gradient-to);
683+
--tw-gradient-via-position: ;
684+
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
685+
--tw-gradient-to-position: ;
686+
--tw-gradient-stops: var(--tw-gradient-from), var(--color) var(--tw-gradient-via-position),
687+
var(--tw-gradient-to);
678688
}
679689
.to-\[\#da5b66\] {
680-
--tw-gradient-to: #da5b66;
690+
--tw-gradient-to: #da5b66 var(--tw-gradient-to-position);
691+
--tw-gradient-to-position: ;
681692
}
682693
.to-\[var\(--color\)\] {
683-
--tw-gradient-to: var(--color);
694+
--tw-gradient-to: var(--color) var(--tw-gradient-to-position);
695+
--tw-gradient-to-position: ;
684696
}
685697
.bg-\[length\:200px_100px\] {
686698
background-size: 200px 100px;

tests/arbitrary-values.test.css

+22-10
Original file line numberDiff line numberDiff line change
@@ -688,28 +688,40 @@
688688
background-image: var(--url);
689689
}
690690
.from-\[\#da5b66\] {
691-
--tw-gradient-from: #da5b66;
692-
--tw-gradient-to: #da5b6600;
691+
--tw-gradient-from: #da5b66 var(--tw-gradient-from-position);
692+
--tw-gradient-from-position: ;
693+
--tw-gradient-to: #da5b6600 var(--tw-gradient-from-position);
694+
--tw-gradient-to-position: ;
693695
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
694696
}
695697
.from-\[var\(--color\)\] {
696-
--tw-gradient-from: var(--color);
697-
--tw-gradient-to: #fff0;
698+
--tw-gradient-from: var(--color) var(--tw-gradient-from-position);
699+
--tw-gradient-from-position: ;
700+
--tw-gradient-to: #fff0 var(--tw-gradient-from-position);
701+
--tw-gradient-to-position: ;
698702
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
699703
}
700704
.via-\[\#da5b66\] {
701-
--tw-gradient-to: #da5b6600;
702-
--tw-gradient-stops: var(--tw-gradient-from), #da5b66, var(--tw-gradient-to);
705+
--tw-gradient-via-position: ;
706+
--tw-gradient-to: #da5b6600 var(--tw-gradient-to-position);
707+
--tw-gradient-to-position: ;
708+
--tw-gradient-stops: var(--tw-gradient-from), #da5b66 var(--tw-gradient-via-position),
709+
var(--tw-gradient-to);
703710
}
704711
.via-\[var\(--color\)\] {
705-
--tw-gradient-to: #fff0;
706-
--tw-gradient-stops: var(--tw-gradient-from), var(--color), var(--tw-gradient-to);
712+
--tw-gradient-via-position: ;
713+
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
714+
--tw-gradient-to-position: ;
715+
--tw-gradient-stops: var(--tw-gradient-from), var(--color) var(--tw-gradient-via-position),
716+
var(--tw-gradient-to);
707717
}
708718
.to-\[\#da5b66\] {
709-
--tw-gradient-to: #da5b66;
719+
--tw-gradient-to: #da5b66 var(--tw-gradient-to-position);
720+
--tw-gradient-to-position: ;
710721
}
711722
.to-\[var\(--color\)\] {
712-
--tw-gradient-to: var(--color);
723+
--tw-gradient-to: var(--color) var(--tw-gradient-to-position);
724+
--tw-gradient-to-position: ;
713725
}
714726
.bg-\[length\:200px_100px\] {
715727
background-size: 200px 100px;

tests/basic-usage.oxide.test.css

+11-5
Original file line numberDiff line numberDiff line change
@@ -607,16 +607,22 @@
607607
background-image: linear-gradient(to right, var(--tw-gradient-stops));
608608
}
609609
.from-red-300 {
610-
--tw-gradient-from: #fca5a5;
611-
--tw-gradient-to: #fca5a500;
610+
--tw-gradient-from: #fca5a5 var(--tw-gradient-from-position);
611+
--tw-gradient-from-position: ;
612+
--tw-gradient-to: #fca5a500 var(--tw-gradient-from-position);
613+
--tw-gradient-to-position: ;
612614
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
613615
}
614616
.via-purple-200 {
615-
--tw-gradient-to: #e9d5ff00;
616-
--tw-gradient-stops: var(--tw-gradient-from), #e9d5ff, var(--tw-gradient-to);
617+
--tw-gradient-via-position: ;
618+
--tw-gradient-to: #e9d5ff00 var(--tw-gradient-to-position);
619+
--tw-gradient-to-position: ;
620+
--tw-gradient-stops: var(--tw-gradient-from), #e9d5ff var(--tw-gradient-via-position),
621+
var(--tw-gradient-to);
617622
}
618623
.to-blue-400 {
619-
--tw-gradient-to: #60a5fa;
624+
--tw-gradient-to: #60a5fa var(--tw-gradient-to-position);
625+
--tw-gradient-to-position: ;
620626
}
621627
.decoration-slice {
622628
-webkit-box-decoration-break: slice;

0 commit comments

Comments
 (0)