From a24387c6dec3ddfb779e0457e81a4f909b02d147 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 15 Jan 2023 21:08:17 +0100 Subject: [PATCH] fix(search): modal input focus Signed-off-by: Camila Belo --- .changeset/breezy-carpets-tie.md | 5 ++ .../app/src/components/search/SearchModal.tsx | 57 +++++++++++-------- .../SearchModal/SearchModal.test.tsx | 15 +++++ .../components/SearchModal/SearchModal.tsx | 49 +++++++++------- 4 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 .changeset/breezy-carpets-tie.md diff --git a/.changeset/breezy-carpets-tie.md b/.changeset/breezy-carpets-tie.md new file mode 100644 index 0000000000..7b106b071f --- /dev/null +++ b/.changeset/breezy-carpets-tie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +When the search modal is opened, the focus is placed on the search bar input field. diff --git a/packages/app/src/components/search/SearchModal.tsx b/packages/app/src/components/search/SearchModal.tsx index 4557857ba5..527351a857 100644 --- a/packages/app/src/components/search/SearchModal.tsx +++ b/packages/app/src/components/search/SearchModal.tsx @@ -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(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) => { + if (e.key === 'Enter') { + navigate(searchPagePath); + toggleModal(); + } + }, + [navigate, toggleModal, searchPagePath], + ); return ( <> - + @@ -169,16 +189,7 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { alignItems="center" > - { - toggleModal(); - setTimeout( - focusContent, - transitions.duration.leavingScreen, - ); - }} - to={`${getSearchLink()}?query=${term}`} - > + void }) => { role="button" tabIndex={0} key={`${document.location}-btn`} - onClick={handleResultClick} - onKeyPress={handleKeyPress} + onClick={handleSearchResulClick} + onKeyDown={handleSearchResulClick} > {resultItem} diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx index 3ef9291585..d24764f5d4 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -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( + + , + { + mountedRoutes: { + '/search': rootRouteRef, + }, + }, + ); + + expect(screen.getByLabelText('Search')).toHaveFocus(); + }); }); diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 956f62a01e..b1a0b2cd45 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -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(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) => { + if (e.key === 'Enter') { + navigate(searchPagePath); + handleSearchResultClick(); + } + }, + [navigate, handleSearchResultClick, searchPagePath], + ); return ( <> - + @@ -125,13 +142,7 @@ export const Modal = ({ toggleModal }: SearchModalProps) => { alignItems="center" > - { - toggleModal(); - setTimeout(focusContent, transitions.duration.leavingScreen); - }} - to={`${getSearchLink()}?query=${term}`} - > + View Full Results @@ -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} >