Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export type SearchBoxSlots = {
};

// @public
export type SearchBoxState = ComponentState<SearchBoxSlots> & Required<Pick<InputState, 'size'>> & Required<Pick<SearchBoxProps, 'disabled'>>;
export type SearchBoxState = ComponentState<SearchBoxSlots> & Required<Pick<InputState, 'size'>> & Required<Pick<SearchBoxProps, 'disabled'>> & {
focused: boolean;
};

// @public
export const useSearchBox_unstable: (props: SearchBoxProps, ref: React_2.Ref<HTMLInputElement>) => SearchBoxState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ export type SearchBoxProps = ComponentProps<SearchBoxSlots>;
*/
export type SearchBoxState = ComponentState<SearchBoxSlots> &
Required<Pick<InputState, 'size'>> &
Required<Pick<SearchBoxProps, 'disabled'>>;
Required<Pick<SearchBoxProps, 'disabled'>> & {
focused: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ exports[`SearchBox renders a default state 1`] = `
aria-label="clear"
class="fui-SearchBox__dismiss"
role="button"
tabindex="-1"
>
<svg
aria-hidden="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as React from 'react';
import { mergeCallbacks, resolveShorthand, useControllableState, useEventCallback } from '@fluentui/react-utilities';
import { Input } from '@fluentui/react-input';
import {
mergeCallbacks,
resolveShorthand,
useControllableState,
useEventCallback,
useMergedRefs,
} from '@fluentui/react-utilities';
import { Input, InputState } from '@fluentui/react-input';
import type { SearchBoxProps, SearchBoxState } from './SearchBox.types';
import { DismissRegular, SearchRegular } from '@fluentui/react-icons';

Expand All @@ -14,14 +20,28 @@ import { DismissRegular, SearchRegular } from '@fluentui/react-icons';
* @param ref - reference to root HTMLElement of SearchBox
*/
export const useSearchBox_unstable = (props: SearchBoxProps, ref: React.Ref<HTMLInputElement>): SearchBoxState => {
const { size = 'medium', disabled = false, contentBefore, dismiss, contentAfter, ...inputProps } = props;
const { size = 'medium', disabled = false, root, contentBefore, dismiss, contentAfter, ...inputProps } = props;

const searchBoxRootRef = React.useRef<HTMLDivElement>(null);
const searchBoxRef = React.useRef<HTMLInputElement>(null);

const [value, setValue] = useControllableState({
state: props.value,
defaultState: props.defaultValue,
initialState: '',
});

// Tracks the focus of the component for the contentAfter and dismiss button
const [focused, setFocused] = React.useState(false);

const onFocus = useEventCallback(() => {
setFocused(true);
});

const onBlur: React.FocusEventHandler<HTMLSpanElement> = useEventCallback(ev => {
setFocused(!!searchBoxRootRef.current?.contains(ev.relatedTarget));
});

const state: SearchBoxState = {
components: {
root: Input,
Expand All @@ -30,9 +50,12 @@ export const useSearchBox_unstable = (props: SearchBoxProps, ref: React.Ref<HTML
},

root: {
ref,
ref: useMergedRefs(searchBoxRef, ref),
type: 'search',
input: {}, // defining here to have access in styles hook

disabled,
size,
value,

contentBefore: resolveShorthand(contentBefore, {
Expand All @@ -44,6 +67,10 @@ export const useSearchBox_unstable = (props: SearchBoxProps, ref: React.Ref<HTML

...inputProps,

root: resolveShorthand(root, {
required: true,
}),

onChange: useEventCallback(ev => {
const newValue = ev.target.value;
props.onChange?.(ev, { value: newValue });
Expand All @@ -55,15 +82,24 @@ export const useSearchBox_unstable = (props: SearchBoxProps, ref: React.Ref<HTML
children: <DismissRegular />,
role: 'button',
'aria-label': 'clear',
tabIndex: -1,
},
required: true,
}),
contentAfter: resolveShorthand(contentAfter, { required: true }),
contentAfter: resolveShorthand(contentAfter, {
required: true,
}),

disabled,
focused,
size,
};

const searchBoxRoot = state.root.root as InputState['root'];
searchBoxRoot.ref = useMergedRefs(searchBoxRoot.ref, searchBoxRootRef);
searchBoxRoot.onFocus = useEventCallback(mergeCallbacks(searchBoxRoot.onFocus, onFocus));
searchBoxRoot.onBlur = useEventCallback(mergeCallbacks(searchBoxRoot.onBlur, onBlur));

const onDismissClick = useEventCallback(mergeCallbacks(state.dismiss?.onClick, () => setValue('')));
if (state.dismiss) {
state.dismiss.onClick = onDismissClick;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,6 @@ const useRootStyles = makeStyles({
paddingLeft: tokens.spacingHorizontalSNudge,
paddingRight: 0,

// dismiss + contentAfter appear on focus
'& + span': {
display: 'none',
},
'&:focus + span': {
display: 'flex',
},

// removes the WebKit pseudoelement styling
'::-webkit-search-decoration': {
display: 'none',
Expand All @@ -62,6 +54,11 @@ const useContentAfterStyles = makeStyles({
paddingLeft: tokens.spacingHorizontalM,
columnGap: tokens.spacingHorizontalXS,
},
rest: {
opacity: 0,
height: 0,
width: 0,
},
});

const useDismissClassName = makeResetStyles({
Expand Down Expand Up @@ -93,7 +90,7 @@ const useDismissStyles = makeStyles({
* Apply styling to the SearchBox slots based on the state
*/
export const useSearchBoxStyles_unstable = (state: SearchBoxState): SearchBoxState => {
const { disabled, size } = state;
const { disabled, focused, size } = state;

const rootStyles = useRootStyles();
const contentAfterStyles = useContentAfterStyles();
Expand All @@ -109,14 +106,18 @@ export const useSearchBoxStyles_unstable = (state: SearchBoxState): SearchBoxSta
dismissClassName,
disabled && dismissStyles.disabled,
dismissStyles[size],

state.dismiss.className,
);
}

if (state.contentAfter) {
state.contentAfter!.className = mergeClasses(
state.contentAfter.className = mergeClasses(
searchBoxClassNames.contentAfter,
contentAfterStyles.contentAfter,

!focused && contentAfterStyles.rest,

state.contentAfter.className,
);
} else if (state.dismiss) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,19 @@ import { SearchBox, SearchBoxProps } from '@fluentui/react-search';

import { FilterRegular } from '@fluentui/react-icons';

export const Default = (props: Partial<SearchBoxProps>) => <SearchBox {...props} contentAfter={<FilterRegular />} />;
// TODO: split into different stories
export const Default = (props: Partial<SearchBoxProps>) => (
<>
<SearchBox {...props} contentAfter={<FilterRegular />} size="small" placeholder="small" />
<SearchBox {...props} contentAfter={<FilterRegular />} size="medium" placeholder="medium" />
<SearchBox {...props} contentAfter={<FilterRegular />} size="large" placeholder="large" />
<SearchBox {...props} contentAfter={null} placeholder="no contentAfter" />
<SearchBox {...props} contentAfter={<FilterRegular />} disabled placeholder="disabled" />
<SearchBox
{...props}
contentAfter={<FilterRegular tabIndex={0} onClick={() => console.log('clicked')} />}
placeholder="contentAfter button"
/>
<SearchBox root={{ onFocus: () => console.log('test') }} placeholder="custom onFocus" />
</>
);