Skip to content

Commit

Permalink
fix: correcting focus behavior of react-search (#28241)
Browse files Browse the repository at this point in the history
* focus fix

* attempted style fix

* appearance fixes

* update api + Tests

* fix focus

* api update

* sean's nits

* add contentAfter as a button

* minor changes

* minor fixes

* fix linting issues

* Update packages/react-components/react-search/src/components/SearchBox/useSearchBox.tsx

Co-authored-by: Sean Monahan <seanmonahan@microsoft.com>

* fixing props

* move ref onto root

* fix ref

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

Co-authored-by: Sarah Higley <smhigley@users.noreply.github.com>

* small tweak

---------

Co-authored-by: Sean Monahan <seanmonahan@microsoft.com>
Co-authored-by: Sarah Higley <smhigley@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 21, 2023
1 parent 64fb6d6 commit 5809e04
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 18 deletions.
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" />
</>
);

0 comments on commit 5809e04

Please sign in to comment.