Skip to content

Commit

Permalink
feat: support animationStyle composition (#2701)
Browse files Browse the repository at this point in the history
* feat: init

* refactor: minimize changes

* docs: flesh out changeset

* chore: add tests
  • Loading branch information
segunadebayo authored Dec 8, 2024
1 parent 25c3037 commit 97a0e4d
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 10 deletions.
60 changes: 60 additions & 0 deletions .changeset/rotten-plums-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
'@pandacss/preset-panda': minor
'@pandacss/generator': minor
'@pandacss/types': minor
'@pandacss/core': minor
'@pandacss/dev': minor
---

Add support for animation styles. Animation styles focus solely on animations, allowing you to orchestrate animation
properties.

> Pairing animation styles with text styles and layer styles can make your styles a lot cleaner.
Here's an example of this:

```jsx
import { defineAnimationStyles } from '@pandacss/dev'

export const animationStyles = defineAnimationStyles({
'slide-fade-in': {
value: {
transformOrigin: 'var(--transform-origin)',
animationDuration: 'fast',
'&[data-placement^=top]': {
animationName: 'slide-from-top, fade-in',
},
'&[data-placement^=bottom]': {
animationName: 'slide-from-bottom, fade-in',
},
'&[data-placement^=left]': {
animationName: 'slide-from-left, fade-in',
},
'&[data-placement^=right]': {
animationName: 'slide-from-right, fade-in',
},
},
},
})
```

With that defined, I can use it in my recipe or css like so:

```js
export const popoverSlotRecipe = defineSlotRecipe({
slots: anatomy.keys(),
base: {
content: {
_open: {
animationStyle: 'scale-fade-in',
},
_closed: {
animationStyle: 'scale-fade-out',
},
},
},
})
```

This feature will drive consumers to lean in towards CSS for animations rather than JS. Composing animation names is a
powerful feature we should encourage consumers to use.
4 changes: 4 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ export function defineStyles(definition: SystemStyleObject) {
return definition
}

export function defineAnimationStyles(definition: CompositionStyles['animationStyles']) {
return definition
}

export type {
CompositionStyles,
Config,
Expand Down
44 changes: 38 additions & 6 deletions packages/core/__tests__/composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@ import { describe, expect, test } from 'vitest'
import { createRuleProcessor } from './fixture'

function css(styles: SystemStyleObject) {
return createRuleProcessor().css(styles).toCss()
return createRuleProcessor({
theme: {
animationStyles: {
'scale-fade-in': {
value: {
transformOrigin: 'var(--transform-origin)',
animationName: 'scale-in, fade-in',
},
},
'scale-fade-out': {
value: {
transformOrigin: 'var(--transform-origin)',
animationName: 'scale-out, fade-out',
},
},
},
},
})
.css(styles)
.toCss()
}

describe('compositions', () => {
Expand All @@ -26,11 +45,11 @@ describe('compositions', () => {
`)

expect(ctx.utility.transform('textStyle', 'headline.h5')).toMatchInlineSnapshot(`
{
"className": "textStyle_headline.h5",
"layer": "compositions",
"styles": {},
}
{
"className": "textStyle_headline.h5",
"layer": "compositions",
"styles": {},
}
`)
})

Expand Down Expand Up @@ -76,4 +95,17 @@ describe('compositions', () => {
}"
`)
})

test('should resolve animation styles', () => {
expect(css({ animationStyle: 'scale-fade-in' })).toMatchInlineSnapshot(`
"@layer utilities {
@layer compositions {
.animationStyle_scale-fade-in {
transform-origin: var(--transform-origin);
animation-name: scale-in, fade-in;
}
}
}"
`)
})
})
8 changes: 6 additions & 2 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,13 @@ export class Context {
}

setupCompositions = (theme: Theme): void => {
const { textStyles, layerStyles } = theme
const { textStyles, layerStyles, animationStyles } = theme

const compositions = compact({ textStyle: textStyles, layerStyle: layerStyles })
const compositions = compact({
textStyle: textStyles,
layerStyle: layerStyles,
animationStyle: animationStyles,
})

const stylesheetCtx = {
...this.baseSheetContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"content": "import type { CompositionStyleObject } from './system-types'\n\ninterface Token<T> {\n value: T\n description?: string\n}\n\ninterface Recursive<T> {\n [key: string]: Recursive<T> | T\n}\n\n/* -----------------------------------------------------------------------------\n * Text styles\n * -----------------------------------------------------------------------------*/\n\ntype TextStyleProperty =\n | 'font'\n | 'fontFamily'\n | 'fontFeatureSettings'\n | 'fontKerning'\n | 'fontLanguageOverride'\n | 'fontOpticalSizing'\n | 'fontPalette'\n | 'fontSize'\n | 'fontSizeAdjust'\n | 'fontStretch'\n | 'fontStyle'\n | 'fontSynthesis'\n | 'fontVariant'\n | 'fontVariantAlternates'\n | 'fontVariantCaps'\n | 'fontVariantLigatures'\n | 'fontVariantNumeric'\n | 'fontVariantPosition'\n | 'fontVariationSettings'\n | 'fontWeight'\n | 'hypens'\n | 'hyphenateCharacter'\n | 'hyphenateLimitChars'\n | 'letterSpacing'\n | 'lineBreak'\n | 'lineHeight'\n | 'quotes'\n | 'overflowWrap'\n | 'textCombineUpright'\n | 'textDecoration'\n | 'textDecorationColor'\n | 'textDecorationLine'\n | 'textDecorationSkipInk'\n | 'textDecorationStyle'\n | 'textDecorationThickness'\n | 'textEmphasis'\n | 'textEmphasisColor'\n | 'textEmphasisPosition'\n | 'textEmphasisStyle'\n | 'textIndent'\n | 'textJustify'\n | 'textOrientation'\n | 'textOverflow'\n | 'textRendering'\n | 'textShadow'\n | 'textTransform'\n | 'textUnderlineOffset'\n | 'textUnderlinePosition'\n | 'textWrap'\n | 'textWrapMode'\n | 'textWrapStyle'\n | 'verticalAlign'\n | 'whiteSpace'\n | 'wordBreak'\n | 'wordSpacing'\n\nexport type TextStyle = CompositionStyleObject<TextStyleProperty>\n\nexport type TextStyles = Recursive<Token<TextStyle>>\n\n/* -----------------------------------------------------------------------------\n * Layer styles\n * -----------------------------------------------------------------------------*/\n\ntype Placement =\n | 'Top'\n | 'Right'\n | 'Bottom'\n | 'Left'\n | 'Inline'\n | 'Block'\n | 'InlineStart'\n | 'InlineEnd'\n | 'BlockStart'\n | 'BlockEnd'\n\ntype Radius =\n | `Top${'Right' | 'Left'}`\n | `Bottom${'Right' | 'Left'}`\n | `Start${'Start' | 'End'}`\n | `End${'Start' | 'End'}`\n\ntype LayerStyleProperty =\n | 'background'\n | 'backgroundColor'\n | 'backgroundImage'\n | 'borderRadius'\n | 'border'\n | 'borderWidth'\n | 'borderColor'\n | 'borderStyle'\n | 'boxShadow'\n | 'filter'\n | 'backdropFilter'\n | 'transform'\n | 'color'\n | 'opacity'\n | 'backgroundBlendMode'\n | 'backgroundAttachment'\n | 'backgroundClip'\n | 'backgroundOrigin'\n | 'backgroundPosition'\n | 'backgroundRepeat'\n | 'backgroundSize'\n | `border${Placement}`\n | `border${Placement}Width`\n | 'borderRadius'\n | `border${Radius}Radius`\n | `border${Placement}Color`\n | `border${Placement}Style`\n | 'padding'\n | `padding${Placement}`\n\nexport type LayerStyle = CompositionStyleObject<LayerStyleProperty>\n\nexport type LayerStyles = Recursive<Token<LayerStyle>>\n\nexport interface CompositionStyles {\n textStyles: TextStyles\n layerStyles: LayerStyles\n}\n"
"content": "import type { CompositionStyleObject } from './system-types'\n\ninterface Token<T> {\n value: T\n description?: string\n}\n\ninterface Recursive<T> {\n [key: string]: Recursive<T> | T\n}\n\n/* -----------------------------------------------------------------------------\n * Text styles\n * -----------------------------------------------------------------------------*/\n\ntype TextStyleProperty =\n | 'font'\n | 'fontFamily'\n | 'fontFeatureSettings'\n | 'fontKerning'\n | 'fontLanguageOverride'\n | 'fontOpticalSizing'\n | 'fontPalette'\n | 'fontSize'\n | 'fontSizeAdjust'\n | 'fontStretch'\n | 'fontStyle'\n | 'fontSynthesis'\n | 'fontVariant'\n | 'fontVariantAlternates'\n | 'fontVariantCaps'\n | 'fontVariantLigatures'\n | 'fontVariantNumeric'\n | 'fontVariantPosition'\n | 'fontVariationSettings'\n | 'fontWeight'\n | 'hypens'\n | 'hyphenateCharacter'\n | 'hyphenateLimitChars'\n | 'letterSpacing'\n | 'lineBreak'\n | 'lineHeight'\n | 'quotes'\n | 'overflowWrap'\n | 'textCombineUpright'\n | 'textDecoration'\n | 'textDecorationColor'\n | 'textDecorationLine'\n | 'textDecorationSkipInk'\n | 'textDecorationStyle'\n | 'textDecorationThickness'\n | 'textEmphasis'\n | 'textEmphasisColor'\n | 'textEmphasisPosition'\n | 'textEmphasisStyle'\n | 'textIndent'\n | 'textJustify'\n | 'textOrientation'\n | 'textOverflow'\n | 'textRendering'\n | 'textShadow'\n | 'textTransform'\n | 'textUnderlineOffset'\n | 'textUnderlinePosition'\n | 'textWrap'\n | 'textWrapMode'\n | 'textWrapStyle'\n | 'verticalAlign'\n | 'whiteSpace'\n | 'wordBreak'\n | 'wordSpacing'\n\nexport type TextStyle = CompositionStyleObject<TextStyleProperty>\n\nexport type TextStyles = Recursive<Token<TextStyle>>\n\n/* -----------------------------------------------------------------------------\n * Layer styles\n * -----------------------------------------------------------------------------*/\n\ntype Placement =\n | 'Top'\n | 'Right'\n | 'Bottom'\n | 'Left'\n | 'Inline'\n | 'Block'\n | 'InlineStart'\n | 'InlineEnd'\n | 'BlockStart'\n | 'BlockEnd'\n\ntype Radius =\n | `Top${'Right' | 'Left'}`\n | `Bottom${'Right' | 'Left'}`\n | `Start${'Start' | 'End'}`\n | `End${'Start' | 'End'}`\n\ntype LayerStyleProperty =\n | 'background'\n | 'backgroundColor'\n | 'backgroundImage'\n | 'borderRadius'\n | 'border'\n | 'borderWidth'\n | 'borderColor'\n | 'borderStyle'\n | 'boxShadow'\n | 'filter'\n | 'backdropFilter'\n | 'transform'\n | 'color'\n | 'opacity'\n | 'backgroundBlendMode'\n | 'backgroundAttachment'\n | 'backgroundClip'\n | 'backgroundOrigin'\n | 'backgroundPosition'\n | 'backgroundRepeat'\n | 'backgroundSize'\n | `border${Placement}`\n | `border${Placement}Width`\n | 'borderRadius'\n | `border${Radius}Radius`\n | `border${Placement}Color`\n | `border${Placement}Style`\n | 'padding'\n | `padding${Placement}`\n\nexport type LayerStyle = CompositionStyleObject<LayerStyleProperty>\n\nexport type LayerStyles = Recursive<Token<LayerStyle>>\n\n/* -----------------------------------------------------------------------------\n * Motion styles\n * -----------------------------------------------------------------------------*/\n\ntype AnimationStyleProperty =\n | 'animation'\n | 'animationComposition'\n | 'animationDelay'\n | 'animationDirection'\n | 'animationDuration'\n | 'animationFillMode'\n | 'animationIterationCount'\n | 'animationName'\n | 'animationPlayState'\n | 'animationTimingFunction'\n | 'animationRange'\n | 'animationRangeStart'\n | 'animationRangeEnd'\n | 'animationTimeline'\n | 'transformOrigin'\n\nexport type AnimationStyle = CompositionStyleObject<AnimationStyleProperty>\n\nexport type AnimationStyles = Recursive<Token<AnimationStyle>>\n\nexport interface CompositionStyles {\n textStyles: TextStyles\n layerStyles: LayerStyles\n animationStyles: AnimationStyles\n}\n"
}
1 change: 1 addition & 0 deletions packages/generator/src/artifacts/types/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const generateTypesEntry = (ctx: Context, isJsxRequired: boolean) => {
export function defineStyles(definition: SystemStyleObject): SystemStyleObject
export function defineGlobalStyles(definition: GlobalStyleObject): Panda.GlobalStyleObject
export function defineTextStyles(definition: CompositionStyles['textStyles']): Panda.TextStyles
export function defineAnimationStyles(definition: CompositionStyles['animationStyles']): Panda.AnimationStyles
export function defineLayerStyles(definition: CompositionStyles['layerStyles']): Panda.LayerStyles
export function definePattern<T extends PatternProperties>(config: PatternConfig<T>): Panda.PatternConfig
export function defineParts<T extends Parts>(parts: T): (config: Partial<Record<keyof T, SystemStyleObject>>) => Partial<Record<keyof T, SystemStyleObject>>
Expand Down
26 changes: 26 additions & 0 deletions packages/studio/styled-system/types/composition.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,33 @@ export type LayerStyle = CompositionStyleObject<LayerStyleProperty>

export type LayerStyles = Recursive<Token<LayerStyle>>

/* -----------------------------------------------------------------------------
* Motion styles
* -----------------------------------------------------------------------------*/

type AnimationStyleProperty =
| 'animation'
| 'animationComposition'
| 'animationDelay'
| 'animationDirection'
| 'animationDuration'
| 'animationFillMode'
| 'animationIterationCount'
| 'animationName'
| 'animationPlayState'
| 'animationTimingFunction'
| 'animationRange'
| 'animationRangeStart'
| 'animationRangeEnd'
| 'animationTimeline'
| 'transformOrigin'

export type AnimationStyle = CompositionStyleObject<AnimationStyleProperty>

export type AnimationStyles = Recursive<Token<AnimationStyle>>

export interface CompositionStyles {
textStyles: TextStyles
layerStyles: LayerStyles
animationStyles: AnimationStyles
}
1 change: 1 addition & 0 deletions packages/studio/styled-system/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare module '@pandacss/dev' {
export function defineStyles(definition: SystemStyleObject): SystemStyleObject
export function defineGlobalStyles(definition: GlobalStyleObject): Panda.GlobalStyleObject
export function defineTextStyles(definition: CompositionStyles['textStyles']): Panda.TextStyles
export function defineAnimationStyles(definition: CompositionStyles['animationStyles']): Panda.AnimationStyles
export function defineLayerStyles(definition: CompositionStyles['layerStyles']): Panda.LayerStyles
export function definePattern<T extends PatternProperties>(config: PatternConfig<T>): Panda.PatternConfig
export function defineParts<T extends Parts>(parts: T): (config: Partial<Record<keyof T, SystemStyleObject>>) => Partial<Record<keyof T, SystemStyleObject>>
Expand Down
26 changes: 26 additions & 0 deletions packages/types/src/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,33 @@ export type LayerStyle = CompositionStyleObject<LayerStyleProperty>

export type LayerStyles = Recursive<Token<LayerStyle>>

/* -----------------------------------------------------------------------------
* Motion styles
* -----------------------------------------------------------------------------*/

type AnimationStyleProperty =
| 'animation'
| 'animationComposition'
| 'animationDelay'
| 'animationDirection'
| 'animationDuration'
| 'animationFillMode'
| 'animationIterationCount'
| 'animationName'
| 'animationPlayState'
| 'animationTimingFunction'
| 'animationRange'
| 'animationRangeStart'
| 'animationRangeEnd'
| 'animationTimeline'
| 'transformOrigin'

export type AnimationStyle = CompositionStyleObject<AnimationStyleProperty>

export type AnimationStyles = Recursive<Token<AnimationStyle>>

export interface CompositionStyles {
textStyles: TextStyles
layerStyles: LayerStyles
animationStyles: AnimationStyles
}
6 changes: 5 additions & 1 deletion packages/types/src/theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { LayerStyles, TextStyles } from './composition'
import type { AnimationStyles, LayerStyles, TextStyles } from './composition'
import type { RecipeConfig, SlotRecipeConfig } from './recipe'
import type { CssKeyframes } from './system-types'
import type { SemanticTokens, Tokens } from './tokens'
Expand Down Expand Up @@ -28,6 +28,10 @@ export interface Theme {
* The layer styles for your project.
*/
layerStyles?: LayerStyles
/**
* The animation styles for your project.
*/
animationStyles?: AnimationStyles
/**
* Multi-variant style definitions for your project.
* Useful for defining component styles.
Expand Down

0 comments on commit 97a0e4d

Please sign in to comment.