fix(search): modal input focus
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user