feat(SidebarSearchModal): support customizing content

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2022-03-23 15:26:08 -04:00
parent db21d78179
commit 5c062f275e
11 changed files with 407 additions and 8 deletions
@@ -15,11 +15,25 @@
*/
import { wrapInTestApp } from '@backstage/test-utils';
import { Button } from '@material-ui/core';
import {
Button,
DialogActions,
DialogContent,
DialogTitle,
Grid,
List,
Paper,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import React, { ComponentType } from 'react';
import { rootRouteRef } from '../../plugin';
import { DefaultResultListItem } from '../DefaultResultListItem';
import { SearchBar } from '../SearchBar';
import { SearchApiProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchModal } from './SearchModal';
import { SearchResult } from '../SearchResult';
import { SearchResultPager } from '../SearchResultPager';
import { SearchType } from '../SearchType';
import { useSearchModal } from './useSearchModal';
const mockResults = {
@@ -77,3 +91,87 @@ export const Default = () => {
</>
);
};
const useStyles = makeStyles(theme => ({
container: {
borderRadius: 30,
display: 'flex',
height: '2.4em',
},
input: {
flex: 1,
},
dialogActionsContainer: { padding: theme.spacing(1, 3) },
}));
export const CustomModal = () => {
const classes = useStyles();
const { state, toggleModal } = useSearchModal();
return (
<>
<Button variant="contained" color="primary" onClick={toggleModal}>
Toggle Custom Search Modal
</Button>
<SearchModal {...state} toggleModal={toggleModal}>
{() => (
<>
<DialogTitle>
<Paper className={classes.container}>
<SearchBar className={classes.input} />
</Paper>
</DialogTitle>
<DialogContent>
<Grid container direction="column">
<Grid item>
<SearchType.Tabs
defaultValue=""
types={[
{
value: 'custom-result-item',
name: 'Custom Item',
},
{
value: 'no-custom-result-item',
name: 'No Custom Item',
},
]}
/>
</Grid>
<Grid item>
<SearchResult>
{({ results }) => (
<List>
{results.map(({ document }) => (
<div
role="button"
tabIndex={0}
key={`${document.location}-btn`}
onClick={toggleModal}
onKeyPress={toggleModal}
>
<DefaultResultListItem
key={document.location}
result={document}
/>
</div>
))}
</List>
)}
</SearchResult>
</Grid>
</Grid>
</DialogContent>
<DialogActions className={classes.dialogActionsContainer}>
<Grid container direction="row">
<Grid item xs={12}>
<SearchResultPager />
</Grid>
</Grid>
</DialogActions>
</>
)}
</SearchModal>
</>
);
};
@@ -55,6 +55,23 @@ describe('SearchModal', () => {
expect(query).toHaveBeenCalledTimes(1);
});
it('Should render a custom Modal correctly', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<SearchModal open hidden={false} toggleModal={toggleModal}>
{() => <div>Custom Search Modal</div>}
</SearchModal>
</ApiProvider>,
{
mountedRoutes: {
'/search': rootRouteRef,
},
},
);
expect(screen.getByText('Custom Search Modal')).toBeInTheDocument();
});
it('Calls toggleModal handler', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
@@ -37,6 +37,16 @@ import { useRouteRef } from '@backstage/core-plugin-api';
import { Link, useContent } from '@backstage/core-components';
import { rootRouteRef } from '../../plugin';
/**
* @public
**/
export interface SearchModalChildrenProps {
/**
* A function that should be invoked when navigating away from the modal.
*/
toggleModal: () => void;
}
export interface SearchModalProps {
/**
* If true, it renders the modal.
@@ -54,6 +64,11 @@ export interface SearchModalProps {
* should be closed.
*/
toggleModal: () => void;
/**
* A function that returns custom content to render in the search modal in
* place of the default.
*/
children?: (props: SearchModalChildrenProps) => JSX.Element;
}
const useStyles = makeStyles(theme => ({
@@ -152,6 +167,7 @@ export const SearchModal = ({
open = true,
hidden,
toggleModal,
children,
}: SearchModalProps) => {
const classes = useStyles();
@@ -169,7 +185,9 @@ export const SearchModal = ({
>
{open && (
<SearchContextProvider>
<Modal toggleModal={toggleModal} />
{(children && children({ toggleModal })) ?? (
<Modal toggleModal={toggleModal} />
)}
</SearchContextProvider>
)}
</Dialog>
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { SearchModal } from './SearchModal';
export type { SearchModalProps } from './SearchModal';
export type { SearchModalChildrenProps, SearchModalProps } from './SearchModal';
export { useSearchModal } from './useSearchModal';
@@ -77,8 +77,9 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => {
value={types.length === 0 ? '' : types[0]}
onChange={changeTab}
>
{definedTypes.map(type => (
{definedTypes.map((type, idx) => (
<Tab
key={idx}
className={classes.tab}
disableRipple
label={type.name}
@@ -17,10 +17,15 @@ import React from 'react';
import SearchIcon from '@material-ui/icons/Search';
import { SidebarItem } from '@backstage/core-components';
import { IconComponent } from '@backstage/core-plugin-api';
import { SearchModal, useSearchModal } from '../SearchModal';
import {
SearchModal,
SearchModalChildrenProps,
useSearchModal,
} from '../SearchModal';
export type SidebarSearchModalProps = {
icon?: IconComponent;
children?: (props: SearchModalChildrenProps) => JSX.Element;
};
export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
@@ -35,7 +40,11 @@ export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
text="Search"
onClick={toggleModal}
/>
<SearchModal {...state} toggleModal={toggleModal} />
<SearchModal
{...state}
toggleModal={toggleModal}
children={props.children}
/>
</>
);
};
+4 -1
View File
@@ -40,7 +40,10 @@ export type {
SearchFilterWrapperProps,
} from './components/SearchFilter';
export { SearchModal, useSearchModal } from './components/SearchModal';
export type { SearchModalProps } from './components/SearchModal';
export type {
SearchModalChildrenProps,
SearchModalProps,
} from './components/SearchModal';
export { SearchPage as Router } from './components/SearchPage';
export { SearchResultPager } from './components/SearchResultPager';
export { SearchType } from './components/SearchType';