Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added props to highlight all matches in EuiHighlight #2957

Merged
merged 12 commits into from
Mar 6, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)

- Added `highlightAll` prop to `EuiHighlight` to highlight all matches ([#2957](https://github.com/elastic/eui/pull/2957))
- Added `showOnFocus` prop to `EuiScreenReaderOnly` to force display on keyboard focus ([#2976](https://github.com/elastic/eui/pull/2976))
- Added `EuiSkipLink` component ([#2976](https://github.com/elastic/eui/pull/2976))
- Created `EuiBadgeGroup` component ([#2921](https://github.com/elastic/eui/pull/2921))
Expand Down
17 changes: 14 additions & 3 deletions src-docs/src/views/highlight/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EuiHighlight,
EuiFieldSearch,
EuiSpacer,
EuiSwitch,
} from '../../../../src/components';

export class Highlight extends Component {
Expand All @@ -12,6 +13,7 @@ export class Highlight extends Component {

this.state = {
searchValue: 'jumped over',
isHighlightAll: false,
};
}

Expand All @@ -22,15 +24,24 @@ export class Highlight extends Component {
});
};

setHighlightAll = e => {
this.setState({ isHighlightAll: e.target.checked });
};

render() {
const { searchValue } = this.state;
const { searchValue, isHighlightAll } = this.state;
return (
<Fragment>
<EuiFieldSearch value={searchValue} onChange={this.onSearchChange} />

<EuiSpacer size="m" />

<EuiHighlight search={searchValue}>
<EuiSwitch
label="Highlight all"
checked={isHighlightAll}
onChange={e => this.setHighlightAll(e)}
/>
<EuiSpacer size="m" />
<EuiHighlight search={searchValue} highlightAll={isHighlightAll}>
The quick brown fox jumped over the lazy dog
</EuiHighlight>
</Fragment>
Expand Down
1 change: 1 addition & 0 deletions src-docs/src/views/highlight/highlight_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const HighlightExample = {
string, typically in response to user input.
</p>
),
props: { EuiHighlight },
components: { EuiHighlight },
demo: <Highlight />,
},
Expand Down
89 changes: 86 additions & 3 deletions src/components/highlight/highlight.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import React, { Fragment, HTMLAttributes, FunctionComponent } from 'react';
import { CommonProps } from '../common';

interface EuiHighlightChunk {
/**
* Start of the chunk
*/
start: number;
/**
* End of the chunk
*/
end: number;
/**
* Whether to highlight chunk or not
*/
highlight?: boolean;
}

export type EuiHighlightProps = HTMLAttributes<HTMLSpanElement> &
CommonProps & {
children: string;
Expand All @@ -14,12 +29,18 @@ export type EuiHighlightProps = HTMLAttributes<HTMLSpanElement> &
* Should the search be strict or not
*/
strict?: boolean;

/**
* Should highlight all matches
*/
highlightAll?: boolean;
};

const highlight = (
searchSubject: string,
searchValue: string,
isStrict: boolean = false
isStrict: boolean,
highlightAll: boolean
) => {
if (!searchValue) {
return searchSubject;
Expand All @@ -29,6 +50,22 @@ const highlight = (
return null;
}

if (highlightAll) {
const chunks = getHightlightWords(searchSubject, searchValue, isStrict);
return (
<Fragment>
{chunks.map(chunk => {
const { end, highlight, start } = chunk;
const value = searchSubject.substr(start, end - start);
if (highlight) {
return <strong key={start}>{value}</strong>;
}
return value;
})}
</Fragment>
);
}

const normalizedSearchSubject: string = isStrict
? searchSubject
: searchSubject.toLowerCase();
Expand Down Expand Up @@ -58,16 +95,62 @@ const highlight = (
);
};

const getHightlightWords = (
searchSubject: string,
searchValue: string,
isStrict: boolean
) => {
const regex = new RegExp(searchValue, isStrict ? 'g' : 'gi');
const matches = [];
let match;
while ((match = regex.exec(searchSubject)) !== null) {
matches.push({
start: match.index,
end: (match.index || 0) + match[0].length,
});
}
return fillInChunks(matches, searchSubject.length);
};

const fillInChunks = (
chunksToHighlight: EuiHighlightChunk[],
totalLength: number
) => {
const allChunks: EuiHighlightChunk[] = [];
const append = (start: number, end: number, highlight: boolean) => {
if (end - start > 0) {
allChunks.push({
start,
end,
highlight,
});
}
};
if (chunksToHighlight.length === 0) {
append(0, totalLength, false);
} else {
let lastIndex = 0;
chunksToHighlight.forEach(chunk => {
append(lastIndex, chunk.start, false);
append(chunk.start, chunk.end, true);
lastIndex = chunk.end;
});
append(lastIndex, totalLength, false);
}
return allChunks;
};

export const EuiHighlight: FunctionComponent<EuiHighlightProps> = ({
children,
className,
search,
strict,
strict = false,
highlightAll = false,
...rest
}) => {
return (
<span className={className} {...rest}>
{highlight(children, search, strict)}
{highlight(children, search, strict, highlightAll)}
</span>
);
};