Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Comment thread
mainframev marked this conversation as resolved.
"type": "patch",
"comment": "fix: do not expose default icon in base hook",
"packageName": "@fluentui/react-button",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import '@testing-library/jest-dom';
import { ButtonContextProvider } from '../../contexts/ButtonContext';
import type { ButtonContextValue } from '../../contexts/ButtonContext';
import { useMenuButton_unstable } from './useMenuButton';
import { MenuButton } from './MenuButton';

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

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

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

it('renders the default chevron when menuIcon is explicitly undefined', () => {
const result = render(<MenuButton menuIcon={undefined} />);
expect(result.container.querySelector('svg')).toBeInTheDocument();
});

it('hides the menuIcon slot when menuIcon is null', () => {
const { result } = renderHook(() => useMenuButton_unstable({ menuIcon: null }, React.createRef()));
Comment thread
mainframev marked this conversation as resolved.
expect(result.current.menuIcon).toBeUndefined();
});

it('defaults aria-expanded to false when not provided', () => {
const { result } = renderHook(() => useMenuButton_unstable({}, React.createRef()));
expect(result.current.root['aria-expanded']).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import type { MenuButtonBaseProps, MenuButtonBaseState, MenuButtonProps, MenuBut
/**
* Base hook for MenuButton.
*
* The `menuIcon` slot ships no icon of its own and only renders when a consumer
* provides one, so headless consumers can supply their own visuals. The styled
* `useMenuButton_unstable` adds the default chevron on top of this.
*
* @param props - User provided props to the MenuButton component.
* @param ref - User provided ref to be passed to the MenuButton component.
*/
Expand Down Expand Up @@ -40,10 +44,6 @@ export const useMenuButtonBase_unstable = (
},

menuIcon: slot.optional(menuIcon, {
defaultProps: {
children: <ChevronDownRegular />,
},
renderByDefault: true,
elementType: 'span',
}),
};
Expand All @@ -61,11 +61,18 @@ export const useMenuButton_unstable = (
ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
): MenuButtonState => {
const { size: contextSize } = useButtonContext();
const { appearance = 'secondary', shape = 'rounded', size = contextSize ?? 'medium', ...baseProps } = props;
const { appearance = 'secondary', menuIcon, shape = 'rounded', size = contextSize ?? 'medium', ...baseProps } = props;
const baseState = useMenuButtonBase_unstable(baseProps, ref);

return {
...baseState,
menuIcon: slot.optional(menuIcon, {
defaultProps: {
children: <ChevronDownRegular />,
},
renderByDefault: true,
elementType: 'span',
}),
appearance,
shape,
size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ import '@testing-library/jest-dom';
import { useMenuButtonBase_unstable } from './useMenuButton';

describe('useMenuButtonBase_unstable', () => {
it('returns a menuIcon slot by default', () => {
it('does not render the menuIcon slot when none is provided', () => {
const { result } = renderHook(() => useMenuButtonBase_unstable({}, React.createRef()));
expect(result.current.menuIcon).toBeUndefined();
});

it('renders the menuIcon slot only when one is provided, shipping no default icon', () => {
const customIcon = <span data-testid="custom-menu-icon" />;
const { result } = renderHook(() =>
useMenuButtonBase_unstable({ menuIcon: { children: customIcon } }, React.createRef()),
);
expect(result.current.menuIcon).toBeDefined();
expect(result.current.menuIcon?.children).toBe(customIcon);
});

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