fix(search): modal input focus

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-01-15 21:08:17 +01:00
parent 1bd940a083
commit a24387c6de
4 changed files with 84 additions and 42 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search': patch
---
When the search modal is opened, the focus is placed on the search bar input field.
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import React, { KeyboardEvent, useRef, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
DialogActions,
DialogContent,
@@ -74,29 +75,48 @@ const useStyles = makeStyles(theme => ({
viewResultsLink: { verticalAlign: '0.5em' },
}));
const rootRouteRef = searchPlugin.routes.root;
export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => {
const getSearchLink = useRouteRef(searchPlugin.routes.root);
const classes = useStyles();
const navigate = useNavigate();
const { transitions } = useTheme();
const { focusContent } = useContent();
const catalogApi = useApi(catalogApiRef);
const { term, types } = useSearch();
const { focusContent } = useContent();
const { transitions } = useTheme();
const handleResultClick = () => {
const { term, types } = useSearch();
const searchBarRef = useRef<HTMLInputElement | null>(null);
const searchPagePath = `${useRouteRef(rootRouteRef)()}?query=${term}`;
useEffect(() => {
searchBarRef?.current?.focus();
});
const handleSearchResulClick = useCallback(() => {
toggleModal();
setTimeout(focusContent, transitions.duration.leavingScreen);
};
}, [toggleModal, focusContent, transitions]);
const handleKeyPress = () => {
handleResultClick();
};
const handleSearchBarKeyDown = useCallback(
(e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
navigate(searchPagePath);
toggleModal();
}
},
[navigate, toggleModal, searchPagePath],
);
return (
<>
<DialogTitle>
<Paper className={classes.container}>
<SearchBar className={classes.input} />
<SearchBar
className={classes.input}
inputProps={{ ref: searchBarRef }}
onKeyDown={handleSearchBarKeyDown}
/>
</Paper>
</DialogTitle>
<DialogContent>
@@ -169,16 +189,7 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => {
alignItems="center"
>
<Grid item>
<Link
onClick={() => {
toggleModal();
setTimeout(
focusContent,
transitions.duration.leavingScreen,
);
}}
to={`${getSearchLink()}?query=${term}`}
>
<Link to={searchPagePath} onClick={handleSearchResulClick}>
<Typography
component="span"
className={classes.viewResultsLink}
@@ -245,8 +256,8 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => {
role="button"
tabIndex={0}
key={`${document.location}-btn`}
onClick={handleResultClick}
onKeyPress={handleKeyPress}
onClick={handleSearchResulClick}
onKeyDown={handleSearchResulClick}
>
{resultItem}
</div>
@@ -153,4 +153,19 @@ describe('SearchModal', () => {
expect(getByTestId('search-bar-next')).toBeInTheDocument();
expect(getByTestId('search-bar-next')).not.toBeVisible();
});
it('should focus on its search bar when opened', async () => {
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<SearchModal open hidden={false} toggleModal={toggleModal} />
</ApiProvider>,
{
mountedRoutes: {
'/search': rootRouteRef,
},
},
);
expect(screen.getByLabelText('Search')).toHaveFocus();
});
});
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import React, { KeyboardEvent, useRef, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Dialog,
DialogActions,
@@ -94,27 +95,43 @@ const useStyles = makeStyles(theme => ({
}));
export const Modal = ({ toggleModal }: SearchModalProps) => {
const getSearchLink = useRouteRef(rootRouteRef);
const classes = useStyles();
const navigate = useNavigate();
const { transitions } = useTheme();
const { focusContent } = useContent();
const { term } = useSearch();
const { focusContent } = useContent();
const { transitions } = useTheme();
const searchBarRef = useRef<HTMLInputElement | null>(null);
const searchPagePath = `${useRouteRef(rootRouteRef)()}?query=${term}`;
const handleResultClick = () => {
useEffect(() => {
searchBarRef?.current?.focus();
});
const handleSearchResultClick = useCallback(() => {
toggleModal();
setTimeout(focusContent, transitions.duration.leavingScreen);
};
}, [toggleModal, focusContent, transitions]);
const handleKeyPress = () => {
handleResultClick();
};
const handleSearchBarKeyDown = useCallback(
(e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
navigate(searchPagePath);
handleSearchResultClick();
}
},
[navigate, handleSearchResultClick, searchPagePath],
);
return (
<>
<DialogTitle>
<Paper className={classes.container}>
<SearchBar className={classes.input} />
<SearchBar
className={classes.input}
inputProps={{ ref: searchBarRef }}
onKeyDown={handleSearchBarKeyDown}
/>
</Paper>
</DialogTitle>
<DialogContent>
@@ -125,13 +142,7 @@ export const Modal = ({ toggleModal }: SearchModalProps) => {
alignItems="center"
>
<Grid item>
<Link
onClick={() => {
toggleModal();
setTimeout(focusContent, transitions.duration.leavingScreen);
}}
to={`${getSearchLink()}?query=${term}`}
>
<Link to={searchPagePath} onClick={handleSearchResultClick}>
<Typography component="span" className={classes.viewResultsLink}>
View Full Results
</Typography>
@@ -148,8 +159,8 @@ export const Modal = ({ toggleModal }: SearchModalProps) => {
role="button"
tabIndex={0}
key={`${document.location}-btn`}
onClick={handleResultClick}
onKeyPress={handleKeyPress}
onClick={handleSearchResultClick}
onKeyDown={handleSearchResultClick}
>
<DefaultResultListItem
key={document.location}