Skip to content

refactor(motion): simplify Fade & Scale variant creation with createPresenceComponentVariant #34042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual regressions to review in the fluentuiv9 Visual Regression Report

Drawer 2 screenshots
Image Name Diff(in Pixels) Image Type
Drawer.overlay drawer full - Dark Mode.chromium.png 919 Changed
Drawer.overlay drawer full.chromium.png 1138 Changed

"type": "minor",
"comment": "fix(motion): createPresenceComponentVariant did not support motion arrays",
"packageName": "@fluentui/react-motion",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';
import type { PresenceMotionCreator } from '../../types';
import {
motionTokens,
createPresenceComponent,
PresenceMotionFn,
createPresenceComponentVariant,
} from '@fluentui/react-motion';
import { fadeAtom } from '../../atoms/fade-atom';

type FadeVariantParams = {
/** Time (ms) for the enter transition (fade-in). Defaults to the `durationNormal` value (200 ms). */
enterDuration?: number;
duration?: number;

/** Easing curve for the enter transition (fade-in). Defaults to the `easeEase` value. */
enterEasing?: string;
easing?: string;

/** Time (ms) for the exit transition (fade-out). Defaults to the `enterDuration` param for symmetry. */
exitDuration?: number;
Expand All @@ -17,19 +21,21 @@ type FadeVariantParams = {
};

/** Define a presence motion for fade in/out */
export const createFadePresence: PresenceMotionCreator<FadeVariantParams> = ({
enterDuration = motionTokens.durationNormal,
enterEasing = motionTokens.curveEasyEase,
exitDuration = enterDuration,
exitEasing = enterEasing,
} = {}) => ({
enter: fadeAtom({ direction: 'enter', duration: enterDuration, easing: enterEasing }),
exit: fadeAtom({ direction: 'exit', duration: exitDuration, easing: exitEasing }),
});
export const fadePresenceFn: PresenceMotionFn<FadeVariantParams> = ({
duration = motionTokens.durationNormal,
easing = motionTokens.curveEasyEase,
exitDuration = duration,
exitEasing = easing,
}) => {
return {
enter: fadeAtom({ direction: 'enter', duration, easing }),
exit: fadeAtom({ direction: 'exit', duration: exitDuration, easing: exitEasing }),
};
};

/** A React component that applies fade in/out transitions to its children. */
export const Fade = createPresenceComponent(createFadePresence());
export const Fade = createPresenceComponent(fadePresenceFn);

export const FadeSnappy = createPresenceComponent(createFadePresence({ enterDuration: motionTokens.durationFast }));
export const FadeSnappy = createPresenceComponentVariant(Fade, { duration: motionTokens.durationFast });

export const FadeRelaxed = createPresenceComponent(createFadePresence({ enterDuration: motionTokens.durationGentle }));
export const FadeRelaxed = createPresenceComponentVariant(Fade, { duration: motionTokens.durationGentle });
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Fade, FadeRelaxed, FadeSnappy, createFadePresence } from './Fade';
export { Fade, FadeRelaxed, FadeSnappy } from './Fade';
Original file line number Diff line number Diff line change
@@ -1,57 +1,63 @@
import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';
import { PresenceMotionFnCreator } from '../../types';
import { ScaleRuntimeParams_unstable, ScaleVariantParams_unstable } from './Scale.types';
import {
motionTokens,
createPresenceComponent,
PresenceMotionFn,
createPresenceComponentVariant,
} from '@fluentui/react-motion';

