Skip to content

Commit d159ec4

Browse files
authored
refactor(Collapse): simplify parameters and add tests (#35262)
1 parent 8de9979 commit d159ec4

File tree

15 files changed

+1057
-158
lines changed

15 files changed

+1057
-158
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(Collapse): simplify parameter types",
4+
"packageName": "@fluentui/react-motion-components-preview",
5+
"email": "robertpenner@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-motion-components-preview/library/etc/react-motion-components-preview.api.md

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,38 @@
33
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
44
55
```ts
6+
67
import { PresenceComponent } from '@fluentui/react-motion';
78

89
// @public
910
export const Blur: PresenceComponent<BlurParams>;
1011

1112
// @public (undocumented)
12-
export type BlurParams = BasePresenceParams &
13-
AnimateOpacity & {
13+
export type BlurParams = BasePresenceParams & AnimateOpacity & {
1414
fromRadius?: string;
15-
};
15+
};
1616

1717
// @public
1818
export const Collapse: PresenceComponent<CollapseParams>;
1919

2020
// @public
21-
export const CollapseDelayed: PresenceComponent<CollapseDelayedParams>;
21+
export const CollapseDelayed: PresenceComponent<CollapseParams>;
2222

23-
// @public (undocumented)
24-
export type CollapseDelayedParams = CollapseBaseParams & {
25-
sizeDuration?: number;
26-
opacityDuration?: number;
27-
exitSizeDuration?: number;
28-
exitOpacityDuration?: number;
29-
delay?: number;
30-
exitDelay?: number;
23+
// @public
24+
export type CollapseDurations = {
25+
sizeDuration?: number;
26+
opacityDuration?: number;
27+
exitSizeDuration?: number;
28+
exitOpacityDuration?: number;
3129
};
3230

3331
// @public (undocumented)
34-
export type CollapseParams = BasePresenceParams &
35-
AnimateOpacity & {
32+
export type CollapseParams = BasePresenceParams & AnimateOpacity & CollapseDurations & {
3633
orientation?: CollapseOrientation;
37-
};
34+
fromSize?: string;
35+
staggerDelay?: number;
36+
exitStaggerDelay?: number;
37+
};
3838

3939
// @public (undocumented)
4040
export const CollapseRelaxed: PresenceComponent<CollapseParams>;
@@ -58,21 +58,19 @@ export const FadeSnappy: PresenceComponent<BasePresenceParams>;
5858
export const Rotate: PresenceComponent<RotateParams>;
5959

6060
// @public (undocumented)
61-
export type RotateParams = BasePresenceParams &
62-
AnimateOpacity & {
61+
export type RotateParams = BasePresenceParams & AnimateOpacity & {
6362
axis?: Axis3D;
6463
angle?: number;
6564
exitAngle?: number;
66-
};
65+
};
6766

6867
// @public
6968
export const Scale: PresenceComponent<ScaleParams>;
7069

7170
// @public (undocumented)
72-
export type ScaleParams = BasePresenceParams &
73-
AnimateOpacity & {
71+
export type ScaleParams = BasePresenceParams & AnimateOpacity & {
7472
fromScale?: number;
75-
};
73+
};
7674

7775
// @public (undocumented)
7876
export const ScaleRelaxed: PresenceComponent<ScaleParams>;
@@ -84,11 +82,10 @@ export const ScaleSnappy: PresenceComponent<ScaleParams>;
8482
export const Slide: PresenceComponent<SlideParams>;
8583

8684
// @public (undocumented)
87-
export type SlideParams = BasePresenceParams &
88-
AnimateOpacity & {
85+
export type SlideParams = BasePresenceParams & AnimateOpacity & {
8986
fromX?: string;
9087
fromY?: string;
91-
};
88+
};
9289

9390
// @public (undocumented)
9491
export const SlideRelaxed: PresenceComponent<SlideParams>;
@@ -97,4 +94,5 @@ export const SlideRelaxed: PresenceComponent<SlideParams>;
9794
export const SlideSnappy: PresenceComponent<SlideParams>;
9895

9996
// (No @packageDocumentation comment for this package)
97+
10098
```

packages/react-components/react-motion-components-preview/library/src/atoms/blur-atom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface BlurAtomParams extends BaseAtomParams {
1111
* @param duration - The duration of the motion in milliseconds.
1212
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveLinear`.
1313
* @param fromRadius - The blur radius value with units (e.g., '20px', '1rem'). Defaults to '20px'.
14+
* @param delay - Time (ms) to delay the animation. Defaults to 0.
1415
* @returns A motion atom object with filter blur keyframes and the supplied duration and easing.
1516
*/
1617
export const blurAtom = ({

packages/react-components/react-motion-components-preview/library/src/atoms/rotate-atom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const createRotateValue = (axis: Axis3D, angle: number): string => {
2222
* @param axis - The axis of rotation: 'x', 'y', or 'z'. Defaults to 'y'.
2323
* @param angle - The starting rotation angle in degrees. Defaults to -90.
2424
* @param exitAngle - The ending rotation angle in degrees. Defaults to the negation of `angle`.
25+
* @param delay - Time (ms) to delay the animation. Defaults to 0.
2526
* @returns A motion atom object with rotate keyframes and the supplied duration and easing.
2627
*/
2728
export const rotateAtom = ({

packages/react-components/react-motion-components-preview/library/src/atoms/scale-atom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface ScaleAtomParams extends BaseAtomParams {
1313
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveLinear`.
1414
* @param fromScale - The starting scale value. Defaults to 0.9.
1515
* @param toScale - The ending scale value. Defaults to 1.
16+
* @param delay - Time (ms) to delay the animation. Defaults to 0.
1617
* @returns A motion atom object with scale keyframes and the supplied duration and easing.
1718
*/
1819
export const scaleAtom = ({

packages/react-components/react-motion-components-preview/library/src/atoms/slide-atom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface SlideAtomParams extends BaseAtomParams {
1313
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveLinear`.
1414
* @param fromX - The starting X translate value with units (e.g., '0px', '100%'). Defaults to '0px'.
1515
* @param fromY - The starting Y translate value with units (e.g., '-20px', '100%'). Defaults to '0px'.
16+
* @param delay - Time (ms) to delay the animation. Defaults to 0.
1617
* @returns A motion atom object with translate keyframes and the supplied duration and easing.
1718
*/
1819
export const slideAtom = ({

packages/react-components/react-motion-components-preview/library/src/components/Collapse/Collapse.ts

Lines changed: 75 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -5,146 +5,101 @@ import {
55
createPresenceComponentVariant,
66
AtomMotion,
77
} from '@fluentui/react-motion';
8-
import type { CollapseDelayedParams, CollapseParams } from './collapse-types';
8+
import type { CollapseParams } from './collapse-types';
99
import { sizeEnterAtom, sizeExitAtom, whitespaceAtom } from './collapse-atoms';
1010
import { fadeAtom } from '../../atoms/fade-atom';
1111

12-
/** Internal helper to create collapse atoms with shared logic */
13-
function createCollapseAtoms({
12+
/**
13+
* Define a presence motion for collapse/expand
14+
*
15+
* @param element - The element to apply the collapse motion to
16+
* @param duration - Time (ms) for the enter transition (expand). Defaults to the `durationNormal` value (200 ms)
17+
* @param easing - Easing curve for the enter transition. Defaults to the `curveEasyEaseMax` value
18+
* @param delay - Time (ms) to delay the entire enter transition. Defaults to 0
19+
* @param exitDuration - Time (ms) for the exit transition (collapse). Defaults to the `duration` param for symmetry
20+
* @param exitEasing - Easing curve for the exit transition. Defaults to the `easing` param for symmetry
21+
* @param exitDelay - Time (ms) to delay the entire exit transition. Defaults to the `delay` param for symmetry
22+
* @param staggerDelay - Time (ms) offset between the size and opacity animations. Defaults to 0
23+
* @param exitStaggerDelay - Time (ms) offset between the size and opacity animations on exit. Defaults to the `staggerDelay` param for symmetry
24+
* @param sizeDuration - Time (ms) for the size animation during enter. Defaults to `duration` for unified timing
25+
* @param opacityDuration - Time (ms) for the opacity animation during enter. Defaults to `sizeDuration` for synchronized timing
26+
* @param exitSizeDuration - Time (ms) for the size animation during exit. Defaults to `exitDuration` for unified timing
27+
* @param exitOpacityDuration - Time (ms) for the opacity animation during exit. Defaults to `exitSizeDuration` for synchronized timing
28+
* @param animateOpacity - Whether to animate the opacity. Defaults to `true`
29+
* @param orientation - The orientation of the size animation. Defaults to `'vertical'` to expand/collapse the height
30+
* @param fromSize - The starting size for the expand animation. Defaults to `'0px'`
31+
*/
32+
const collapsePresenceFn: PresenceMotionFn<CollapseParams> = ({
1433
element,
15-
orientation,
16-
animateOpacity,
34+
// Primary duration controls (simple API)
35+
duration = motionTokens.durationNormal,
36+
exitDuration = duration,
1737

18-
// Enter params
19-
sizeDuration,
38+
// Granular duration controls with smart defaults (advanced API)
39+
sizeDuration = duration,
2040
opacityDuration = sizeDuration,
21-
easing,
22-
delay,
23-
24-
// Exit params
25-
exitSizeDuration,
41+
exitSizeDuration = exitDuration,
2642
exitOpacityDuration = exitSizeDuration,
27-
exitEasing,
28-
exitDelay,
29-
}: {
30-
element: HTMLElement;
31-
} & Required<CollapseDelayedParams>) {
43+
44+
// Other timing controls
45+
easing = motionTokens.curveEasyEaseMax,
46+
delay = 0,
47+
exitEasing = easing,
48+
exitDelay = delay,
49+
staggerDelay = 0,
50+
exitStaggerDelay = staggerDelay,
51+
52+
// Animation controls
53+
animateOpacity = true,
54+
orientation = 'vertical',
55+
fromSize = '0px',
56+
}) => {
3257
// ----- ENTER -----
3358
// The enter transition is an array of up to 3 motion atoms: size, whitespace and opacity.
59+
// For enter: size expands first, then opacity fades in after staggerDelay
3460
const enterAtoms: AtomMotion[] = [
35-
sizeEnterAtom({ orientation, duration: sizeDuration, easing, element }),
36-
whitespaceAtom({ direction: 'enter', orientation, duration: sizeDuration, easing }),
61+
// Apply global delay to size atom - size expansion starts first
62+
sizeEnterAtom({ orientation, duration: sizeDuration, easing, element, fromSize, delay }),
63+
whitespaceAtom({ direction: 'enter', orientation, duration: sizeDuration, easing, delay }),
3764
];
3865
// Fade in only if animateOpacity is true. Otherwise, leave opacity unaffected.
3966
if (animateOpacity) {
40-
enterAtoms.push({
41-
...fadeAtom({ direction: 'enter', duration: opacityDuration, easing }),
42-
delay,
43-
fill: 'both',
44-
});
67+
enterAtoms.push(fadeAtom({ direction: 'enter', duration: opacityDuration, easing, delay: delay + staggerDelay }));
4568
}
4669

4770
// ----- EXIT -----
4871
// The exit transition is an array of up to 3 motion atoms: opacity, size and whitespace.
72+
// For exit: opacity fades out first, then size collapses after exitStaggerDelay
4973
const exitAtoms: AtomMotion[] = [];
5074
// Fade out only if animateOpacity is true. Otherwise, leave opacity unaffected.
5175
if (animateOpacity) {
52-
exitAtoms.push(fadeAtom({ direction: 'exit', duration: exitOpacityDuration, easing: exitEasing }));
76+
exitAtoms.push(
77+
fadeAtom({ direction: 'exit', duration: exitOpacityDuration, easing: exitEasing, delay: exitDelay }),
78+
);
5379
}
5480

5581
exitAtoms.push(
56-
sizeExitAtom({ orientation, duration: exitSizeDuration, easing: exitEasing, element, delay: exitDelay }),
82+
sizeExitAtom({
83+
orientation,
84+
duration: exitSizeDuration,
85+
easing: exitEasing,
86+
element,
87+
delay: exitDelay + exitStaggerDelay,
88+
fromSize,
89+
}),
5790
whitespaceAtom({
5891
direction: 'exit',
5992
orientation,
6093
duration: exitSizeDuration,
6194
easing: exitEasing,
62-
delay: exitDelay,
95+
delay: exitDelay + exitStaggerDelay, // Size/whitespace collapse after opacity finishes fading out
6396
}),
6497
);
6598

6699
return {
67100
enter: enterAtoms,
68101
exit: exitAtoms,
69102
};
70-
}
71-
72-
/**
73-
* Define a presence motion for collapse/expand
74-
*
75-
* @param element - The element to apply the collapse motion to
76-
* @param duration - Time (ms) for the enter transition (expand). Defaults to the `durationNormal` value (200 ms)
77-
* @param easing - Easing curve for the enter transition. Defaults to the `curveEasyEaseMax` value
78-
* @param exitDuration - Time (ms) for the exit transition (collapse). Defaults to the `duration` param for symmetry
79-
* @param exitEasing - Easing curve for the exit transition. Defaults to the `easing` param for symmetry
80-
* @param animateOpacity - Whether to animate the opacity. Defaults to `true`
81-
* @param orientation - The orientation of the size animation. Defaults to `'vertical'` to expand/collapse the height
82-
*/
83-
const collapsePresenceFn: PresenceMotionFn<CollapseParams> = ({
84-
element,
85-
duration = motionTokens.durationNormal,
86-
easing = motionTokens.curveEasyEaseMax,
87-
exitDuration = duration,
88-
exitEasing = easing,
89-
animateOpacity = true,
90-
orientation = 'vertical',
91-
}) => {
92-
return createCollapseAtoms({
93-
element,
94-
orientation,
95-
animateOpacity,
96-
sizeDuration: duration,
97-
opacityDuration: duration,
98-
easing,
99-
exitSizeDuration: exitDuration,
100-
exitOpacityDuration: exitDuration,
101-
exitEasing,
102-
delay: 0,
103-
exitDelay: 0,
104-
});
105-
};
106-
107-
/**
108-
* Define a presence motion for collapse/expand that can stagger the size and opacity motions by a given delay
109-
*
110-
* @param element - The element to apply the collapse motion to
111-
* @param sizeDuration - Time (ms) for the size expand. Defaults to the `durationNormal` value (200 ms)
112-
* @param opacityDuration - Time (ms) for the fade-in. Defaults to the `durationSlower` value (400 ms)
113-
* @param easing - Easing curve for the enter transition. Defaults to the `curveEasyEase` value
114-
* @param delay - Time (ms) between the size expand start and the fade-in start. Defaults to the `durationNormal` value (200 ms)
115-
* @param exitSizeDuration - Time (ms) for the size collapse. Defaults to the `sizeDuration` param for temporal symmetry
116-
* @param exitOpacityDuration - Time (ms) for the fade-out. Defaults to the `opacityDuration` param for temporal symmetry
117-
* @param exitEasing - Easing curve for the exit transition. Defaults to the `easing` param for symmetry
118-
* @param exitDelay - Time (ms) between the fade-out start and the size collapse start. Defaults to the `durationSlower` value (400 ms)
119-
* @param animateOpacity - Whether to animate the opacity. Defaults to `true`
120-
* @param orientation - The orientation of the size animation. Defaults to `'vertical'` to expand/collapse the height
121-
*/
122-
const collapseDelayedPresenceFn: PresenceMotionFn<CollapseDelayedParams> = ({
123-
element,
124-
sizeDuration = motionTokens.durationNormal,
125-
opacityDuration = motionTokens.durationSlower,
126-
easing = motionTokens.curveEasyEase,
127-
delay = motionTokens.durationNormal,
128-
exitSizeDuration = sizeDuration,
129-
exitOpacityDuration = opacityDuration,
130-
exitEasing = easing,
131-
exitDelay = motionTokens.durationSlower,
132-
animateOpacity = true,
133-
orientation = 'vertical',
134-
}) => {
135-
return createCollapseAtoms({
136-
element,
137-
orientation,
138-
animateOpacity,
139-
sizeDuration,
140-
opacityDuration,
141-
easing,
142-
delay,
143-
exitSizeDuration,
144-
exitOpacityDuration,
145-
exitEasing,
146-
exitDelay,
147-
});
148103
};
149104

150105
/** A React component that applies collapse/expand transitions to its children. */
@@ -159,4 +114,18 @@ export const CollapseRelaxed = createPresenceComponentVariant(Collapse, {
159114
});
160115

161116
/** A React component that applies collapse/expand transitions with delayed fade to its children. */
162-
export const CollapseDelayed = createPresenceComponent(collapseDelayedPresenceFn);
117+
export const CollapseDelayed = createPresenceComponentVariant(Collapse, {
118+
// Enter timing per motion design spec
119+
sizeDuration: motionTokens.durationNormal, // 200ms
120+
opacityDuration: motionTokens.durationSlower, // 400ms
121+
staggerDelay: motionTokens.durationNormal, // 200ms
122+
123+
// Exit timing per motion design spec
124+
exitSizeDuration: motionTokens.durationNormal, // 200ms
125+
exitOpacityDuration: motionTokens.durationSlower, // 400ms
126+
exitStaggerDelay: motionTokens.durationSlower, // 400ms
127+
128+
// Easing per motion design spec
129+
easing: motionTokens.curveEasyEase,
130+
exitEasing: motionTokens.curveEasyEase,
131+
});

0 commit comments

Comments
 (0)