Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(custom): add onDropdownInputChange optional prop to FilterSearch #421

Merged
merged 9 commits into from
Jan 24, 2024
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/search-ui-react.filtersearch.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ A component which allows a user to search for filters associated with specific e
**Signature:**

```typescript
declare function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, sectioned, customCssClasses }: FilterSearchProps): JSX.Element;
declare function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses }: FilterSearchProps): JSX.Element;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| { searchFields, label, placeholder, searchOnSelect, onSelect, sectioned, customCssClasses } | [FilterSearchProps](./search-ui-react.filtersearchprops.md) | |
| { searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses } | [FilterSearchProps](./search-ui-react.filtersearchprops.md) | |

**Returns:**

Expand Down
1 change: 1 addition & 0 deletions docs/search-ui-react.filtersearchprops.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface FilterSearchProps
| --- | --- | --- | --- |
| [customCssClasses?](./search-ui-react.filtersearchprops.customcssclasses.md) | | [FilterSearchCssClasses](./search-ui-react.filtersearchcssclasses.md) | _(Optional)_ CSS classes for customizing the component styling. |
| [label?](./search-ui-react.filtersearchprops.label.md) | | string | _(Optional)_ The display label for the component. |
| [onDropdownInputChange?](./search-ui-react.filtersearchprops.ondropdowninputchange.md) | | (params: [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md)<!-- -->) =&gt; void | _(Optional)_ A function which is called when the input element's value changes. Replaces the default behavior. |
| [onSelect?](./search-ui-react.filtersearchprops.onselect.md) | | (params: [OnSelectParams](./search-ui-react.onselectparams.md)<!-- -->) =&gt; void | _(Optional)_ A function which is called when a filter is selected. |
| [placeholder?](./search-ui-react.filtersearchprops.placeholder.md) | | string | _(Optional)_ The search input's placeholder text when no text has been entered by the user. Defaults to "Search here...". |
| [searchFields](./search-ui-react.filtersearchprops.searchfields.md) | | Omit&lt;SearchParameterField, 'fetchEntities'&gt;\[\] | An array of fieldApiName and entityType which indicates what to perform the filter search against. |
Expand Down
13 changes: 13 additions & 0 deletions docs/search-ui-react.filtersearchprops.ondropdowninputchange.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [FilterSearchProps](./search-ui-react.filtersearchprops.md) &gt; [onDropdownInputChange](./search-ui-react.filtersearchprops.ondropdowninputchange.md)

## FilterSearchProps.onDropdownInputChange property

A function which is called when the input element's value changes. Replaces the default behavior.

**Signature:**

```typescript
onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void;
```
3 changes: 2 additions & 1 deletion docs/search-ui-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
| [executeSearch(searchActions)](./search-ui-react.executesearch.md) | Executes a universal/vertical search. |
| [Facets(props)](./search-ui-react.facets.md) | A component that displays all facets applicable to the current vertical search. |
| [FilterDivider({ className })](./search-ui-react.filterdivider.md) | A divider component used to separate NumericalFacets, HierarchicalFacets, StandardFacets, and StaticFilters. |
| [FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, sectioned, customCssClasses })](./search-ui-react.filtersearch.md) | A component which allows a user to search for filters associated with specific entities and fields. |
| [FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses })](./search-ui-react.filtersearch.md) | A component which allows a user to search for filters associated with specific entities and fields. |
| [Geolocation\_2({ geolocationOptions, radius, label, GeolocationIcon, handleClick, customCssClasses, })](./search-ui-react.geolocation_2.md) | A React Component which collects location information to create a location filter and perform a new search. |
| [getSearchIntents(searchActions)](./search-ui-react.getsearchintents.md) | Get search intents of the current query stored in headless using autocomplete request. |
| [getUserLocation(geolocationOptions)](./search-ui-react.getuserlocation.md) | Retrieves user's location using navigator.geolocation API. |
Expand Down Expand Up @@ -84,6 +84,7 @@
| [NumericalFacetProps](./search-ui-react.numericalfacetprops.md) | Props for the [StandardFacet()](./search-ui-react.standardfacet.md) component. |
| [NumericalFacetsCssClasses](./search-ui-react.numericalfacetscssclasses.md) | The CSS class interface for [NumericalFacets()](./search-ui-react.numericalfacets.md)<!-- -->. |
| [NumericalFacetsProps](./search-ui-react.numericalfacetsprops.md) | Props for the [NumericalFacets()](./search-ui-react.numericalfacets.md) component. |
| [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md) | The parameters that are passed into [FilterSearchProps.onDropdownInputChange](./search-ui-react.filtersearchprops.ondropdowninputchange.md)<!-- -->. |
| [OnSelectParams](./search-ui-react.onselectparams.md) | The parameters that are passed into [FilterSearchProps.onSelect](./search-ui-react.filtersearchprops.onselect.md)<!-- -->. |
| [PaginationCssClasses](./search-ui-react.paginationcssclasses.md) | The CSS classes used for pagination. |
| [PaginationProps](./search-ui-react.paginationprops.md) | Props for [Pagination()](./search-ui-react.pagination.md) component |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md) &gt; [executeFilterSearch](./search-ui-react.ondropdowninputchangeprops.executefiltersearch.md)

## OnDropdownInputChangeProps.executeFilterSearch property

A function that executes a filter search and updates the input and dropdown options with the response.

**Signature:**

```typescript
executeFilterSearch: (query?: string) => Promise<FilterSearchResponse | undefined>;
```
21 changes: 21 additions & 0 deletions docs/search-ui-react.ondropdowninputchangeprops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md)

## OnDropdownInputChangeProps interface

The parameters that are passed into [FilterSearchProps.onDropdownInputChange](./search-ui-react.filtersearchprops.ondropdowninputchange.md)<!-- -->.

**Signature:**

```typescript
interface OnDropdownInputChangeProps
```

## Properties

| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [executeFilterSearch](./search-ui-react.ondropdowninputchangeprops.executefiltersearch.md) | | (query?: string) =&gt; Promise&lt;FilterSearchResponse \| undefined&gt; | A function that executes a filter search and updates the input and dropdown options with the response. |
| [value](./search-ui-react.ondropdowninputchangeprops.value.md) | | string | The input element's new value after the change |

13 changes: 13 additions & 0 deletions docs/search-ui-react.ondropdowninputchangeprops.value.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md) &gt; [value](./search-ui-react.ondropdowninputchangeprops.value.md)

## OnDropdownInputChangeProps.value property

The input element's new value after the change

**Signature:**

```typescript
value: string;
```
9 changes: 8 additions & 1 deletion etc/search-ui-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export interface FilterOptionConfig {
}

// @public
export function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, sectioned, customCssClasses }: FilterSearchProps): JSX.Element;
export function FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, sectioned, customCssClasses }: FilterSearchProps): JSX.Element;

// @public
export interface FilterSearchCssClasses extends AutocompleteResultCssClasses {
Expand All @@ -290,6 +290,7 @@ export interface FilterSearchCssClasses extends AutocompleteResultCssClasses {
export interface FilterSearchProps {
customCssClasses?: FilterSearchCssClasses;
label?: string;
onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void;
onSelect?: (params: OnSelectParams) => void;
placeholder?: string;
searchFields: Omit<SearchParameterField, 'fetchEntities'>[];
Expand Down Expand Up @@ -461,6 +462,12 @@ export interface NumericalFacetsProps extends Omit<StandardFacetsProps, 'exclude
// @public
export type OnDragHandler = (center: mapboxgl_2.LngLat, bounds: mapboxgl_2.LngLatBounds) => void;

// @public
export interface OnDropdownInputChangeProps {
executeFilterSearch: (query?: string) => Promise<FilterSearchResponse | undefined>;
value: string;
}

// @public
export type onSearchFunc = (searchEventData: {
verticalKey?: string;
Expand Down
30 changes: 29 additions & 1 deletion src/components/FilterSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ export interface OnSelectParams {
executeFilterSearch: (query?: string) => Promise<FilterSearchResponse | undefined>
}

/**
* The parameters that are passed into {@link FilterSearchProps.onDropdownInputChange}.
*
* @public
*/
export interface OnDropdownInputChangeProps {
/** The input element's new value after the change */
value: string,
/**
* A function that executes a filter search and updates the input and dropdown options
* with the response.
*/
executeFilterSearch: (query?: string) => Promise<FilterSearchResponse | undefined>
}

/**
* The props for the {@link FilterSearch} component.
*
Expand All @@ -78,6 +93,8 @@ export interface FilterSearchProps {
searchOnSelect?: boolean,
/** A function which is called when a filter is selected. */
onSelect?: (params: OnSelectParams) => void,
/** A function which is called when the input element's value changes. Replaces the default behavior. */
onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void,
/** Determines whether or not the results of the filter search are separated by field. Defaults to false. */
sectioned?: boolean,
/** CSS classes for customizing the component styling. */
Expand All @@ -98,6 +115,7 @@ export function FilterSearch({
placeholder = 'Search here...',
searchOnSelect,
onSelect,
onDropdownInputChange,
sectioned = false,
customCssClasses
}: FilterSearchProps): JSX.Element {
Expand Down Expand Up @@ -224,6 +242,16 @@ export function FilterSearch({
matchingFieldIds
]);

const handleInputChange = useCallback((value) => {
onDropdownInputChange ? onDropdownInputChange({
value,
executeFilterSearch
}) : executeFilterSearch(value);
}, [
onDropdownInputChange,
executeFilterSearch
]);

const meetsSubmitCritera = useCallback(index => index >= 0, []);

const itemDataMatrix = useMemo(() => {
Expand Down Expand Up @@ -279,7 +307,7 @@ export function FilterSearch({
<DropdownInput
className={cssClasses.inputElement}
placeholder={placeholder}
onChange={executeFilterSearch}
onChange={handleInputChange}
onFocus={handleInputFocus}
submitCriteria={meetsSubmitCritera}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export {
FilterSearch,
FilterSearchCssClasses,
FilterSearchProps,
OnSelectParams
OnSelectParams,
OnDropdownInputChangeProps
} from './FilterSearch';

export {
Expand Down
40 changes: 37 additions & 3 deletions test-site/src/pages/PeoplePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useLayoutEffect } from 'react';
import { useSearchActions } from '@yext/search-headless-react';
import { FieldValueStaticFilter, SelectableStaticFilter, useSearchActions } from '@yext/search-headless-react';
import {
AppliedFilters,
FilterSearch,
Expand All @@ -19,12 +19,14 @@ import {
NumericalFacets,
AlternativeVerticals,
StandardFacet,
NumericalFacet
NumericalFacet,
OnDropdownInputChangeProps
} from '@yext/search-ui-react';
// import { CustomCard } from '../components/CustomCard';

const hierarchicalFacetFieldIds = ['c_hierarchicalFacet'];
const filterSearchFields = [{ fieldApiName: 'name', entityType: 'ce_person' }];
const employeeFilterSearchFields = [{fieldApiName: 'c_employeeDepartment', entityType: 'ce_person'}];
const employeeFilterConfigs = [
{ value: 'Consulting' },
{ value: 'Technology' }
Expand All @@ -43,6 +45,31 @@ export function PeoplePage() {
searchActions.executeVerticalQuery();
});

/**
* This example function that's being used for onDropdownInputChange allows for clearing the filter in the search state when the input is empty.
* This is especially useful for implementations that have multiple FilterSearch components.
* Ex. a user can search using both inputs initially, but then wants to clear one of the FilterSearch inputs and re-run a search.
*/
const removeAssociatedFilterWhenInputIsEmpty = (searchFields: { fieldApiName: string; entityType: string; }[]) => (params: OnDropdownInputChangeProps) => {
const { value, executeFilterSearch } = params;
// If there is still an input value, execute the filter search as normal
if (value !== "") {
executeFilterSearch(value);
}
// When the input is empty, remove the associated filter from the search state while keeping any other filters that are applied.
else {
const fieldIds = searchFields.map((field: {fieldApiName: string, entityType: string}) => field.fieldApiName);
const filtersToKeep: SelectableStaticFilter[] = [];
searchActions.state.filters.static?.forEach((staticFilter) => {
const filter = staticFilter.filter as FieldValueStaticFilter;
if (!fieldIds.includes(filter.fieldId)) {
filtersToKeep.push(staticFilter);
}
});
searchActions.setStaticFilters(filtersToKeep);
}
}

return (
<div>
<SearchBar />
Expand All @@ -51,7 +78,14 @@ export function PeoplePage() {
<FilterSearch
searchFields={filterSearchFields}
searchOnSelect={true}
label='Filters'
label='FilterSearch Name Filter'
onDropdownInputChange={removeAssociatedFilterWhenInputIsEmpty(filterSearchFields)}
/>
<FilterSearch
searchFields={employeeFilterSearchFields}
searchOnSelect={true}
label='FilterSearch Department Filter'
onDropdownInputChange={removeAssociatedFilterWhenInputIsEmpty(employeeFilterSearchFields)}
/>
<FilterDivider />
<StaticFilters
Expand Down
2 changes: 1 addition & 1 deletion tests/components/FilterSearch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default meta;
export const Primary: StoryFn<FilterSearchProps> = (args) => {
return (
<SearchHeadlessContext.Provider value={generateMockedHeadless(mockedHeadlessState)}>
<FilterSearch {...args} searchFields={searchFields} />
<FilterSearch {...args} searchFields={searchFields} onDropdownInputChange={undefined} />
Jeffrey-Rhoads17 marked this conversation as resolved.
Show resolved Hide resolved
</SearchHeadlessContext.Provider>
);
};
Expand Down
10 changes: 10 additions & 0 deletions tests/components/FilterSearch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,16 @@ describe('search without section labels', () => {
userEvent.keyboard('{enter}');
expect(inputNode).toHaveValue('first name 1');
});

it('when an onDropdownInputChange prop is specified, it gets called each time after the input changes and executeFilterSearch does not', async () => {
const mockedOnDropdownInputChange = jest.fn();
const executeFilterSearch = jest
.spyOn(SearchHeadless.prototype, 'executeFilterSearch');
renderFilterSearch({ searchFields: searchFieldsProp, onDropdownInputChange: mockedOnDropdownInputChange});
userEvent.type(screen.getByRole('textbox'), 'a');
await waitFor(() => expect(mockedOnDropdownInputChange).toHaveBeenCalledTimes(1));
Jeffrey-Rhoads17 marked this conversation as resolved.
Show resolved Hide resolved
() => expect(executeFilterSearch).toHaveBeenCalledTimes(0);
Jeffrey-Rhoads17 marked this conversation as resolved.
Show resolved Hide resolved
})
});

describe('screen reader', () => {
Expand Down
Loading