From 915f1e2dd4a202fe0f0ccbf7857fa699d9aebb7f Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 27 Sep 2024 17:11:35 +0000 Subject: [PATCH] refactor(motion): simplify variant creation w/functional approach, starting with Collapse --- .../src/components/Collapse/Collapse.ts | 104 +++++++++++------- .../library/src/types.ts | 7 ++ .../react-motion/library/src/index.ts | 1 + 3 files changed, 70 insertions(+), 42 deletions(-) create mode 100644 packages/react-components/react-motion-components-preview/library/src/types.ts diff --git a/packages/react-components/react-motion-components-preview/library/src/components/Collapse/Collapse.ts b/packages/react-components/react-motion-components-preview/library/src/components/Collapse/Collapse.ts index 498884eb718f3..71244de0d3563 100644 --- a/packages/react-components/react-motion-components-preview/library/src/components/Collapse/Collapse.ts +++ b/packages/react-components/react-motion-components-preview/library/src/components/Collapse/Collapse.ts @@ -1,49 +1,69 @@ -import { - motionTokens, - type PresenceMotionFn, - createPresenceComponent, - createPresenceComponentVariant, -} from '@fluentui/react-motion'; +import { motionTokens, createPresenceComponent } from '@fluentui/react-motion'; +import type { PresenceMotionFnCreator } from '../../types'; + +const { durationNormal, durationSlower, durationFast, curveEasyEaseMax } = motionTokens; + +type CollapseVariantParams = { + /** Time (ms) for the enter transition (expand). Defaults to the `durationNormal` value (200 ms). */ + enterDuration?: number; + + /** Time (ms) for the exit transition (collapse). Defaults to the `durationNormal` value (200 ms). */ + exitDuration?: number; + + /** Easing curve for the enter transition (expand). Defaults to the `easeEaseMax` value. */ + enterEasing?: string; + + /** Easing curve for the exit transition (expand). Defaults to the `easeEaseMax` value. */ + exitEasing?: string; +}; + +type CollapseRuntimeParams = { + /** Whether to animate the opacity. Defaults to `true`. */ + animateOpacity?: boolean; +}; /** Define a presence motion for collapse/expand */ -const collapseMotion: PresenceMotionFn<{ animateOpacity?: boolean }> = ({ element, animateOpacity = true }) => { - const fromOpacity = animateOpacity ? 0 : 1; - const toOpacity = 1; - const fromHeight = '0'; // Could be a custom param in the future: start partially expanded - const toHeight = `${element.scrollHeight}px`; - const overflow = 'hidden'; - - const duration = motionTokens.durationNormal; - const easing = motionTokens.curveEasyEaseMax; - - const enterKeyframes = [ - { opacity: fromOpacity, maxHeight: fromHeight, overflow }, - // Transition to the height of the content, at 99.99% of the duration. - { opacity: toOpacity, maxHeight: toHeight, offset: 0.9999, overflow }, - // On completion, remove the maxHeight because the content might need to expand later. - // This extra keyframe is simpler than firing a callback on completion. - { opacity: toOpacity, maxHeight: 'unset', overflow }, - ]; - - const exitKeyframes = [ - { opacity: toOpacity, maxHeight: toHeight, overflow }, - { opacity: fromOpacity, maxHeight: fromHeight, overflow }, - ]; - - return { - enter: { duration, easing, keyframes: enterKeyframes }, - exit: { duration, easing, keyframes: exitKeyframes }, +export const createCollapsePresence: PresenceMotionFnCreator = + ({ + enterDuration = durationNormal, + exitDuration = durationNormal, + enterEasing = curveEasyEaseMax, + exitEasing = curveEasyEaseMax, + } = {}) => + ({ element, animateOpacity = true }) => { + const fromOpacity = animateOpacity ? 0 : 1; + const toOpacity = 1; + const fromHeight = '0'; // Could be a custom param in the future: start partially expanded + const toHeight = `${element.scrollHeight}px`; + const overflow = 'hidden'; + + const enterKeyframes = [ + { opacity: fromOpacity, maxHeight: fromHeight, overflow }, + // Transition to the height of the content, at 99.99% of the duration. + { opacity: toOpacity, maxHeight: toHeight, offset: 0.9999, overflow }, + // On completion, remove the maxHeight because the content might need to expand later. + // This extra keyframe is simpler than firing a callback on completion. + { opacity: toOpacity, maxHeight: 'unset', overflow }, + ]; + + const exitKeyframes = [ + { opacity: toOpacity, maxHeight: toHeight, overflow }, + { opacity: fromOpacity, maxHeight: fromHeight, overflow }, + ]; + + return { + enter: { enterDuration, enterEasing, keyframes: enterKeyframes }, + exit: { exitDuration, exitEasing, keyframes: exitKeyframes }, + }; }; -}; /** A React component that applies collapse/expand transitions to its children. */ -export const Collapse = createPresenceComponent(collapseMotion); +export const Collapse = createPresenceComponent(createCollapsePresence()); -export const CollapseSnappy = createPresenceComponentVariant(Collapse, { - all: { duration: motionTokens.durationUltraFast }, -}); +export const CollapseSnappy = createPresenceComponent( + createCollapsePresence({ enterDuration: durationFast, exitDuration: durationFast }), +); -export const CollapseExaggerated = createPresenceComponentVariant(Collapse, { - enter: { duration: motionTokens.durationSlow, easing: motionTokens.curveEasyEaseMax }, - exit: { duration: motionTokens.durationNormal, easing: motionTokens.curveEasyEaseMax }, -}); +export const CollapseExaggerated = createPresenceComponent( + createCollapsePresence({ enterDuration: durationSlower, exitDuration: durationNormal }), +); diff --git a/packages/react-components/react-motion-components-preview/library/src/types.ts b/packages/react-components/react-motion-components-preview/library/src/types.ts new file mode 100644 index 0000000000000..2cf74f7df01a0 --- /dev/null +++ b/packages/react-components/react-motion-components-preview/library/src/types.ts @@ -0,0 +1,7 @@ +import type { MotionParam, PresenceMotionFn } from '@fluentui/react-motion'; + +// TODO: move to @fluentui/react-motion when stable +export type PresenceMotionFnCreator< + MotionVariantParams extends Record = {}, + MotionRuntimeParams extends Record = {}, +> = (variantParams?: MotionVariantParams) => PresenceMotionFn; diff --git a/packages/react-components/react-motion/library/src/index.ts b/packages/react-components/react-motion/library/src/index.ts index fe49b7011d977..74495e48de51a 100644 --- a/packages/react-components/react-motion/library/src/index.ts +++ b/packages/react-components/react-motion/library/src/index.ts @@ -19,6 +19,7 @@ export type { PresenceMotionFn, PresenceDirection, MotionImperativeRef, + MotionParam, } from './types'; export { MotionBehaviourProvider } from './contexts/MotionBehaviourContext';