fix: move Field control props to base hooks and support tooltip-wrapped triggers#36334
fix: move Field control props to base hooks and support tooltip-wrapped triggers#36334dmytrokirpa wants to merge 3 commits into
Conversation
…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>
…all in useCheckbox_unstable
There was a problem hiding this comment.
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 styleduse*_*unstablehooks intouse*Base_unstablefor SpinButton, Select, Input, and Checkbox. - Adjust Input base hook to use merged
props.onChangewhen 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_unstablenow destructuressize = 'medium'from the unmergedprops, whileuseSelectBase_unstablemerges Field props withoutsupportsSize. As a result,<Field size="small"><Select /></Field>will still returnsize: '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);
| export const useInputBase_unstable = (props: InputBaseProps, ref: React.Ref<HTMLInputElement>): InputBaseState => { | ||
| const { onChange } = props; | ||
| props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true, supportsSize: true }); | ||
|
|
| // Merge props from surrounding <Field>, if any | ||
| props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true }); |
Description
Moves the
useFieldControlProps_unstablecall out of the top-level component hooks and into the corresponding base hooks forCheckbox,Input,Select, andSpinButton. Previously, Field integration (label association, required state, size) was only applied in the publicuse*_unstablehooks, 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-previewby marking the trigger components as Fluent trigger components, so aTooltipcan wrap aDialogTrigger,MenuTrigger, orPopoverTriggerwithout breaking the trigger ref/event composition.Changes
Move Field control props to base hooks
react-checkbox: moveduseFieldControlProps_unstablefromuseCheckbox_unstableintouseCheckboxBase_unstable.react-input: moveduseFieldControlProps_unstablefromuseInput_unstableintouseInputBase_unstable(base hook now consumes the merged Field props for value, slots, andonChange).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
DialogTrigger,MenuTrigger,PopoverTrigger, andTooltipwithisFluentTriggerComponent = true(viaFluentTriggerComponentcast) so nested triggers compose correctly.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