mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PanelEdit: Highlight matched words when searching options (#33717)
This commit is contained in:
parent
86f2ed8ef2
commit
1e810b3d59
@ -112,6 +112,7 @@
|
|||||||
"@types/react-beautiful-dnd": "12.1.2",
|
"@types/react-beautiful-dnd": "12.1.2",
|
||||||
"@types/react-dom": "16.9.9",
|
"@types/react-dom": "16.9.9",
|
||||||
"@types/react-grid-layout": "1.1.1",
|
"@types/react-grid-layout": "1.1.1",
|
||||||
|
"@types/react-highlight-words": "^0.16.2",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/react-select": "4.0.13",
|
"@types/react-select": "4.0.13",
|
||||||
@ -277,7 +278,7 @@
|
|||||||
"react-diff-viewer": "^3.1.1",
|
"react-diff-viewer": "^3.1.1",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"react-grid-layout": "1.2.0",
|
"react-grid-layout": "1.2.0",
|
||||||
"react-highlight-words": "0.16.0",
|
"react-highlight-words": "0.17.0",
|
||||||
"react-inlinesvg": "2.3.0",
|
"react-inlinesvg": "2.3.0",
|
||||||
"react-loadable": "5.5.0",
|
"react-loadable": "5.5.0",
|
||||||
"react-popper": "2.2.4",
|
"react-popper": "2.2.4",
|
||||||
|
@ -13,10 +13,14 @@ export function findHighlightChunksInText({
|
|||||||
searchWords,
|
searchWords,
|
||||||
textToHighlight,
|
textToHighlight,
|
||||||
}: {
|
}: {
|
||||||
searchWords: string[];
|
searchWords: Array<string | RegExp>;
|
||||||
textToHighlight: string;
|
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 => {
|
const cleanNeedle = (needle: string): string => {
|
||||||
|
@ -7,7 +7,7 @@ import { Icon } from '../Icon/Icon';
|
|||||||
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
category?: string[];
|
category?: React.ReactNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLabelStyles = stylesFactory((theme: GrafanaTheme2) => {
|
export const getLabelStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||||
|
@ -106,7 +106,7 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
|
|||||||
{needsHighlighter ? (
|
{needsHighlighter ? (
|
||||||
<Highlighter
|
<Highlighter
|
||||||
textToHighlight={entry}
|
textToHighlight={entry}
|
||||||
searchWords={highlights}
|
searchWords={highlights ?? []}
|
||||||
findChunks={findHighlightChunksInText}
|
findChunks={findHighlightChunksInText}
|
||||||
highlightClassName={highlightClassName}
|
highlightClassName={highlightClassName}
|
||||||
/>
|
/>
|
||||||
|
@ -90,7 +90,7 @@ export const TypeaheadItem: React.FC<Props> = (props: Props) => {
|
|||||||
highlightParts={item.highlightParts}
|
highlightParts={item.highlightParts}
|
||||||
></PartialHighlighter>
|
></PartialHighlighter>
|
||||||
) : (
|
) : (
|
||||||
<Highlighter textToHighlight={label} searchWords={[prefix]} highlightClassName={highlightClassName} />
|
<Highlighter textToHighlight={label} searchWords={[prefix ?? '']} highlightClassName={highlightClassName} />
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -37,7 +37,7 @@ export class OptionsPaneCategoryDescriptor {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(isSearching?: boolean) {
|
render(searchQuery?: string) {
|
||||||
if (this.props.customRender) {
|
if (this.props.customRender) {
|
||||||
return this.props.customRender();
|
return this.props.customRender();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { Field, Label } from '@grafana/ui';
|
import { Field, Label } from '@grafana/ui';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
|
import Highlighter from 'react-highlight-words';
|
||||||
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
|
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
|
||||||
|
|
||||||
export interface OptionsPaneItemProps {
|
export interface OptionsPaneItemProps {
|
||||||
@ -21,10 +22,10 @@ export class OptionsPaneItemDescriptor {
|
|||||||
|
|
||||||
constructor(public props: OptionsPaneItemProps) {}
|
constructor(public props: OptionsPaneItemProps) {}
|
||||||
|
|
||||||
getLabel(isSearching?: boolean): ReactNode {
|
getLabel(searchQuery?: string): ReactNode {
|
||||||
const { title, description } = this.props;
|
const { title, description } = this.props;
|
||||||
|
|
||||||
if (!isSearching) {
|
if (!searchQuery) {
|
||||||
// Do not render label for categories with only one child
|
// Do not render label for categories with only one child
|
||||||
if (this.parent.props.title === title) {
|
if (this.parent.props.title === title) {
|
||||||
return null;
|
return null;
|
||||||
@ -33,24 +34,30 @@ export class OptionsPaneItemDescriptor {
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
const categories: string[] = [];
|
const categories: React.ReactNode[] = [];
|
||||||
|
|
||||||
if (this.parent.parent) {
|
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) {
|
if (this.parent.props.title !== title) {
|
||||||
categories.push(this.parent.props.title);
|
categories.push(this.highlightWord(this.parent.props.title, searchQuery));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label description={description} category={categories}>
|
<Label description={description && this.highlightWord(description, searchQuery)} category={categories}>
|
||||||
{title}
|
{this.highlightWord(title, searchQuery)}
|
||||||
</Label>
|
</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 { title, description, render, showIf, skipField } = this.props;
|
||||||
const key = `${this.parent.props.id} ${title}`;
|
const key = `${this.parent.props.id} ${title}`;
|
||||||
|
|
||||||
@ -64,7 +71,7 @@ export class OptionsPaneItemDescriptor {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
label={this.getLabel(isSearching)}
|
label={this.getLabel(searchQuery)}
|
||||||
description={description}
|
description={description}
|
||||||
key={key}
|
key={key}
|
||||||
aria-label={selectors.components.PanelEditor.OptionsPane.fieldLabel(key)}
|
aria-label={selectors.components.PanelEditor.OptionsPane.fieldLabel(key)}
|
||||||
|
@ -139,9 +139,9 @@ function renderSearchHits(
|
|||||||
key="Normal options"
|
key="Normal options"
|
||||||
forceOpen={1}
|
forceOpen={1}
|
||||||
>
|
>
|
||||||
{optionHits.map((hit) => hit.render(true))}
|
{optionHits.map((hit) => hit.render(searchQuery))}
|
||||||
</OptionsPaneCategory>
|
</OptionsPaneCategory>
|
||||||
{overrideHits.map((override) => override.render(true))}
|
{overrideHits.map((override) => override.render(searchQuery))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -152,6 +152,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
|
||||||
|
.search-fragment-highlight {
|
||||||
|
color: ${theme.colors.warning.text};
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
searchBox: css`
|
searchBox: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
16
yarn.lock
16
yarn.lock
@ -5894,6 +5894,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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@*":
|
"@types/react-icon-base@*":
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-icon-base/-/react-icon-base-2.1.3.tgz#0faf840b854a9dbc3fa6fe1935c7c40eb4153114"
|
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"
|
memoize-one "^4.0.0"
|
||||||
prop-types "^15.5.8"
|
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:
|
react-hook-form@7.2.3:
|
||||||
version "7.2.3"
|
version "7.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.2.3.tgz#a4be9214cab3a6e6358f95d342da2e7ded37e3f0"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.2.3.tgz#a4be9214cab3a6e6358f95d342da2e7ded37e3f0"
|
||||||
|
Loading…
Reference in New Issue
Block a user