diff --git a/.changeset/mighty-bears-glow.md b/.changeset/mighty-bears-glow.md new file mode 100644 index 0000000000..e540cdd8ae --- /dev/null +++ b/.changeset/mighty-bears-glow.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-search-react': minor +'@backstage/plugin-search': minor +--- + +Add close button & improve search input. + +MUI's Paper wrapping the SearchBar in the SearchPage was removed, we recommend users update their apps accordingly. + +SearchBarBase's TextField's label support added & aria-label uses label string if present, tests relying on the default placeholder value should still work unless custom placeholder was given. diff --git a/packages/app/src/components/search/SearchModal.tsx b/packages/app/src/components/search/SearchModal.tsx index fea80f9952..e2d46b2e19 100644 --- a/packages/app/src/components/search/SearchModal.tsx +++ b/packages/app/src/components/search/SearchModal.tsx @@ -13,26 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { KeyboardEvent, useRef, useCallback, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - DialogActions, - DialogContent, - DialogTitle, - Grid, - makeStyles, - Paper, -} from '@material-ui/core'; -import Typography from '@material-ui/core/Typography'; -import BuildIcon from '@material-ui/icons/Build'; -import LaunchIcon from '@material-ui/icons/Launch'; import { CatalogIcon, DocsIcon, Link } from '@backstage/core-components'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized'; import { - catalogApiRef, CATALOG_FILTER_EXISTS, + catalogApiRef, } from '@backstage/plugin-catalog-react'; import { ToolSearchResultListItem } from '@backstage/plugin-explore'; import { searchPlugin, SearchType } from '@backstage/plugin-search'; @@ -44,12 +29,38 @@ import { useSearch, } from '@backstage/plugin-search-react'; import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; +import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized'; +import { + Box, + DialogActions, + DialogContent, + DialogTitle, + Grid, + makeStyles, +} from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; +import BuildIcon from '@material-ui/icons/Build'; +import CloseIcon from '@material-ui/icons/Close'; +import React, { KeyboardEvent, useCallback, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; const useStyles = makeStyles(theme => ({ + dialogTitle: { + gap: theme.spacing(1), + display: 'grid', + alignItems: 'center', + gridTemplateColumns: '1fr auto', + '&> button': { + marginTop: theme.spacing(1), + }, + }, container: { borderRadius: 30, display: 'flex', height: '2.4em', + padding: theme.spacing(1), }, filter: { '& + &': { @@ -83,7 +94,7 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { }); const handleSearchBarKeyDown = useCallback( - (e: KeyboardEvent) => { + (e: KeyboardEvent) => { if (e.key === 'Enter') { toggleModal(); navigate(searchPagePath); @@ -95,13 +106,17 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { return ( <> - + - + + + + + @@ -173,15 +188,15 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { alignItems="center" > - - - View Full Results - - - + diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 6c4f06aed9..ecae90a06a 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -44,9 +44,6 @@ import { ToolSearchResultListItem } from '@backstage/plugin-explore'; import BuildIcon from '@material-ui/icons/Build'; const useStyles = makeStyles((theme: Theme) => ({ - bar: { - padding: theme.spacing(1, 0), - }, filter: { '& + &': { marginTop: theme.spacing(2.5), @@ -70,9 +67,7 @@ const SearchPage = () => { - - - + {!isMobile && ( diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 30b5d4b581..e39e91de72 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -10,7 +10,6 @@ import { AsyncState } from 'react-use/lib/useAsync'; import { AutocompleteProps } from '@material-ui/lab'; import { Extension } from '@backstage/core-plugin-api'; import { ForwardRefExoticComponent } from 'react'; -import { InputBaseProps } from '@material-ui/core'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { LinkProps } from '@backstage/core-components'; @@ -26,6 +25,7 @@ import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common'; import { SearchResultSet } from '@backstage/plugin-search-common'; +import { TextFieldProps } from '@material-ui/core/TextField'; import { TypographyProps } from '@material-ui/core'; // @public (undocumented) @@ -136,12 +136,13 @@ export const SearchBar: ForwardRefExoticComponent; export const SearchBarBase: ForwardRefExoticComponent; // @public -export type SearchBarBaseProps = Omit & { +export type SearchBarBaseProps = Omit & { debounceTime?: number; clearButton?: boolean; onClear?: () => void; onSubmit?: () => void; onChange: (value: string) => void; + endAdornment?: React_2.ReactNode; }; // @public diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx index 88aed35f8d..559ae1776e 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx @@ -44,7 +44,7 @@ export default { export const Default = () => { return ( - + ); @@ -52,15 +52,23 @@ export const Default = () => { export const CustomPlaceholder = () => { return ( - + ); }; +export const CustomLabel = () => { + return ( + + + + ); +}; + export const Focused = () => { return ( - + {/* decision up to adopter, read https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-autofocus.md#no-autofocus */} {/* eslint-disable-next-line jsx-a11y/no-autofocus */} @@ -70,7 +78,7 @@ export const Focused = () => { export const WithoutClearButton = () => { return ( - + ); @@ -80,7 +88,7 @@ const useStyles = makeStyles({ search: { display: 'flex', justifyContent: 'space-between', - padding: '8px 0', + padding: '8px 16px', borderRadius: '50px', margin: 'auto', }, diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx index fba50f74d8..e4859861d9 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx @@ -70,9 +70,28 @@ describe('SearchBar', () => { ); await waitFor(() => { - expect( - screen.getByPlaceholderText('Search in Mock title'), - ).toBeInTheDocument(); + expect(screen.getByLabelText('Search')).toBeInTheDocument(); + }); + }); + + it('Renders with custom label', async () => { + const label = 'label'; + + const result = await renderWithEffects( + + + + + , + ); + + await waitFor(() => { + expect(result.getByLabelText(label)).toBeInTheDocument(); }); }); diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.tsx index 9c312718bf..913f515aaa 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.tsx @@ -13,39 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { - ChangeEvent, - KeyboardEvent, - useState, - useEffect, - useCallback, - forwardRef, - ComponentType, - ForwardRefExoticComponent, -} from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; - -import { - InputBase, - InputBaseProps, - InputAdornment, - IconButton, -} from '@material-ui/core'; -import SearchIcon from '@material-ui/icons/Search'; -import ClearButton from '@material-ui/icons/Clear'; - import { AnalyticsContext, configApiRef, useApi, } from '@backstage/core-plugin-api'; +import { IconButton, InputAdornment, TextField } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import { TextFieldProps } from '@material-ui/core/TextField'; +import SearchIcon from '@material-ui/icons/Search'; +import React, { + ChangeEvent, + ComponentType, + forwardRef, + ForwardRefExoticComponent, + KeyboardEvent, + useCallback, + useEffect, + useState, +} from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; import { SearchContextProvider, useSearch } from '../../context'; import { TrackSearch } from '../SearchTracker'; function withContext(Component: ComponentType) { - return forwardRef((props, ref) => ( + return forwardRef((props, ref) => ( @@ -57,12 +50,13 @@ function withContext(Component: ComponentType) { * * @public */ -export type SearchBarBaseProps = Omit & { +export type SearchBarBaseProps = Omit & { debounceTime?: number; clearButton?: boolean; onClear?: () => void; onSubmit?: () => void; onChange: (value: string) => void; + endAdornment?: React.ReactNode; }; /** @@ -84,9 +78,10 @@ export const SearchBarBase: ForwardRefExoticComponent = clearButton = true, fullWidth = true, value: defaultValue, - placeholder: defaultPlaceholder, + label, + placeholder, inputProps: defaultInputProps = {}, - endAdornment: defaultEndAdornment, + endAdornment, ...rest } = props; @@ -109,7 +104,7 @@ export const SearchBarBase: ForwardRefExoticComponent = ); const handleKeyDown = useCallback( - (e: KeyboardEvent) => { + (e: KeyboardEvent) => { if (onKeyDown) onKeyDown(e); if (onSubmit && e.key === 'Enter') { onSubmit(); @@ -125,8 +120,10 @@ export const SearchBarBase: ForwardRefExoticComponent = } }, [onChange, onClear]); - const placeholder = - defaultPlaceholder ?? + const ariaLabel: string | undefined = label ? undefined : 'Search'; + + const inputPlaceholder = + placeholder ?? `Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`; const startAdornment = ( @@ -137,24 +134,45 @@ export const SearchBarBase: ForwardRefExoticComponent = ); - const endAdornment = ( + const clearButtonEndAdornment = ( - - - + ); return ( - ({ - container: { - borderRadius: 30, - display: 'flex', - height: '2.4em', + dialogTitle: { + gap: theme.spacing(1), + display: 'grid', + alignItems: 'center', + gridTemplateColumns: '1fr auto', + '&> button': { + marginTop: theme.spacing(1), + }, }, input: { flex: 1, @@ -92,7 +98,7 @@ const useStyles = makeStyles(theme => ({ viewResultsLink: { verticalAlign: '0.5em' }, })); -export const Modal = () => { +export const Modal = ({ toggleModal }: SearchModalChildrenProps) => { const classes = useStyles(); const navigate = useNavigate(); const { transitions } = useTheme(); @@ -111,7 +117,7 @@ export const Modal = () => { }, [focusContent, transitions]); const handleSearchBarKeyDown = useCallback( - (e: KeyboardEvent) => { + (e: KeyboardEvent) => { if (e.key === 'Enter') { navigate(searchPagePath); handleSearchResultClick(); @@ -123,13 +129,17 @@ export const Modal = () => { return ( <> - + - + + + + + { alignItems="center" > - - - View Full Results - - - + @@ -186,7 +199,9 @@ export const SearchModal = (props: SearchModalProps) => { > {open && ( - {(children && children({ toggleModal })) ?? } + {(children && children({ toggleModal })) ?? ( + + )} )}