diff --git a/change/@fluentui-react-button-de57007e-ed5d-4006-b38f-0715f6088ed4.json b/change/@fluentui-react-button-de57007e-ed5d-4006-b38f-0715f6088ed4.json new file mode 100644 index 00000000000000..20094ddabdfe7d --- /dev/null +++ b/change/@fluentui-react-button-de57007e-ed5d-4006-b38f-0715f6088ed4.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: do not expose default icon in base hook", + "packageName": "@fluentui/react-button", + "email": "vgenaev@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.test.tsx b/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.test.tsx index d3fe07832432e2..dd013f7df19a50 100644 --- a/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.test.tsx +++ b/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.test.tsx @@ -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 }) => ( @@ -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(); + expect(result.container.querySelector('svg')).toBeInTheDocument(); }); it('preserves a user-provided menuIcon over the default chevron', () => { @@ -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(); + expect(result.container.querySelector('svg')).toBeInTheDocument(); + }); + + it('hides the menuIcon slot when menuIcon is null', () => { + const { result } = renderHook(() => useMenuButton_unstable({ menuIcon: null }, React.createRef())); + 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); diff --git a/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.tsx b/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.tsx index 6ece2ab7c6c2ce..74960c094bc888 100644 --- a/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.tsx +++ b/packages/react-components/react-button/library/src/components/MenuButton/useMenuButton.tsx @@ -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. */ @@ -40,10 +44,6 @@ export const useMenuButtonBase_unstable = ( }, menuIcon: slot.optional(menuIcon, { - defaultProps: { - children: , - }, - renderByDefault: true, elementType: 'span', }), }; @@ -61,8 +61,14 @@ export const useMenuButton_unstable = ( ref: React.Ref, ): MenuButtonState => { const { size: contextSize } = useButtonContext(); - const { appearance = 'secondary', shape = 'rounded', size = contextSize ?? 'medium', ...baseProps } = props; - const baseState = useMenuButtonBase_unstable(baseProps, ref); + const { appearance = 'secondary', menuIcon, shape = 'rounded', size = contextSize ?? 'medium', ...baseProps } = props; + const baseState = useMenuButtonBase_unstable( + { + ...baseProps, + menuIcon: menuIcon === null ? null : { children: , ...slot.resolveShorthand(menuIcon) }, + }, + ref, + ); return { ...baseState, diff --git a/packages/react-components/react-button/library/src/components/MenuButton/useMenuButtonBase.test.tsx b/packages/react-components/react-button/library/src/components/MenuButton/useMenuButtonBase.test.tsx index 186d30b12acf74..80e42abb28deaa 100644 --- a/packages/react-components/react-button/library/src/components/MenuButton/useMenuButtonBase.test.tsx +++ b/packages/react-components/react-button/library/src/components/MenuButton/useMenuButtonBase.test.tsx @@ -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 = ; + 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', () => {