diff --git a/.changeset/angry-rules-tease.md b/.changeset/angry-rules-tease.md new file mode 100644 index 000000000..6d2c4755b --- /dev/null +++ b/.changeset/angry-rules-tease.md @@ -0,0 +1,5 @@ +--- +"@vanilla-extract/babel-plugin-debug-ids": minor +--- + +Add support for `createVar` calls that declare `@property` rules diff --git a/.changeset/modern-cobras-sort.md b/.changeset/modern-cobras-sort.md new file mode 100644 index 000000000..54bb0da62 --- /dev/null +++ b/.changeset/modern-cobras-sort.md @@ -0,0 +1,30 @@ +--- +"@vanilla-extract/css": minor +--- + +`keyframes`: Add support for a `vars` property to steps within `keyframes` declarations + +Example usage: + +```ts +import { createVar, keyframes } from '@vanilla-extract/css'; + +const angle = createVar({ + syntax: '', + inherits: false, + initialValue: '0deg' +}); + +export const angleKeyframes = keyframes({ + '0%': { + vars: { + [angle]: '0deg' + } + }, + '100%': { + vars: { + [angle]: '360deg' + } + } +}); +``` diff --git a/.changeset/poor-mirrors-care.md b/.changeset/poor-mirrors-care.md new file mode 100644 index 000000000..4565ff30e --- /dev/null +++ b/.changeset/poor-mirrors-care.md @@ -0,0 +1,29 @@ +--- +"@vanilla-extract/css": minor +--- + +`createVar`: Add support for defining [`@property`] rules + +Example usage: + +```ts +import { createVar } from '@vanilla-extract/css'; + +export const myVar = createVar({ + syntax: '', + inherits: false, + initialValue: '0.5', +}); +``` + +This will generate the following CSS: + +```css +@property --myVar__jteyb14 { + syntax: ""; + inherits: false; + initial-value: 0.5; +} +``` + +[`@property`]: https://developer.mozilla.org/en-US/docs/Web/CSS/@property diff --git a/examples/next/components/HelloWorld.css.ts b/examples/next/components/HelloWorld.css.ts index ec3a899ac..909a23c86 100644 --- a/examples/next/components/HelloWorld.css.ts +++ b/examples/next/components/HelloWorld.css.ts @@ -1,11 +1,34 @@ -import { style } from '@vanilla-extract/css'; +import { style, createVar, keyframes, fallbackVar } from '@vanilla-extract/css'; + +const color = createVar(); +const angle = createVar({ + syntax: '', + inherits: false, + initialValue: '0deg', +}); + +const angleKeyframes = keyframes({ + '100%': { + vars: { + [angle]: '360deg', + }, + }, +}); export const root = style({ background: 'pink', color: 'blue', padding: '16px', - transition: 'opacity .1s ease', // Testing autoprefixer + transition: `opacity .1s ease, color .1s ease`, // Testing autoprefixer + backgroundImage: `linear-gradient(${angle}, rgba(153, 70, 198, 0.35) 0%, rgba(28, 56, 240, 0.46) 100%)`, + animation: `${angleKeyframes} 7s infinite ease-in-out both`, ':hover': { opacity: 0.8, + color: color, + }, + + vars: { + [color]: '#fef', + [angle]: fallbackVar(angle, '138deg'), }, }); diff --git a/fixtures/themed/src/styles.css.ts b/fixtures/themed/src/styles.css.ts index a68ffea22..8e15a8806 100644 --- a/fixtures/themed/src/styles.css.ts +++ b/fixtures/themed/src/styles.css.ts @@ -64,12 +64,16 @@ globalStyle(`body ${iDunno}:after`, { content: "'I am content'", }); -const blankVar1 = createVar(); +const blankVar1 = createVar({ + syntax: '', + inherits: false, + initialValue: '0.5', +}); const blankVar2 = createVar(); export const opacity = styleVariants( { - '1/2': fallbackVar(blankVar1, '0.5'), + '1/2': blankVar1, '1/4': fallbackVar(blankVar1, blankVar2, '0.25'), }, (value) => ({ diff --git a/packages/babel-plugin-debug-ids/src/index.test.ts b/packages/babel-plugin-debug-ids/src/index.test.ts index 9b9d4e862..0b879b7eb 100644 --- a/packages/babel-plugin-debug-ids/src/index.test.ts +++ b/packages/babel-plugin-debug-ids/src/index.test.ts @@ -217,6 +217,22 @@ describe('babel plugin', () => { `); }); + it('should handle typed createVar assigned to const', () => { + const source = ` + import { createVar } from '@vanilla-extract/css'; + + const myVar = createVar({ syntax: '*', inherits: true }); + `; + + expect(transform(source)).toMatchInlineSnapshot(` + import { createVar } from '@vanilla-extract/css'; + const myVar = createVar({ + syntax: '*', + inherits: true + }, "myVar"); + `); + }); + it('should handle createContainer assigned to const', () => { const source = ` import { createContainer } from '@vanilla-extract/css'; diff --git a/packages/babel-plugin-debug-ids/src/index.ts b/packages/babel-plugin-debug-ids/src/index.ts index 2b0ef6d2f..f5e826aef 100644 --- a/packages/babel-plugin-debug-ids/src/index.ts +++ b/packages/babel-plugin-debug-ids/src/index.ts @@ -31,7 +31,11 @@ const debuggableFunctionConfig = { maxParams: 2, }, createVar: { - maxParams: 1, + maxParams: 2, + hasDebugId: ({ arguments: args }) => { + const previousArg = args[args.length - 1]; + return t.isStringLiteral(previousArg) || t.isTemplateLiteral(previousArg); + }, }, recipe: { maxParams: 2, diff --git a/packages/css/src/transformCss.test.ts b/packages/css/src/transformCss.test.ts index 5debcbc1d..167152766 100644 --- a/packages/css/src/transformCss.test.ts +++ b/packages/css/src/transformCss.test.ts @@ -6,6 +6,14 @@ import { style } from './style'; setFileScope('test'); const testVar = createVar(); +const testPropertyVar = createVar( + { + syntax: '', + inherits: false, + initialValue: '0deg', + }, + 'test-property', +); const style1 = style({}); const style2 = style({}); @@ -1845,6 +1853,46 @@ describe('transformCss', () => { `); }); + it('should handle animations with vars', () => { + expect( + transformCss({ + composedClassLists: [], + localClassNames: ['testClass'], + cssObjs: [ + { + type: 'keyframes', + name: 'myAnimation', + rule: { + from: { + vars: { + '--my-var': 'red', + [testVar]: 'green', + }, + }, + to: { + vars: { + '--my-var': 'orange', + [testVar]: 'blue', + }, + }, + }, + }, + ], + }).join('\n'), + ).toMatchInlineSnapshot(` + @keyframes myAnimation { + from { + --my-var: red; + --skkcyc0: green; + } + to { + --my-var: orange; + --skkcyc0: blue; + } + } + `); + }); + it('should handle font face', () => { expect( transformCss({ @@ -2022,7 +2070,7 @@ describe('transformCss', () => { `); }); - it('should handle css vars', () => { + it('should handle simple css properties', () => { expect( transformCss({ composedClassLists: [], @@ -2076,6 +2124,60 @@ describe('transformCss', () => { `); }); + it('should handle complicated css properties', () => { + expect( + transformCss({ + composedClassLists: [], + localClassNames: ['testClass'], + cssObjs: [ + { + type: 'local', + selector: 'testClass', + rule: { + display: 'block', + vars: { + '--my-var': 'red', + [testPropertyVar]: '10deg', + }, + selectors: { + '&:nth-child(3)': { + vars: { + '--my-var': 'orange', + [testPropertyVar]: '20deg', + }, + }, + }, + '@media': { + 'screen and (min-width: 700px)': { + vars: { + '--my-var': 'yellow', + [testPropertyVar]: '50deg', + }, + }, + }, + }, + }, + ], + }).join('\n'), + ).toMatchInlineSnapshot(` + .testClass { + --my-var: red; + --test-property__skkcyc1: 10deg; + display: block; + } + .testClass:nth-child(3) { + --my-var: orange; + --test-property__skkcyc1: 20deg; + } + @media screen and (min-width: 700px) { + .testClass { + --my-var: yellow; + --test-property__skkcyc1: 50deg; + } + } + `); + }); + it('should cast property values to pixels when relevant', () => { expect( transformCss({ @@ -2288,13 +2390,13 @@ describe('transformCss', () => { ], }).join('\n'), ).toMatchInlineSnapshot(` - .skkcyc2 .skkcyc1:before, .skkcyc2 .skkcyc1:after { + .skkcyc3 .skkcyc2:before, .skkcyc3 .skkcyc2:after { background: black; } - ._1g1ptzo1._1g1ptzo10 .skkcyc1 { + ._1g1ptzo1._1g1ptzo10 .skkcyc2 { background: blue; } - ._1g1ptzo10._1g1ptzo1 .skkcyc1 { + ._1g1ptzo10._1g1ptzo1 .skkcyc2 { background: blue; } `); diff --git a/packages/css/src/transformCss.ts b/packages/css/src/transformCss.ts index 3750eaa09..daa14e4ef 100644 --- a/packages/css/src/transformCss.ts +++ b/packages/css/src/transformCss.ts @@ -13,6 +13,7 @@ import type { CSSSelectorBlock, Composition, WithQueries, + CSSPropertyBlock, } from './types'; import { markCompositionUsed } from './adapter'; import { forEach, omit, mapKeys } from './utils'; @@ -119,6 +120,7 @@ class Stylesheet { localClassNamesSearch: AhoCorasick; composedClassLists: Array<{ identifier: string; regex: RegExp }>; layers: Map>; + propertyRules: Array; constructor( localClassNames: Array, @@ -128,6 +130,7 @@ class Stylesheet { this.conditionalRulesets = [new ConditionalRuleset()]; this.fontFaceRules = []; this.keyframesRules = []; + this.propertyRules = []; this.localClassNamesMap = new Map( localClassNames.map((localClassName) => [localClassName, localClassName]), ); @@ -150,10 +153,17 @@ class Stylesheet { return; } + + if (root.type === 'property') { + this.propertyRules.push(root); + + return; + } + if (root.type === 'keyframes') { root.rule = Object.fromEntries( Object.entries(root.rule).map(([keyframe, rule]) => { - return [keyframe, this.transformProperties(rule)]; + return [keyframe, this.transformVars(this.transformProperties(rule))]; }), ); this.keyframesRules.push(root); @@ -582,6 +592,11 @@ class Stylesheet { css.push(renderCss({ '@font-face': fontFaceRule })); } + // Render property rules + for (const property of this.propertyRules) { + css.push(renderCss({ [`@property ${property.name}`]: property.rule })); + } + // Render keyframes for (const keyframe of this.keyframesRules) { css.push(renderCss({ [`@keyframes ${keyframe.name}`]: keyframe.rule })); diff --git a/packages/css/src/types.ts b/packages/css/src/types.ts index 419e87236..4af194842 100644 --- a/packages/css/src/types.ts +++ b/packages/css/src/types.ts @@ -27,7 +27,7 @@ export type CSSProperties = { }; export interface CSSKeyframes { - [time: string]: CSSProperties; + [time: string]: CSSPropertiesWithVars; } export type CSSPropertiesWithVars = CSSProperties & { @@ -107,12 +107,19 @@ export type CSSLayerDeclaration = { name: string; }; +export type CSSPropertyBlock = { + type: 'property'; + name: string; + rule: AtRule.Property; +}; + export type CSS = | CSSStyleBlock | CSSFontFaceBlock | CSSKeyframesBlock | CSSSelectorBlock - | CSSLayerDeclaration; + | CSSLayerDeclaration + | CSSPropertyBlock; export type FileScope = { packageName?: string; @@ -159,3 +166,28 @@ export type ThemeVars = MapLeafNodes< export type ClassNames = string | Array; export type ComplexStyleRule = StyleRule | Array; + +type _PropertySyntax = + | '' + | '' + | '' + | '' + | '' + | '' + | '' + | '' + | '' + | '' + | '' + | '