Skip to content

Commit 019b0e7

Browse files
authored
feat(react-motion): add MotionRefForwarder helper (#35774)
1 parent cbf0ef2 commit 019b0e7

File tree

12 files changed

+92
-25
lines changed

12 files changed

+92
-25
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "refactor: use shared MotionRefForwarder from react-motion",
4+
"packageName": "@fluentui/react-dialog",
5+
"email": "robertpenner@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "refactor: use shared MotionRefForwarder from react-motion",
4+
"packageName": "@fluentui/react-message-bar",
5+
"email": "robertpenner@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "feat: export MotionRefForwarder and useMotionForwardedRef for shared motion ref forwarding",
4+
"packageName": "@fluentui/react-motion",
5+
"email": "robertpenner@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-dialog/library/src/components/Dialog/renderDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { assertSlots } from '@fluentui/react-utilities';
55
import type { JSXElement } from '@fluentui/react-utilities';
66
import * as React from 'react';
77

8-
import { MotionRefForwarder } from '../MotionRefForwarder';
8+
import { MotionRefForwarder } from '@fluentui/react-motion';
99
import { DialogProvider, DialogSurfaceProvider } from '../../contexts';
1010
import type { DialogState, DialogContextValues, InternalDialogSlots } from './Dialog.types';
1111

packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as React from 'react';
1515
import { useDialogContext_unstable, useDialogBackdropContext_unstable } from '../../contexts';
1616
import { useDisableBodyScroll } from '../../utils/useDisableBodyScroll';
1717
import { DialogBackdropMotion } from '../DialogBackdropMotion';
18-
import { useMotionForwardedRef } from '../MotionRefForwarder';
18+
import { useMotionForwardedRef } from '@fluentui/react-motion';
1919
import type { DialogSurfaceElement, DialogSurfaceProps, DialogSurfaceState } from './DialogSurface.types';
2020

2121
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { MessageBarProps, MessageBarState } from './MessageBar.types';
77
import { getIntentIcon } from './getIntentIcon';
88
import { useMessageBarReflow } from './useMessageBarReflow';
99
import { useMessageBarTransitionContext } from '../../contexts/messageBarTransitionContext';
10-
import { useMotionForwardedRef } from '../MotionRefForwarder';
10+
import { useMotionForwardedRef } from '@fluentui/react-motion';
1111

1212
/**
1313
* Create the state required to render MessageBar.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { JSXElement } from '@fluentui/react-utilities';
66
import type { MessageBarGroupState, MessageBarGroupSlots } from './MessageBarGroup.types';
77
import { PresenceGroup } from '@fluentui/react-motion';
88
import { MessageBarMotion } from './MessageBarGroup.motions';
9-
import { MotionRefForwarder } from '../MotionRefForwarder';
9+
import { MotionRefForwarder } from '@fluentui/react-motion';
1010

1111
/**
1212
* Render the final JSX of MessageBarGroup

packages/react-components/react-message-bar/library/src/components/MotionRefForwarder.tsx

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

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ export type MotionImperativeRef = {
8383
// @public
8484
export type MotionParam = boolean | number | string;
8585

86+
// @internal
87+
export const MotionRefForwarder: React_2.ForwardRefExoticComponent<{
88+
children: React_2.ReactElement;
89+
} & React_2.RefAttributes<HTMLElement>>;
90+
8691
// @public (undocumented)
8792
export const motionTokens: {
8893
curveAccelerateMax: "cubic-bezier(0.9,0.1,1,0.2)";
@@ -179,6 +184,9 @@ export type PresenceMotionSlotProps<MotionParams extends Record<string, MotionPa
179184
}>;
180185
};
181186

187+
// @internal
188+
export function useMotionForwardedRef(): React_2.Ref<HTMLElement> | undefined;
189+
182190
// @public (undocumented)
183191
export const usePresenceGroupChildContext: () => PresenceGroupChildContextValue | undefined;
184192

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { render } from '@testing-library/react';
2+
import * as React from 'react';
3+
4+
import { MotionRefForwarder, useMotionForwardedRef } from './MotionRefForwarder';
5+
6+
const TestConsumer: React.FC = () => {
7+
const ref = useMotionForwardedRef();
8+
9+
return <div data-testid="consumer" ref={ref as React.Ref<HTMLDivElement>} />;
10+
};
11+
12+
describe('MotionRefForwarder', () => {
13+
it('should provide a ref to children via context', () => {
14+
const ref = React.createRef<HTMLElement>();
15+
16+
const { getByTestId } = render(
17+
<MotionRefForwarder ref={ref}>
18+
<TestConsumer />
19+
</MotionRefForwarder>,
20+
);
21+
22+
expect(getByTestId('consumer')).toBe(ref.current);
23+
});
24+
25+
it('should provide undefined when not wrapped in MotionRefForwarder', () => {
26+
let capturedRef: React.Ref<HTMLElement> | undefined;
27+
28+
const RefCapture: React.FC = () => {
29+
capturedRef = useMotionForwardedRef();
30+
return null;
31+
};
32+
33+
render(<RefCapture />);
34+
35+
expect(capturedRef).toBeUndefined();
36+
});
37+
38+
it('should forward callback refs', () => {
39+
const callbackRef = jest.fn();
40+
41+
const { getByTestId } = render(
42+
<MotionRefForwarder ref={callbackRef}>
43+
<TestConsumer />
44+
</MotionRefForwarder>,
45+
);
46+
47+
const element = getByTestId('consumer');
48+
// The callback ref is passed via context to TestConsumer, which applies it to its div.
49+
expect(callbackRef).toHaveBeenCalledWith(element);
50+
});
51+
});

0 commit comments

Comments
 (0)