Skip to content

Commit 9c716b2

Browse files
refactor(MessageBar): migrate slide & fade to motion components (#33465)
Co-authored-by: Oleksandr Fediashov <olfedias@microsoft.com>
1 parent dc7bb66 commit 9c716b2

File tree

11 files changed

+121
-125
lines changed

11 files changed

+121
-125
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "refactor(MessageBar): migrate slide & fade to motion components",
4+
"packageName": "@fluentui/react-message-bar",
5+
"email": "robertpenner@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-message-bar/library/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
"@fluentui/react-button": "^9.3.98",
2222
"@fluentui/react-icons": "^2.0.245",
2323
"@fluentui/react-jsx-runtime": "^9.0.48",
24+
"@fluentui/react-motion": "^9.6.4",
25+
"@fluentui/react-motion-components-preview": "^0.4.0",
2426
"@fluentui/react-shared-contexts": "^9.21.2",
2527
"@fluentui/react-link": "^9.3.5",
2628
"@fluentui/react-theme": "^9.1.24",
2729
"@fluentui/react-utilities": "^9.18.19",
2830
"@griffel/react": "^1.5.22",
29-
"@swc/helpers": "^0.5.1",
30-
"react-transition-group": "^4.4.1"
31+
"@swc/helpers": "^0.5.1"
3132
},
3233
"peerDependencies": {
3334
"@types/react": ">=16.8.0 <19.0.0",

packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,8 @@ export type MessageBarProps = ComponentProps<MessageBarSlots> &
4848
export type MessageBarState = ComponentState<MessageBarSlots> &
4949
Required<Pick<MessageBarProps, 'layout' | 'intent' | 'shape'>> &
5050
Pick<MessageBarContextValue, 'actionsRef' | 'bodyRef' | 'titleId'> & {
51+
/**
52+
* @deprecated Code is unused, replaced by motion components
53+
*/
5154
transitionClassName: string;
5255
};

packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref<HT
2121
const autoReflow = layout === 'auto';
2222
const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow);
2323
const computedLayout = autoReflow ? (reflowing ? 'multiline' : 'singleline') : layout;
24+
// eslint-disable-next-line deprecation/deprecation
2425
const { className: transitionClassName, nodeRef } = useMessageBarTransitionContext();
2526
const actionsRef = React.useRef<HTMLDivElement | null>(null);
2627
const bodyRef = React.useRef<HTMLDivElement | null>(null);

packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBarStyles.styles.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ export const useMessageBarStyles_unstable = (state: MessageBarState): MessageBar
114114
state.layout === 'multiline' && styles.rootMultiline,
115115
state.shape === 'square' && styles.square,
116116
rootIntentStyles[state.intent],
117-
state.transitionClassName,
118117
state.root.className,
119118
);
120119

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { motionTokens, createPresenceComponent, PresenceDirection, AtomMotion } from '@fluentui/react-motion';
2+
import { MessageBarGroupProps } from './MessageBarGroup.types';
3+
4+
// TODO: import these atoms from react-motion-components-preview once they're available there
5+
6+
interface FadeAtomParams {
7+
direction: PresenceDirection;
8+
duration: number;
9+
easing?: string;
10+
fromValue?: number;
11+
}
12+
13+
/**
14+
* Generates a motion atom object for a fade in or fade out.
15+
* @param direction - The functional direction of the motion: 'enter' or 'exit'.
16+
* @param duration - The duration of the motion in milliseconds.
17+
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveLinear`.
18+
* @param fromValue - The starting opacity value. Defaults to 0.
19+
* @returns A motion atom object with opacity keyframes and the supplied duration and easing.
20+
*/
21+
const fadeAtom = ({
22+
direction,
23+
duration,
24+
easing = motionTokens.curveLinear,
25+
fromValue = 0,
26+
}: FadeAtomParams): AtomMotion => {
27+
const keyframes = [{ opacity: fromValue }, { opacity: 1 }];
28+
if (direction === 'exit') {
29+
keyframes.reverse();
30+
}
31+
return {
32+
keyframes,
33+
duration,
34+
easing,
35+
};
36+
};
37+
38+
/**
39+
* Generates a motion atom object for an X or Y translation, from a specified distance to zero.
40+
* @param direction - The functional direction of the motion: 'enter' or 'exit'.
41+
* @param axis - The axis of the translation: 'X' or 'Y'.
42+
* @param fromValue - The starting position of the slide; it can be a percentage or pixels.
43+
* @param duration - The duration of the motion in milliseconds.
44+
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveDecelerateMid`.
45+
*/
46+
const slideAtom = ({
47+
direction,
48+
axis,
49+
fromValue,
50+
duration,
51+
easing = motionTokens.curveDecelerateMid,
52+
}: {
53+
direction: PresenceDirection;
54+
axis: 'X' | 'Y';
55+
fromValue: string;
56+
duration: number;
57+
easing?: string;
58+
}): AtomMotion => {
59+
const keyframes = [{ transform: `translate${axis}(${fromValue})` }, { transform: `translate${axis}(0)` }];
60+
if (direction === 'exit') {
61+
keyframes.reverse();
62+
}
63+
return {
64+
keyframes,
65+
duration,
66+
easing,
67+
};
68+
};
69+
70+
/**
71+
* A presence component for a MessageBar to enter and exit from a MessageBarGroup.
72+
* It has an optional enter transition of a slide-in and fade-in,
73+
* when the `animate` prop is set to `'both'`.
74+
* It always has an exit transition of a fade-out.
75+
*/
76+
export const MessageBarMotion = createPresenceComponent<{ animate?: MessageBarGroupProps['animate'] }>(
77+
({ animate }) => {
78+
const duration = motionTokens.durationGentle;
79+
80+
return {
81+
enter:
82+
animate === 'both'
83+
? // enter with slide and fade
84+
[
85+
fadeAtom({ direction: 'enter', duration }),
86+
slideAtom({ direction: 'enter', axis: 'Y', fromValue: '-100%', duration }),
87+
]
88+
: [], // no enter motion
89+
90+
// Always exit with a fade
91+
exit: fadeAtom({ direction: 'exit', duration }),
92+
};
93+
},
94+
);

