Skip to content

Commit 8f84c46

Browse files
authored
fix(motion): make createPresenceComponentVariant support motion groups (#33996)
1 parent 0a12c33 commit 8f84c46

File tree

4 files changed

+66
-116
lines changed

4 files changed

+66
-116
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": "fix(motion): make createPresenceComponentVariant support motion arrays",
4+
"packageName": "@fluentui/react-motion",
5+
"email": "robertpenner@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export function createMotionComponent<MotionParams extends Record<string, Motion
2424
// @public (undocumented)
2525
export function createPresenceComponent<MotionParams extends Record<string, MotionParam> = {}>(value: PresenceMotion | PresenceMotionFn<MotionParams>): PresenceComponent<MotionParams>;
2626

27-
// @public (undocumented)
28-
export function createPresenceComponentVariant<MotionParams extends Record<string, MotionParam> = {}>(component: PresenceComponent<MotionParams>, override: PresenceOverride): PresenceComponent<MotionParams>;
27+
// @public
28+
export function createPresenceComponentVariant<MotionParams extends Record<string, MotionParam> = {}>(component: PresenceComponent<MotionParams>, variantParams: Partial<MotionParams>): PresenceComponent<MotionParams>;
2929

3030
// @public (undocumented)
3131
export const curves: {

packages/react-components/react-motion/library/src/factories/createPresenceComponentVariant.test.tsx

Lines changed: 33 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render } from '@testing-library/react';
33

44
import type { PresenceMotionFn } from '../types';
55
import { createPresenceComponent } from './createPresenceComponent';
6-
import { overridePresenceMotion, createPresenceComponentVariant } from './createPresenceComponentVariant';
6+
import { createPresenceComponentVariant } from './createPresenceComponentVariant';
77

88
jest.mock('./createPresenceComponent', () => {
99
const module = jest.requireActual('./createPresenceComponent');
@@ -14,79 +14,36 @@ jest.mock('./createPresenceComponent', () => {
1414
};
1515
});
1616

17-
const PRESENCE_MOTION: PresenceMotionFn<{ direction: 'start' | 'end' }> = () => ({
18-
enter: { keyframes: [], duration: 1000, easing: 'linear' },
19-
exit: { keyframes: [], duration: 1000, easing: 'linear' },
17+
const PRESENCE_MOTION: PresenceMotionFn<{ outOpacity?: number; duration?: number; easing?: string }> = ({
18+
outOpacity = 0,
19+
duration = 1000,
20+
easing = 'linear',
21+
}) => ({
22+
enter: [{ keyframes: [{ opacity: outOpacity }, { opacity: 1 }], duration, easing }],
23+
exit: [{ keyframes: [{ opacity: 1 }, { opacity: outOpacity }], duration, easing }],
2024
});
21-
const PRESENCE_COMPONENT = createPresenceComponent<{ direction: 'start' | 'end' }>(PRESENCE_MOTION);
25+
const PRESENCE_COMPONENT = createPresenceComponent(PRESENCE_MOTION);
2226

2327
const MOTION_PARAMS = {
2428
element: document.createElement('div'),
25-
direction: 'start' as const,
2629
};
2730

28-
describe('overridePresenceMotion', () => {
29-
it('overrides "all"', () => {
30-
expect(
31-
overridePresenceMotion(PRESENCE_MOTION, { all: { duration: 500, easing: 'ease-in-out' } })(MOTION_PARAMS),
32-
).toEqual({
33-
enter: {
34-
duration: 500,
35-
easing: 'ease-in-out',
36-
keyframes: [],
37-
},
38-
exit: {
39-
duration: 500,
40-
easing: 'ease-in-out',
41-
keyframes: [],
42-
},
43-
});
44-
});
45-
46-
it('overrides "enter"', () => {
47-
expect(
48-
overridePresenceMotion(PRESENCE_MOTION, { enter: { duration: 500, easing: 'ease-in-out' } })(MOTION_PARAMS),
49-
).toEqual({
50-
enter: {
51-
duration: 500,
52-
easing: 'ease-in-out',
53-
keyframes: [],
54-
},
55-
exit: {
56-
keyframes: [],
57-
duration: 1000,
58-
easing: 'linear',
59-
},
60-
});
61-
});
62-
63-
it('overrides "exit"', () => {
64-
expect(
65-
overridePresenceMotion(PRESENCE_MOTION, { exit: { duration: 500, easing: 'ease-in-out' } })(MOTION_PARAMS),
66-
).toEqual({
67-
enter: {
68-
keyframes: [],
69-
duration: 1000,
70-
easing: 'linear',
71-
},
72-
exit: {
73-
duration: 500,
74-
easing: 'ease-in-out',
75-
keyframes: [],
76-
},
77-
});
78-
});
79-
});
80-
8131
describe('createPresenceComponentVariant', () => {
82-
it('appends override to the original motion', () => {
32+
it('overrides motion parameters used within motion atom arrays', () => {
33+
// variant params overriding the default motion params
34+
const outOpacity = 0.3;
35+
const duration = 500;
36+
const easing = 'ease-in-out';
37+
8338
const PresenceVariant = createPresenceComponentVariant(PRESENCE_COMPONENT, {
84-
all: { duration: 500, easing: 'ease-in-out' },
39+
outOpacity,
40+
duration,
41+
easing,
8542
});
8643
const overrideFn = (createPresenceComponent as jest.Mock).mock.calls[0][0];
8744

8845
const { getByText } = render(
89-
<PresenceVariant direction="start" visible>
46+
<PresenceVariant visible>
9047
<div>Hello world!</div>
9148
</PresenceVariant>,
9249
);
@@ -99,16 +56,20 @@ describe('createPresenceComponentVariant', () => {
9956

10057
expect(overrideFn).toBeInstanceOf(Function);
10158
expect(overrideFn(MOTION_PARAMS)).toEqual({
102-
enter: {
103-
duration: 500,
104-
easing: 'ease-in-out',
105-
keyframes: [],
106-
},
107-
exit: {
108-
duration: 500,
109-
easing: 'ease-in-out',
110-
keyframes: [],
111-
},
59+
enter: [
60+
{
61+
duration,
62+
easing,
63+
keyframes: [{ opacity: outOpacity }, { opacity: 1 }],
64+
},
65+
],
66+
exit: [
67+
{
68+
duration,
69+
easing,
70+
keyframes: [{ opacity: 1 }, { opacity: outOpacity }],
71+
},
72+
],
11273
});
11374
});
11475
});
Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,37 @@
1-
import type { MotionParam, PresenceDirection, PresenceMotionFn } from '../types';
1+
import type { MotionParam, PresenceMotionFn } from '../types';
22
import { MOTION_DEFINITION, createPresenceComponent, PresenceComponent } from './createPresenceComponent';
33

44
/**
55
* @internal
6-
*/
7-
type PresenceOverrideFields = {
8-
duration: KeyframeEffectOptions['duration'];
9-
easing: KeyframeEffectOptions['easing'];
10-
};
11-
12-
/**
13-
* @internal
14-
*
15-
* Override properties for presence transitions.
16-
*
17-
* @example <caption>Override duration for all transitions</caption>
18-
* ```
19-
* const override: PresenceOverride = {
20-
* all: { duration: 1000 },
21-
* };
22-
* ```
236
*
24-
* @example <caption>Override easing for exit transition</caption>
25-
* ```
26-
* const override: PresenceOverride = {
27-
* exit: { easing: 'ease-out' },
28-
* };
29-
* ```
7+
* Create a variant function that wraps a presence function to customize it.
8+
* The new presence function has the supplied variant params as defaults,
9+
* but these can still be overridden by runtime params when the new function is called.
3010
*/
31-
type PresenceOverride = Partial<Record<PresenceDirection | 'all', Partial<PresenceOverrideFields>>>;
11+
export function createPresenceFnVariant<MotionParams extends Record<string, MotionParam> = {}>(
12+
presenceFn: PresenceMotionFn<MotionParams>,
13+
variantParams: Partial<MotionParams>,
14+
): typeof presenceFn {
15+
const variantFn: typeof presenceFn = runtimeParams => presenceFn({ ...variantParams, ...runtimeParams });
16+
return variantFn;
17+
}
3218

3319
/**
34-
* @internal
20+
* Create a new presence component based on another presence component,
21+
* using the provided variant parameters as defaults.
22+
*
23+
* @param component - A component created by `createPresenceComponent`.
24+
* @param variantParams - An object containing the variant parameters to be used as defaults.
25+
* The variant parameters should match the type of the component's motion parameters.
26+
* @returns A new presence component that uses the provided variant parameters as defaults.
27+
* The new component can still accept runtime parameters that override the defaults.
3528
*/
36-
export function overridePresenceMotion<MotionParams extends Record<string, MotionParam> = {}>(
37-
presenceMotion: PresenceMotionFn<MotionParams>,
38-
override: PresenceOverride,
39-
): PresenceMotionFn<MotionParams> {
40-
return (...args: Parameters<PresenceMotionFn<MotionParams>>) => {
41-
const { enter, exit } = presenceMotion(...args);
42-
43-
return {
44-
enter: { ...enter, ...override.all, ...override.enter },
45-
exit: { ...exit, ...override.all, ...override.exit },
46-
};
47-
};
48-
}
49-
5029
export function createPresenceComponentVariant<MotionParams extends Record<string, MotionParam> = {}>(
5130
component: PresenceComponent<MotionParams>,
52-
override: PresenceOverride,
31+
variantParams: Partial<MotionParams>,
5332
): PresenceComponent<MotionParams> {
54-
return createPresenceComponent(overridePresenceMotion(component[MOTION_DEFINITION], override));
33+
const originalFn = component[MOTION_DEFINITION];
34+
// The variant params become new defaults, but they can still be be overridden by runtime params.
35+
const variantFn = createPresenceFnVariant(originalFn, variantParams);
36+
return createPresenceComponent(variantFn);
5537
}

0 commit comments

Comments
 (0)