Skip to content

Commit 3731e6a

Browse files
Add @layer support (#147)
Co-authored-by: Rogin Farrer <rogin@roginfarrer.com>
1 parent e1ed0f1 commit 3731e6a

File tree

12 files changed

+255
-20
lines changed

12 files changed

+255
-20
lines changed

.changeset/selfish-yaks-destroy.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'rainbow-sprinkles': minor
3+
---
4+
5+
Support assigning properties to layers via `@layer` option on `defineProperties`
6+
7+
**Example usage:**
8+
9+
```ts
10+
// sprinkles.css.ts
11+
import { defineProperties } from 'rainbow-sprinkles';
12+
import { layer } from '@vanilla-extract/css';
13+
14+
export const sprinklesLayer = layer();
15+
16+
const properties = defineProperties({
17+
'@layer': sprinklesLayer,
18+
// etc.
19+
});

examples/react/components/rainbow-sprinkles.css.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createRainbowSprinkles, defineProperties } from 'rainbow-sprinkles';
22
import { vars } from '../vars.css';
3+
import { interactiveLayer, responsiveLayer } from '../layers.css';
34

45
const positiveSpace = {
56
'250': vars.space['250'],
@@ -16,6 +17,7 @@ const positiveSpace = {
1617
} as const;
1718

1819
const responsiveProperties = defineProperties({
20+
'@layer': responsiveLayer,
1921
conditions: {
2022
mobile: {},
2123
tablet: { '@media': 'screen and (min-width: 768px)' },
@@ -87,6 +89,7 @@ const responsiveProperties = defineProperties({
8789
});
8890

8991
const interactiveProperties = defineProperties({
92+
'@layer': interactiveLayer,
9093
conditions: {
9194
base: {},
9295
hover: { selector: '&:hover' },

examples/react/layers.css.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { layer } from '@vanilla-extract/css';
2+
3+
export const resetLayer = layer('reset');
4+
export const responsiveLayer = layer('responsive');
5+
export const interactiveLayer = layer('interactive');

examples/react/styles/index.css.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { globalStyle, globalKeyframes } from '@vanilla-extract/css';
1+
import { globalStyle, globalKeyframes, layer } from '@vanilla-extract/css';
2+
import { resetLayer } from '../layers.css';
23

34
globalStyle('body, h1, h2, h3, h4, h5, h6, p, div', {
4-
all: 'unset',
5-
boxSizing: 'border-box',
5+
'@layer': {
6+
[resetLayer]: {
7+
all: 'unset',
8+
boxSizing: 'border-box',
9+
},
10+
},
611
});
712

813
globalKeyframes('pinwheelSpin', {

packages/rainbow-sprinkles/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,22 @@ function App() {
160160
}
161161
```
162162

163+
## CSS Layers
164+
165+
You can define a [css layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) for a given set of properties.
166+
167+
```typescript
168+
// rainbow-sprinkles.css.ts
169+
import { layer } from '@vanilla-extract/css';
170+
import { defineProperties } from 'rainbow-sprinkles';
171+
172+
export const sprinklesLayer = layer();
173+
174+
const properties = defineProperties({
175+
'@layer': sprinklesLayer
176+
// etc.
177+
});
178+
163179
### `dynamicProperties` vs `staticProperties`
164180

165181
One trade off that's made for supporting dynamic values is that we have to increase the size of the document. Instead of just appending a single class to an element to add a style, both a utility class and an inline style assignment is added to an element. While this setup will still produce an overall smaller bundle in many cases, some large applications may observe frequent recurrence of specific combinations of CSS properties and values. In these cases, those combinations can be set-up in `staticProperties` in the initial configuration. `staticProperties` will produce typical CSS utility classes. The runtime portion of Rainbow Sprinkles will defer to the CSS classes created by `staticProperties` and not apply any inline style assignments.

packages/rainbow-sprinkles/src/__tests__/createStaticStyles.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,86 @@ it('returns expected configuration given array scale', () => {
116116

117117
style.mockClear();
118118
});
119+
120+
it('returns expected configuration for css layers', () => {
121+
const style = jest.spyOn(VE, 'style');
122+
const result = createStaticStyles(
123+
'display',
124+
['block', 'inline-block'],
125+
conditions,
126+
'mobile',
127+
{ '@layer': 'primary' },
128+
);
129+
130+
const calledArgs = style.mock.calls;
131+
132+
expect(calledArgs.length).toBe(6);
133+
134+
expect(calledArgs[0][0]).toMatchObject({
135+
'@layer': { primary: { display: 'block' } },
136+
});
137+
expect(calledArgs[1][0]).toMatchObject({
138+
'@layer': {
139+
primary: {
140+
'@media': { 'screen and (min-width: 768px)': { display: 'block' } },
141+
},
142+
},
143+
});
144+
expect(calledArgs[2][0]).toMatchObject({
145+
'@layer': {
146+
primary: {
147+
'@media': { 'screen and (min-width: 1024px)': { display: 'block' } },
148+
},
149+
},
150+
});
151+
expect(calledArgs[3][0]).toMatchObject({
152+
'@layer': { primary: { display: 'inline-block' } },
153+
});
154+
expect(calledArgs[4][0]).toMatchObject({
155+
'@layer': {
156+
primary: {
157+
'@media': {
158+
'screen and (min-width: 768px)': {
159+
display: 'inline-block',
160+
},
161+
},
162+
},
163+
},
164+
});
165+
expect(calledArgs[5][0]).toMatchObject({
166+
'@layer': {
167+
primary: {
168+
'@media': {
169+
'screen and (min-width: 1024px)': {
170+
display: 'inline-block',
171+
},
172+
},
173+
},
174+
},
175+
});
176+
177+
expect(result).toMatchObject({
178+
values: {
179+
block: {
180+
conditions: {
181+
mobile: 'display-block-mobile',
182+
tablet: 'display-block-tablet',
183+
desktop: 'display-block-desktop',
184+
},
185+
default: 'display-block-mobile',
186+
},
187+
'inline-block': {
188+
conditions: {
189+
mobile: 'display-inline-block-mobile',
190+
tablet: 'display-inline-block-tablet',
191+
desktop: 'display-inline-block-desktop',
192+
},
193+
default: 'display-inline-block-mobile',
194+
},
195+
},
196+
name: 'display',
197+
staticScale: ['block', 'inline-block'],
198+
});
199+
200+
style.mockClear();
201+
});

packages/rainbow-sprinkles/src/__tests__/createStyles.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,69 @@ it('returns expected configuration with scale: true', () => {
9494

9595
style.mockClear();
9696
});
97+
98+
it('returns expected configuration for css layers', () => {
99+
const style = jest.spyOn(VE, 'style');
100+
const result = createStyles('backgroundColor', scale, conditions, 'mobile', {
101+
'@layer': 'primary',
102+
});
103+
104+
const calledArgs = style.mock.calls;
105+
expect(calledArgs.length).toBe(3);
106+
107+
expect(calledArgs[0][0]).toMatchObject({
108+
'@layer': {
109+
primary: {
110+
backgroundColor: '--backgroundColor-mobile',
111+
},
112+
},
113+
});
114+
expect(calledArgs[1][0]).toMatchObject({
115+
'@layer': {
116+
primary: {
117+
'@media': {
118+
'screen and (min-width: 768px)': {
119+
backgroundColor: '--backgroundColor-tablet',
120+
},
121+
},
122+
},
123+
},
124+
});
125+
expect(calledArgs[2][0]).toMatchObject({
126+
'@layer': {
127+
primary: {
128+
'@media': {
129+
'screen and (min-width: 1024px)': {
130+
backgroundColor: '--backgroundColor-desktop',
131+
},
132+
},
133+
},
134+
},
135+
});
136+
137+
expect(result).toMatchObject({
138+
dynamic: {
139+
default: 'backgroundColor-mobile',
140+
conditions: {
141+
mobile: 'backgroundColor-mobile',
142+
tablet: 'backgroundColor-tablet',
143+
desktop: 'backgroundColor-desktop',
144+
},
145+
},
146+
name: 'backgroundColor',
147+
vars: {
148+
conditions: {
149+
mobile: '--backgroundColor-mobile',
150+
tablet: '--backgroundColor-tablet',
151+
desktop: '--backgroundColor-desktop',
152+
},
153+
default: '--backgroundColor-mobile',
154+
},
155+
dynamicScale: {
156+
primary: 'primary-color',
157+
secondary: 'secondary-color',
158+
},
159+
});
160+
161+
style.mockClear();
162+
});

packages/rainbow-sprinkles/src/createStaticStyles.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { style } from '@vanilla-extract/css';
2-
import type { CreateStylesOutput } from './types';
2+
import type { CommonOptions, CreateStylesOutput } from './types';
33
import { mapValues } from './utils';
44

55
export function createStaticStyles(
66
property: string,
77
scale: ReadonlyArray<string> | Record<string, string>,
88
conditions: Record<string, Record<string, string>>,
99
defaultCondition: string,
10+
options: CommonOptions = {},
1011
): CreateStylesOutput {
1112
const scaleObj = Array.isArray(scale)
1213
? Object.assign(
@@ -18,9 +19,16 @@ export function createStaticStyles(
1819
: scale;
1920

2021
const values = mapValues(scaleObj, (scaleValue, scaleKey) => {
22+
const styleValue = { [property]: scaleValue };
23+
2124
if (!conditions) {
2225
return {
23-
default: style({ [property]: scaleValue }, `${property}-${scaleKey}`),
26+
default: style(
27+
options['@layer']
28+
? { ['@layer']: { [options['@layer']]: styleValue } }
29+
: styleValue,
30+
`${property}-${scaleKey}`,
31+
),
2432
};
2533
}
2634

@@ -54,7 +62,12 @@ export function createStaticStyles(
5462
},
5563
};
5664
}
57-
return style(styleValue, `${property}-${scaleKey}-${conditionName}`);
65+
return style(
66+
options['@layer']
67+
? { ['@layer']: { [options['@layer']]: styleValue } }
68+
: styleValue,
69+
`${property}-${scaleKey}-${conditionName}`,
70+
);
5871
});
5972
return {
6073
conditions: classes,

packages/rainbow-sprinkles/src/createStyles.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import { createVar, style } from '@vanilla-extract/css';
2-
import type { CreateStylesOutput } from './types';
2+
import type { CommonOptions, CreateStylesOutput } from './types';
33
import { mapValues } from './utils';
44

55
export function createStyles(
66
property: string,
77
scale: true | Record<string, string>,
88
conditions: Record<string, Record<string, string>>,
99
defaultCondition: string,
10+
options: CommonOptions = {},
1011
): CreateStylesOutput {
1112
if (!conditions) {
1213
const cssVar = createVar(property);
13-
const className = style({ [property]: cssVar }, property);
14+
const styleValue = { [property]: cssVar };
15+
16+
const className = style(
17+
options['@layer']
18+
? { ['@layer']: { [options['@layer']]: styleValue } }
19+
: styleValue,
20+
property,
21+
);
1422

1523
return {
1624
vars: { default: cssVar },
@@ -56,7 +64,12 @@ export function createStyles(
5664
};
5765
}
5866

59-
return style(styleValue, `${property}-${conditionName}`);
67+
return style(
68+
options['@layer']
69+
? { ['@layer']: { [options['@layer']]: styleValue } }
70+
: styleValue,
71+
`${property}-${conditionName}`,
72+
);
6073
});
6174

6275
return {

packages/rainbow-sprinkles/src/css.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7161,6 +7161,7 @@ export type AtRules =
71617161
| '@import'
71627162
| '@keyframes'
71637163
| '@media'
7164+
| '@layer'
71647165
| '@namespace'
71657166
| '@page'
71667167
| '@property'

0 commit comments

Comments
 (0)