Skip to content

Commit 156b491

Browse files
Add composeStyles function (#38)
1 parent ae9864c commit 156b491

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed

.changeset/small-guests-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vanilla-extract/css': minor
3+
---
4+
5+
Add `composeStyles` function

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ document.write(`
9494
- [globalFontFace](#globalfontface)
9595
- [keyframes](#keyframes)
9696
- [globalKeyframes](#globalkeyframes)
97+
- [composeStyles](#composestyles)
9798
- [Dynamic API](#dynamic-api)
9899
- [createInlineTheme](#createinlinetheme)
99100
- [setElementTheme](#setelementtheme)
@@ -580,6 +581,28 @@ export const animated = style({
580581
});
581582
```
582583

584+
### composeStyles
585+
586+
Combines mutliple styles into a single class string, while also deduplicating and removing unnecessary spaces.
587+
588+
```ts
589+
import { style, composeStyles } from '@vanilla-extract/css';
590+
591+
const base = style({
592+
padding: 12
593+
});
594+
595+
export const blue = composeStyles(base, style({
596+
background: 'blue'
597+
}));
598+
599+
export const green = composeStyles(base, style({
600+
background: 'green'
601+
}));
602+
```
603+
604+
> 💡 Styles can also be provided in shallow and deeply nested arrays. Think of it as a static version of [classnames.](https://github.com/JedWatson/classnames)
605+
583606
## Dynamic API
584607

585608
We also provide a lightweight standalone package to support dynamic runtime theming.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { composeStyles } from './composeStyles';
2+
3+
describe('composeStyles', () => {
4+
it.each([
5+
{ args: ['1'], output: '1' },
6+
{ args: ['1 1'], output: '1' },
7+
{ args: ['1 2 3'], output: '1 2 3' },
8+
{ args: ['1', '2', '3'], output: '1 2 3' },
9+
{ args: [['1', '2'], '3'], output: '1 2 3' },
10+
{ args: [['1', ['2', '3']], '4'], output: '1 2 3 4' },
11+
{
12+
args: [['1', ['2', ['3', '4']]], ['5', '6'], '7'],
13+
output: '1 2 3 4 5 6 7',
14+
},
15+
{ args: ['1', '1', '1'], output: '1' },
16+
{
17+
args: ['1', ['1', '2'], ['1', '2', '3'], ['1', '2', '3', '4']],
18+
output: '1 2 3 4',
19+
},
20+
{ args: ['1 2 3', '2 3 4', '1 5'], output: '1 2 3 4 5' },
21+
{ args: [' 1 2 3 2 ', ' 2 3 4 2 ', ' 1 5 1 '], output: '1 2 3 4 5' },
22+
])('composeStyles', ({ args, output }) => {
23+
expect(composeStyles(...args)).toBe(output);
24+
});
25+
});

packages/css/src/composeStyles.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
type ClassNames = string | Array<ClassNames>;
2+
3+
function composeStylesIntoSet(
4+
set: Set<string>,
5+
...classNames: Array<ClassNames>
6+
) {
7+
for (const className of classNames) {
8+
if (className.length === 0) {
9+
continue;
10+
}
11+
12+
if (typeof className === 'string') {
13+
if (className.includes(' ')) {
14+
composeStylesIntoSet(set, ...className.trim().split(' '));
15+
} else {
16+
set.add(className);
17+
}
18+
} else if (Array.isArray(className)) {
19+
composeStylesIntoSet(set, ...className);
20+
}
21+
}
22+
}
23+
24+
export function composeStyles(...classNames: Array<ClassNames>) {
25+
const set: Set<string> = new Set();
26+
27+
composeStylesIntoSet(set, ...classNames);
28+
29+
return Array.from(set).join(' ');
30+
}

packages/css/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './identifier';
55
export * from './theme';
66
export * from './style';
77
export * from './vars';
8+
export { composeStyles } from './composeStyles';

0 commit comments

Comments
 (0)