Skip to content

Commit 4dfc754

Browse files
dmytrokirpaclaude
andcommitted
feat(react-select): add useSelectBase_unstable hook
Adds `useSelectBase_unstable` hook and `SelectBaseProps`/`SelectBaseState` types that expose the core slot/event logic without design-specific props (`appearance`, `size`). The styled `useSelect_unstable` now delegates to the base hook and applies defaults (ChevronDownRegular icon, field control props, overrides). Updates `renderSelect_unstable` to accept `SelectBaseState`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ee6a69d commit 4dfc754

8 files changed

Lines changed: 84 additions & 36 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "feat(react-select): add useSelectBase_unstable hook",
4+
"packageName": "@fluentui/react-select",
5+
"email": "dmytrokirpa@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-select/library/etc/react-select.api.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@
66

77
import type { ComponentProps } from '@fluentui/react-utilities';
88
import type { ComponentState } from '@fluentui/react-utilities';
9+
import type { DistributiveOmit } from '@fluentui/react-utilities';
910
import type { ForwardRefComponent } from '@fluentui/react-utilities';
1011
import type { JSXElement } from '@fluentui/react-utilities';
1112
import * as React_2 from 'react';
1213
import type { Slot } from '@fluentui/react-utilities';
1314
import { SlotClassNames } from '@fluentui/react-utilities';
1415

1516
// @public
16-
export const renderSelect_unstable: (state: SelectState) => JSXElement;
17+
export const renderSelect_unstable: (state: SelectBaseState) => JSXElement;
1718

1819
// @public
1920
export const Select: ForwardRefComponent<SelectProps>;
2021

22+
// @public (undocumented)
23+
export type SelectBaseProps = DistributiveOmit<SelectProps, 'appearance' | 'size'>;
24+
25+
// @public (undocumented)
26+
export type SelectBaseState = DistributiveOmit<SelectState, 'appearance' | 'size'>;
27+
2128
// @public (undocumented)
2229
export const selectClassNames: SlotClassNames<SelectSlots>;
2330

@@ -46,6 +53,9 @@ export type SelectState = ComponentState<SelectSlots> & Required<Pick<SelectProp
4653
// @public
4754
export const useSelect_unstable: (props: SelectProps, ref: React_2.Ref<HTMLSelectElement>) => SelectState;
4855

56+
// @public
57+
export const useSelectBase_unstable: (props: SelectBaseProps, ref: React_2.Ref<HTMLSelectElement>) => SelectBaseState;
58+
4959
// @public
5060
export const useSelectStyles_unstable: (state: SelectState) => SelectState;
5161

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
export type { SelectOnChangeData, SelectProps, SelectSlots, SelectState } from './components/Select/index';
1+
export type {
2+
SelectBaseProps,
3+
SelectBaseState,
4+
SelectOnChangeData,
5+
SelectProps,
6+
SelectSlots,
7+
SelectState,
8+
} from './components/Select/index';
29
export {
310
Select,
411
renderSelect_unstable,
512
selectClassNames,
613
useSelectStyles_unstable,
14+
useSelectBase_unstable,
715
useSelect_unstable,
816
} from './components/Select/index';

packages/react-components/react-select/library/src/components/Select/Select.types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
2+
import type { ComponentProps, ComponentState, DistributiveOmit, Slot } from '@fluentui/react-utilities';
33

