Skip to content

Commit 9462a63

Browse files
committed
fix(react-components): use custom RefAttributes instead of React.RefAttributes
1 parent 813b1fb commit 9462a63

File tree

14 files changed

+124
-79
lines changed

14 files changed

+124
-79
lines changed

packages/eslint-plugin/src/configs/react.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ module.exports = {
3737
},
3838
],
3939
'react-compiler/react-compiler': ['error'],
40+
'@typescript-eslint/no-restricted-types': [
41+
'error',
42+
{
43+
types: {
44+
'React.RefAttributes': {
45+
message:
46+
'`React.RefAttributes` is leaking string starting @types/[email protected] creating invalid type contracts. Use `RefAttributes` from @fluentui/react-utilities instead',
47+
fixWith: 'RefAttributes',
48+
},
49+
},
50+
},
51+
],
4052
},
4153
overrides: [
4254
// Enable rules requiring type info only for appropriate files/circumstances

packages/react-components/react-migration-v8-v9/library/etc/react-migration-v8-v9.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const brandWeb: BrandVariants;
4242
export const ButtonShim: React_2.ForwardRefExoticComponent<IBaseButtonProps & React_2.RefAttributes<HTMLButtonElement>>;
4343

4444
// @public (undocumented)
45-
export const CheckboxShim: React_2.ForwardRefExoticComponent<Pick<ICheckboxProps, "label" | "title" | "className" | "key" | "disabled" | "name" | "defaultChecked" | "id" | "onChange" | "componentRef" | "styles" | "theme" | "checked" | "ariaLabel" | "required" | "ariaDescribedBy" | "ariaLabelledBy" | "ariaPositionInSet" | "ariaSetSize" | "boxSide" | "checkmarkIconProps" | "defaultIndeterminate" | "indeterminate" | "inputProps" | "onRenderLabel"> & React_2.RefAttributes<HTMLInputElement>>;
45+
export const CheckboxShim: React_2.ForwardRefExoticComponent<ICheckboxProps & React_2.RefAttributes<HTMLInputElement>>;
4646

4747
// @public
4848
export type ColorVariants = {

packages/react-components/react-migration-v8-v9/library/src/components/Button/ActionButtonShim.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@ import * as React from 'react';
33
import type { IButtonProps } from '@fluentui/react';
44

55
import { Button } from '@fluentui/react-components';
6+
import type { RefAttributes } from '@fluentui/react-utilities';
67

78
import { shimButtonProps } from './shimButtonProps';
89

910
/**
1011
* Shims a v8 ActionButton to render a v9 Button
1112
*/
12-
export const ActionButtonShim: React.ForwardRefExoticComponent<IButtonProps & React.RefAttributes<HTMLButtonElement>> =
13-
React.forwardRef((props, _ref) => {
14-
const variantProps = {
15-
...props,
16-
variantClassName: 'ms-Button--action ms-Button--command',
17-
};
13+
export const ActionButtonShim: React.ForwardRefExoticComponent<
14+
IButtonProps &
15+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
16+
React.RefAttributes<HTMLButtonElement>
17+
> = React.forwardRef((props, _ref) => {
18+
const variantProps = {
19+
...props,
20+
variantClassName: 'ms-Button--action ms-Button--command',
21+
};
1822

19-
const shimProps = shimButtonProps(variantProps);
23+
const shimProps = shimButtonProps(variantProps);
2024

21-
return <Button {...(props as React.RefAttributes<HTMLButtonElement>)} {...shimProps} appearance="transparent" />;
22-
});
25+
return <Button {...(props as RefAttributes<HTMLButtonElement>)} {...shimProps} appearance="transparent" />;
26+
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/ButtonShim.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@ import * as React from 'react';
33
import type { IBaseButtonProps } from '@fluentui/react';
44

55
import { Button } from '@fluentui/react-components';
6+
import type { RefAttributes } from '@fluentui/react-utilities';
67

78
import { shimButtonProps } from './shimButtonProps';
89
import { ToggleButtonShim } from './ToggleButtonShim';
910
import { CompoundButtonShim } from './CompoundButtonShim';
1011

11-
export const ButtonShim: React.ForwardRefExoticComponent<IBaseButtonProps & React.RefAttributes<HTMLButtonElement>> =
12-
React.forwardRef((props, _ref) => {
13-
const shimProps = shimButtonProps(props);
12+
export const ButtonShim: React.ForwardRefExoticComponent<
13+
IBaseButtonProps &
14+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
15+
React.RefAttributes<HTMLButtonElement>
16+
> = React.forwardRef((props, _ref) => {
17+
const shimProps = shimButtonProps(props);
1418

15-
if (props.toggle) {
16-
return <ToggleButtonShim {...props}>{props.children}</ToggleButtonShim>;
17-
}
18-
if (props.secondaryText || props.onRenderDescription?.(props)) {
19-
return <CompoundButtonShim {...props} />;
20-
}
19+
if (props.toggle) {
20+
return <ToggleButtonShim {...props}>{props.children}</ToggleButtonShim>;
21+
}
22+
if (props.secondaryText || props.onRenderDescription?.(props)) {
23+
return <CompoundButtonShim {...props} />;
24+
}
2125

22-
return <Button {...(props as React.RefAttributes<HTMLButtonElement>)} {...shimProps} />;
23-
});
26+
return <Button {...(props as RefAttributes<HTMLButtonElement>)} {...shimProps} />;
27+
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/CompoundButtonShim.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import type { IButtonProps } from '@fluentui/react';
44

55
import { CompoundButton } from '@fluentui/react-components';
66
import type { CompoundButtonProps } from '@fluentui/react-components';
7+
import type { RefAttributes } from '@fluentui/react-utilities';
78

89
import { shimButtonProps } from './shimButtonProps';
910

1011
/**
1112
* Shims v8 CompoundButton to render a v9 CompoundButton
1213
*/
1314
export const CompoundButtonShim: React.ForwardRefExoticComponent<
14-
IButtonProps & React.RefAttributes<HTMLButtonElement>
15+
IButtonProps &
16+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
17+
React.RefAttributes<HTMLButtonElement>
1518
> = React.forwardRef((props, _ref) => {
1619
const variantProps = {
1720
...props,
@@ -23,5 +26,5 @@ export const CompoundButtonShim: React.ForwardRefExoticComponent<
2326
secondaryContent: props.secondaryText || props.onRenderDescription?.(props),
2427
};
2528

26-
return <CompoundButton {...(props as React.RefAttributes<HTMLButtonElement>)} {...shimProps} />;
29+
return <CompoundButton {...(props as RefAttributes<HTMLButtonElement>)} {...shimProps} />;
2730
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/DefaultButtonShim.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { ButtonShim } from './ButtonShim';
77
/**
88
* Shims a v8 DefaultButton to render a v9 Button
99
*/
10-
export const DefaultButtonShim: React.ForwardRefExoticComponent<IButtonProps & React.RefAttributes<HTMLButtonElement>> =
11-
React.forwardRef((props, _ref) => {
12-
return <ButtonShim {...props} variantClassName={props.primary ? 'ms-Button--primary' : 'ms-Button--default'} />;
13-
});
10+
export const DefaultButtonShim: React.ForwardRefExoticComponent<
11+
IButtonProps &
12+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
13+
React.RefAttributes<HTMLButtonElement>
14+
> = React.forwardRef((props, _ref) => {
15+
return <ButtonShim {...props} variantClassName={props.primary ? 'ms-Button--primary' : 'ms-Button--default'} />;
16+
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/MenuButtonShim.tsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,36 @@ import { MenuItemShim, shimMenuProps } from '../Menu/index';
77

88
import { shimButtonProps } from './shimButtonProps';
99

10-
export const MenuButtonShim: React.ForwardRefExoticComponent<IButtonProps & React.RefAttributes<HTMLButtonElement>> =
11-
React.forwardRef((props, _ref) => {
12-
const variantProps = {
13-
...props,
14-
variantClassName: props.primary ? 'ms-Button--primary' : 'ms-Button--default',
15-
};
10+
export const MenuButtonShim: React.ForwardRefExoticComponent<
11+
IButtonProps &
12+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
13+
React.RefAttributes<HTMLButtonElement>
14+
> = React.forwardRef((props, _ref) => {
15+
const variantProps = {
16+
...props,
17+
variantClassName: props.primary ? 'ms-Button--primary' : 'ms-Button--default',
18+
};
1619

17-
const shimProps: MenuButtonProps = {
18-
...shimButtonProps(variantProps),
19-
};
20+
const shimProps: MenuButtonProps = {
21+
...shimButtonProps(variantProps),
22+
};
2023

21-
const shimmedMenuProps = props.menuProps ? shimMenuProps(props.menuProps) : {};
24+
const shimmedMenuProps = props.menuProps ? shimMenuProps(props.menuProps) : {};
2225

23-
return (
24-
<Menu {...shimmedMenuProps}>
25-
<MenuTrigger>
26-
<MenuButton {...shimProps} />
27-
</MenuTrigger>
28-
<MenuPopover>
29-
<MenuList>
30-
{props.menuProps?.items.map(item => (
31-
// key is added through item spread
32-
// eslint-disable-next-line react/jsx-key
33-
<MenuItemShim {...item} />
34-
))}
35-
</MenuList>
36-
</MenuPopover>
37-
</Menu>
38-
);
39-
});
26+
return (
27+
<Menu {...shimmedMenuProps}>
28+
<MenuTrigger>
29+
<MenuButton {...shimProps} />
30+
</MenuTrigger>
31+
<MenuPopover>
32+
<MenuList>
33+
{props.menuProps?.items.map(item => (
34+
// key is added through item spread
35+
// eslint-disable-next-line react/jsx-key
36+
<MenuItemShim {...item} />
37+
))}
38+
</MenuList>
39+
</MenuPopover>
40+
</Menu>
41+
);
42+
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/PrimaryButtonShim.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { ButtonShim } from './ButtonShim';
66
/**
77
* Shims v8 PrimaryButton to render a v9 Button
88
*/
9-
export const PrimaryButtonShim: React.ForwardRefExoticComponent<IButtonProps & React.RefAttributes<HTMLButtonElement>> =
10-
React.forwardRef((props, _ref) => {
11-
return <ButtonShim {...props} primary variantClassName="ms-Button--primary" />;
12-
});
9+
export const PrimaryButtonShim: React.ForwardRefExoticComponent<
10+
IButtonProps &
11+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
12+
React.RefAttributes<HTMLButtonElement>
13+
> = React.forwardRef((props, _ref) => {
14+
return <ButtonShim {...props} primary variantClassName="ms-Button--primary" />;
15+
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/ToggleButtonShim.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,28 @@ import type { IButtonProps } from '@fluentui/react';
44

55
import { ToggleButton } from '@fluentui/react-components';
66
import type { ToggleButtonProps } from '@fluentui/react-components';
7+
import type { RefAttributes } from '@fluentui/react-utilities';
78

89
import { shimButtonProps } from './shimButtonProps';
910

1011
/**
1112
* Shims v8 ToggleButton to render a v9 ToggleButton
1213
*/
13-
export const ToggleButtonShim: React.ForwardRefExoticComponent<IButtonProps & React.RefAttributes<HTMLButtonElement>> =
14-
React.forwardRef((props, _ref) => {
15-
const variantProps = {
16-
...props,
17-
variantClassName: props.primary ? 'ms-Button--compoundPrimary' : 'ms-Button--compound',
18-
};
14+
export const ToggleButtonShim: React.ForwardRefExoticComponent<
15+
IButtonProps &
16+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
17+
React.RefAttributes<HTMLButtonElement>
18+
> = React.forwardRef((props, _ref) => {
19+
const variantProps = {
20+
...props,
21+
variantClassName: props.primary ? 'ms-Button--compoundPrimary' : 'ms-Button--compound',
22+
};
1923

20-
const shimProps: ToggleButtonProps = {
21-
...shimButtonProps(variantProps),
22-
checked: props.checked,
23-
defaultChecked: props.defaultChecked,
24-
};
24+
const shimProps: ToggleButtonProps = {
25+
...shimButtonProps(variantProps),
26+
checked: props.checked,
27+
defaultChecked: props.defaultChecked,
28+
};
2529

26-
return <ToggleButton {...(props as React.RefAttributes<HTMLButtonElement>)} {...shimProps} />;
27-
});
30+
return <ToggleButton {...(props as RefAttributes<HTMLButtonElement>)} {...shimProps} />;
31+
});

packages/react-components/react-migration-v8-v9/library/src/components/Button/shimButtonProps.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import type { IBaseButtonProps } from '@fluentui/react';
55

66
import type { ButtonProps } from '@fluentui/react-components';
77

8-
export const shimButtonProps = (props: IBaseButtonProps & React.RefAttributes<HTMLButtonElement>): ButtonProps => {
8+
export const shimButtonProps = (
9+
props: IBaseButtonProps &
10+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
11+
React.RefAttributes<HTMLButtonElement>,
12+
): ButtonProps => {
913
//TODO: Icon shim. This still renders the v8 icon.
1014
const icon = props.onRenderIcon ? (
1115
props.onRenderIcon(props)

packages/react-components/react-migration-v8-v9/library/src/components/Checkbox/CheckboxShim.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const getClassNames = classNamesFunction<ICheckboxStyleProps, ICheckboxStyles>({
99
useStaticStyles: false,
1010
});
1111

12-
export const CheckboxShim = React.forwardRef((props: ICheckboxProps, _ref: React.ForwardedRef<HTMLInputElement>) => {
12+
export const CheckboxShim = React.forwardRef((props, _ref) => {
1313
'use no memo';
1414

1515
const { className, styles: stylesV8, onRenderLabel, label, componentRef } = props;
@@ -51,6 +51,11 @@ export const CheckboxShim = React.forwardRef((props: ICheckboxProps, _ref: React
5151
indicator={{ className: mergeClasses('ms-Checkbox-checkbox', styles.checkbox) }}
5252
/>
5353
);
54-
});
54+
// NOTE: cast is necessary as `ICheckboxProps` extends React.Ref<HTMLDivElement> which is not compatible with our defined React.Ref<HTMLInputElement>
55+
}) as React.ForwardRefExoticComponent<
56+
ICheckboxProps &
57+
// eslint-disable-next-line @typescript-eslint/no-restricted-types -- this is expected in order to be compatible with v8, as every v8 interface contains `React.RefAttributes` to accept ref as string
58+
React.RefAttributes<HTMLInputElement>
59+
>;
5560

5661
CheckboxShim.displayName = 'CheckboxShim';

packages/react-components/react-nav-preview/library/src/components/NavDrawer/useNavDrawer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import { Drawer, DrawerProps } from '@fluentui/react-drawer';
33
import { useArrowNavigationGroup } from '@fluentui/react-tabster';
4-
import { slot } from '@fluentui/react-utilities';
4+
import { RefAttributes, slot } from '@fluentui/react-utilities';
55

66
import { useNav_unstable } from '../Nav/useNav';
77
import type { NavDrawerProps, NavDrawerState } from './NavDrawer.types';
@@ -49,7 +49,7 @@ export const useNavDrawer_unstable = (props: NavDrawerProps, ref: React.Ref<HTML
4949
// this is a problem with the lack of support for union types on React v18
5050
// ComponentState is using React.ComponentType which will try to infer propType
5151
// propTypes WeakValidator signature will break distributive unions making this type invalid
52-
elementType: Drawer as React.FC<DrawerProps & React.RefAttributes<HTMLDivElement>>,
52+
elementType: Drawer as React.FC<DrawerProps & RefAttributes<HTMLDivElement>>,
5353
},
5454
),
5555
};

packages/react-components/react-utilities/etc/react-utilities.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export function getEventClientCoords(event: TouchOrMouseEvent): {
7272
};
7373

7474
// @public
75-
export const getIntrinsicElementProps: <Props extends UnknownSlotProps, ExcludedPropKeys extends Extract<keyof Props, string> = never>(tagName: NonNullable<Props["as"]>, props: Props & React_2.RefAttributes<InferredElementRefType<Props>>, excludedPropNames?: ExcludedPropKeys[] | undefined) => DistributiveOmit<Props, ExcludedPropKeys | Exclude<keyof Props, "as" | keyof HTMLAttributes>>;
75+
export const getIntrinsicElementProps: <Props extends UnknownSlotProps, ExcludedPropKeys extends Extract<keyof Props, string> = never>(tagName: NonNullable<Props["as"]>, props: Props & RefAttributes<InferredElementRefType<Props>>, excludedPropNames?: ExcludedPropKeys[] | undefined) => DistributiveOmit<Props, ExcludedPropKeys | Exclude<keyof Props, "as" | keyof HTMLAttributes>>;
7676

7777
// @public @deprecated
7878
export function getNativeElementProps<TAttributes extends React_2.HTMLAttributes<any>>(tagName: string, props: {}, excludedPropNames?: string[]): TAttributes;

packages/react-components/react-utilities/src/compose/getIntrinsicElementProps.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import { getNativeElementProps } from '../utils/getNativeElementProps';
33
import type { InferredElementRefType, UnknownSlotProps } from './types';
4-
import type { DistributiveOmit } from '../utils/types';
4+
import type { DistributiveOmit, RefAttributes } from '../utils/types';
55

66
// eslint-disable-next-line @typescript-eslint/no-explicit-any
77
type HTMLAttributes = React.HTMLAttributes<any>;
@@ -19,7 +19,7 @@ export const getIntrinsicElementProps = <
1919
/** The slot's default element type (e.g. 'div') */
2020
tagName: NonNullable<Props['as']>,
2121
/** The component's props object */
22-
props: Props & React.RefAttributes<InferredElementRefType<Props>>,
22+
props: Props & RefAttributes<InferredElementRefType<Props>>,
2323
/** List of native props to exclude from the returned value */
2424
excludedPropNames?: ExcludedPropKeys[],
2525
) => {

0 commit comments

Comments
 (0)