From 5545f1f1d3dc84d1e03fe945871ea17e9d3b6c67 Mon Sep 17 00:00:00 2001 From: jrhoads Date: Thu, 18 Jan 2024 15:25:40 -0500 Subject: [PATCH] feat(custom): update onDropdownInputSelect prop to replace default behavior, add documentation specify a type for the props passed into onDropdownInputSelect and pass back the searchFields used in the FilterSearch component so we can properly remove/keep the right filters J=BACK-2765 TEST=manual --- docs/search-ui-react.filtersearchprops.md | 2 +- ...filtersearchprops.ondropdowninputchange.md | 4 +- docs/search-ui-react.md | 1 + ...owninputchangeprops.executefiltersearch.md | 13 +++++ ...rch-ui-react.ondropdowninputchangeprops.md | 22 ++++++++ ...ondropdowninputchangeprops.searchfields.md | 11 ++++ ...-react.ondropdowninputchangeprops.value.md | 13 +++++ etc/search-ui-react.api.md | 10 +++- src/components/FilterSearch.tsx | 43 +++++++++++++--- src/components/index.ts | 3 +- test-site/src/pages/PeoplePage.tsx | 50 +++++++++++++------ tests/components/FilterSearch.test.tsx | 2 - 12 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 docs/search-ui-react.ondropdowninputchangeprops.executefiltersearch.md create mode 100644 docs/search-ui-react.ondropdowninputchangeprops.md create mode 100644 docs/search-ui-react.ondropdowninputchangeprops.searchfields.md create mode 100644 docs/search-ui-react.ondropdowninputchangeprops.value.md diff --git a/docs/search-ui-react.filtersearchprops.md b/docs/search-ui-react.filtersearchprops.md index e586bc55b..cb2ba8a69 100644 --- a/docs/search-ui-react.filtersearchprops.md +++ b/docs/search-ui-react.filtersearchprops.md @@ -18,7 +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) | | (value: string) => void | _(Optional)_ A function which is called when the input's value changes. This does not replace executeFilterSearch, but gets called just before it | +| [onDropdownInputChange?](./search-ui-react.filtersearchprops.ondropdowninputchange.md) | | (params: [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md)) => void | _(Optional)_ A function which is called everytime the input element's value changes. Helpful for removing static filters from the static filter state if a user clears the search input completely. Replaces the default behavior of calling executeFilterSearch whenever the input changes. | | [onSelect?](./search-ui-react.filtersearchprops.onselect.md) | | (params: [OnSelectParams](./search-ui-react.onselectparams.md)) => 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<SearchParameterField, 'fetchEntities'>\[\] | An array of fieldApiName and entityType which indicates what to perform the filter search against. | diff --git a/docs/search-ui-react.filtersearchprops.ondropdowninputchange.md b/docs/search-ui-react.filtersearchprops.ondropdowninputchange.md index 47bfa9826..0d5427f8c 100644 --- a/docs/search-ui-react.filtersearchprops.ondropdowninputchange.md +++ b/docs/search-ui-react.filtersearchprops.ondropdowninputchange.md @@ -4,10 +4,10 @@ ## FilterSearchProps.onDropdownInputChange property -A function which is called when the input's value changes. This does not replace executeFilterSearch, but gets called just before it +A function which is called everytime the input element's value changes. Helpful for removing static filters from the static filter state if a user clears the search input completely. Replaces the default behavior of calling executeFilterSearch whenever the input changes. **Signature:** ```typescript -onDropdownInputChange?: (value: string) => void; +onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void; ``` diff --git a/docs/search-ui-react.md b/docs/search-ui-react.md index e9315a51a..e0acb9338 100644 --- a/docs/search-ui-react.md +++ b/docs/search-ui-react.md @@ -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 | diff --git a/docs/search-ui-react.ondropdowninputchangeprops.executefiltersearch.md b/docs/search-ui-react.ondropdowninputchangeprops.executefiltersearch.md new file mode 100644 index 000000000..7ddd0d295 --- /dev/null +++ b/docs/search-ui-react.ondropdowninputchangeprops.executefiltersearch.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md) > [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; +``` diff --git a/docs/search-ui-react.ondropdowninputchangeprops.md b/docs/search-ui-react.ondropdowninputchangeprops.md new file mode 100644 index 000000000..0eb1b10a0 --- /dev/null +++ b/docs/search-ui-react.ondropdowninputchangeprops.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [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) => Promise<FilterSearchResponse \| undefined> | A function that executes a filter search and updates the input and dropdown options with the response. | +| [searchFields](./search-ui-react.ondropdowninputchangeprops.searchfields.md) | | Omit<SearchParameterField, 'fetchEntities'>\[\] | | +| [value](./search-ui-react.ondropdowninputchangeprops.value.md) | | string | The new input element's new value after the change | + diff --git a/docs/search-ui-react.ondropdowninputchangeprops.searchfields.md b/docs/search-ui-react.ondropdowninputchangeprops.searchfields.md new file mode 100644 index 000000000..079ddb906 --- /dev/null +++ b/docs/search-ui-react.ondropdowninputchangeprops.searchfields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md) > [searchFields](./search-ui-react.ondropdowninputchangeprops.searchfields.md) + +## OnDropdownInputChangeProps.searchFields property + +**Signature:** + +```typescript +searchFields: Omit[]; +``` diff --git a/docs/search-ui-react.ondropdowninputchangeprops.value.md b/docs/search-ui-react.ondropdowninputchangeprops.value.md new file mode 100644 index 000000000..a6d953811 --- /dev/null +++ b/docs/search-ui-react.ondropdowninputchangeprops.value.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [OnDropdownInputChangeProps](./search-ui-react.ondropdowninputchangeprops.md) > [value](./search-ui-react.ondropdowninputchangeprops.value.md) + +## OnDropdownInputChangeProps.value property + +The new input element's new value after the change + +**Signature:** + +```typescript +value: string; +``` diff --git a/etc/search-ui-react.api.md b/etc/search-ui-react.api.md index f546e9d29..62f131dd7 100644 --- a/etc/search-ui-react.api.md +++ b/etc/search-ui-react.api.md @@ -290,7 +290,7 @@ export interface FilterSearchCssClasses extends AutocompleteResultCssClasses { export interface FilterSearchProps { customCssClasses?: FilterSearchCssClasses; label?: string; - onDropdownInputChange?: (value: string) => void; + onDropdownInputChange?: (params: OnDropdownInputChangeProps) => void; onSelect?: (params: OnSelectParams) => void; placeholder?: string; searchFields: Omit[]; @@ -462,6 +462,14 @@ export interface NumericalFacetsProps extends Omit void; +// @public +export interface OnDropdownInputChangeProps { + executeFilterSearch: (query?: string) => Promise; + // (undocumented) + searchFields: Omit[]; + value: string; +} + // @public export type onSearchFunc = (searchEventData: { verticalKey?: string; diff --git a/src/components/FilterSearch.tsx b/src/components/FilterSearch.tsx index f56bd9443..473910298 100644 --- a/src/components/FilterSearch.tsx +++ b/src/components/FilterSearch.tsx @@ -55,6 +55,22 @@ export interface OnSelectParams { executeFilterSearch: (query?: string) => Promise } +/** + * The parameters that are passed into {@link FilterSearchProps.onDropdownInputChange}. + * + * @public + */ +export interface OnDropdownInputChangeProps { + /** The new input element's new value after the change */ + value: string, + searchFields: Omit[], + /** + * A function that executes a filter search and updates the input and dropdown options + * with the response. + */ + executeFilterSearch: (query?: string) => Promise +} + /** * The props for the {@link FilterSearch} component. * @@ -78,8 +94,11 @@ 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's value changes. This does not replace executeFilterSearch, but gets called just before it */ - onDropdownInputChange?: (value: string) => void, + /** A function which is called everytime the input element's value changes. + * Helpful for removing static filters from the static filter state if a user clears the search input completely. + * Replaces the default behavior of calling executeFilterSearch whenever the input changes. + */ + 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. */ @@ -227,6 +246,21 @@ export function FilterSearch({ matchingFieldIds ]); + const handleInputChange = useCallback((value) => { + if (onDropdownInputChange) { + onDropdownInputChange({ + value, + searchFields, + executeFilterSearch + }); + } else { + executeFilterSearch(value); + } + }, [ + onDropdownInputChange, + executeFilterSearch + ]); + const meetsSubmitCritera = useCallback(index => index >= 0, []); const itemDataMatrix = useMemo(() => { @@ -270,11 +304,6 @@ export function FilterSearch({ } }, [executeFilterSearch]); - const handleInputChange = onDropdownInputChange ? (value: string) => { - onDropdownInputChange(value); - executeFilterSearch(value); - } : executeFilterSearch; - return (
{label &&

{label}

} diff --git a/src/components/index.ts b/src/components/index.ts index a44d52e21..8ee06ebca 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -36,7 +36,8 @@ export { FilterSearch, FilterSearchCssClasses, FilterSearchProps, - OnSelectParams + OnSelectParams, + OnDropdownInputChangeProps } from './FilterSearch'; export { diff --git a/test-site/src/pages/PeoplePage.tsx b/test-site/src/pages/PeoplePage.tsx index d2bd04c8f..5b0fd6a33 100644 --- a/test-site/src/pages/PeoplePage.tsx +++ b/test-site/src/pages/PeoplePage.tsx @@ -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' } @@ -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 = (params: OnDropdownInputChangeProps) => { + const { value, searchFields, 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 (
@@ -51,19 +78,14 @@ export function PeoplePage() { { - if (value !== "") return; - const fieldIds = filterSearchFields.map(field => 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); - }} + label='FilterSearch Name Filter' + onDropdownInputChange={removeAssociatedFilterWhenInputIsEmpty} + /> + { it('when an onDropdownInputChange prop is specified, it gets called each time after the input changes', async () => { const mockedOnDropdownInputChange = jest.fn(); renderFilterSearch({ searchFields: searchFieldsProp, onDropdownInputChange: mockedOnDropdownInputChange}); - const mockedFilterSearch = jest.spyOn(SearchHeadless.prototype, 'executeFilterSearch'); userEvent.type(screen.getByRole('textbox'), 'a'); await waitFor(() => expect(mockedOnDropdownInputChange).toHaveBeenCalledTimes(1)); - await waitFor(() => expect(mockedFilterSearch).toHaveBeenCalled()); }) });