packages/react-components/react-message-bar/library/src/components/MessageBarGroup/MessageBarGroup.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export type MessageBarGroupProps = ComponentProps<MessageBarGroupSlots> & {
1818
*/
1919
export type MessageBarGroupState = ComponentState<MessageBarGroupSlots> &
2020
Pick<MessageBarGroupProps, 'animate'> & {
21+
/** @deprecated property is unused; these CSS animations were replaced by motion components */
2122
enterStyles: string;
23+
/** @deprecated property is unused; these CSS animations were replaced by motion components */
2224
exitStyles: string;
2325
children: React.ReactElement[];
2426
};

packages/react-components/react-message-bar/library/src/components/MessageBarGroup/MessageBarTransition.tsx

Lines changed: 0 additions & 71 deletions
This file was deleted.

packages/react-components/react-message-bar/library/src/components/MessageBarGroup/renderMessageBarGroup.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

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

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

1515
return (
1616
<state.root>
17-
<TransitionGroup component={null}>
17+
<PresenceGroup>
1818
{state.children.map(child => (
19-
<MessageBarTransition
20-
animate={state.animate}
21-
key={child.key}
22-
enterClassName={state.enterStyles}
23-
exitClassName={state.exitStyles}
24-
>
19+
<MessageBarMotion key={child.key} animate={state.animate}>
2520
{child}
26-
</MessageBarTransition>
21+
</MessageBarMotion>
2722
))}
28-
</TransitionGroup>
23+
</PresenceGroup>
2924
</state.root>
3025
);
3126
};
Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,17 @@
1-
import { makeStyles, mergeClasses } from '@griffel/react';
2-
import { tokens } from '@fluentui/react-theme';
1+
import { mergeClasses } from '@griffel/react';
32
import type { SlotClassNames } from '@fluentui/react-utilities';
43
import type { MessageBarGroupSlots, MessageBarGroupState } from './MessageBarGroup.types';
54

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

10-
/**
11-
* Styles for the root slot
12-
*/
13-
const useStyles = makeStyles({
14-
base: {
15-
animationFillMode: 'forwards',
16-
animationDuration: tokens.durationNormal,
17-
},
18-
19-
enter: {
20-
animationName: {
21-
from: {
22-
opacity: 0,
23-
transform: 'translateY(-100%)',
24-
},
25-
to: {
26-
opacity: 1,
27-
transform: 'translateY(0)',
28-
},
29-
},
30-
},
31-
32-
exit: {
33-
animationName: {
34-
from: {
35-
opacity: 1,
36-
},
37-
to: {
38-
opacity: 0,
39-
},
40-
},
41-
},
42-
});
43-
449
/**
4510
* Apply styling to the MessageBarGroup slots based on the state
4611
*/
4712
export const useMessageBarGroupStyles_unstable = (state: MessageBarGroupState): MessageBarGroupState => {
4813
'use no memo';
4914

50-
const styles = useStyles();
5115
state.root.className = mergeClasses(messageBarGroupClassNames.root, state.root.className);
52-
state.enterStyles = mergeClasses(styles.base, styles.enter);
53-
state.exitStyles = mergeClasses(styles.base, styles.exit);
5416
return state;
5517
};

0 commit comments

Comments
 (0)