feat(search): highlight search result terms

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2022-04-20 00:05:52 -04:00
parent 894f4022b6
commit 3a74e203a8
42 changed files with 947 additions and 69 deletions
@@ -15,7 +15,10 @@
*/
import { Button } from '@backstage/core-components';
import { lightTheme } from '@backstage/theme';
import { Grid } from '@material-ui/core';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/core/styles';
import FindInPageIcon from '@material-ui/icons/FindInPage';
import GroupIcon from '@material-ui/icons/Group';
import React from 'react';
@@ -77,3 +80,47 @@ export const WithSecondaryAction = () => {
/>
);
};
export const WithHighlightedResults = () => {
return (
<DefaultResultListItem
result={mockSearchResult}
highlight={{
preTag: '<tag>',
postTag: '</tag>',
fields: { text: 'some <tag>text</tag> from the search result' },
}}
/>
);
};
export const WithCustomHighlightedResults = () => {
const customTheme = {
...lightTheme,
overrides: {
...lightTheme.overrides,
BackstageHighlightedSearchResultText: {
highlight: {
color: 'inherit',
backgroundColor: 'inherit',
fontWeight: 'bold',
textDecoration: 'underline',
},
},
},
};
return (
<ThemeProvider theme={customTheme}>
<CssBaseline>
<DefaultResultListItem
result={mockSearchResult}
highlight={{
preTag: '<tag>',
postTag: '</tag>',
fields: { text: 'some <tag>text</tag> from the search result' },
}}
/>
</CssBaseline>
</ThemeProvider>
);
};
@@ -20,11 +20,6 @@ import { renderInTestApp } from '@backstage/test-utils';
import FindInPageIcon from '@material-ui/icons/FindInPage';
import { DefaultResultListItem } from './DefaultResultListItem';
// Using canvas to render text..
jest.mock('react-text-truncate', () => {
return ({ text }: { text: string }) => <span>{text}</span>;
});
describe('DefaultResultListItem', () => {
const result = {
title: 'title',
@@ -15,7 +15,11 @@
*/
import React, { ReactNode } from 'react';
import { SearchDocument } from '@backstage/plugin-search-common';
import {
ResultHighlight,
SearchDocument,
} from '@backstage/plugin-search-common';
import { HighlightedSearchResultText } from '@backstage/plugin-search-react';
import {
ListItem,
ListItemIcon,
@@ -24,17 +28,18 @@ import {
Divider,
} from '@material-ui/core';
import { Link } from '@backstage/core-components';
import TextTruncate from 'react-text-truncate';
type Props = {
icon?: ReactNode;
secondaryAction?: ReactNode;
result: SearchDocument;
highlight?: ResultHighlight;
lineClamp?: number;
};
export const DefaultResultListItem = ({
result,
highlight,
icon,
secondaryAction,
lineClamp = 5,
@@ -45,14 +50,36 @@ export const DefaultResultListItem = ({
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText
primaryTypographyProps={{ variant: 'h6' }}
primary={result.title}
primary={
highlight?.fields.title ? (
<HighlightedSearchResultText
text={highlight.fields.title}
preTag={highlight.preTag}
postTag={highlight.postTag}
/>
) : (
result.title
)
}
secondary={
<TextTruncate
line={lineClamp}
truncateText="…"
text={result.text}
element="span"
/>
<span
style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: lineClamp,
overflow: 'hidden',
}}
>
{highlight?.fields.text ? (
<HighlightedSearchResultText
text={highlight.fields.text}
preTag={highlight.preTag}
postTag={highlight.postTag}
/>
) : (
result.text
)}
</span>
}
/>
{secondaryAction && <Box alignItems="flex-end">{secondaryAction}</Box>}
@@ -137,7 +137,7 @@ export const Modal = ({ toggleModal }: SearchModalProps) => {
<SearchResult>
{({ results }) => (
<List>
{results.map(({ document }) => (
{results.map(({ document, highlight }) => (
<div
role="button"
tabIndex={0}
@@ -148,6 +148,7 @@ export const Modal = ({ toggleModal }: SearchModalProps) => {
<DefaultResultListItem
key={document.location}
result={document}
highlight={highlight}
/>
</div>
))}