Skip to content

feat(react-button): expose useSplitButtonBase_unstable base hook - #36524

Open
Victor Genaev (mainframev) wants to merge 1 commit into
test/split-button-hook-regression-testsfrom
feat/split-button-base-hook
Open

feat(react-button): expose useSplitButtonBase_unstable base hook#36524
Victor Genaev (mainframev) wants to merge 1 commit into
test/split-button-hook-regression-testsfrom
feat/split-button-base-hook

Conversation

@mainframev

@mainframev Victor Genaev (mainframev) commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR 2 of a 3-PR stack. Base: #36523.

Extracts a design-agnostic useSplitButtonBase_unstable base hook (plus SplitButtonBaseProps/SplitButtonBaseSlots/SplitButtonBaseState) out of the styled useSplitButton_unstable, so a future headless SplitButton (PR 3) can compose the same behavior with headless Button/MenuButton instead of the styled ones.

Stack:

  1. test(react-button): add SplitButton regression test before base-hook extraction #36523 — regression tests (merged into this branch's history)
  2. this PR — @fluentui/react-button base-hook extraction
  3. feat/split-button-headless — headless SplitButton (base: this branch)

What changed

  • SplitButton.types.ts: added SplitButtonBaseSlots, SplitButtonBaseProps, SplitButtonBaseState — component-agnostic, typed against ButtonBaseProps/MenuButtonBaseProps instead of concrete Button/MenuButton. Existing styled types (SplitButtonSlots/SplitButtonProps/SplitButtonState) are unchanged.
  • useSplitButton.ts: added useSplitButtonBase_unstable, which owns all design-agnostic behavior (generated primary-action id, aria-labelledby fallback naming, disabled/disabledFocusable propagation, icon propagation). Refactored useSplitButton_unstable to call the base hook and recreate both child slots via slot.optional with the styled Button/MenuButton element types (production rendering resolves each slot's element type from its SLOT_ELEMENT_TYPE_SYMBOL metadata, not from state.components, so recreating rather than patching state.components is required).
  • Exported the new base types/hook from the component and package barrels.
  • Added useSplitButtonBase.test.tsx (16 tests) for the new base contract.
  • Added the useSplitButtonBase.fixture.js bundle-size fixture.
  • Regenerated react-button.api.md.

No behavior change to the styled SplitButton/useSplitButton_unstable — the regression tests added in #36523 continue to pass unmodified.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Pull request demo site: URL

@mainframev
Victor Genaev (mainframev) force-pushed the feat/split-button-base-hook branch 3 times, most recently from 752def6 to 7b50ac1 Compare August 5, 2026 21:43
…c base hook

Splits SplitButton's internal logic into two layers:
- useSplitButtonBase_unstable: design-agnostic base hook (no appearance/shape/
  size), producing ARIA-wired root/menuButton/primaryActionButton slots with
  placeholder 'button' element metadata.
- useSplitButton_unstable: styled wrapper that composes the base hook and
  recreates both child slots via slot.optional with the concrete Button/
  MenuButton element types and styling defaults.

Also widens renderSplitButton_unstable to accept SplitButtonBaseState so it
can be reused directly by headless consumers instead of being duplicated.

Adds SplitButtonBaseProps/SplitButtonBaseSlots/SplitButtonBaseState, exported
from the SplitButton and package root barrels, together with a bundle-size
fixture. This unblocks a future headless SplitButton built directly on the
base hook.
@mainframev
Victor Genaev (mainframev) marked this pull request as ready for review August 5, 2026 21:54
@mainframev Victor Genaev (mainframev) changed the title feat(react-button): extract useSplitButtonBase_unstable design-agnostic base hook feat(react-button): expose useSplitButtonBase_unstable base hook Aug 5, 2026

console.log(useSplitButtonBase_unstable);

export default {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this dev-only thing or do you want to keep this?

* structure and ARIA attributes, without design-specific props such as `appearance`, `shape`, or
* `size`.
*
* The `menuButton` and `primaryActionButton` slots returned by this hook use placeholder element

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we shorten the comment a bit?

const { appearance = 'secondary', shape = 'rounded', size = 'medium', ...rest } = props;
const baseState = useSplitButtonBase_unstable(rest, ref as React.Ref<HTMLDivElement>);

const menuButtonShorthand = slot.optional<ExtractSlotProps<NonNullable<SplitButtonSlots['menuButton']>>>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use slot from props and pass slot from base state to default props, something like:

const menuButtonShorthand = slot.optional(props.menuButton, {
   {
      defaultProps: {
        ...baseState.menuButton,
        appearance,
        shape,
        size,
      },
      renderByDefault: true,
      elementType: MenuButton,
    },
})

In that case no type-casting is needed, and everything should work as expected

},
);
const primaryActionButtonShorthand = slot.optional<
ExtractSlotProps<NonNullable<SplitButtonSlots['primaryActionButton']>>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same as above, pls use slot from props

appearance,
children,
disabled,
disabledFocusable,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disabledFocusable, icon, iconPosition are not HMTML button attributes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants