From 8b532a6c02d7148f49569237cafd9e9619e76adc Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 27 Dec 2021 18:40:20 +0100 Subject: [PATCH] Initial SearchTypeFacet implementation. Signed-off-by: Eric Peterson --- .changeset/search-en-jul-hemma.md | 7 + .../app/src/components/search/SearchPage.tsx | 33 +++- plugins/search/api-report.md | 14 ++ .../SearchTypeFacet.stories.tsx | 49 +++++ .../SearchTypeFacet/SearchTypeFacet.tsx | 184 ++++++++++++++++++ .../src/components/SearchTypeFacet/index.ts | 18 ++ plugins/search/src/components/index.tsx | 1 + plugins/search/src/index.ts | 2 + 8 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 .changeset/search-en-jul-hemma.md create mode 100644 plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.stories.tsx create mode 100644 plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.tsx create mode 100644 plugins/search/src/components/SearchTypeFacet/index.ts diff --git a/.changeset/search-en-jul-hemma.md b/.changeset/search-en-jul-hemma.md new file mode 100644 index 0000000000..a2eee15e7c --- /dev/null +++ b/.changeset/search-en-jul-hemma.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-search': patch +--- + +Introduces a `` component, which operates on the same part of a search query as the `` component, but in a more opinionated way (as a single-select control surface suitable for faceted search UIs). + +Check the [search plugin storybook](https://backstage.io/storybook/?path=/story/plugins-search-searchtypefacet--default) to see how it can be used. diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 54f59d1a6e..e8d590bc92 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -14,7 +14,14 @@ * limitations under the License. */ -import { Content, Header, Lifecycle, Page } from '@backstage/core-components'; +import { + CatalogIcon, + Content, + DocsIcon, + Header, + Lifecycle, + Page, +} from '@backstage/core-components'; import { CatalogResultListItem } from '@backstage/plugin-catalog'; import { DefaultResultListItem, @@ -22,7 +29,7 @@ import { SearchFilter, SearchResult, SearchResultPager, - SearchType, + SearchTypeFacet, } from '@backstage/plugin-search'; import { DocsResultListItem } from '@backstage/plugin-techdocs'; import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core'; @@ -39,6 +46,7 @@ const useStyles = makeStyles((theme: Theme) => ({ }, filters: { padding: theme.spacing(2), + marginTop: theme.spacing(2), }, })); @@ -55,12 +63,23 @@ const SearchPage = () => { + , + }, + { + value: 'techdocs', + name: 'Documentation', + icon: , + }, + ]} + /> - JSX.Element; +// @public +export const SearchTypeFacet: (props: SearchTypeFacetProps) => JSX.Element; + +// @public (undocumented) +export type SearchTypeFacetProps = { + name: string; + types: Array<{ + value: string; + name: string; + icon: JSX.Element; + }>; + defaultValue?: string; +}; + // Warning: (ae-missing-release-tag) "SidebarSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.stories.tsx b/plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.stories.tsx new file mode 100644 index 0000000000..c83f3d88d7 --- /dev/null +++ b/plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.stories.tsx @@ -0,0 +1,49 @@ +/* + * 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, { useState } from 'react'; +import CatalogIcon from '@material-ui/icons/MenuBook'; +import DocsIcon from '@material-ui/icons/Description'; + +import { SearchTypeFacet } from '../index'; +import { SearchContext } from '../SearchContext'; + +export default { + title: 'Plugins/Search/SearchTypeFacet', + component: SearchTypeFacet, +}; + +export const Default = () => { + const [types, setTypes] = useState([]); + + return ( + {} } as any} + > + , + }, + { value: 'techdocs', name: 'Documentation', icon: }, + ]} + /> + + ); +}; diff --git a/plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.tsx b/plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.tsx new file mode 100644 index 0000000000..e054e0d028 --- /dev/null +++ b/plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.tsx @@ -0,0 +1,184 @@ +/* + * 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, { + ChangeEvent, + cloneElement, + Fragment, + useEffect, + useState, +} from 'react'; +import { useSearch } from '../SearchContext'; +import { + Accordion, + AccordionSummary, + AccordionDetails, + Card, + CardContent, + CardHeader, + Divider, + List, + ListItem, + ListItemIcon, + ListItemText, + makeStyles, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import AllIcon from '@material-ui/icons/FontDownload'; + +const useStyles = makeStyles(theme => ({ + card: { + backgroundColor: 'rgba(0, 0, 0, .11)', + }, + cardContent: { + paddingTop: theme.spacing(1), + }, + icon: { + color: theme.palette.common.black, + }, + list: { + width: '100%', + }, + listItemIcon: { + width: '24px', + height: '24px', + }, + accordion: { + backgroundColor: theme.palette.background.paper, + }, + accordionSummary: { + minHeight: 'auto', + '&.Mui-expanded': { + minHeight: 'auto', + }, + }, + accordionSummaryContent: { + margin: theme.spacing(2, 0), + '&.Mui-expanded': { + margin: theme.spacing(2, 0), + }, + }, + accordionDetails: { + padding: theme.spacing(0, 0, 1), + }, +})); + +/** + * @public + */ +export type SearchTypeFacetProps = { + /* what about this? */ + name: string; + types: Array<{ + value: string; + name: string; + icon: JSX.Element; + }>; + defaultValue?: string; +}; + +/** + * A control surface for the search query's "types" property, displayed as a + * single-select collapsible accordion suitable for use in faceted search UIs. + * @public + */ +export const SearchTypeFacet = (props: SearchTypeFacetProps) => { + const classes = useStyles(); + const { setPageCursor, setTypes, types } = useSearch(); + const [expanded, setExpanded] = useState(true); + const { defaultValue, name, types: givenTypes } = props; + + const handleChange = (_event: ChangeEvent<{}>, newExpanded: boolean) => + setExpanded(newExpanded); + const handleClick = (type: string) => { + return () => { + setTypes(type !== '' ? [type] : []); + setPageCursor('0'); + setExpanded(false); + }; + }; + + // Handle any provided defaultValue + useEffect(() => { + if (defaultValue) { + setTypes([defaultValue]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const definedTypes = [ + { + value: '', + name: 'All', + icon: , + }, + ...givenTypes, + ]; + const selected = types[0] || ''; + + return ( + + + + + } + IconButtonProps={{ size: 'small' }} + > + {expanded + ? 'Collapse' + : definedTypes.filter(t => t.value === selected)[0]!.name} + + + + {definedTypes.map(type => ( + + + + + {cloneElement(type.icon, { + className: classes.listItemIcon, + })} + + + + + ))} + + + + + + ); +}; diff --git a/plugins/search/src/components/SearchTypeFacet/index.ts b/plugins/search/src/components/SearchTypeFacet/index.ts new file mode 100644 index 0000000000..f98c6f8326 --- /dev/null +++ b/plugins/search/src/components/SearchTypeFacet/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { SearchTypeFacet } from './SearchTypeFacet'; +export type { SearchTypeFacetProps } from './SearchTypeFacet'; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index e413e9505d..82cc7bf762 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -24,6 +24,7 @@ export * from './SearchPage'; export * from './SearchResult'; export * from './SearchResultPager'; export * from './SearchType'; +export * from './SearchTypeFacet'; export * from './SidebarSearch'; export * from './SidebarSearchModal'; export * from './HomePageComponent'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 5bfdcf4318..8f760f42db 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -34,6 +34,7 @@ export { SearchPage as Router, SearchResultPager, SearchType, + SearchTypeFacet, SidebarSearch, useSearch, } from './components'; @@ -45,6 +46,7 @@ export type { FiltersState, SearchBarProps, SearchBarBaseProps, + SearchTypeFacetProps, } from './components'; export { DefaultResultListItem,