feat(SidebarSearchModal): support customizing content
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user