/** Define a presence motion for scale in/out */
export const createScalePresence: PresenceMotionFnCreator<ScaleVariantParams_unstable, ScaleRuntimeParams_unstable> =
({
enterDuration = motionTokens.durationGentle,
enterEasing = motionTokens.curveDecelerateMax,
exitDuration = motionTokens.durationNormal,
exitEasing = motionTokens.curveAccelerateMax,
} = {}) =>
({ animateOpacity = true }) => {
const fromOpacity = animateOpacity ? 0 : 1;
const toOpacity = 1;
const fromScale = 0.9; // Could be a custom param in the future
const toScale = 1;
const scalePresenceFn: PresenceMotionFn<{
duration?: number;
easing?: string;
exitDuration?: number;
exitEasing?: string;
fromScale?: number;
animateOpacity?: boolean;
}> = ({
duration = motionTokens.durationNormal,
easing = motionTokens.curveDecelerateMid,
exitDuration = duration,
exitEasing = motionTokens.curveAccelerateMid,
fromScale = 0.9,
animateOpacity = true,
}) => {
const fromOpacity = animateOpacity ? 0 : 1;
const toOpacity = 1;
const toScale = 1;

const enterKeyframes = [
{ opacity: fromOpacity, transform: `scale3d(${fromScale}, ${fromScale}, 1)`, visibility: 'visible' },
{ opacity: toOpacity, transform: `scale3d(${toScale}, ${toScale}, 1)` },
];
// TODO: use fadeAtom
// TODO: make scaleAtom
const enterKeyframes = [
{ opacity: fromOpacity, transform: `scale3d(${fromScale}, ${fromScale}, 1)`, visibility: 'visible' },
{ opacity: toOpacity, transform: `scale3d(${toScale}, ${toScale}, 1)` },
];

const exitKeyframes = [
{ opacity: toOpacity, transform: `scale3d(${toScale}, ${toScale}, 1)` },
{ opacity: fromOpacity, transform: `scale3d(${fromScale}, ${fromScale}, 1)`, visibility: 'hidden' },
];
return {
enter: {
duration: enterDuration,
easing: enterEasing,
keyframes: enterKeyframes,
},
exit: { duration: exitDuration, easing: exitEasing, keyframes: exitKeyframes },
};
const exitKeyframes = [
{ opacity: toOpacity, transform: `scale3d(${toScale}, ${toScale}, 1)` },
{ opacity: fromOpacity, transform: `scale3d(${fromScale}, ${fromScale}, 1)`, visibility: 'hidden' },
];

return {
enter: {
duration,
easing,
keyframes: enterKeyframes,
},
exit: { duration: exitDuration, easing: exitEasing, keyframes: exitKeyframes },
};
};

/** A React component that applies scale in/out transitions to its children. */
export const Scale = createPresenceComponent(createScalePresence());
export const Scale = createPresenceComponent(scalePresenceFn);

export const ScaleSnappy = createPresenceComponent(
createScalePresence({
enterDuration: motionTokens.durationNormal,
enterEasing: motionTokens.curveDecelerateMax,
exitDuration: motionTokens.durationFast,
exitEasing: motionTokens.curveAccelerateMax,
}),
);
export const ScaleSnappy = createPresenceComponentVariant(Scale, {
duration: motionTokens.durationFast,
easing: motionTokens.curveDecelerateMax,
exitEasing: motionTokens.curveAccelerateMax,
});

export const ScaleRelaxed = createPresenceComponent(
createScalePresence({
enterDuration: motionTokens.durationSlow,
enterEasing: motionTokens.curveDecelerateMax,
exitDuration: motionTokens.durationGentle,
exitEasing: motionTokens.curveAccelerateMax,
}),
);
export const ScaleRelaxed = createPresenceComponentVariant(Scale, {
duration: motionTokens.durationGentle,
easing: motionTokens.curveDecelerateMid,
exitEasing: motionTokens.curveAccelerateMid,
});
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Scale, ScaleRelaxed, ScaleSnappy, createScalePresence } from './Scale';
export { Scale, ScaleRelaxed, ScaleSnappy } from './Scale';
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
```tsx
import { motionTokens } from '@fluentui/react-components';

const CustomFadeVariant = createPresenceComponent(
createFadePresence({
enterDuration: motionTokens.durationSlow,
enterEasing: motionTokens.curveEasyEaseMax,
exitDuration: motionTokens.durationNormal,
}),
);
const CustomFadeVariant = createPresenceComponentVariant(Fade, {
duration: motionTokens.durationSlower,
exitDuration: motionTokens.durationFast,
});

const CustomFade = ({ visible }) => (
<CustomFadeVariant unmountOnExit visible={visible}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import {
createPresenceComponent,
createPresenceComponentVariant,
Field,
makeStyles,
mergeClasses,
Expand All @@ -10,7 +10,7 @@ import {
Switch,
tokens,
} from '@fluentui/react-components';
import { createFadePresence } from '@fluentui/react-motion-components-preview';
import { Fade } from '@fluentui/react-motion-components-preview';

import description from './FadeCustomization.stories.md';

Expand Down Expand Up @@ -54,13 +54,10 @@ const useClasses = makeStyles({
},
});

const CustomFadeVariant = createPresenceComponent(
createFadePresence({
enterDuration: motionTokens.durationSlow,
enterEasing: motionTokens.curveEasyEaseMax,
exitDuration: motionTokens.durationNormal,
}),
);
const CustomFadeVariant = createPresenceComponentVariant(Fade, {
duration: motionTokens.durationSlower,
exitDuration: motionTokens.durationFast,
});

const LoremIpsum = () => (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@
import { motionTokens, createPresenceComponentVariant } from '@fluentui/react-components';
import { createScalePresence } from '@fluentui/react-motion-components-preview';

const CustomScaleVariant = createPresenceComponent(
createScalePresence({
enterDuration: motionTokens.durationSlow,
enterEasing: motionTokens.curveEasyEaseMax,
exitDuration: motionTokens.durationNormal,
exitEasing: motionTokens.curveEasyEaseMax,
}),
);
const CustomScaleVariant = createPresenceComponentVariant(Scale, {
duration: [motionTokens.durationSlow, motionTokens.durationNormal],
easing: motionTokens.curveEasyEaseMax,
});

const CustomScale = ({ visible }) => (
<CustomScaleVariant animateOpacity={false} unmountOnExit visible={visible}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import {
createPresenceComponent,
createPresenceComponentVariant,
Field,
makeStyles,
mergeClasses,
Expand All @@ -10,9 +11,9 @@ import {
Switch,
tokens,
} from '@fluentui/react-components';
import { createScalePresence } from '@fluentui/react-motion-components-preview';

import description from './ScaleCustomization.stories.md';
import { Scale } from '../../../library/src/index';

const useClasses = makeStyles({
container: {
Expand Down Expand Up @@ -54,14 +55,26 @@ const useClasses = makeStyles({
},
});

const CustomScaleVariant = createPresenceComponent(
createScalePresence({
enterDuration: motionTokens.durationSlow,
enterEasing: motionTokens.curveEasyEaseMax,
exitDuration: motionTokens.durationNormal,
exitEasing: motionTokens.curveEasyEaseMax,
}),
);
const curveEmphasized = `linear(
0, 0.002, 0.01 3.6%, 0.034, 0.074 9.1%, 0.128 11.4%, 0.194 13.4%, 0.271 15%,
0.344 16.1%, 0.544, 0.66 20.6%, 0.717 22.4%, 0.765 24.6%, 0.808 27.3%,
0.845 30.4%, 0.883 35.1%, 0.916 40.6%, 0.942 47.2%, 0.963 55%, 0.979 64%,
0.991 74.4%, 0.998 86.4%, 1
)`;

const curveSpringOut = `linear(
0, 0.009, 0.035 2.1%, 0.141, 0.281 6.7%, 0.723 12.9%, 0.938 16.7%, 1.017,
1.077, 1.121, 1.149 24.3%, 1.159, 1.163, 1.161, 1.154 29.9%, 1.129 32.8%,
1.051 39.6%, 1.017 43.1%, 0.991, 0.977 51%, 0.974 53.8%, 0.975 57.1%,
0.997 69.8%, 1.003 76.9%, 1.004 83.8%, 1
)`;

const curveEaseOutBack = `cubic-bezier(0.34, 1.56, 0.64, 1)`;

const CustomScaleVariant = createPresenceComponentVariant(Scale, {
duration: [motionTokens.durationSlow, motionTokens.durationNormal],
easing: curveSpringOut,
});

const LoremIpsum = () => (
<>
Expand Down Expand Up @@ -135,6 +148,9 @@ export const Customization = () => {
imperativeRef={motionRef}
visible={visible}
unmountOnExit={unmountOnExit}
fromScale={0.5}
easing={[curveEaseOutBack, 'linear']}
duration={300}
>
<div className={classes.card}>
<LoremIpsum />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function createMotionComponent<MotionParams extends Record<string, Motion
export function createPresenceComponent<MotionParams extends Record<string, MotionParam> = {}>(value: PresenceMotion | PresenceMotionFn<MotionParams>): PresenceComponent<MotionParams>;

// @public (undocumented)
export function createPresenceComponentVariant<MotionParams extends Record<string, MotionParam> = {}>(component: PresenceComponent<MotionParams>, override: PresenceOverride): PresenceComponent<MotionParams>;
export function createPresenceComponentVariant<MotionParams extends Record<string, MotionParam> = {}>(component: PresenceComponent<MotionParams>, variantParams: Partial<MotionParams>): PresenceComponent<MotionParams>;

// @public (undocumented)
export const curves: {
Expand Down
Loading
Loading