Initial SearchTypeFacet implementation.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -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.
|
||||
@@ -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 = () => {
|
||||
</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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user