From c27186ff010836333016e5b1647deeed8b501a8d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 2 Oct 2022 10:30:44 +0200 Subject: [PATCH] feat(search-react): custom result list empty state Signed-off-by: Camila Belo --- .../SearchResultList.stories.tsx | 17 ++++- .../SearchResultList.test.tsx | 36 ++++++++++ .../SearchResultList/SearchResultList.tsx | 69 +++++++++++++------ 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx index 5762aba2d8..fcda4cec87 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx @@ -119,7 +119,7 @@ export const WithError = () => { ); }; -export const WithNoResults = () => { +export const WithDefaultNoResultsComponent = () => { const [query] = useState>({ types: ['techdocs'], }); @@ -131,6 +131,21 @@ export const WithNoResults = () => { ); }; +export const WithCustomNoResultsComponent = () => { + const [query] = useState>({ + types: ['techdocs'], + }); + + return ( + + } + /> + + ); +}; + const CustomResultListItem = (props: any) => { const { icon, result } = props; diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx index 20e5a1cba1..93bbaa5602 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx @@ -121,6 +121,42 @@ describe('SearchResultList', () => { }); }); + it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => { + query.mockResolvedValueOnce({ results: [] }); + await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + await waitFor(() => { + expect(screen.queryByText('Documentation')).not.toBeInTheDocument(); + }); + }); + + it('Should render custom component when no results returned', async () => { + query.mockResolvedValueOnce({ results: [] }); + await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + await waitFor(() => { + expect(screen.getByText('No results were found')).toBeInTheDocument(); + }); + }); + it('Shows an error panel when results rendering fails', async () => { query.mockRejectedValueOnce(new Error()); await renderWithEffects( diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx index 8f184f7080..c152162b6a 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; -import { List, ListProps } from '@material-ui/core'; +import { List, ListItem, ListProps } from '@material-ui/core'; import { EmptyState, @@ -41,7 +41,11 @@ export type SearchResultListLayoutProps = ListProps & { /** * Function to customize how result items are rendered. */ - renderResultItem?: (resultItem: SearchResult) => JSX.Element; + renderResultItem?: ( + value: SearchResult, + index: number, + array: SearchResult[], + ) => JSX.Element | null; /** * If defined, will render a default error panel. */ @@ -50,6 +54,10 @@ export type SearchResultListLayoutProps = ListProps & { * If defined, will render a default loading progress. */ loading?: boolean; + /** + * Optional component to render when no results. Default to component. + */ + noResultsComponent?: ReactNode; }; /** @@ -58,7 +66,21 @@ export type SearchResultListLayoutProps = ListProps & { * @public */ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { - const { loading, error, resultItems, renderResultItem, ...rest } = props; + const { + loading, + error, + resultItems, + renderResultItem = resultItem => ( + + ), + noResultsComponent = ( + + ), + ...rest + } = props; return ( @@ -70,10 +92,10 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { /> ) : null} {!loading && !error && resultItems?.length - ? resultItems.map(resultItem => renderResultItem?.(resultItem) ?? null) + ? resultItems.map(renderResultItem) : null} {!loading && !error && !resultItems?.length ? ( - + {noResultsComponent} ) : null} ); @@ -91,6 +113,10 @@ export type SearchResultListProps = Omit< * A search query used for requesting the results to be listed. */ query: Partial; + /** + * Optional property to provide if component should not render the group when no results are found. + */ + disableRenderingWithNoResults?: boolean; }; /** @@ -99,13 +125,7 @@ export type SearchResultListProps = Omit< * @public */ export const SearchResultList = (props: SearchResultListProps) => { - const { - query, - renderResultItem = ({ document }) => ( - - ), - ...rest - } = props; + const { query, disableRenderingWithNoResults, ...rest } = props; return ( { }} > - {({ loading, error, value }) => ( - - )} + {({ loading, error, value }) => { + if (!value?.results?.length && disableRenderingWithNoResults) { + return null; + } + + return ( + + ); + }} );