diff --git a/change/@fluentui-react-headless-components-preview-36d8a49b-db93-4a50-84b4-98bccd2f1e61.json b/change/@fluentui-react-headless-components-preview-36d8a49b-db93-4a50-84b4-98bccd2f1e61.json new file mode 100644 index 00000000000000..0a686162cc8839 --- /dev/null +++ b/change/@fluentui-react-headless-components-preview-36d8a49b-db93-4a50-84b4-98bccd2f1e61.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: add headless SplitButton", + "packageName": "@fluentui/react-headless-components-preview", + "email": "vgenaev@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-button/library/bundle-size/useSplitButtonBase.fixture.js b/packages/react-components/react-button/library/bundle-size/useSplitButtonBase.fixture.js new file mode 100644 index 00000000000000..a1e15d1fbde158 --- /dev/null +++ b/packages/react-components/react-button/library/bundle-size/useSplitButtonBase.fixture.js @@ -0,0 +1,7 @@ +import { useSplitButtonBase_unstable } from '@fluentui/react-button'; + +console.log(useSplitButtonBase_unstable); + +export default { + name: 'useSplitButtonBase_unstable', +}; diff --git a/packages/react-components/react-button/library/src/components/SplitButton/useSplitButton.ts b/packages/react-components/react-button/library/src/components/SplitButton/useSplitButton.ts index a221b9c53dc5bd..377dbd07cb7eb3 100644 --- a/packages/react-components/react-button/library/src/components/SplitButton/useSplitButton.ts +++ b/packages/react-components/react-button/library/src/components/SplitButton/useSplitButton.ts @@ -23,6 +23,7 @@ export const useSplitButton_unstable = (props: SplitButtonProps, ref: React.Ref< const menuButtonShorthand = slot.optional(props.menuButton, { defaultProps: { ...baseState.menuButton, + children: undefined, appearance, disabledFocusable: baseState.disabledFocusable, menuIcon, @@ -35,6 +36,7 @@ export const useSplitButton_unstable = (props: SplitButtonProps, ref: React.Ref< const primaryActionButtonShorthand = slot.optional(props.primaryActionButton, { defaultProps: { ...baseState.primaryActionButton, + children: props.children, appearance, disabledFocusable: baseState.disabledFocusable, icon, diff --git a/packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js b/packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js index 3485a0285aa9ea..55dcfcc1972c14 100644 --- a/packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js +++ b/packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js @@ -36,6 +36,7 @@ import * as Skeleton from '@fluentui/react-headless-components-preview/skeleton' import * as Slider from '@fluentui/react-headless-components-preview/slider'; import * as SpinButton from '@fluentui/react-headless-components-preview/spin-button'; import * as Spinner from '@fluentui/react-headless-components-preview/spinner'; +import * as SplitButton from '@fluentui/react-headless-components-preview/split-button'; import * as Switch from '@fluentui/react-headless-components-preview/switch'; import * as TabList from '@fluentui/react-headless-components-preview/tab-list'; import * as Tag from '@fluentui/react-headless-components-preview/tag'; @@ -87,6 +88,7 @@ console.log({ Slider, SpinButton, Spinner, + SplitButton, Switch, TabList, Tag, diff --git a/packages/react-components/react-headless-components-preview/library/etc/split-button.api.md b/packages/react-components/react-headless-components-preview/library/etc/split-button.api.md new file mode 100644 index 00000000000000..d074015338298c --- /dev/null +++ b/packages/react-components/react-headless-components-preview/library/etc/split-button.api.md @@ -0,0 +1,42 @@ +## API Report File for "@fluentui/react-headless-components-preview" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { ButtonBaseProps } from '@fluentui/react-button'; +import type { ComponentProps } from '@fluentui/react-utilities'; +import type { ComponentState } from '@fluentui/react-utilities'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import type { JSXElement } from '@fluentui/react-utilities'; +import { MenuButtonBaseProps } from '@fluentui/react-button'; +import type * as React_2 from 'react'; +import type { Slot } from '@fluentui/react-utilities'; +import type { SplitButtonBaseProps } from '@fluentui/react-button'; +import type { SplitButtonBaseSlots } from '@fluentui/react-button'; +import type { SplitButtonBaseState } from '@fluentui/react-button'; + +// @public +export const renderSplitButton: (state: SplitButtonState) => JSXElement; + +// @public +export const SplitButton: ForwardRefComponent; + +// @public +export type SplitButtonProps = ComponentProps & Pick; + +// @public (undocumented) +export type SplitButtonSlots = Omit & { + menuButton?: Slot; + primaryActionButton?: Slot; +}; + +// @public +export type SplitButtonState = ComponentState & Pick; + +// @public +export const useSplitButton: (props: SplitButtonProps, ref: React_2.Ref) => SplitButtonState; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/react-components/react-headless-components-preview/library/package.json b/packages/react-components/react-headless-components-preview/library/package.json index 718fb2bcb09198..572dcb978a95de 100644 --- a/packages/react-components/react-headless-components-preview/library/package.json +++ b/packages/react-components/react-headless-components-preview/library/package.json @@ -316,6 +316,12 @@ "import": "./lib/spinner.js", "require": "./lib-commonjs/spinner.js" }, + "./split-button": { + "types": "./dist/split-button.d.ts", + "node": "./lib-commonjs/split-button.js", + "import": "./lib/split-button.js", + "require": "./lib-commonjs/split-button.js" + }, "./switch": { "types": "./dist/switch.d.ts", "node": "./lib-commonjs/switch.js", diff --git a/packages/react-components/react-headless-components-preview/library/src/components/SplitButton/SplitButton.test.tsx b/packages/react-components/react-headless-components-preview/library/src/components/SplitButton/SplitButton.test.tsx new file mode 100644 index 00000000000000..76e2246b7966d7 --- /dev/null +++ b/packages/react-components/react-headless-components-preview/library/src/components/SplitButton/SplitButton.test.tsx @@ -0,0 +1,167 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { SlotRenderFunction } from '@fluentui/react-utilities'; +import { isConformant } from '../../testing/isConformant'; +import { SplitButton } from './SplitButton'; +import type { ButtonProps } from '../Button/Button.types'; +import type { MenuButtonProps } from '../MenuButton/MenuButton.types'; + +describe('SplitButton', () => { + isConformant({ + Component: SplitButton, + displayName: 'SplitButton', + }); + + it('renders both the primary action button and the menu button', () => { + const { getAllByRole } = render(This is a button); + const [primaryActionButton, menuButton] = getAllByRole('button'); + + expect(primaryActionButton).toBeInTheDocument(); + expect(menuButton).toBeInTheDocument(); + }); + + it('preserves default children for a primary action button render function', () => { + const renderPrimaryActionButton: SlotRenderFunction = (_Component, slotProps) => slotProps.children; + const { getByText } = render( + This is a button, + ); + + expect(getByText('This is a button')).toBeInTheDocument(); + }); + + it('preserves undefined children for a menu button render function', () => { + const renderMenuButton: SlotRenderFunction = (_Component, slotProps) => { + expect(slotProps.children).toBeUndefined(); + return