diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 251ba49..9e7bdcd 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -12,7 +12,7 @@ import { Settings as SidebarSettings, UserSettingsSignInAvatar, } from '@backstage/plugin-user-settings'; -import { SidebarSearchModal } from '@backstage/plugin-search'; +import { SearchModalProvider, useSearchModal } from '@backstage/plugin-search'; import { Sidebar, sidebarConfig, @@ -30,6 +30,7 @@ import { // import { useApp } from '@backstage/core-plugin-api'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; +import { CustomSearchModal } from '../search/CustomModal'; import {useApi, configApiRef} from '@backstage/core-plugin-api'; @@ -118,13 +119,27 @@ const SidebarLogo = () => { ); }; -export const Root = ({ children }: PropsWithChildren<{}>) => ( +export const Root = ({ children }: PropsWithChildren<{}>) => { + const { state, toggleModal } = useSearchModal(); + + return ( } to="/search"> - + + + + }> @@ -191,4 +206,4 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( {children} -); +)}; diff --git a/packages/app/src/components/search/CustomModal.tsx b/packages/app/src/components/search/CustomModal.tsx new file mode 100644 index 0000000..8978a5a --- /dev/null +++ b/packages/app/src/components/search/CustomModal.tsx @@ -0,0 +1,157 @@ +/* + * CustomModal.tsx is a customized version of the original SearchModal.tsx + * The searchResultCustomList component is used rather than element + */ + +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useCallback, useEffect, useRef } from 'react'; +import { SearchBar, SearchContextProvider, SearchResultPager } from "@backstage/plugin-search-react"; +import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, Grid, IconButton, makeStyles, useTheme } from "@material-ui/core"; +import CloseIcon from '@material-ui/icons/Close'; +import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; +import { searchResultCustomList } from './SearchResultCustomList'; +import { SearchModalChildrenProps, SearchModalProps } from '@backstage/plugin-search'; +import { useNavigate } from 'react-router-dom'; +import { useContent } from '@backstage/core-components'; + +const useStyles = makeStyles(theme => ({ + dialogTitle: { + gap: theme.spacing(1), + display: 'grid', + alignItems: 'center', + gridTemplateColumns: '1fr auto', + '&> button': { + marginTop: theme.spacing(1), + }, + }, + input: { + flex: 1, + }, + button: { + '&:hover': { + background: 'none', + }, + }, + // Reduces default height of the modal, keeping a gap of 128px between the top and bottom of the page. + paperFullWidth: { height: 'calc(100% - 128px)' }, + dialogActionsContainer: { padding: theme.spacing(1, 3) }, + viewResultsLink: { verticalAlign: '0.5em' }, + })); + +export const CustomModal = ({ toggleModal }: SearchModalChildrenProps) => { + const classes = useStyles(); + const navigate = useNavigate(); + const { transitions } = useTheme(); + const { focusContent } = useContent(); + const searchBarRef = useRef(null); + + useEffect(() => { + searchBarRef?.current?.focus(); + }); + + const handleSearchResultClick = useCallback(() => { + setTimeout(focusContent, transitions.duration.leavingScreen); + }, [focusContent, transitions]); + + // This handler is called when "enter" is pressed + const handleSearchBarSubmit = useCallback(() => { + // Using ref to get the current field value without waiting for a query debounce + const query = searchBarRef.current?.value ?? ''; + navigate(`/search?query=${query}`); + handleSearchResultClick(); + }, [navigate, handleSearchResultClick]); + + return ( + <> + + + + + + + + + + + + + + + + + + + {searchResultCustomList} + + + + + + + + + + + + ); + }; + +export const CustomSearchModal = (props: SearchModalProps) => { + const { open = true, hidden, toggleModal, children } = props; + + const classes = useStyles(); + + return ( + + ); +}; \ No newline at end of file diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 3bc85fc..2602aab 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -1,21 +1,17 @@ import React from 'react'; -import { makeStyles, Theme, Grid, Paper, List } from '@material-ui/core'; +import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; -import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; import { catalogApiRef, CATALOG_FILTER_EXISTS, } from '@backstage/plugin-catalog-react'; -import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; import { SearchType } from '@backstage/plugin-search'; import { SearchBar, SearchFilter, - SearchResult, SearchPagination, - useSearch, - DefaultResultListItem + useSearch } from '@backstage/plugin-search-react'; import { CatalogIcon, @@ -25,7 +21,8 @@ import { Page, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { StackOverflowSearchResultListItem, StackOverflowIcon } from '@backstage/plugin-stack-overflow'; +import { StackOverflowIcon } from '@backstage/plugin-stack-overflow'; +import { searchResultCustomList } from './SearchResultCustomList'; const useStyles = makeStyles((theme: Theme) => ({ search: { @@ -119,56 +116,7 @@ const SearchPage = () => { - - {({ results }) => ( - - {results.map(({ type, document, highlight, rank }) => { - switch (type) { - case 'software-catalog': - return ( - } - /> - ); - case 'techdocs': - return ( - } - /> - ); - case 'stack-overflow': - return ( - } - /> - ); - default: - return ( - - ); - } - })} - - )} - {/* } /> - } /> - } /> */} - + {searchResultCustomList} diff --git a/packages/app/src/components/search/SearchResultCustomList.tsx b/packages/app/src/components/search/SearchResultCustomList.tsx new file mode 100644 index 0000000..4cfbc5b --- /dev/null +++ b/packages/app/src/components/search/SearchResultCustomList.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import { List } from '@material-ui/core'; +import { SearchResult, DefaultResultListItem } from '@backstage/plugin-search-react'; + +import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; +import { StackOverflowSearchResultListItem, StackOverflowIcon } from '@backstage/plugin-stack-overflow'; +import { CatalogIcon, DocsIcon } from '@backstage/core-components'; +import { TechDocsSearchResultCustomListItem } from './TechDocsSearchResultCustomListItem'; + +const SearchResultCustomList = () => { + return ( + + {({ results }) => ( + + {results.map(({ type, document, highlight, rank }) => { + switch (type) { + case 'software-catalog': + return ( + } + /> + ); + case 'techdocs': + return ( + } + /> + ); + case 'stack-overflow': + return ( + } + /> + ); + default: + return ( + + ); + } + })} + + )} + + ) +} + +export const searchResultCustomList = ; \ No newline at end of file diff --git a/packages/app/src/components/search/TechDocsSearchResultCustomListItem.tsx b/packages/app/src/components/search/TechDocsSearchResultCustomListItem.tsx new file mode 100644 index 0000000..e485998 --- /dev/null +++ b/packages/app/src/components/search/TechDocsSearchResultCustomListItem.tsx @@ -0,0 +1,183 @@ +/* + * TechDocsSearchResultCustomListItem.tsx is a customized version of the original TechDocsSearchResultListItem.tsx + * This version removes the entityTitle from the result title and places it as a tag instead + */ + +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { PropsWithChildren, ReactNode } from 'react'; +import { Box, Chip, Divider, ListItem, ListItemIcon, ListItemText, makeStyles } from '@material-ui/core'; +import Typography from '@material-ui/core/Typography'; +import { Link } from '@backstage/core-components'; +import { ResultHighlight } from '@backstage/plugin-search-common'; +import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; + +const useStyles = makeStyles({ + flexContainer: { + flexWrap: 'wrap', + }, + itemText: { + width: '100%', + marginBottom: '1rem', + }, +}); + +/** + * Props for {@link TechDocsSearchResultCustomListItem}. + * + * @public + */ +export type TechDocsSearchResultListItemProps = { + icon?: ReactNode | ((result: any) => ReactNode); + result?: any; + highlight?: ResultHighlight; + rank?: number; + lineClamp?: number; + asListItem?: boolean; + asLink?: boolean; + title?: string; +}; + +/** + * Component which renders documentation and related metadata. + * + * @public + */ +export const TechDocsSearchResultCustomListItem = ( + props: TechDocsSearchResultListItemProps, +) => { + const { + result, + highlight, + lineClamp = 5, + asListItem = true, + asLink = true, + title, + icon, + } = props; + const classes = useStyles(); + + const LinkWrapper = ({ children }: PropsWithChildren<{}>) => + asLink ? ( + + {children} + + ) : ( + <>{children} + ); + + const TextItem = () => { + const resultTitle = highlight?.fields.title ? ( + + ) : ( + result.title + ); + + const entityTitle = highlight?.fields.entityTitle ? ( + + ) : ( + result.entityTitle + ); + + const resultName = highlight?.fields.name ? ( + + ) : ( + result.name + ); + + if (!result) return null; + + return ( + <> + + {icon && + {typeof icon === 'function' ? icon(result) : icon} + } + + + {title ? ( + title + ) : ( + <> + {resultTitle} docs + + )} + + } + secondary={ + + {highlight?.fields.text ? ( + + ) : ( + result.text + )} + + } + /> + + + + + + ); + }; + + const ListItemWrapper = ({ children }: PropsWithChildren<{}>) => + asListItem ? ( + <> +
{children}
+ + ) : ( + <>{children} + ); + + return ( + + + + ); +}; \ No newline at end of file diff --git a/packages/app/src/components/utils/icons.tsx b/packages/app/src/components/utils/icons.tsx index 3af2c35..9565547 100644 --- a/packages/app/src/components/utils/icons.tsx +++ b/packages/app/src/components/utils/icons.tsx @@ -33,3 +33,16 @@ export const StackOverFlowIcon = createSvgIcon( , "StackOverflow"); + +// @see https://icon-sets.iconify.design/logos/openshift/ +export const OpenShiftSvgIcon = createSvgIcon( + + + + + + + + + , "OpenShift"); \ No newline at end of file diff --git a/packages/app/src/devex-theme.ts b/packages/app/src/devex-theme.ts index 956c7e0..01a8ad2 100644 --- a/packages/app/src/devex-theme.ts +++ b/packages/app/src/devex-theme.ts @@ -252,7 +252,7 @@ const createCustomThemeOverrides = ( MuiChip: { root: { borderRadius: 3, - backgroundColor: theme.palette.grey[50], + backgroundColor: 'rgba(0, 0, 0, .11)', color: theme.palette.primary.dark, margin: 4, },