Skip to content

Commit 2839ea7

Browse files
committed
chore(react-search-preview): migrate to new slots API
1 parent 80a90fb commit 2839ea7

7 files changed

Lines changed: 95 additions & 73 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "chore: migrate to new slots API",
4+
"packageName": "@fluentui/react-search-preview",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-search-preview/etc/react-search-preview.api.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
import type { ComponentProps } from '@fluentui/react-utilities';
1010
import type { ComponentState } from '@fluentui/react-utilities';
1111
import type { ForwardRefComponent } from '@fluentui/react-utilities';
12-
import { Input } from '@fluentui/react-input';
13-
import { InputState } from '@fluentui/react-input';
12+
import type { InputProps } from '@fluentui/react-input';
13+
import type { InputSlots } from '@fluentui/react-input';
14+
import type { InputState } from '@fluentui/react-input';
1415
import * as React_2 from 'react';
1516
import type { Slot } from '@fluentui/react-utilities';
1617
import type { SlotClassNames } from '@fluentui/react-utilities';
@@ -25,17 +26,15 @@ export const SearchBox: ForwardRefComponent<SearchBoxProps>;
2526
export const searchBoxClassNames: SlotClassNames<SearchBoxSlots>;
2627

2728
// @public
28-
export type SearchBoxProps = ComponentProps<SearchBoxSlots>;
29+
export type SearchBoxProps = Omit<ComponentProps<Partial<SearchBoxSlots>, 'input'>, 'children' | 'defaultValue' | 'onChange' | 'size' | 'type' | 'value'> & InputProps;
2930

