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

[EuiHighlight] Support passing an array of search strings + performance improvements #7496

Merged
merged 11 commits into from
Feb 5, 2024
5 changes: 5 additions & 0 deletions changelogs/upcoming/7496.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Updated `EuiHighlight` to accept an array of `search` strings, which allows highlighting multiple, separate words within its children. This new type and behavior *only* works if `highlightAll` is also set to true.

**Bug fixes**

- Fixed `EuiHighlight` to not parse `search` strings as regexes
33 changes: 20 additions & 13 deletions scripts/eslint-plugin/forward_ref_display_name.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Enforce display name to forwardRef components',
description:
'Enforce display name on components wrapped in forwardRef & memo',
},
},
create: function(context) {
const forwardRefUsages = [];
create: function (context) {
const usagesToCheck = [];
const displayNameUsages = [];
return {
VariableDeclarator(node) {
Expand All @@ -16,14 +17,20 @@ module.exports = {
node.init.callee.type === 'MemberExpression'
) {
if (
node.init.callee.property &&
node.init.callee.property.name === 'forwardRef'
node.init.callee.property?.name === 'forwardRef' ||
node.init.callee.property?.name === 'memo'
) {
forwardRefUsages.push(node.id);
usagesToCheck.push({
id: node.id,
type: node.init.callee.property.name,
});
}
}
if (node.init.callee && node.init.callee.name === 'forwardRef') {
forwardRefUsages.push(node.id);
if (
node.init.callee?.name === 'forwardRef' ||
node.init.callee?.name === 'memo'
) {
usagesToCheck.push({ id: node.id, type: node.init.callee.name });
}
}
},
Expand All @@ -38,19 +45,19 @@ module.exports = {
}
},
'Program:exit'() {
forwardRefUsages.forEach(identifier => {
if (!isDisplayNameUsed(identifier)) {
usagesToCheck.forEach(({ id, type }) => {
if (!isDisplayNameUsed(id)) {
context.report({
node: identifier,
message: 'Forward ref components must use a display name',
node: id,
message: `Components wrapped in React.${type} must set a manual displayName`,
});
}
});
},
};
function isDisplayNameUsed(identifier) {
const node = displayNameUsages.find(
displayName => displayName.name === identifier.name
(displayName) => displayName.name === identifier.name
);
return !!node;
}
Expand Down
15 changes: 14 additions & 1 deletion scripts/eslint-plugin/forward_ref_display_name.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const ruleTester = new RuleTester({
const valid = [
`const Component = React.forwardRef<ref>(() => {})
Component.displayName = "EuiBadgeGroup"
`,
`const Component = React.memo(() => {})
Component.displayName = "EuiHighlight"
`,
];

Expand All @@ -16,7 +19,17 @@ const invalid = [
code: 'const Component = React.forwardRef<ref>(() => {})',
errors: [
{
message: 'Forward ref components must use a display name',
message:
'Components wrapped in React.forwardRef must set a manual displayName',
},
],
},
{
code: 'const Component = React.memo(() => {})',
errors: [
{
message:
'Components wrapped in React.memo must set a manual displayName',
},
],
},
Expand Down
45 changes: 0 additions & 45 deletions src-docs/src/views/highlight_and_mark/highlight.js

This file was deleted.

64 changes: 64 additions & 0 deletions src-docs/src/views/highlight_and_mark/highlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState, useMemo } from 'react';

import {
EuiHighlight,
EuiFieldSearch,
EuiFormRow,
EuiSpacer,
EuiSwitch,
EuiFlexGroup,
} from '../../../../src/components';

export default () => {
const [searchInput, setSearchInput] = useState('jumped over');
const [isHighlightAll, setHighlightAll] = useState(false);
const [searchMultiple, setSearchMultiple] = useState(false);
const [caseSensitive, setCaseSensitive] = useState(false);

const searchValues = useMemo(() => {
return searchMultiple && isHighlightAll
? searchInput.split(' ')
: searchInput;
}, [searchMultiple, searchInput, isHighlightAll]);

return (
<>
<EuiFlexGroup>
<EuiSwitch
label="Case sensitive"
checked={caseSensitive}
onChange={(e) => setCaseSensitive(e.target.checked)}
/>
<EuiSwitch
label="Highlight all"
checked={isHighlightAll}
onChange={(e) => setHighlightAll(e.target.checked)}
/>
{isHighlightAll && (
<EuiSwitch
label="Search for an array of separate words"
checked={searchMultiple}
onChange={(e) => setSearchMultiple(e.target.checked)}
/>
)}
</EuiFlexGroup>
<EuiSpacer size="xl" />

<EuiFormRow label="Enter text to highlight substrings within a string">
<EuiFieldSearch
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
</EuiFormRow>
<EuiSpacer size="m" />

<EuiHighlight
strict={caseSensitive}
highlightAll={isHighlightAll}
search={searchValues}
>
The quick brown fox jumped over the lazy dog
</EuiHighlight>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { Fragment } from 'react';
import React from 'react';

import { EuiMark } from '../../../../src/components';

export default () => {
return (
<Fragment>
<>
The quick brown fox <EuiMark>jumped over</EuiMark> the lazy dog
</Fragment>
</>
);
};
6 changes: 5 additions & 1 deletion src-docs/src/views/highlight_and_mark/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export const highlightConfig = () => {
value: 'The quick brown fox jumped over the lazy dog',
};

propsToUse.search.value = 'quick';
propsToUse.search = {
...propsToUse.search,
type: PropTypes.String,
value: 'quick',
};

return {
config: {
Expand Down
1 change: 1 addition & 0 deletions src/components/datagrid/body/cell/data_grid_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ const EuiDataGridCellContent: FunctionComponent<
);
}
);
EuiDataGridCellContent.displayName = 'EuiDataGridCellContent';

export class EuiDataGridCell extends Component<
EuiDataGridCellProps,
Expand Down
80 changes: 6 additions & 74 deletions src/components/highlight/__snapshots__/highlight.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,84 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiHighlight behavior loose matching matches strings with different casing 1`] = `
<span>
different
<mark
class="euiMark emotion-euiMark-hasScreenReaderHelpText"
>
case
</mark>
match
</span>
`;

exports[`EuiHighlight behavior matching applies to all matches 1`] = `
<span>
<mark
class="euiMark emotion-euiMark-hasScreenReaderHelpText"
>
match
</mark>

<mark
class="euiMark emotion-euiMark-hasScreenReaderHelpText"
>
match
</mark>

<mark
class="euiMark emotion-euiMark-hasScreenReaderHelpText"
>
match
</mark>
</span>
`;

exports[`EuiHighlight behavior matching hasScreenReaderHelpText can be false 1`] = `
<span>
<mark
class="euiMark emotion-euiMark"
>
match
</mark>

<mark
class="euiMark emotion-euiMark"
>
match
</mark>

<mark
class="euiMark emotion-euiMark"
>
match
</mark>
</span>
`;

exports[`EuiHighlight behavior matching only applies to first match 1`] = `
<span>
<mark
class="euiMark emotion-euiMark-hasScreenReaderHelpText"
>
match
</mark>
match match
</span>
`;

exports[`EuiHighlight behavior strict matching doesn't match strings with different casing 1`] = `
<span>
different case match
</span>
`;

exports[`EuiHighlight is rendered 1`] = `
<span
aria-label="aria-label"
class="testClass1 testClass2 emotion-euiTestCss"
data-test-subj="test subject string"
>
value
<mark
class="euiMark emotion-euiMark-hasScreenReaderHelpText"
>
va
</mark>
lue
</span>
`;
Loading
Loading