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-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",
|
||||
|
@ -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 => {
|
||||
|
@ -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) => {
|
||||
|
@ -106,7 +106,7 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
|
||||
{needsHighlighter ? (
|
||||
<Highlighter
|
||||
textToHighlight={entry}
|
||||
searchWords={highlights}
|
||||
searchWords={highlights ?? []}
|
||||
findChunks={findHighlightChunksInText}
|
||||
highlightClassName={highlightClassName}
|
||||
/>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -37,7 +37,7 @@ export class OptionsPaneCategoryDescriptor {
|
||||
return this;
|
||||
}
|
||||
|
||||
render(isSearching?: boolean) {
|
||||
render(searchQuery?: string) {
|
||||
if (this.props.customRender) {
|
||||
return this.props.customRender();
|
||||
}
|
||||
|
@ -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)}
|
||||
|
@ -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;
|
||||
|
16
yarn.lock
16
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user