From 92da032e0e1a2dbf4138b18fad8f8d1f6cdf1116 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 2 Oct 2022 10:29:59 +0200 Subject: [PATCH 1/5] feat(search-react): context provider accepts null children Signed-off-by: Camila Belo --- .../search-react/src/components/SearchResult/SearchResult.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.tsx index a3e4c0e1f0..40c60c3263 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.tsx @@ -36,7 +36,7 @@ export type SearchResultContextProps = { /** * A child function that receives an asynchronous result set and returns a react element. */ - children: (state: AsyncState) => JSX.Element; + children: (state: AsyncState) => JSX.Element | null; }; /** From c27186ff010836333016e5b1647deeed8b501a8d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 2 Oct 2022 10:30:44 +0200 Subject: [PATCH 2/5] 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 ( + + ); + }} ); From 41ee0f37119088e21b14c8283bcd5bbfeac7701a Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 2 Oct 2022 10:31:12 +0200 Subject: [PATCH 3/5] core: update api reports Signed-off-by: Camila Belo --- plugins/search-react/api-report.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 5ec4124fcc..2cddfafaba 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -213,7 +213,9 @@ export type SearchFilterWrapperProps = SearchFilterComponentProps & { export const SearchResult: (props: SearchResultProps) => JSX.Element; // @public -export const SearchResultApi: (props: SearchResultApiProps) => JSX.Element; +export const SearchResultApi: ( + props: SearchResultApiProps, +) => JSX.Element | null; // @public export type SearchResultApiProps = SearchResultContextProps & { @@ -226,11 +228,11 @@ export const SearchResultComponent: (props: SearchResultProps) => JSX.Element; // @public export const SearchResultContext: ( props: SearchResultContextProps, -) => JSX.Element; +) => JSX.Element | null; // @public export type SearchResultContextProps = { - children: (state: AsyncState) => JSX.Element; + children: (state: AsyncState) => JSX.Element | null; }; // @public @@ -317,9 +319,14 @@ export const SearchResultListLayout: ( // @public export type SearchResultListLayoutProps = ListProps & { resultItems?: SearchResult_2[]; - renderResultItem?: (resultItem: SearchResult_2) => JSX.Element; + renderResultItem?: ( + value: SearchResult_2, + index: number, + array: SearchResult_2[], + ) => JSX.Element | null; error?: Error; loading?: boolean; + noResultsComponent?: ReactNode; }; // @public @@ -328,6 +335,7 @@ export type SearchResultListProps = Omit< 'loading' | 'error' | 'resultItems' > & { query: Partial; + disableRenderingWithNoResults?: boolean; }; // @public (undocumented) From bed5a1dc6ee8cbd550bf7229ef3065d2295004ab Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 2 Oct 2022 10:31:35 +0200 Subject: [PATCH 4/5] core: add changeset file Signed-off-by: Camila Belo --- .changeset/search-clouds-begin.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .changeset/search-clouds-begin.md diff --git a/.changeset/search-clouds-begin.md b/.changeset/search-clouds-begin.md new file mode 100644 index 0000000000..b3c6fa4b69 --- /dev/null +++ b/.changeset/search-clouds-begin.md @@ -0,0 +1,23 @@ +--- +'@backstage/plugin-search-react': minor +--- + +The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. +Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + +Examples: + +_Rendering a custom no results component_ + +```jsx +} +/> +``` + +_Disable rendering when there are no results_ + +```jsx + +``` From 310438cf28da0f7998f98179a308164d8db2eca0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 3 Oct 2022 13:33:04 +0200 Subject: [PATCH 5/5] refactor: apply review suggestions Co-authored-by: Eric Peterson Signed-off-by: Camila Belo --- .../src/components/SearchResultList/SearchResultList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx index c152162b6a..4279e8936a 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx @@ -114,7 +114,7 @@ export type SearchResultListProps = Omit< */ query: Partial; /** - * Optional property to provide if component should not render the group when no results are found. + * Optional property to provide if component should not render the component when no results are found. */ disableRenderingWithNoResults?: boolean; };