Skip to content

Commit

Permalink
refactor(MessageBar): migrate slide & fade to motion components (#33465)
Browse files Browse the repository at this point in the history
Co-authored-by: Oleksandr Fediashov <olfedias@microsoft.com>
  • Loading branch information
robertpenner and layershifter authored Dec 20, 2024
1 parent dc7bb66 commit 9c716b2
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "refactor(MessageBar): migrate slide & fade to motion components",
"packageName": "@fluentui/react-message-bar",
"email": "robertpenner@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
"@fluentui/react-button": "^9.3.98",
"@fluentui/react-icons": "^2.0.245",
"@fluentui/react-jsx-runtime": "^9.0.48",
"@fluentui/react-motion": "^9.6.4",
"@fluentui/react-motion-components-preview": "^0.4.0",
"@fluentui/react-shared-contexts": "^9.21.2",
"@fluentui/react-link": "^9.3.5",
"@fluentui/react-theme": "^9.1.24",
"@fluentui/react-utilities": "^9.18.19",
"@griffel/react": "^1.5.22",
"@swc/helpers": "^0.5.1",
"react-transition-group": "^4.4.1"
"@swc/helpers": "^0.5.1"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <19.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ export type MessageBarProps = ComponentProps<MessageBarSlots> &
export type MessageBarState = ComponentState<MessageBarSlots> &
Required<Pick<MessageBarProps, 'layout' | 'intent' | 'shape'>> &
Pick<MessageBarContextValue, 'actionsRef' | 'bodyRef' | 'titleId'> & {
/**
* @deprecated Code is unused, replaced by motion components
*/
transitionClassName: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
const autoReflow = layout === 'auto';
const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow);
const computedLayout = autoReflow ? (reflowing ? 'multiline' : 'singleline') : layout;
// eslint-disable-next-line deprecation/deprecation
const { className: transitionClassName, nodeRef } = useMessageBarTransitionContext();
const actionsRef = React.useRef<HTMLDivElement | null>(null);
const bodyRef = React.useRef<HTMLDivElement | null>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBar
state.layout === 'multiline' && styles.rootMultiline,
state.shape === 'square' && styles.square,
rootIntentStyles[state.intent],
state.transitionClassName,
state.root.className,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { motionTokens, createPresenceComponent, PresenceDirection, AtomMotion } from '@fluentui/react-motion';
import { MessageBarGroupProps } from './MessageBarGroup.types';

// TODO: import these atoms from react-motion-components-preview once they're available there

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.
*/
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,
};
};

/**
* Generates a motion atom object for an X or Y translation, from a specified distance to zero.
* @param direction - The functional direction of the motion: 'enter' or 'exit'.
* @param axis - The axis of the translation: 'X' or 'Y'.
* @param fromValue - The starting position of the slide; it can be a percentage or pixels.
* @param duration - The duration of the motion in milliseconds.
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveDecelerateMid`.
*/
const slideAtom = ({
direction,
axis,
fromValue,
duration,
easing = motionTokens.curveDecelerateMid,
}: {
direction: PresenceDirection;
axis: 'X' | 'Y';
fromValue: string;
duration: number;
easing?: string;
}): AtomMotion => {
const keyframes = [{ transform: `translate${axis}(${fromValue})` }, { transform: `translate${axis}(0)` }];
if (direction === 'exit') {
keyframes.reverse();
}
return {
keyframes,
duration,
easing,
};
};

/**
* A presence component for a MessageBar to enter and exit from a MessageBarGroup.
* It has an optional enter transition of a slide-in and fade-in,
* when the `animate` prop is set to `'both'`.
* It always has an exit transition of a fade-out.
*/
export const MessageBarMotion = createPresenceComponent<{ animate?: MessageBarGroupProps['animate'] }>(
({ animate }) => {
const duration = motionTokens.durationGentle;

return {
enter:
animate === 'both'
? // enter with slide and fade
[
fadeAtom({ direction: 'enter', duration }),
slideAtom({ direction: 'enter', axis: 'Y', fromValue: '-100%', duration }),
]
: [], // no enter motion

// Always exit with a fade
exit: fadeAtom({ direction: 'exit', duration }),
};
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export type MessageBarGroupProps = ComponentProps<MessageBarGroupSlots> & {
*/
export type MessageBarGroupState = ComponentState<MessageBarGroupSlots> &
Pick<MessageBarGroupProps, 'animate'> & {
/** @deprecated property is unused; these CSS animations were replaced by motion components */
enterStyles: string;
/** @deprecated property is unused; these CSS animations were replaced by motion components */
exitStyles: string;
children: React.ReactElement[];
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import { assertSlots } from '@fluentui/react-utilities';
import type { MessageBarGroupState, MessageBarGroupSlots } from './MessageBarGroup.types';
import { TransitionGroup } from 'react-transition-group';
import { MessageBarTransition } from './MessageBarTransition';
import { PresenceGroup } from '@fluentui/react-motion';
import { MessageBarMotion } from './MessageBarGroup.motions';

/**
* Render the final JSX of MessageBarGroup
Expand All @@ -14,18 +14,13 @@ export const renderMessageBarGroup_unstable = (state: MessageBarGroupState) => {

return (
<state.root>
<TransitionGroup component={null}>
<PresenceGroup>
{state.children.map(child => (
<MessageBarTransition
animate={state.animate}
key={child.key}
enterClassName={state.enterStyles}
exitClassName={state.exitStyles}
>
<MessageBarMotion key={child.key} animate={state.animate}>
{child}
</MessageBarTransition>
</MessageBarMotion>
))}
</TransitionGroup>
</PresenceGroup>
</state.root>
);
};
Original file line number Diff line number Diff line change
@@ -1,55 +1,17 @@
import { makeStyles, mergeClasses } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';
import { mergeClasses } from '@griffel/react';
import type { SlotClassNames } from '@fluentui/react-utilities';
import type { MessageBarGroupSlots, MessageBarGroupState } from './MessageBarGroup.types';

export const messageBarGroupClassNames: SlotClassNames<MessageBarGroupSlots> = {
root: 'fui-MessageBarGroup',
};

/**
* Styles for the root slot
*/
const useStyles = makeStyles({
base: {
animationFillMode: 'forwards',
animationDuration: tokens.durationNormal,
},

enter: {
animationName: {
from: {
opacity: 0,
transform: 'translateY(-100%)',
},
to: {
opacity: 1,
transform: 'translateY(0)',
},
},
},

exit: {
animationName: {
from: {
opacity: 1,
},
to: {
opacity: 0,
},
},
},
});

/**
* Apply styling to the MessageBarGroup slots based on the state
*/
export const useMessageBarGroupStyles_unstable = (state: MessageBarGroupState): MessageBarGroupState => {
'use no memo';

const styles = useStyles();
state.root.className = mergeClasses(messageBarGroupClassNames.root, state.root.className);
state.enterStyles = mergeClasses(styles.base, styles.enter);
state.exitStyles = mergeClasses(styles.base, styles.exit);
return state;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as React from 'react';

export type MessageBarTransitionContextValue = {
/**
* @deprecated CSS className is no longer used for this transition, replaced by motion components
*/
className: string;
nodeRef: React.Ref<HTMLDivElement | null>;
};
Expand All @@ -16,7 +19,7 @@ export const messageBarTransitionContextDefaultValue: MessageBarTransitionContex
};

/**
* Context to pass animation className to MessageBar components
* Context to pass nodeRef for animation to MessageBar components
* @internal
*/
export const MessageBarTransitionContextProvider = messageBarTransitionContext.Provider;
Expand Down

0 comments on commit 9c716b2

Please sign in to comment.