From 97a0e4da7ed2fb85dbe55b1bb75e0f610399e4ee Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Sun, 8 Dec 2024 18:04:54 +0000 Subject: [PATCH] feat: support `animationStyle` composition (#2701) * feat: init * refactor: minimize changes * docs: flesh out changeset * chore: add tests --- .changeset/rotten-plums-sniff.md | 60 +++++++++++++++++++ packages/cli/src/index.ts | 4 ++ packages/core/__tests__/composition.test.ts | 44 ++++++++++++-- packages/core/src/context.ts | 8 ++- .../artifacts/generated/composition.d.ts.json | 2 +- .../generator/src/artifacts/types/main.ts | 1 + .../styled-system/types/composition.d.ts | 26 ++++++++ .../studio/styled-system/types/global.d.ts | 1 + packages/types/src/composition.ts | 26 ++++++++ packages/types/src/theme.ts | 6 +- 10 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 .changeset/rotten-plums-sniff.md diff --git a/.changeset/rotten-plums-sniff.md b/.changeset/rotten-plums-sniff.md new file mode 100644 index 0000000000..dce69027f4 --- /dev/null +++ b/.changeset/rotten-plums-sniff.md @@ -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. diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 896150fecb..c30eb6682c 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -127,6 +127,10 @@ export function defineStyles(definition: SystemStyleObject) { return definition } +export function defineAnimationStyles(definition: CompositionStyles['animationStyles']) { + return definition +} + export type { CompositionStyles, Config, diff --git a/packages/core/__tests__/composition.test.ts b/packages/core/__tests__/composition.test.ts index a681019d2e..f9d9a7bd24 100644 --- a/packages/core/__tests__/composition.test.ts +++ b/packages/core/__tests__/composition.test.ts @@ -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', () => { @@ -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": {}, + } `) }) @@ -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; + } + } + }" + `) + }) }) diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index 2289980ef9..ee6a0fef8a 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -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, diff --git a/packages/generator/src/artifacts/generated/composition.d.ts.json b/packages/generator/src/artifacts/generated/composition.d.ts.json index fd7bf47f3d..c4841a31ce 100644 --- a/packages/generator/src/artifacts/generated/composition.d.ts.json +++ b/packages/generator/src/artifacts/generated/composition.d.ts.json @@ -1,3 +1,3 @@ { - "content": "import type { CompositionStyleObject } from './system-types'\n\ninterface Token {\n value: T\n description?: string\n}\n\ninterface Recursive {\n [key: string]: Recursive | 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\n\nexport type TextStyles = Recursive>\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\n\nexport type LayerStyles = Recursive>\n\nexport interface CompositionStyles {\n textStyles: TextStyles\n layerStyles: LayerStyles\n}\n" + "content": "import type { CompositionStyleObject } from './system-types'\n\ninterface Token {\n value: T\n description?: string\n}\n\ninterface Recursive {\n [key: string]: Recursive | 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\n\nexport type TextStyles = Recursive>\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\n\nexport type LayerStyles = Recursive>\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\n\nexport type AnimationStyles = Recursive>\n\nexport interface CompositionStyles {\n textStyles: TextStyles\n layerStyles: LayerStyles\n animationStyles: AnimationStyles\n}\n" } \ No newline at end of file diff --git a/packages/generator/src/artifacts/types/main.ts b/packages/generator/src/artifacts/types/main.ts index a58728321e..d72a60d66f 100644 --- a/packages/generator/src/artifacts/types/main.ts +++ b/packages/generator/src/artifacts/types/main.ts @@ -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(config: PatternConfig): Panda.PatternConfig export function defineParts(parts: T): (config: Partial>) => Partial> diff --git a/packages/studio/styled-system/types/composition.d.ts b/packages/studio/styled-system/types/composition.d.ts index 62d63dec17..c21dc8fa1a 100644 --- a/packages/studio/styled-system/types/composition.d.ts +++ b/packages/studio/styled-system/types/composition.d.ts @@ -132,7 +132,33 @@ export type LayerStyle = CompositionStyleObject export type LayerStyles = Recursive> +/* ----------------------------------------------------------------------------- + * Motion styles + * -----------------------------------------------------------------------------*/ + +type AnimationStyleProperty = + | 'animation' + | 'animationComposition' + | 'animationDelay' + | 'animationDirection' + | 'animationDuration' + | 'animationFillMode' + | 'animationIterationCount' + | 'animationName' + | 'animationPlayState' + | 'animationTimingFunction' + | 'animationRange' + | 'animationRangeStart' + | 'animationRangeEnd' + | 'animationTimeline' + | 'transformOrigin' + +export type AnimationStyle = CompositionStyleObject + +export type AnimationStyles = Recursive> + export interface CompositionStyles { textStyles: TextStyles layerStyles: LayerStyles + animationStyles: AnimationStyles } diff --git a/packages/studio/styled-system/types/global.d.ts b/packages/studio/styled-system/types/global.d.ts index 6425b40b68..58b77155e5 100644 --- a/packages/studio/styled-system/types/global.d.ts +++ b/packages/studio/styled-system/types/global.d.ts @@ -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(config: PatternConfig): Panda.PatternConfig export function defineParts(parts: T): (config: Partial>) => Partial> diff --git a/packages/types/src/composition.ts b/packages/types/src/composition.ts index 945cc3e9b0..c6ae6666b9 100644 --- a/packages/types/src/composition.ts +++ b/packages/types/src/composition.ts @@ -131,7 +131,33 @@ export type LayerStyle = CompositionStyleObject export type LayerStyles = Recursive> +/* ----------------------------------------------------------------------------- + * Motion styles + * -----------------------------------------------------------------------------*/ + +type AnimationStyleProperty = + | 'animation' + | 'animationComposition' + | 'animationDelay' + | 'animationDirection' + | 'animationDuration' + | 'animationFillMode' + | 'animationIterationCount' + | 'animationName' + | 'animationPlayState' + | 'animationTimingFunction' + | 'animationRange' + | 'animationRangeStart' + | 'animationRangeEnd' + | 'animationTimeline' + | 'transformOrigin' + +export type AnimationStyle = CompositionStyleObject + +export type AnimationStyles = Recursive> + export interface CompositionStyles { textStyles: TextStyles layerStyles: LayerStyles + animationStyles: AnimationStyles } diff --git a/packages/types/src/theme.ts b/packages/types/src/theme.ts index d05b9c8ec3..3b8375bf32 100644 --- a/packages/types/src/theme.ts +++ b/packages/types/src/theme.ts @@ -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' @@ -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.