Skip to content

Commit 6bc2c66

Browse files
authored
fix(react-button): do not expose default menuIcon in useMenuButtonBase_unstable (#36345)
1 parent 5d8f8f0 commit 6bc2c66

4 files changed

Lines changed: 44 additions & 10 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix: do not expose default icon in base hook",
4+
"packageName": "@fluentui/react-button",
5+
"email": "vgenaev@gmail.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.test.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as React from 'react';
2+
import { render } from '@testing-library/react';
23
import { renderHook } from '@testing-library/react-hooks';
34
import '@testing-library/jest-dom';
45
import { ButtonContextProvider } from '../../contexts/ButtonContext';
56
import type { ButtonContextValue } from '../../contexts/ButtonContext';
67
import { useMenuButton_unstable } from './useMenuButton';
8+
import { MenuButton } from './MenuButton';
79

810
const wrap = (contextValue: ButtonContextValue = {}): React.FC<{ children?: React.ReactNode }> => {
911
const Wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
@@ -19,10 +21,9 @@ describe('useMenuButton_unstable', () => {
1921
expect(result.current.components).toEqual({ root: 'button', icon: 'span', menuIcon: 'span' });
2022
});
2123

22-
it('renders a menuIcon slot with a default chevron icon', () => {
23-
const { result } = renderHook(() => useMenuButton_unstable({}, React.createRef()));
24-
expect(result.current.menuIcon).toBeDefined();
25-
expect(React.isValidElement(result.current.menuIcon?.children)).toBe(true);
24+
it('renders a menuIcon slot with a default icon', () => {
25+
const result = render(<MenuButton />);
26+
expect(result.container.querySelector('svg')).toBeInTheDocument();
2627
});
2728

2829
it('preserves a user-provided menuIcon over the default chevron', () => {
@@ -33,6 +34,16 @@ describe('useMenuButton_unstable', () => {
3334
expect(result.current.menuIcon?.children).toBe(customIcon);
3435
});
3536

37+
it('renders the default chevron when menuIcon is explicitly undefined', () => {
38+
const result = render(<MenuButton menuIcon={undefined} />);
39+
expect(result.container.querySelector('svg')).toBeInTheDocument();
40+
});
41+
42+
it('hides the menuIcon slot when menuIcon is null', () => {
43+
const { result } = renderHook(() => useMenuButton_unstable({ menuIcon: null }, React.createRef()));
44+
expect(result.current.menuIcon).toBeUndefined();
45+
});
46+
3647
it('defaults aria-expanded to false when not provided', () => {
3748
const { result } = renderHook(() => useMenuButton_unstable({}, React.createRef()));
3849
expect(result.current.root['aria-expanded']).toBe(false);

packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import type { MenuButtonBaseProps, MenuButtonBaseState, MenuButtonProps, MenuBut
1010
/**
1111
* Base hook for MenuButton.
1212
*
13+
* The `menuIcon` slot ships no icon of its own and only renders when a consumer
14+
* provides one, so headless consumers can supply their own visuals. The styled
15+
* `useMenuButton_unstable` adds the default chevron on top of this.
16+
*
1317
* @param props - User provided props to the MenuButton component.
1418
* @param ref - User provided ref to be passed to the MenuButton component.
1519
*/
@@ -40,10 +44,6 @@ export const useMenuButtonBase_unstable = (
4044
},
4145

4246
menuIcon: slot.optional(menuIcon, {
43-
defaultProps: {
44-
children: <ChevronDownRegular />,
45-
},
46-
renderByDefault: true,
4747
elementType: 'span',
4848
}),
4949
};
@@ -61,11 +61,18 @@ export const useMenuButton_unstable = (
6161
ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
6262
): MenuButtonState => {
6363
const { size: contextSize } = useButtonContext();
64-
const { appearance = 'secondary', shape = 'rounded', size = contextSize ?? 'medium', ...baseProps } = props;
64+
const { appearance = 'secondary', menuIcon, shape = 'rounded', size = contextSize ?? 'medium', ...baseProps } = props;
6565
const baseState = useMenuButtonBase_unstable(baseProps, ref);
6666

6767
return {
6868
...baseState,
69+
menuIcon: slot.optional(menuIcon, {
70+
defaultProps: {
71+
children: <ChevronDownRegular />,
72+
},
73+
renderByDefault: true,
74+
elementType: 'span',
75+
}),
6976
appearance,
7077
shape,
7178
size,

packages/react-components/react-button/library/src/components/MenuButton/useMenuButtonBase.test.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@ import '@testing-library/jest-dom';
44
import { useMenuButtonBase_unstable } from './useMenuButton';
55

66
describe('useMenuButtonBase_unstable', () => {
7-
it('returns a menuIcon slot by default', () => {
7+
it('does not render the menuIcon slot when none is provided', () => {
88
const { result } = renderHook(() => useMenuButtonBase_unstable({}, React.createRef()));
9+
expect(result.current.menuIcon).toBeUndefined();
10+
});
11+
12+
it('renders the menuIcon slot only when one is provided, shipping no default icon', () => {
13+
const customIcon = <span data-testid="custom-menu-icon" />;
14+
const { result } = renderHook(() =>
15+
useMenuButtonBase_unstable({ menuIcon: { children: customIcon } }, React.createRef()),
16+
);
917
expect(result.current.menuIcon).toBeDefined();
18+
expect(result.current.menuIcon?.children).toBe(customIcon);
1019
});
1120

1221
it('forces aria-expanded to a boolean on root', () => {

0 commit comments

Comments
 (0)