3031
// @public (undocumented)
31-
export type SearchBoxSlots = {
32-
root: NonNullable<Slot<typeof Input>>;
32+
export type SearchBoxSlots = InputSlots & {
3333
dismiss?: Slot<'span'>;
34-
contentAfter?: Slot<'span'>;
3534
};
3635

3736
// @public
38-
export type SearchBoxState = ComponentState<SearchBoxSlots> & Required<Pick<InputState, 'size'>> & Required<Pick<SearchBoxProps, 'disabled'>> & {
37+
export type SearchBoxState = ComponentState<SearchBoxSlots> & InputState & Required<Pick<InputState, 'size'>> & Required<Pick<SearchBoxProps, 'disabled'>> & {
3938
focused: boolean;
4039
};
4140

packages/react-components/react-search-preview/src/components/SearchBox/SearchBox.types.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
2-
import { Input, InputState } from '@fluentui/react-input';
3-
4-
export type SearchBoxSlots = {
5-
// Root of the component, wrapping the inputs
6-
root: NonNullable<Slot<typeof Input>>;
2+
import type { InputProps, InputSlots, InputState } from '@fluentui/react-input';
73

4+
export type SearchBoxSlots = InputSlots & {
85
// Last element in the input, within the input border
96
dismiss?: Slot<'span'>;
10-
11-
// Element after the input text, within the input border
12-
contentAfter?: Slot<'span'>;
137
};
148

159
/**
1610
* SearchBox Props
1711
*/
18-
export type SearchBoxProps = ComponentProps<SearchBoxSlots>;
12+
export type SearchBoxProps = Omit<
13+
ComponentProps<Partial<SearchBoxSlots>, 'input'>,
14+
// `children` is unsupported. The rest of these native props have customized definitions.
15+
'children' | 'defaultValue' | 'onChange' | 'size' | 'type' | 'value'
16+
> &
17+
InputProps;
1918

2019
/**
2120
* State used in rendering SearchBox
2221
*/
2322
export type SearchBoxState = ComponentState<SearchBoxSlots> &
23+
InputState &
2424
Required<Pick<InputState, 'size'>> &
2525
Required<Pick<SearchBoxProps, 'disabled'>> & {
2626
focused: boolean;

packages/react-components/react-search-preview/src/components/SearchBox/__snapshots__/SearchBox.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
exports[`SearchBox renders a default state 1`] = `
44
<div>
55
<span
6-
class="fui-Input fui-SearchBox"
6+
class="fui-SearchBox fui-Input"
77
>
88
<span
9-
class="fui-Input__contentBefore"
9+
class="fui-SearchBox__contentBefore fui-Input__contentBefore"
1010
>
1111
<svg
1212
aria-hidden="true"
@@ -24,12 +24,12 @@ exports[`SearchBox renders a default state 1`] = `
2424
</svg>
2525
</span>
2626
<input
27-
class="fui-Input__input"
27+
class="fui-SearchBox__input fui-Input__input"
2828
type="search"
2929
value=""
3030
/>
3131
<span
32-
class="fui-Input__contentAfter fui-SearchBox__contentAfter"
32+
class="fui-SearchBox__contentAfter fui-Input__contentAfter"
3333
>
3434
<span
3535
aria-label="clear"

packages/react-components/react-search-preview/src/components/SearchBox/renderSearchBox.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,26 @@
22
/** @jsx createElement */
33
/** @jsxFrag React.Fragment */
44

5-
import * as React from 'react';
65
import { createElement } from '@fluentui/react-jsx-runtime';
7-
import { getSlotsNext } from '@fluentui/react-utilities';
6+
import { assertSlots } from '@fluentui/react-utilities';
87
import type { SearchBoxState, SearchBoxSlots } from './SearchBox.types';
98

109
/**
1110
* Render the final JSX of SearchBox
1211
*/
1312
export const renderSearchBox_unstable = (state: SearchBoxState) => {
14-
const { slots, slotProps } = getSlotsNext<SearchBoxSlots>(state);
13+
assertSlots<SearchBoxSlots>(state);
1514

16-
// TODO Add additional slots in the appropriate place
17-
const rootSlots = {
18-
contentAfter: slots.contentAfter && {
19-
...slotProps.contentAfter,
20-
children: (
21-
<>
22-
{slotProps.contentAfter.children}
23-
{slots.dismiss && <slots.dismiss {...slotProps.dismiss} />}
24-
</>
25-
),
26-
},
27-
};
28-
29-
return <slots.root {...slotProps.root} {...rootSlots} />;
15+
return (
16+
<state.root>
17+
{state.contentBefore && <state.contentBefore />}
18+
<state.input />
19+
{state.contentAfter && (
20+
<state.contentAfter>
21+
{state.contentAfter.children}
22+
{state.dismiss && <state.dismiss />}
23+
</state.contentAfter>
24+
)}
25+
</state.root>
26+
);
3027
};

packages/react-components/react-search-preview/src/components/SearchBox/useSearchBox.tsx

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import * as React from 'react';
22
import {
3+
isResolvedShorthand,
34
mergeCallbacks,
4-
resolveShorthand,
5+
slot,
56
useControllableState,
67
useEventCallback,
78
useMergedRefs,
89
} from '@fluentui/react-utilities';
9-
import { Input, InputState } from '@fluentui/react-input';
10-
import type { SearchBoxProps, SearchBoxState } from './SearchBox.types';
10+
import { useInput_unstable } from '@fluentui/react-input';
1111
import { DismissRegular, SearchRegular } from '@fluentui/react-icons';
12+
import type { ExtractSlotProps } from '@fluentui/react-utilities';
13+
import type { SearchBoxSlots, SearchBoxProps, SearchBoxState } from './SearchBox.types';
1214

1315
/**
1416
* Create the state required to render SearchBox.
@@ -42,67 +44,76 @@ export const useSearchBox_unstable = (props: SearchBoxProps, ref: React.Ref<HTML
4244
setFocused(!!searchBoxRootRef.current?.contains(ev.relatedTarget));
4345
});
4446

45-
const state: SearchBoxState = {
46-
components: {
47-
root: Input,
48-
dismiss: 'span',
49-
contentAfter: 'span',
50-
},
47+
const rootProps = slot.resolveShorthand(root);
5148

52-
root: {
53-
ref: useMergedRefs(searchBoxRef, ref),
54-
type: 'search',
55-
input: {}, // defining here to have access in styles hook
49+
const handleDismissClick = useEventCallback((event: React.MouseEvent<HTMLSpanElement>) => {
50+
if (isResolvedShorthand(dismiss)) {
51+
dismiss.onClick?.(event);
52+
}
53+
setValue('');
54+
});
5655

56+
const inputState = useInput_unstable(
57+
{
58+
type: 'search',
5759
disabled,
5860
size,
5961
value,
60-
61-
contentBefore: resolveShorthand(contentBefore, {
62+
root: slot.always<ExtractSlotProps<SearchBoxSlots['root']>>(
63+
{
64+
...rootProps,
65+
ref: useMergedRefs(rootProps?.ref, searchBoxRootRef),
66+
onFocus: useEventCallback(mergeCallbacks(rootProps?.onFocus, onFocus)),
67+
onBlur: useEventCallback(mergeCallbacks(rootProps?.onBlur, onBlur)),
68+
},
69+
{
70+
elementType: 'span',
71+
},
72+
),
73+
contentBefore: slot.optional(contentBefore, {
74+
renderByDefault: true,
6275
defaultProps: {
6376
children: <SearchRegular />,
6477
},
65-
required: true, // TODO need to allow users to remove
78+
elementType: 'span',
6679
}),
67-
68-
...inputProps,
69-
70-
root: resolveShorthand(root, {
71-
required: true,
80+
contentAfter: slot.optional(contentAfter, {
81+
renderByDefault: true,
82+
elementType: 'span',
7283
}),
73-
84+
...inputProps,
7485
onChange: useEventCallback(ev => {
7586
const newValue = ev.target.value;
7687
props.onChange?.(ev, { value: newValue });
7788
setValue(newValue);
7889
}),
7990
},
80-
dismiss: resolveShorthand(dismiss, {
91+
useMergedRefs(searchBoxRef, ref),
92+
);
93+
94+
const state: SearchBoxState = {
95+
...inputState,
96+
components: {
97+
...inputState.components,
98+
dismiss: 'span',
99+
},
100+
dismiss: slot.optional(dismiss, {
81101
defaultProps: {
82102
children: <DismissRegular />,
83103
role: 'button',
84104
'aria-label': 'clear',
85105
tabIndex: -1,
86106
},
87-
required: true,
88-
}),
89-
contentAfter: resolveShorthand(contentAfter, {
90-
required: true,
107+
renderByDefault: true,
108+
elementType: 'span',
91109
}),
92-
93110
disabled,
94111
focused,
95112
size,
96113
};
97114

98-
const searchBoxRoot = state.root.root as InputState['root'];
99-
searchBoxRoot.ref = useMergedRefs(searchBoxRoot.ref, searchBoxRootRef);
100-
searchBoxRoot.onFocus = useEventCallback(mergeCallbacks(searchBoxRoot.onFocus, onFocus));
101-
searchBoxRoot.onBlur = useEventCallback(mergeCallbacks(searchBoxRoot.onBlur, onBlur));
102-
103-
const onDismissClick = useEventCallback(mergeCallbacks(state.dismiss?.onClick, () => setValue('')));
104115
if (state.dismiss) {
105-
state.dismiss.onClick = onDismissClick;
116+
state.dismiss.onClick = handleDismissClick;
106117
}
107118

108119
return state;

packages/react-components/react-search-preview/src/components/SearchBox/useSearchBoxStyles.styles.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import { makeResetStyles, makeStyles, mergeClasses } from '@griffel/react';
22
import type { SearchBoxSlots, SearchBoxState } from './SearchBox.types';
33
import type { SlotClassNames } from '@fluentui/react-utilities';
44
import { tokens } from '@fluentui/react-theme';
5+
import { useInputStyles_unstable } from '@fluentui/react-input';
56

67
export const searchBoxClassNames: SlotClassNames<SearchBoxSlots> = {
78
root: 'fui-SearchBox',
89
dismiss: 'fui-SearchBox__dismiss',
910
contentAfter: 'fui-SearchBox__contentAfter',
11+
contentBefore: 'fui-SearchBox__contentBefore',
12+
input: 'fui-SearchBox__input',
1013
};
1114

1215
/**
@@ -90,6 +93,7 @@ const useDismissStyles = makeStyles({
9093
* Apply styling to the SearchBox slots based on the state
9194
*/
9295
export const useSearchBoxStyles_unstable = (state: SearchBoxState): SearchBoxState => {
96+
useInputStyles_unstable(state);
9397
const { disabled, focused, size } = state;
9498

9599
const rootStyles = useRootStyles();
@@ -98,7 +102,7 @@ export const useSearchBoxStyles_unstable = (state: SearchBoxState): SearchBoxSta
98102
const dismissStyles = useDismissStyles();
99103

100104
state.root.className = mergeClasses(searchBoxClassNames.root, rootStyles[size], state.root.className);
101-
state.root.input!.className = rootStyles.input;
105+
state.input.className = mergeClasses(searchBoxClassNames.input, rootStyles.input, state.input.className);
102106

103107
if (state.dismiss) {
104108
state.dismiss.className = mergeClasses(
@@ -111,6 +115,10 @@ export const useSearchBoxStyles_unstable = (state: SearchBoxState): SearchBoxSta
111115
);
112116
}
113117

118+
if (state.contentBefore) {
119+
state.contentBefore.className = mergeClasses(searchBoxClassNames.contentBefore, state.contentBefore.className);
120+
}
121+
114122
if (state.contentAfter) {
115123
state.contentAfter.className = mergeClasses(
116124
searchBoxClassNames.contentAfter,

0 commit comments

Comments
 (0)