44
export type SelectSlots = {
55
/*
@@ -40,6 +40,9 @@ export type SelectProps = Omit<ComponentProps<Partial<SelectSlots>, 'select'>, '
4040

4141
export type SelectState = ComponentState<SelectSlots> & Required<Pick<SelectProps, 'appearance' | 'size'>>;
4242

43+
export type SelectBaseProps = DistributiveOmit<SelectProps, 'appearance' | 'size'>;
44+
export type SelectBaseState = DistributiveOmit<SelectState, 'appearance' | 'size'>;
45+
4346
/**
4447
* Data passed to the `onChange` callback when a new option is selected.
4548
*/
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
export { Select } from './Select';
2-
export type { SelectOnChangeData, SelectProps, SelectSlots, SelectState } from './Select.types';
2+
export type {
3+
SelectBaseProps,
4+
SelectBaseState,
5+
SelectOnChangeData,
6+
SelectProps,
7+
SelectSlots,
8+
SelectState,
9+
} from './Select.types';
310
export { renderSelect_unstable } from './renderSelect';
4-
export { useSelect_unstable } from './useSelect';
11+
export { useSelectBase_unstable, useSelect_unstable } from './useSelect';
512
export { selectClassNames, useSelectStyles_unstable } from './useSelectStyles.styles';

packages/react-components/react-select/library/src/components/Select/renderSelect.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
import { assertSlots } from '@fluentui/react-utilities';
55
import type { JSXElement } from '@fluentui/react-utilities';
6-
import type { SelectSlots, SelectState } from './Select.types';
6+
import type { SelectBaseState, SelectSlots } from './Select.types';
77

88
/**
99
* Render the final JSX of Select
1010
*/
11-
export const renderSelect_unstable = (state: SelectState): JSXElement => {
11+
export const renderSelect_unstable = (state: SelectBaseState): JSXElement => {
1212
assertSlots<SelectSlots>(state);
1313
return (
1414
<state.root>

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

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,25 @@ import * as React from 'react';
44
import { useFieldControlProps_unstable } from '@fluentui/react-field';
55
import { getPartitionedNativeProps, useEventCallback, slot } from '@fluentui/react-utilities';
66
import { ChevronDownRegular } from '@fluentui/react-icons';
7-
import type { SelectProps, SelectState } from './Select.types';
7+
import type { SelectBaseProps, SelectBaseState, SelectProps, SelectState } from './Select.types';
88
import { useOverrides_unstable as useOverrides } from '@fluentui/react-shared-contexts';
99

1010
/**
11-
* Create the state required to render Select.
12-
*
13-
* The returned state can be modified with hooks such as useSelectStyles,
14-
* before being passed to renderSelect.
11+
* Create the base state required to render Select without design-specific props.
1512
*
16-
* @param props - props from this instance of Select
13+
* @param props - props from this instance of Select (without appearance/size)
1714
* @param ref - reference to the `<select>` element in Select
1815
*/
19-
export const useSelect_unstable = (props: SelectProps, ref: React.Ref<HTMLSelectElement>): SelectState => {
20-
// Merge props from surrounding <Field>, if any
21-
props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true, supportsSize: true });
22-
23-
const overrides = useOverrides();
24-
25-
const {
26-
defaultValue,
27-
value,
28-
select,
29-
icon,
30-
root,
31-
appearance = overrides.inputDefaultAppearance ?? 'outline',
32-
33-
onChange,
34-
size = 'medium',
35-
} = props;
16+
export const useSelectBase_unstable = (props: SelectBaseProps, ref: React.Ref<HTMLSelectElement>): SelectBaseState => {
17+
const { defaultValue, value, select, icon, root, onChange } = props;
3618

3719
const nativeProps = getPartitionedNativeProps({
3820
props,
3921
primarySlotTagName: 'select',
40-
excludedPropNames: ['appearance', 'defaultValue', 'onChange', 'size', 'value'],
22+
excludedPropNames: ['defaultValue', 'onChange', 'value'],
4123
});
4224

43-
const state: SelectState = {
44-
size,
45-
appearance,
25+
const state: SelectBaseState = {
4626
components: {
4727
root: 'span',
4828
select: 'select',
@@ -59,7 +39,6 @@ export const useSelect_unstable = (props: SelectProps, ref: React.Ref<HTMLSelect
5939
}),
6040
icon: slot.optional(icon, {
6141
renderByDefault: true,
62-
defaultProps: { children: <ChevronDownRegular /> },
6342
elementType: 'span',
6443
}),
6544
root: slot.always(root, {
@@ -74,3 +53,29 @@ export const useSelect_unstable = (props: SelectProps, ref: React.Ref<HTMLSelect
7453

7554
return state;
7655
};
56+
57+
/**
58+
* Create the state required to render Select.
59+
*
60+
* The returned state can be modified with hooks such as useSelectStyles,
61+
* before being passed to renderSelect.
62+
*
63+
* @param props - props from this instance of Select
64+
* @param ref - reference to the `<select>` element in Select
65+
*/
66+
export const useSelect_unstable = (props: SelectProps, ref: React.Ref<HTMLSelectElement>): SelectState => {
67+
// Merge props from surrounding <Field>, if any
68+
props = useFieldControlProps_unstable(props, { supportsLabelFor: true, supportsRequired: true, supportsSize: true });
69+
70+
const overrides = useOverrides();
71+
72+
const { appearance = overrides.inputDefaultAppearance ?? 'outline', size = 'medium', ...baseProps } = props;
73+
74+
const state = useSelectBase_unstable(baseProps, ref);
75+
76+
if (state.icon) {
77+
state.icon.children ??= <ChevronDownRegular />;
78+
}
79+
80+
return { ...state, appearance, size };
81+
};

packages/react-components/react-select/library/src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ export {
33
selectClassNames,
44
renderSelect_unstable,
55
useSelectStyles_unstable,
6+
useSelectBase_unstable,
67
useSelect_unstable,
78
} from './Select';
8-
export type { SelectOnChangeData, SelectProps, SelectSlots, SelectState } from './Select';
9+
export type {
10+
SelectBaseProps,
11+
SelectBaseState,
12+
SelectOnChangeData,
13+
SelectProps,
14+
SelectSlots,
15+
SelectState,
16+
} from './Select';

0 commit comments

Comments
 (0)