Skip to content

fix: move Field control props to base hooks and support tooltip-wrapped triggers#36334

Open
dmytrokirpa wants to merge 3 commits into
masterfrom
fix/field-control-props-base-hooks
Open

fix: move Field control props to base hooks and support tooltip-wrapped triggers#36334
dmytrokirpa wants to merge 3 commits into
masterfrom
fix/field-control-props-base-hooks

Conversation

@dmytrokirpa

@dmytrokirpa dmytrokirpa commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Description

Moves the useFieldControlProps_unstable call out of the top-level component hooks and into the corresponding base hooks for Checkbox, Input, Select, and SpinButton. Previously, Field integration (label association, required state, size) was only applied in the public use*_unstable hooks, so consumers building on the base hooks (e.g. headless compositions) did not inherit the surrounding <Field> props. Merging these props in the base hook ensures consistent Field behavior across both the standard and headless usages.

This PR also adds support for tooltip-wrapped trigger composition in @fluentui/react-headless-components-preview by marking the trigger components as Fluent trigger components, so a Tooltip can wrap a DialogTrigger, MenuTrigger, or PopoverTrigger without breaking the trigger ref/event composition.

Changes

Move Field control props to base hooks

  • react-checkbox: moved useFieldControlProps_unstable from useCheckbox_unstable into useCheckboxBase_unstable.
  • react-input: moved useFieldControlProps_unstable from useInput_unstable into useInputBase_unstable (base hook now consumes the merged Field props for value, slots, and onChange).
  • react-select: moved the Field control props merge into the base hook.
  • react-spinbutton: moved the Field control props merge into the base hook.

Headless components – tooltip-wrapped trigger composition

  • Marked DialogTrigger, MenuTrigger, PopoverTrigger, and Tooltip with isFluentTriggerComponent = true (via FluentTriggerComponent cast) so nested triggers compose correctly.
  • Added e2e (Cypress) coverage for Dialog, Menu, and Popover wrapped by Tooltip, verifying open/close, keyboard interaction (Enter/Escape), focus restoration, ARIA attributes, and that the tooltip shows on hover without opening the wrapped surface.

Testing

  • Existing unit tests updated/passing for the affected base hooks.
  • e2e tests pass locally:
yarn nx e2e react-headless-components-preview

dmytrokirpa and others added 3 commits June 15, 2026 18:27
…ge Field control props in base hooks

Move the useFieldControlProps_unstable call into the *Base_unstable hooks so
that Field integration (labelFor, required, size) applies when the base hooks
are consumed directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

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.

Pull request overview

This PR refactors several v9 input-like components to merge <Field> control props (id, aria-*, required, and optionally size) inside their base hooks, so Field integration applies when consumers call base hooks directly.

Changes:

  • Move useFieldControlProps_unstable(...) calls from the styled use*_*unstable hooks into use*Base_unstable for SpinButton, Select, Input, and Checkbox.
  • Adjust Input base hook to use merged props.onChange when firing the change callback.
  • Add Beachball change files for the affected packages.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/react-components/react-spinbutton/library/src/components/SpinButton/useSpinButton.tsx Moves Field prop merging into useSpinButtonBase_unstable.
packages/react-components/react-select/library/src/components/Select/useSelect.tsx Moves Field prop merging into useSelectBase_unstable (but currently drops Field size inheritance).
packages/react-components/react-input/library/src/components/Input/useInput.ts Moves Field prop merging into useInputBase_unstable and changes onChange wiring (currently risks leaking Field size into native <input size>).
packages/react-components/react-checkbox/library/src/components/Checkbox/useCheckbox.tsx Moves Field prop merging into useCheckboxBase_unstable.
change/@fluentui-react-spinbutton-a3d28c04-3912-4039-aab4-d0003a1ade9a.json Patch change file for SpinButton.
change/@fluentui-react-select-36e2824c-a3a4-4dcc-adb4-b0b589bc8d39.json Patch change file for Select.
change/@fluentui-react-input-2acf0c8e-b57c-403f-b8e2-06b1df6d2739.json Patch change file for Input.
change/@fluentui-react-checkbox-2ca6c5d6-4811-4070-aded-61eba14ae02c.json Patch change file for Checkbox.
Comments suppressed due to low confidence (1)

packages/react-components/react-select/library/src/components/Select/useSelect.tsx:24

  • Field size inheritance for Select appears to have regressed: useSelect_unstable now destructures size = 'medium' from the unmerged props, while useSelectBase_unstable merges Field props without supportsSize. As a result, <Field size="small"><Select /></Field> will still return size: 'medium' in state and style as medium.

Fixing this likely requires restructuring so Field size is incorporated before size is read (without calling useFieldControlProps_unstable twice, since that can duplicate aria-describedby ids). One option is to keep Field prop merging in one place and derive size directly from Field context when props.size is undefined.

export const useSelect_unstable = (props: SelectProps, ref: React.Ref<HTMLSelectElement>): SelectState => {
  const overrides = useOverrides();

  const { appearance = overrides.inputDefaultAppearance ?? 'outline', size = 'medium', ...baseProps } = props;

  const state = useSelectBase_unstable(baseProps, ref);

Comment on lines 50 to 52
export const useInputBase_unstable = (props: InputBaseProps, ref: React.Ref<HTMLInputElement>): InputBaseState => {
const { onChange } = props;
props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true, supportsSize: true });

Comment on lines +55 to +56
// Merge props from surrounding <Field>, if any
props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true });
@dmytrokirpa dmytrokirpa changed the title test: add e2e tests for headless components with Tooltip wrapping and hover verification fix: move Field control props to base hooks and support tooltip-wrapped triggers Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants