feat(search): add optional icon and secondary action for default result items

Co-authored-by: Emma Indal <emma.indahl@gmail.com>
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2021-12-01 17:03:33 +01:00
parent b7b33e0293
commit 00da5bfca7
3 changed files with 87 additions and 10 deletions
@@ -16,6 +16,9 @@
import React from 'react';
import { Grid } from '@material-ui/core';
import FindInPageIcon from '@material-ui/icons/FindInPage';
import GroupIcon from '@material-ui/icons/Group';
import { Button } from '@backstage/core-components';
import { DefaultResultListItem } from '../index';
import { MemoryRouter } from 'react-router';
@@ -24,17 +27,59 @@ export default {
component: DefaultResultListItem,
};
const mockSearchResult = {
location: 'search/search-result',
title: 'Search Result 1',
text: 'some text from the search result',
owner: 'some-example-owner',
};
export const Default = () => {
return (
<MemoryRouter>
<Grid container direction="row">
<Grid item xs={12}>
<DefaultResultListItem result={mockSearchResult} />
</Grid>
</Grid>
</MemoryRouter>
);
};
export const WithIcon = () => {
return (
<MemoryRouter>
<Grid container direction="row">
<Grid item xs={12}>
<DefaultResultListItem
result={{
location: 'search/search-result',
title: 'Search Result 1',
text: 'some text from the search result',
}}
result={mockSearchResult}
icon={<FindInPageIcon color="primary" />}
/>
</Grid>
</Grid>
</MemoryRouter>
);
};
export const WithSecondaryAction = () => {
return (
<MemoryRouter>
<Grid container direction="row">
<Grid item xs={12}>
<DefaultResultListItem
result={mockSearchResult}
secondaryAction={
<Button
to="#"
size="small"
aria-label="owner"
variant="text"
startIcon={<GroupIcon />}
style={{ textTransform: 'lowercase' }}
>
{mockSearchResult.owner}
</Button>
}
/>
</Grid>
</Grid>
@@ -17,7 +17,7 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/test-utils';
import FindInPageIcon from '@material-ui/icons/FindInPage';
import { DefaultResultListItem } from './DefaultResultListItem';
describe('DefaultResultListItem', () => {
@@ -25,6 +25,7 @@ describe('DefaultResultListItem', () => {
title: 'title',
text: 'text',
location: '/location',
owner: 'owner',
};
it('Links to result.location', async () => {
@@ -38,4 +39,21 @@ describe('DefaultResultListItem', () => {
result.title + result.text,
);
});
it('should render icon if prop is specified', async () => {
await renderInTestApp(
<DefaultResultListItem
result={result}
icon={<FindInPageIcon title="icon" />}
/>,
);
expect(screen.getByTitle('icon')).toBeInTheDocument();
});
it('should render secondary action if prop is specified', async () => {
await renderInTestApp(
<DefaultResultListItem result={result} secondaryAction={result.owner} />,
);
expect(screen.getByText('owner')).toBeInTheDocument();
});
});
@@ -14,24 +14,38 @@
* limitations under the License.
*/
import React from 'react';
import React, { ReactNode } from 'react';
import { IndexableDocument } from '@backstage/search-common';
import { ListItem, ListItemText, Divider } from '@material-ui/core';
import {
ListItem,
ListItemIcon,
ListItemText,
Box,
Divider,
} from '@material-ui/core';
import { Link } from '@backstage/core-components';
type Props = {
icon?: ReactNode;
secondaryAction?: ReactNode;
result: IndexableDocument;
};
export const DefaultResultListItem = ({ result }: Props) => {
export const DefaultResultListItem = ({
result,
icon,
secondaryAction,
}: Props) => {
return (
<Link to={result.location}>
<ListItem alignItems="flex-start">
<ListItem alignItems="center">
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText
primaryTypographyProps={{ variant: 'h6' }}
primary={result.title}
secondary={result.text}
/>
{secondaryAction && <Box alignItems="flex-end">{secondaryAction}</Box>}
</ListItem>
<Divider />
</Link>