PanelEdit: Highlight matched words when searching options (#33717)

This commit is contained in:
Torkel Ödegaard 2021-05-05 09:12:06 +02:00 committed by GitHub
parent 86f2ed8ef2
commit 1e810b3d59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 51 additions and 18 deletions

View File

@ -112,6 +112,7 @@
"@types/react-beautiful-dnd": "12.1.2",
"@types/react-dom": "16.9.9",
"@types/react-grid-layout": "1.1.1",
"@types/react-highlight-words": "^0.16.2",
"@types/react-redux": "7.1.7",
"@types/react-router-dom": "^5.1.7",
"@types/react-select": "4.0.13",
@ -277,7 +278,7 @@
"react-diff-viewer": "^3.1.1",
"react-dom": "17.0.1",
"react-grid-layout": "1.2.0",
"react-highlight-words": "0.16.0",
"react-highlight-words": "0.17.0",
"react-inlinesvg": "2.3.0",
"react-loadable": "5.5.0",
"react-popper": "2.2.4",

View File

@ -13,10 +13,14 @@ export function findHighlightChunksInText({
searchWords,
textToHighlight,
}: {
searchWords: string[];
searchWords: Array<string | RegExp>;
textToHighlight: string;
}) {
return searchWords.reduce((acc: any, term: string) => [...acc, ...findMatchesInText(textToHighlight, term)], []);
const chunks: TextMatch[] = [];
for (const term of searchWords) {
chunks.push(...findMatchesInText(textToHighlight, term as string));
}
return chunks;
}
const cleanNeedle = (needle: string): string => {

View File

@ -7,7 +7,7 @@ import { Icon } from '../Icon/Icon';
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
children: React.ReactNode;
description?: React.ReactNode;
category?: string[];
category?: React.ReactNode[];
}
export const getLabelStyles = stylesFactory((theme: GrafanaTheme2) => {

View File

@ -106,7 +106,7 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
{needsHighlighter ? (
<Highlighter
textToHighlight={entry}
searchWords={highlights}
searchWords={highlights ?? []}
findChunks={findHighlightChunksInText}
highlightClassName={highlightClassName}
/>

View File

@ -90,7 +90,7 @@ export const TypeaheadItem: React.FC<Props> = (props: Props) => {
highlightParts={item.highlightParts}
></PartialHighlighter>
) : (
<Highlighter textToHighlight={label} searchWords={[prefix]} highlightClassName={highlightClassName} />
<Highlighter textToHighlight={label} searchWords={[prefix ?? '']} highlightClassName={highlightClassName} />
)}
</li>
);

View File

@ -37,7 +37,7 @@ export class OptionsPaneCategoryDescriptor {
return this;
}
render(isSearching?: boolean) {
render(searchQuery?: string) {
if (this.props.customRender) {
return this.props.customRender();
}

View File

@ -1,6 +1,7 @@
import { selectors } from '@grafana/e2e-selectors';
import { Field, Label } from '@grafana/ui';
import React, { ReactNode } from 'react';
import Highlighter from 'react-highlight-words';
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
export interface OptionsPaneItemProps {
@ -21,10 +22,10 @@ export class OptionsPaneItemDescriptor {
constructor(public props: OptionsPaneItemProps) {}
getLabel(isSearching?: boolean): ReactNode {
getLabel(searchQuery?: string): ReactNode {
const { title, description } = this.props;
if (!isSearching) {
if (!searchQuery) {
// Do not render label for categories with only one child
if (this.parent.props.title === title) {
return null;
@ -33,24 +34,30 @@ export class OptionsPaneItemDescriptor {
return title;
}
const categories: string[] = [];
const categories: React.ReactNode[] = [];
if (this.parent.parent) {
categories.push(this.parent.parent.props.title);
categories.push(this.highlightWord(this.parent.parent.props.title, searchQuery));
}
if (this.parent.props.title !== title) {
categories.push(this.parent.props.title);
categories.push(this.highlightWord(this.parent.props.title, searchQuery));
}
return (
<Label description={description} category={categories}>
{title}
<Label description={description && this.highlightWord(description, searchQuery)} category={categories}>
{this.highlightWord(title, searchQuery)}
</Label>
);
}
render(isSearching?: boolean) {
highlightWord(word: string, query: string) {
return (
<Highlighter textToHighlight={word} searchWords={[query]} highlightClassName={'search-fragment-highlight'} />
);
}
render(searchQuery?: string) {
const { title, description, render, showIf, skipField } = this.props;
const key = `${this.parent.props.id} ${title}`;
@ -64,7 +71,7 @@ export class OptionsPaneItemDescriptor {
return (
<Field
label={this.getLabel(isSearching)}
label={this.getLabel(searchQuery)}
description={description}
key={key}
aria-label={selectors.components.PanelEditor.OptionsPane.fieldLabel(key)}

View File

@ -139,9 +139,9 @@ function renderSearchHits(
key="Normal options"
forceOpen={1}
>
{optionHits.map((hit) => hit.render(true))}
{optionHits.map((hit) => hit.render(searchQuery))}
</OptionsPaneCategory>
{overrideHits.map((override) => override.render(true))}
{overrideHits.map((override) => override.render(searchQuery))}
</div>
);
}
@ -152,6 +152,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
display: flex;
flex-direction: column;
flex: 1 1 0;
.search-fragment-highlight {
color: ${theme.colors.warning.text};
background: transparent;
}
`,
searchBox: css`
display: flex;

View File

@ -5894,6 +5894,13 @@
dependencies:
"@types/react" "*"
"@types/react-highlight-words@^0.16.2":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/react-highlight-words/-/react-highlight-words-0.16.2.tgz#c196d1016db7519c607997699f51cb262789fc90"
integrity sha512-l9HsbTTLGUQB6CwrI6/e89QDdnkTjLZt0K1MVAYyrzwVbWUNR6FiwwUpECwPoOcAg0iJjYf2uqonSuurdE91oQ==
dependencies:
"@types/react" "*"
"@types/react-icon-base@*":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@types/react-icon-base/-/react-icon-base-2.1.3.tgz#0faf840b854a9dbc3fa6fe1935c7c40eb4153114"
@ -20742,6 +20749,15 @@ react-highlight-words@0.16.0:
memoize-one "^4.0.0"
prop-types "^15.5.8"
react-highlight-words@0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/react-highlight-words/-/react-highlight-words-0.17.0.tgz#e79a559a2de301548339d7216264d6cd0f1eed6f"
integrity sha512-uX1Qh5IGjnLuJT0Zok234QDwRC8h4hcVMnB99Cb7aquB1NlPPDiWKm0XpSZOTdSactvnClCk8LOmVlP+75dgHA==
dependencies:
highlight-words-core "^1.2.0"
memoize-one "^4.0.0"
prop-types "^15.5.8"
react-hook-form@7.2.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.2.3.tgz#a4be9214cab3a6e6358f95d342da2e7ded37e3f0"