Skip to content

Commit 6661901

Browse files
committed
Make motion variants stackable
1 parent 9f39277 commit 6661901

File tree

2 files changed

+161
-16
lines changed

2 files changed

+161
-16
lines changed

__tests__/variantsAtRule.test.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,120 @@ test('it can generate motion-safe variants', () => {
223223
})
224224
})
225225

226+
test('it can generate motion-safe and motion-reduced variants', () => {
227+
const input = `
228+
@variants motion-safe, motion-reduced {
229+
.banana { color: yellow; }
230+
.chocolate { color: brown; }
231+
}
232+
`
233+
234+
const output = `
235+
.banana { color: yellow; }
236+
.chocolate { color: brown; }
237+
@media (prefers-reduced-motion: no-preference) {
238+
.motion-safe\\:banana { color: yellow; }
239+
.motion-safe\\:chocolate { color: brown; }
240+
}
241+
@media (prefers-reduced-motion: reduce) {
242+
.motion-reduced\\:banana { color: yellow; }
243+
.motion-reduced\\:chocolate { color: brown; }
244+
}
245+
`
246+
247+
return run(input).then(result => {
248+
expect(result.css).toMatchCss(output)
249+
expect(result.warnings().length).toBe(0)
250+
})
251+
})
252+
253+
test('motion-reduced variants stack with basic variants', () => {
254+
const input = `
255+
@variants motion-reduced, hover {
256+
.banana { color: yellow; }
257+
.chocolate { color: brown; }
258+
}
259+
`
260+
261+
const output = `
262+
.banana { color: yellow; }
263+
.chocolate { color: brown; }
264+
.hover\\:banana:hover { color: yellow; }
265+
.hover\\:chocolate:hover { color: brown; }
266+
@media (prefers-reduced-motion: reduce) {
267+
.motion-reduced\\:banana { color: yellow; }
268+
.motion-reduced\\:chocolate { color: brown; }
269+
.motion-reduced\\:hover\\:banana:hover { color: yellow; }
270+
.motion-reduced\\:hover\\:chocolate:hover { color: brown; }
271+
}
272+
`
273+
274+
return run(input).then(result => {
275+
expect(result.css).toMatchCss(output)
276+
expect(result.warnings().length).toBe(0)
277+
})
278+
})
279+
280+
test('motion-safe variants stack with basic variants', () => {
281+
const input = `
282+
@variants motion-safe, hover {
283+
.banana { color: yellow; }
284+
.chocolate { color: brown; }
285+
}
286+
`
287+
288+
const output = `
289+
.banana { color: yellow; }
290+
.chocolate { color: brown; }
291+
.hover\\:banana:hover { color: yellow; }
292+
.hover\\:chocolate:hover { color: brown; }
293+
@media (prefers-reduced-motion: no-preference) {
294+
.motion-safe\\:banana { color: yellow; }
295+
.motion-safe\\:chocolate { color: brown; }
296+
.motion-safe\\:hover\\:banana:hover { color: yellow; }
297+
.motion-safe\\:hover\\:chocolate:hover { color: brown; }
298+
}
299+
`
300+
301+
return run(input).then(result => {
302+
expect(result.css).toMatchCss(output)
303+
expect(result.warnings().length).toBe(0)
304+
})
305+
})
306+
307+
test('motion-safe and motion-reduced variants stack with basic variants', () => {
308+
const input = `
309+
@variants motion-reduced, motion-safe, hover {
310+
.banana { color: yellow; }
311+
.chocolate { color: brown; }
312+
}
313+
`
314+
315+
const output = `
316+
.banana { color: yellow; }
317+
.chocolate { color: brown; }
318+
.hover\\:banana:hover { color: yellow; }
319+
.hover\\:chocolate:hover { color: brown; }
320+
@media (prefers-reduced-motion: reduce) {
321+
.motion-reduced\\:banana { color: yellow; }
322+
.motion-reduced\\:chocolate { color: brown; }
323+
.motion-reduced\\:hover\\:banana:hover { color: yellow; }
324+
.motion-reduced\\:hover\\:chocolate:hover { color: brown; }
325+
}
326+
@media (prefers-reduced-motion: no-preference) {
327+
.motion-safe\\:banana { color: yellow; }
328+
.motion-safe\\:chocolate { color: brown; }
329+
.motion-safe\\:hover\\:banana:hover { color: yellow; }
330+
.motion-safe\\:hover\\:chocolate:hover { color: brown; }
331+
}
332+
`
333+
334+
return run(input).then(result => {
335+
expect(result.css).toMatchCss(output)
336+
expect(result.warnings().length).toBe(0)
337+
})
338+
})
339+
226340
test('it can generate first-child variants', () => {
227341
const input = `
228342
@variants first {

src/lib/substituteVariantsAtRules.js

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -90,32 +90,63 @@ const defaultVariantGenerators = config => ({
9090
even: generatePseudoClassVariant('nth-child(even)', 'even'),
9191
})
9292

93+
function prependStackableVariants(atRule, variants) {
94+
const stackableVariants = ['motion-safe', 'motion-reduced']
95+
96+
if (!_.some(variants, v => stackableVariants.includes(v))) {
97+
return variants
98+
}
99+
100+
if (_.every(variants, v => stackableVariants.includes(v))) {
101+
return variants
102+
}
103+
104+
const variantsParent = postcss.atRule({
105+
name: 'variants',
106+
params: variants.filter(v => stackableVariants.includes(v)).join(', '),
107+
})
108+
atRule.before(variantsParent)
109+
variantsParent.append(atRule)
110+
variants = _.without(variants, ...stackableVariants)
111+
112+
return variants
113+
}
114+
93115
export default function(config, { variantGenerators: pluginVariantGenerators }) {
94116
return function(css) {
95117
const variantGenerators = {
96118
...defaultVariantGenerators(config),
97119
...pluginVariantGenerators,
98120
}
99121

100-
css.walkAtRules('variants', atRule => {
101-
const variants = postcss.list.comma(atRule.params).filter(variant => variant !== '')
122+
let variantsFound = false
102123

103-
if (variants.includes('responsive')) {
104-
const responsiveParent = postcss.atRule({ name: 'responsive' })
105-
atRule.before(responsiveParent)
106-
responsiveParent.append(atRule)
107-
}
124+
do {
125+
variantsFound = false
126+
css.walkAtRules('variants', atRule => {
127+
variantsFound = true
108128

109-
_.forEach(_.without(ensureIncludesDefault(variants), 'responsive'), variant => {
110-
if (!variantGenerators[variant]) {
111-
throw new Error(
112-
`Your config mentions the "${variant}" variant, but "${variant}" doesn't appear to be a variant. Did you forget or misconfigure a plugin that supplies that variant?`
113-
)
129+
let variants = postcss.list.comma(atRule.params).filter(variant => variant !== '')
130+
131+
if (variants.includes('responsive')) {
132+
const responsiveParent = postcss.atRule({ name: 'responsive' })
133+
atRule.before(responsiveParent)
134+
responsiveParent.append(atRule)
114135
}
115-
variantGenerators[variant](atRule, config)
116-
})
117136

118-
atRule.remove()
119-
})
137+
const remainingVariants = prependStackableVariants(atRule, variants)
138+
139+
_.forEach(_.without(ensureIncludesDefault(remainingVariants), 'responsive'), variant => {
140+
if (!variantGenerators[variant]) {
141+
throw new Error(
142+
`Your config mentions the "${variant}" variant, but "${variant}" doesn't appear to be a variant. Did you forget or misconfigure a plugin that supplies that variant?`
143+
)
144+
}
145+
variantGenerators[variant](atRule, config)
146+
})
147+
148+
atRule.remove()
149+
})
150+
} while (variantsFound)
120151
}
121152
}

0 commit comments

Comments
 (0)