Invoke search only when SearchModal is visible

Signed-off-by: Vincenzo Scamporlino <me@vinzscam.dev>
This commit is contained in:
Vincenzo Scamporlino
2022-02-04 14:22:29 +01:00
parent d408d210c2
commit 4a4ab7c603
5 changed files with 81 additions and 41 deletions
@@ -14,12 +14,7 @@
* limitations under the License.
*/
import React, {
useContext,
ReactNode,
PropsWithChildren,
useEffect,
} from 'react';
import React, { useContext, ReactNode, PropsWithChildren } from 'react';
import PropTypes from 'prop-types';
import { ApiHolder } from '@backstage/core-plugin-api';
import { ApiAggregator } from './ApiAggregator';
@@ -169,13 +169,16 @@ export type SearchBarProps = Partial<SearchBarBaseProps>;
export const SearchBar = ({ onChange, ...props }: SearchBarProps) => {
const { term, setTerm } = useSearch();
const handleChange = (newValue: string) => {
if (onChange) {
onChange(newValue);
} else {
setTerm(newValue);
}
};
const handleChange = useCallback(
(newValue: string) => {
if (onChange) {
onChange(newValue);
} else {
setTerm(newValue);
}
},
[onChange, setTerm],
);
return <SearchBarBase value={term} onChange={handleChange} {...props} />;
};
@@ -24,7 +24,6 @@ import { rootRouteRef } from '../../plugin';
import { searchApiRef } from '../../apis';
import { SearchModal } from './SearchModal';
import { SearchContextProvider } from '../SearchContext';
describe('SearchModal', () => {
const query = jest.fn().mockResolvedValue({ results: [] });
@@ -34,14 +33,16 @@ describe('SearchModal', () => {
[searchApiRef, { query }],
);
beforeEach(() => {
query.mockClear();
});
const toggleModal = jest.fn();
it('Should render the Modal correctly', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<SearchContextProvider>
<SearchModal open toggleModal={toggleModal} />
</SearchContextProvider>
<SearchModal open hidden={false} toggleModal={toggleModal} />
</ApiProvider>,
{
mountedRoutes: {
@@ -51,14 +52,13 @@ describe('SearchModal', () => {
);
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(query).toHaveBeenCalledTimes(1);
});
it('Calls toggleModal handler', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<SearchContextProvider>
<SearchModal open toggleModal={toggleModal} />
</SearchContextProvider>
<SearchModal open toggleModal={toggleModal} />
</ApiProvider>,
{
mountedRoutes: {
@@ -66,7 +66,25 @@ describe('SearchModal', () => {
},
},
);
expect(query).toHaveBeenCalledTimes(1);
userEvent.keyboard('{esc}');
expect(toggleModal).toHaveBeenCalledTimes(1);
});
it('should render SearchModal hiding its content', async () => {
const { getByTestId } = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<SearchModal open hidden toggleModal={toggleModal} />
</ApiProvider>,
{
mountedRoutes: {
'/search': rootRouteRef,
},
},
);
expect(getByTestId('search-bar-next')).toBeInTheDocument();
expect(getByTestId('search-bar-next')).not.toBeVisible();
});
});
@@ -39,6 +39,7 @@ import { rootRouteRef } from '../../plugin';
export interface SearchModalProps {
open?: boolean;
hidden?: boolean;
toggleModal: () => void;
}
@@ -57,7 +58,7 @@ const useStyles = makeStyles(theme => ({
viewResultsLink: { verticalAlign: '0.5em' },
}));
export const Modal = ({ open = true, toggleModal }: SearchModalProps) => {
export const Modal = ({ toggleModal }: SearchModalProps) => {
const getSearchLink = useRouteRef(rootRouteRef);
const classes = useStyles();
@@ -75,16 +76,7 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => {
};
return (
<Dialog
classes={{
paperFullWidth: classes.paperFullWidth,
}}
onClose={toggleModal}
aria-labelledby="search-modal-title"
open={open}
fullWidth
maxWidth="lg"
>
<>
<DialogTitle>
<Paper className={classes.container}>
<SearchBar className={classes.input} />
@@ -139,14 +131,34 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => {
</Grid>
</Grid>
</DialogActions>
</Dialog>
</>
);
};
export const SearchModal = ({ open = true, toggleModal }: SearchModalProps) => {
export const SearchModal = ({
open = true,
hidden,
toggleModal,
}: SearchModalProps) => {
const classes = useStyles();
return (
<SearchContextProvider>
<Modal open={open} toggleModal={toggleModal} />
</SearchContextProvider>
<Dialog
classes={{
paperFullWidth: classes.paperFullWidth,
}}
onClose={toggleModal}
aria-labelledby="search-modal-title"
fullWidth
maxWidth="lg"
open={open}
hidden={hidden}
>
{open && (
<SearchContextProvider>
<Modal toggleModal={toggleModal} />
</SearchContextProvider>
)}
</Dialog>
);
};
@@ -13,30 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import React, { useCallback, useState } from 'react';
import SearchIcon from '@material-ui/icons/Search';
import { SidebarItem } from '@backstage/core-components';
import { IconComponent } from '@backstage/core-plugin-api';
import { SearchModal } from '../SearchModal';
import { useSearch } from '../SearchContext';
export type SidebarSearchModalProps = {
icon?: IconComponent;
};
export const SidebarSearchModal = (props: SidebarSearchModalProps) => {
const { open, toggleModal } = useSearch();
const [state, setState] = useState({ hidden: true, opened: false });
const Icon = props.icon ? props.icon : SearchIcon;
const handleToggleModal = useCallback(
() =>
setState(previousState => ({
opened: true,
hidden: !previousState.hidden,
})),
[],
);
return (
<>
<SidebarItem
className="search-icon"
icon={Icon}
text="Search"
onClick={toggleModal}
onClick={handleToggleModal}
/>
<SearchModal
open={state.opened}
hidden={state.hidden}
toggleModal={handleToggleModal}
/>
<SearchModal open={open} toggleModal={toggleModal} />
</>
);
};