Skip to content

Commit 2d9b4c3

Browse files
authored
Support passing arrays of styles to style and styleVariants (#326)
1 parent 63a88e1 commit 2d9b4c3

File tree

25 files changed

+1108
-275
lines changed

25 files changed

+1108
-275
lines changed

.changeset/calm-pillows-jam.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
'@vanilla-extract/css': minor
3+
---
4+
5+
Support passing arrays of styles to `style` and `styleVariants`
6+
7+
Multiple styles can now be composed into a single rule by providing an array of styles.
8+
9+
```ts
10+
import { style } from '@vanilla-extract/css';
11+
12+
const base = style({ padding: 12 });
13+
14+
export const primary = style([
15+
base,
16+
{ background: 'blue' }
17+
]);
18+
19+
export const secondary = style([
20+
base,
21+
{ background: 'aqua' }
22+
]);
23+
```
24+
25+
When composed styles are used in selectors, they are assigned an additional class if required so they can be uniquely identified. When selectors are processed internally, the composed classes are removed, only leaving behind the unique identifier classes. This allows you to treat them as if they were a single class within vanilla-extract selectors.
26+
27+
```ts
28+
import {
29+
style,
30+
globalStyle,
31+
} from '@vanilla-extract/css';
32+
33+
const background = style({ background: 'mintcream' });
34+
const padding = style({ padding: 12 });
35+
36+
export const container = style([background, padding]);
37+
38+
globalStyle(`${container} *`, {
39+
boxSizing: 'border-box'
40+
});
41+
```
42+
43+
This feature is a replacement for the standalone `composeStyles` function which is now marked as deprecated. You can use `style` with an array as a drop-in replacement.
44+
45+
```diff
46+
-export const container = composeStyles(background, padding);
47+
+export const container = style([background, padding]);
48+
```

.changeset/five-weeks-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vanilla-extract/sprinkles': patch
3+
---
4+
5+
Avoid calling `composeStyles` when using the atoms function at runtime

README.md

Lines changed: 66 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,51 @@ export const childClass = style({
393393
});
394394
```
395395

396-
> 💡 To improve maintainability, each `style` block can only target a single element. To enforce this, all selectors must target the `&` character which is a reference to the current element. For example, `'&:hover:not(:active)'` is considered valid, while `'& > a'` and ``[`& ${childClass}`]`` are not.
396+
> 💡 To improve maintainability, each style block can only target a single element. To enforce this, all selectors must target the “&” character which is a reference to the current element.
397397
>
398-
>If you want to target another scoped class then it should be defined within the `style` block of that class instead. For example, ``[`& ${childClass}`]`` is invalid since it targets `${childClass}`, so it should instead be defined in the `style` block for `childClass`.
398+
> For example, `'&:hover:not(:active)'` and `` [`${parentClass} &`] `` are considered valid, while `'& a[href]'` and `` [`& ${childClass}`] `` are not.
399399
>
400-
>If you want to globally target child nodes within the current element (e.g. `'& > a'`), you should use [`globalStyle`](#globalstyle) instead.
400+
> If you want to target another scoped class then it should be defined within the style block of that class instead.
401+
>
402+
> For example, `` [`& ${childClass}`] `` is invalid since it doesn’t target “&”, so it should instead be defined in the style block for `childClass`.
403+
>
404+
> If you want to globally target child nodes within the current element (e.g. `'& a[href]'`), you should use [`globalStyle`](#globalstyle) instead.
405+
406+
Multiple styles can be composed into a single rule by providing an array of styles.
407+
408+
```ts
409+
import { style } from '@vanilla-extract/css';
410+
411+
const base = style({ padding: 12 });
412+
413+
export const primary = style([
414+
base,
415+
{ background: 'blue' }
416+
]);
417+
418+
export const secondary = style([
419+
base,
420+
{ background: 'aqua' }
421+
]);
422+
```
423+
424+
When composed styles are used in selectors, they are assigned an additional class if required so they can be uniquely identified. When selectors are processed internally, the composed classes are removed, only leaving behind the unique identifier classes. This allows you to treat them as if they were a single class within vanilla-extract selectors.
425+
426+
```ts
427+
import {
428+
style,
429+
globalStyle,
430+
} from '@vanilla-extract/css';
431+
432+
const background = style({ background: 'mintcream' });
433+
const padding = style({ padding: 12 });
434+
435+
export const container = style([background, padding]);
436+
437+
globalStyle(`${container} *`, {
438+
boxSizing: 'border-box'
439+
});
440+
```
401441

402442
### styleVariants
403443

@@ -414,20 +454,35 @@ export const variant = styleVariants({
414454

415455
> 💡 This is useful for mapping component props to styles, e.g. `<button className={styles.variant[props.variant]}>`
416456
457+
Multiple styles can be composed into a single rule by providing an array of styles.
458+
459+
```ts
460+
import { styleVariants } from '@vanilla-extract/css';
461+
462+
const base = style({ padding: 12 });
463+
464+
export const variant = styleVariants({
465+
primary: [base, { background: 'blue' }],
466+
secondary: [base, { background: 'aqua' }],
467+
});
468+
```
469+
417470
You can also transform the values by providing a map function as the second argument.
418471

419472
```ts
420473
import { styleVariants } from '@vanilla-extract/css';
421474

422-
const spaceScale = {
423-
small: 4,
424-
medium: 8,
425-
large: 16
426-
};
475+
const base = style({ padding: 12 });
427476

428-
export const padding = styleVariants(spaceScale, (space) => ({
429-
padding: space
430-
}));
477+
const backgrounds = {
478+
primary: 'blue',
479+
secondary: 'aqua'
480+
} as const;
481+
482+
export const variant = styleVariants(
483+
backgrounds,
484+
(background) => [base, { background }]
485+
);
431486
```
432487

433488
### globalStyle
@@ -454,50 +509,6 @@ globalStyle(`${parentClass} > a`, {
454509
});
455510
```
456511

457-
### composeStyles
458-
459-
Combines multiple styles into a single class string, while also deduplicating and removing unnecessary spaces.
460-
461-
```ts
462-
import { style, composeStyles } from '@vanilla-extract/css';
463-
464-
const button = style({
465-
padding: 12,
466-
borderRadius: 8
467-
});
468-
469-
export const primaryButton = composeStyles(
470-
button,
471-
style({ background: 'coral' })
472-
);
473-
474-
export const secondaryButton = composeStyles(
475-
button,
476-
style({ background: 'peachpuff' })
477-
);
478-
```
479-
480-
> 💡 Styles can also be provided in shallow and deeply nested arrays, similar to [classnames.](https://github.com/JedWatson/classnames)
481-
482-
When style compositions are used in selectors, they are assigned an additional class so they can be uniquely identified. When selectors are processed internally, the composed classes are removed, only leaving behind the unique identifier classes. This allows you to treat them as if they were a single class within vanilla-extract selectors.
483-
484-
```ts
485-
import {
486-
style,
487-
globalStyle,
488-
composeStyles
489-
} from '@vanilla-extract/css';
490-
491-
const background = style({ background: 'mintcream' });
492-
const padding = style({ padding: 12 });
493-
494-
export const container = composeStyles(background, padding);
495-
496-
globalStyle(`${container} *`, {
497-
boxSizing: 'border-box'
498-
});
499-
```
500-
501512
### createTheme
502513

503514
Creates a locally scoped theme class and a theme contract which can be consumed within your styles.

fixtures/features/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/index.ts"></script>
11+
</body>
12+
</html>

fixtures/features/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@fixtures/features",
3+
"version": "0.0.0",
4+
"main": "src/index.ts",
5+
"sideEffects": true,
6+
"author": "SEEK",
7+
"private": true,
8+
"dependencies": {
9+
"@vanilla-extract/css": "1.3.0",
10+
"@vanilla-extract/dynamic": "2.0.0"
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Snowpack App</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/dist/index.js"></script>
11+
</body>
12+
</html>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { globalStyle, style, styleVariants } from '@vanilla-extract/css';
2+
3+
export const mergedStyle = style([
4+
{ height: 50, ':after': { display: 'block', content: '"Below 700px"' } },
5+
{
6+
'@media': {
7+
'screen and (min-width: 700px)': {
8+
':after': { content: '"Above 700px"' },
9+
},
10+
},
11+
},
12+
{
13+
'@media': {
14+
'screen and (min-width: 700px)': {
15+
color: 'plum',
16+
},
17+
},
18+
},
19+
]);
20+
21+
export const styleWithComposition = style([
22+
{ backgroundColor: 'powderblue' },
23+
mergedStyle,
24+
{ selectors: { '&:hover': { backgroundColor: 'slategray' } } },
25+
]);
26+
27+
export const styleVariantsWithComposition = styleVariants({
28+
variant: [
29+
{ backgroundColor: 'powderblue' },
30+
mergedStyle,
31+
{ selectors: { '&:hover': { backgroundColor: 'slategray' } } },
32+
],
33+
});
34+
35+
export const styleVariantsWithMappedComposition = styleVariants(
36+
{ variant: 'slategray' },
37+
(backgroundColor) => [
38+
{ backgroundColor: 'powderblue' },
39+
mergedStyle,
40+
{ selectors: { '&:hover': { backgroundColor } } },
41+
],
42+
);
43+
44+
export const compositionOnly = style([mergedStyle, styleWithComposition]);
45+
46+
// Force composition for use in selector
47+
export const styleCompositionInSelector = style([
48+
style({ color: 'white' }),
49+
style({ backgroundColor: 'black' }),
50+
]);
51+
52+
globalStyle(`body ${styleCompositionInSelector}`, {
53+
fontSize: '24px',
54+
});
55+
56+
export const styleVariantsCompositionInSelector = styleVariants({
57+
variant: [style({ color: 'white' }), style({ backgroundColor: 'black' })],
58+
});
59+
60+
globalStyle(`body ${styleVariantsCompositionInSelector.variant}`, {
61+
fontSize: '24px',
62+
});

fixtures/features/src/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as styles from './features.css';
2+
import testNodes from '../test-nodes.json';
3+
4+
function render() {
5+
document.body.innerHTML = `
6+
<div id="${testNodes.mergedStyle}" class="${styles.mergedStyle}">Merged style</div>
7+
<div id="${testNodes.styleWithComposition}" class="${styles.styleWithComposition}">Style with composition</div>
8+
<div id="${testNodes.styleVariantsWithComposition}" class="${styles.styleVariantsWithComposition.variant}">Style variants with composition</div>
9+
<div id="${testNodes.styleVariantsWithMappedComposition}" class="${styles.styleVariantsWithMappedComposition.variant}">Style variants with mapped composition</div>
10+
<div id="${testNodes.compositionOnly}" class="${styles.compositionOnly}">Composition only</div>
11+
<div id="${testNodes.styleCompositionInSelector}" class="${styles.styleCompositionInSelector}">Style composition in selector</div>
12+
<div id="${testNodes.styleVariantsCompositionInSelector}" class="${styles.styleVariantsCompositionInSelector.variant}">Style variants composition in selector</div>
13+
`;
14+
}
15+
16+
render();

fixtures/features/test-nodes.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"mergedStyle": "mergedStyle",
3+
"styleWithComposition": "styleWithComposition",
4+
"styleVariantsWithComposition": "styleVariantsWithComposition",
5+
"styleVariantsWithMappedComposition": "styleVariantsWithMappedComposition",
6+
"compositionOnly": "compositionOnly",
7+
"styleCompositionInSelector": "styleCompositionInSelector",
8+
"styleVariantsCompositionInSelector": "styleVariantsCompositionInSelector"
9+
}

fixtures/themed/src/styles.css.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
globalFontFace,
88
keyframes,
99
globalKeyframes,
10-
composeStyles,
1110
globalStyle,
1211
} from '@vanilla-extract/css';
1312
import { shadow } from './shared.css';
@@ -52,13 +51,10 @@ export const container = style({
5251
},
5352
});
5453

55-
const iDunno = composeStyles(
56-
style({ zIndex: 1 }),
57-
style({ position: 'relative' }),
58-
);
54+
const iDunno = style([{ zIndex: 1 }, { position: 'relative' }]);
5955

60-
export const button = composeStyles(
61-
style({
56+
export const button = style([
57+
{
6258
fontFamily: impact,
6359
backgroundColor: fallbackVar(
6460
vars.colors.backgroundColor,
@@ -80,10 +76,10 @@ export const button = composeStyles(
8076
outline: '5px solid red',
8177
},
8278
},
83-
}),
79+
},
8480
shadow,
8581
iDunno,
86-
);
82+
]);
8783

8884
globalStyle(`body ${iDunno}`, {
8985
animation: `3s infinite alternate ${slide} ease-in-out`,

0 commit comments

Comments
 (0)