Skip to content

Commit

Permalink
Initial SearchTypeFacet implementation.
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
  • Loading branch information
iamEAP committed Dec 27, 2021
1 parent 8e5d716 commit 8b532a6
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 7 deletions.
7 changes: 7 additions & 0 deletions .changeset/search-en-jul-hemma.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@backstage/plugin-search': patch
---

Introduces a `<SearchTypeFacet />` component, which operates on the same part of a search query as the `<SearchType />` 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.
33 changes: 26 additions & 7 deletions packages/app/src/components/search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@
* 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,
SearchBar,
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';
Expand All @@ -39,6 +46,7 @@ const useStyles = makeStyles((theme: Theme) => ({
},
filters: {
padding: theme.spacing(2),
marginTop: theme.spacing(2),
},
}));

Expand All @@ -55,12 +63,23 @@ const SearchPage = () => {
</Paper>
</Grid>
<Grid item xs={3}>
<SearchTypeFacet
name="Result Type"
defaultValue="software-catalog"
types={[
{
value: 'software-catalog',
name: 'Software Catalog',
icon: <CatalogIcon />,
},
{
value: 'techdocs',
name: 'Documentation',
icon: <DocsIcon />,
},
]}
/>
<Paper className={classes.filters}>
<SearchType
values={['techdocs', 'software-catalog']}
name="type"
defaultValue="software-catalog"
/>
<SearchFilter.Select
className={classes.filter}
name="kind"
Expand Down
14 changes: 14 additions & 0 deletions plugins/search/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,20 @@ export const SearchType: ({
defaultValue,
}: SearchTypeProps) => 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);

return (
<SearchContext.Provider
value={{ types, setTypes, setPageCursor: () => {} } as any}
>
<SearchTypeFacet
name="Result Type"
defaultValue="software-catalog"
types={[
{
value: 'software-catalog',
name: 'Software Catalog',
icon: <CatalogIcon />,
},
{ value: 'techdocs', name: 'Documentation', icon: <DocsIcon /> },
]}
/>
</SearchContext.Provider>
);
};
184 changes: 184 additions & 0 deletions plugins/search/src/components/SearchTypeFacet/SearchTypeFacet.tsx
Original file line number Diff line number Diff line change
@@ -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: <AllIcon />,
},
...givenTypes,
];
const selected = types[0] || '';

return (
<Card className={classes.card}>
<CardHeader title={name} titleTypographyProps={{ variant: 'overline' }} />
<CardContent className={classes.cardContent}>
<Accordion
className={classes.accordion}
expanded={expanded}
onChange={handleChange}
>
<AccordionSummary
classes={{
root: classes.accordionSummary,
content: classes.accordionSummaryContent,
}}
expandIcon={<ExpandMoreIcon className={classes.icon} />}
IconButtonProps={{ size: 'small' }}
>
{expanded
? 'Collapse'
: definedTypes.filter(t => t.value === selected)[0]!.name}
</AccordionSummary>
<AccordionDetails classes={{ root: classes.accordionDetails }}>
<List
className={classes.list}
component="nav"
aria-label="filter by type"
disablePadding
dense
>
{definedTypes.map(type => (
<Fragment key={type.value}>
<Divider />
<ListItem
selected={types.includes(type.value)}
onClick={handleClick(type.value)}
button
>
<ListItemIcon>
{cloneElement(type.icon, {
className: classes.listItemIcon,
})}
</ListItemIcon>
<ListItemText primary={type.name} />
</ListItem>
</Fragment>
))}
</List>
</AccordionDetails>
</Accordion>
</CardContent>
</Card>
);
};
18 changes: 18 additions & 0 deletions plugins/search/src/components/SearchTypeFacet/index.ts
Original file line number Diff line number Diff line change
@@ -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';
1 change: 1 addition & 0 deletions plugins/search/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
2 changes: 2 additions & 0 deletions plugins/search/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export {
SearchPage as Router,
SearchResultPager,
SearchType,
SearchTypeFacet,
SidebarSearch,
useSearch,
} from './components';
Expand All @@ -45,6 +46,7 @@ export type {
FiltersState,
SearchBarProps,
SearchBarBaseProps,
SearchTypeFacetProps,
} from './components';
export {
DefaultResultListItem,
Expand Down

0 comments on commit 8b532a6

Please sign in to comment.