Skip to content
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

refactor(Collapse): streamline atom functions #33463

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 @@
{
robertpenner marked this conversation as resolved.
Show resolved Hide resolved
"type": "patch",
"comment": "refactor(Collapse): streamline atom functions",
"packageName": "@fluentui/react-motion-components-preview",
"email": "robertpenner@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AtomMotion, PresenceDirection, motionTokens } from '@fluentui/react-motion';

interface FadeAtomParams {
direction: PresenceDirection;
duration: number;
easing?: string;
fromValue?: number;
}

/**
* Generates a motion atom object for a fade in or fade out.
* @param direction - The functional direction of the motion: 'enter' or 'exit'.
* @param duration - The duration of the motion in milliseconds.
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveLinear`.
* @param fromValue - The starting opacity value. Defaults to 0.
* @returns A motion atom object with opacity keyframes and the supplied duration and easing.
*/
export const fadeAtom = ({
direction,
duration,
easing = motionTokens.curveLinear,
fromValue = 0,
}: FadeAtomParams): AtomMotion => {
const keyframes = [{ opacity: fromValue }, { opacity: 1 }];
if (direction === 'exit') {
keyframes.reverse();
}
return {
keyframes,
duration,
easing,
};
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { motionTokens, createPresenceComponent, AtomMotion } from '@fluentui/react-motion';
import type { PresenceMotionFnCreator } from '../../types';
import type { CollapseDelayedVariantParams, CollapseRuntimeParams, CollapseVariantParams } from './collapse-types';
import {
sizeEnterAtom,
whitespaceEnterAtom,
opacityEnterAtom,
opacityExitAtom,
sizeExitAtom,
whitespaceExitAtom,
} from './collapse-atoms';
import { sizeEnterAtom, sizeExitAtom, whitespaceAtom } from './collapse-atoms';
import { fadeAtom } from '../../atoms/fade-atom';

/** Define a presence motion for collapse/expand that can stagger the size and opacity motions by a given delay. */
export const createCollapseDelayedPresence: PresenceMotionFnCreator<
Expand All @@ -32,52 +26,29 @@ export const createCollapseDelayedPresence: PresenceMotionFnCreator<
// ----- ENTER -----
// The enter transition is an array of up to 3 motion atoms: size, whitespace and opacity.
const enterAtoms: AtomMotion[] = [
sizeEnterAtom({
orientation,
duration: enterSizeDuration,
easing: enterEasing,
element,
}),
whitespaceEnterAtom({
orientation,
duration: enterSizeDuration,
easing: enterEasing,
}),
sizeEnterAtom({ orientation, duration: enterSizeDuration, easing: enterEasing, element }),
whitespaceAtom({ direction: 'enter', orientation, duration: enterSizeDuration, easing: enterEasing }),
];
// Fade in only if animateOpacity is true. Otherwise, leave opacity unaffected.
if (animateOpacity) {
enterAtoms.push(
opacityEnterAtom({
duration: enterOpacityDuration,
easing: enterEasing,
delay: enterDelay,
}),
);
enterAtoms.push({
...fadeAtom({ direction: 'enter', duration: enterOpacityDuration, easing: enterEasing }),
delay: enterDelay,
fill: 'both',
});
}

// ----- EXIT -----
// The exit transition is an array of up to 3 motion atoms: opacity, size and whitespace.
const exitAtoms: AtomMotion[] = [];
// Fade out only if animateOpacity is true. Otherwise, leave opacity unaffected.
if (animateOpacity) {
exitAtoms.push(
opacityExitAtom({
duration: exitOpacityDuration,
easing: exitEasing,
}),
);
exitAtoms.push(fadeAtom({ direction: 'exit', duration: exitOpacityDuration, easing: exitEasing }));
}
exitAtoms.push(
sizeExitAtom({
orientation,
duration: exitSizeDuration,
easing: exitEasing,
element,
delay: exitDelay,
}),
);
exitAtoms.push(
whitespaceExitAtom({
sizeExitAtom({ orientation, duration: exitSizeDuration, easing: exitEasing, element, delay: exitDelay }),
whitespaceAtom({
direction: 'exit',
orientation,
duration: exitSizeDuration,
easing: exitEasing,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AtomMotion } from '@fluentui/react-motion/src/types';
import type { CollapseOrientation } from './collapse-types';
import { AtomMotion, PresenceDirection } from '@fluentui/react-motion';
import { CollapseOrientation } from './collapse-types';

// ----- SIZE -----

Expand All @@ -11,19 +11,21 @@ const sizeValuesForOrientation = (orientation: CollapseOrientation, element: Ele
return { sizeName, overflowName, toSize };
};

interface SizeEnterAtomParams {
orientation: CollapseOrientation;
duration: number;
easing: string;
element: HTMLElement;
fromSize?: string;
}

export const sizeEnterAtom = ({
orientation,
duration,
easing,
element,
fromSize = '0',
}: {
orientation: CollapseOrientation;
duration: number;
easing: string;
element: HTMLElement;
fromSize?: string;
}): AtomMotion => {
}: SizeEnterAtomParams): AtomMotion => {
const { sizeName, overflowName, toSize } = sizeValuesForOrientation(orientation, element);

return {
Expand All @@ -37,21 +39,18 @@ export const sizeEnterAtom = ({
};
};

interface SizeExitAtomParams extends SizeEnterAtomParams {
delay?: number;
}

export const sizeExitAtom = ({
orientation,
duration,
easing,
element,
delay = 0,
fromSize = '0',
}: {
orientation: CollapseOrientation;
duration: number;
easing: string;
element: HTMLElement;
delay?: number;
fromSize?: string;
}): AtomMotion => {
}: SizeExitAtomParams): AtomMotion => {
const { sizeName, overflowName, toSize } = sizeValuesForOrientation(orientation, element);

return {
Expand Down Expand Up @@ -88,82 +87,34 @@ const whitespaceValuesForOrientation = (orientation: CollapseOrientation) => {
};
};

// Because a height of zero does not eliminate padding or margin,
// we will create keyframes to animate them to zero.
export const whitespaceEnterAtom = ({
orientation,
duration,
easing,
}: {
interface WhitespaceAtomParams {
direction: PresenceDirection;
orientation: CollapseOrientation;
duration: number;
easing: string;
}): AtomMotion => {
const { paddingStart, paddingEnd, marginStart, marginEnd } = whitespaceValuesForOrientation(orientation);
return {
// Animate from whitespace of zero to the current whitespace, by omitting the ending keyframe.
keyframes: [{ [paddingStart]: '0', [paddingEnd]: '0', [marginStart]: '0', [marginEnd]: '0', offset: 0 }],
duration,
easing,
};
};
delay?: number;
}

export const whitespaceExitAtom = ({
export const whitespaceAtom = ({
direction,
orientation,
duration,
easing,
delay = 0,
}: {
orientation: CollapseOrientation;
duration: number;
easing: string;
delay?: number;
}): AtomMotion => {
}: WhitespaceAtomParams): AtomMotion => {
const { paddingStart, paddingEnd, marginStart, marginEnd } = whitespaceValuesForOrientation(orientation);
return {
// Animate from the current whitespace to whitespace of zero, by using offset 1 and omitting the starting keyframe.
keyframes: [{ [paddingStart]: '0', [paddingEnd]: '0', [marginStart]: '0', [marginEnd]: '0', offset: 1 }],
// The keyframe with zero whitespace is at the start for enter and at the end for exit.
const offset = direction === 'enter' ? 0 : 1;
const keyframes = [{ [paddingStart]: '0', [paddingEnd]: '0', [marginStart]: '0', [marginEnd]: '0', offset }];

const atom: AtomMotion = {
keyframes,
duration,
easing,
fill: 'forwards',
delay,
};
if (direction === 'exit') {
atom.fill = 'forwards';
}
return atom;
};

// ----- OPACITY -----

export const opacityEnterAtom = ({
duration,
easing,
delay = 0,
fromOpacity = 0,
toOpacity = 1,
}: {
duration: number;
easing: string;
delay?: number;
fromOpacity?: number;
toOpacity?: number;
}): AtomMotion => ({
keyframes: [{ opacity: fromOpacity }, { opacity: toOpacity }],
duration,
easing,
delay,
fill: 'both',
});

export const opacityExitAtom = ({
duration,
easing,
fromOpacity = 0,
toOpacity = 1,
}: {
duration: number;
easing: string;
fromOpacity?: number;
toOpacity?: number;
}): AtomMotion => ({
keyframes: [{ opacity: toOpacity }, { opacity: fromOpacity }],
duration,
easing,
});
Loading