From 90a003c13e2baad703e5ea68ec2c92ca5ff1e8ee Mon Sep 17 00:00:00 2001
From: Greg Thompson
Date: Fri, 11 Feb 2022 09:52:14 -0600
Subject: [PATCH] [EuiFilterGroup][FieldValueSelectionFilter] Use
`EuiSelectable` (#5387)
* use euiselectable
* update docs text
* use message props
* isVirtualized, labelProps support
* refactor search filters to use euiselectable
* CL
* clean up
* add docs for new configuration options
* CL fix
* review feedback
* fragment
* docs
* convert tests to cypress
* update filter count based on exclusions
* add recommendation for sizing
* use errorMessage
* fix cypress tests
* docs
* update docs
---
CHANGELOG.md | 1 +
.../filter_group/filter_group_example.js | 16 +-
.../views/filter_group/filter_group_multi.js | 169 +++----
.../views/selectable/selectable_example.js | 15 +-
...field_value_selection_filter.test.tsx.snap | 431 ------------------
... => field_value_selection_filter.spec.tsx} | 157 ++++---
.../filters/field_value_selection_filter.tsx | 268 ++++-------
.../__snapshots__/selectable.test.tsx.snap | 4 +
src/components/selectable/selectable.tsx | 2 +
9 files changed, 280 insertions(+), 783 deletions(-)
delete mode 100644 src/components/search_bar/filters/__snapshots__/field_value_selection_filter.test.tsx.snap
rename src/components/search_bar/filters/{field_value_selection_filter.test.tsx => field_value_selection_filter.spec.tsx} (62%)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b64808729b..41d8a74a92e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## [`main`](https://github.com/elastic/eui/tree/main)
- Improved `EuiSelectable` keypress scenarios ([#5613](https://github.com/elastic/eui/pull/5613))
+- Converted `FieldValueSelectionFilter` in `EuiSearchBar` to use `EuiSelectable` ([#5387](https://github.com/elastic/eui/issues/5387))
## [`48.0.0`](https://github.com/elastic/eui/tree/v48.0.0)
diff --git a/src-docs/src/views/filter_group/filter_group_example.js b/src-docs/src/views/filter_group/filter_group_example.js
index 292fae7d095..5e0c2365993 100644
--- a/src-docs/src/views/filter_group/filter_group_example.js
+++ b/src-docs/src/views/filter_group/filter_group_example.js
@@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
+import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../components';
@@ -74,11 +75,16 @@ export const FilterGroupExample = {
text: (
- To provide a long list of grouped filter, use a popover for
- filtering an array of passed items. This mostly uses standard
- popover mechanics, but the component{' '}
- EuiFilterSelectItem is used for the items
- themselves.
+ To provide a long list of grouped filters, we recommend wrapping the
+ filter button within an{' '}
+
+ EuiPopover
+ {' '}
+ and passing the items to a searchable{' '}
+
+ EuiSelectable
+
+ .
Indicating number of filters
diff --git a/src-docs/src/views/filter_group/filter_group_multi.js b/src-docs/src/views/filter_group/filter_group_multi.js
index 509e663ca0a..5ea1b757c4b 100644
--- a/src-docs/src/views/filter_group/filter_group_multi.js
+++ b/src-docs/src/views/filter_group/filter_group_multi.js
@@ -1,22 +1,33 @@
-import React, { useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import {
EuiPopover,
EuiPopoverTitle,
- EuiFieldSearch,
- EuiFilterSelectItem,
- EuiLoadingChart,
- EuiSpacer,
- EuiIcon,
EuiFilterGroup,
EuiFilterButton,
+ EuiSelectable,
+ EuiSpacer,
+ EuiSwitch,
} from '../../../../src/components';
import { useGeneratedHtmlId } from '../../../../src/services';
export default () => {
+ const timeoutRef = useRef();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const [withLoading, setWithLoading] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ useEffect(() => {
+ return () => clearTimeout(timeoutRef.current);
+ }, []);
const onButtonClick = () => {
+ if (withLoading && !isPopoverOpen) {
+ setIsLoading(true);
+ timeoutRef.current = setTimeout(() => {
+ setIsLoading(false);
+ }, 1500);
+ }
setIsPopoverOpen(!isPopoverOpen);
};
@@ -29,56 +40,32 @@ export default () => {
});
const [items, setItems] = useState([
- { name: 'Johann Sebastian Bach', checked: 'on' },
- { name: 'Wolfgang Amadeus Mozart', checked: 'on' },
- { name: 'Antonín Dvořák', checked: 'off' },
- { name: 'Dmitri Shostakovich' },
- { name: 'Felix Mendelssohn-Bartholdy' },
- { name: 'Franz Liszt' },
- { name: 'Franz Schubert' },
- { name: 'Frédéric Chopin' },
- { name: 'Georg Friedrich Händel' },
- { name: 'Giuseppe Verdi' },
- { name: 'Gustav Mahler' },
- { name: 'Igor Stravinsky' },
- { name: 'Johannes Brahms' },
- { name: 'Joseph Haydn' },
- { name: 'Ludwig van Beethoven' },
- { name: 'Piotr Illitch Tchaïkovsky' },
- { name: 'Robert Schumann' },
- { name: 'Sergej S. Prokofiew' },
- { name: 'Wolfgang Amadeus Mozart' },
+ { label: 'Johann Sebastian Bach', checked: 'on' },
+ { label: 'Wolfgang Amadeus Mozart', checked: 'on' },
+ { label: 'Antonín Dvořák', checked: 'off' },
+ { label: 'Dmitri Shostakovich' },
+ { label: 'Felix Mendelssohn-Bartholdy' },
+ { label: 'Franz Liszt' },
+ { label: 'Franz Schubert' },
+ { label: 'Frédéric Chopin' },
+ { label: 'Georg Friedrich Händel' },
+ { label: 'Giuseppe Verdi' },
+ { label: 'Gustav Mahler' },
+ { label: 'Igor Stravinsky' },
+ { label: 'Johannes Brahms' },
+ { label: 'Joseph Haydn' },
+ { label: 'Ludwig van Beethoven' },
+ { label: 'Piotr Illitch Tchaïkovsky' },
+ { label: 'Robert Schumann' },
+ { label: 'Sergej S. Prokofiew' },
]);
- function updateItem(index) {
- if (!items[index]) {
- return;
- }
-
- const newItems = [...items];
-
- switch (newItems[index].checked) {
- case 'on':
- newItems[index].checked = 'off';
- break;
-
- case 'off':
- newItems[index].checked = undefined;
- break;
-
- default:
- newItems[index].checked = 'on';
- }
-
- setItems(newItems);
- }
-
const button = (
item.checked !== 'off').length}
hasActiveFilters={!!items.find((item) => item.checked === 'on')}
numActiveFilters={items.filter((item) => item.checked === 'on').length}
>
@@ -87,49 +74,45 @@ export default () => {
);
return (
-
-
-
-
-
-
- {items.map((item, index) => (
-
updateItem(index)}
- >
- {item.name}
-
- ))}
- {/*
- Use when loading items initially
- */}
-
- {/*
- Use when no results are returned
- */}
-
-
-
-
+ <>
+ setWithLoading(e.target.checked)}
+ label="Simulate dynamic loading"
+ />
+
+
+
+ setItems(newOptions)}
+ isLoading={isLoading}
+ loadingMessage="Loading filters"
+ emptyMessage="No filters available"
+ noMatchesMessage="No filters found"
+ >
+ {(list, search) => (
+
+ {search}
+ {list}
+
+ )}
+
+
+
+ >
);
};
diff --git a/src-docs/src/views/selectable/selectable_example.js b/src-docs/src/views/selectable/selectable_example.js
index b1301294ed1..c23b1a6c000 100644
--- a/src-docs/src/views/selectable/selectable_example.js
+++ b/src-docs/src/views/selectable/selectable_example.js
@@ -225,11 +225,16 @@ export const SelectableExample = {
Width and height
The width has been made to always be 100% of its container,
- including stretching the search input. By default, the height is
- capped at showing up to 7.5 items. It shows half of the last one to
- help indicate that there are more options to scroll to. To stretch
- the box to fill its container, pass 'full' to the{' '}
- height prop.
+ including stretching the search input. When used inside of{' '}
+
+ EuiPopover
+
+ , we recommend setting a width (or min/max values) via CSS on the
+ element containing the list to avoid expansion and contraction. By
+ default, the height is capped at showing up to 7.5 items. It shows
+ half of the last one to help indicate that there are more options to
+ scroll to. To stretch the box to fill its container, pass
+ 'full' to the height prop.
Flexbox
diff --git a/src/components/search_bar/filters/__snapshots__/field_value_selection_filter.test.tsx.snap b/src/components/search_bar/filters/__snapshots__/field_value_selection_filter.test.tsx.snap
deleted file mode 100644
index 843b6ba9990..00000000000
--- a/src/components/search_bar/filters/__snapshots__/field_value_selection_filter.test.tsx.snap
+++ /dev/null
@@ -1,431 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FieldValueSelectionFilter active - field is global 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
-`;
-
-exports[`FieldValueSelectionFilter active - fields in options 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
-`;
-
-exports[`FieldValueSelectionFilter inactive - field is global 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
- feature
-
-
- Text
-
-
-
- bug
-
-
-
-
-`;
-
-exports[`FieldValueSelectionFilter inactive - fields in options 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
- feature
-
-
- Text
-
-
-
- bug
-
-
-
-
-`;
-
-exports[`FieldValueSelectionFilter render - all configurations 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
-`;
-
-exports[`FieldValueSelectionFilter render - fields in options 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
- feature
-
-
- Text
-
-
-
- bug
-
-
-
-
-`;
-
-exports[`FieldValueSelectionFilter render - multi-select OR 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
-`;
-
-exports[`FieldValueSelectionFilter render - options as a function 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
-`;
-
-exports[`FieldValueSelectionFilter render - options as an array 1`] = `
-
- Tag
-
- }
- closePopover={[Function]}
- display="inlineBlock"
- hasArrow={true}
- isOpen={false}
- ownFocus={true}
- panelClassName="euiFilterGroup__popoverPanel"
- panelPaddingSize="none"
->
-
-
- feature
-
-
- Text
-
-
-
- bug
-
-
-
-
-`;
diff --git a/src/components/search_bar/filters/field_value_selection_filter.test.tsx b/src/components/search_bar/filters/field_value_selection_filter.spec.tsx
similarity index 62%
rename from src/components/search_bar/filters/field_value_selection_filter.test.tsx
rename to src/components/search_bar/filters/field_value_selection_filter.spec.tsx
index d279137b3ee..23181d23e45 100644
--- a/src/components/search_bar/filters/field_value_selection_filter.test.tsx
+++ b/src/components/search_bar/filters/field_value_selection_filter.spec.tsx
@@ -8,15 +8,29 @@
import React from 'react';
import { requiredProps } from '../../../test';
-import { shallow } from 'enzyme';
import {
FieldValueSelectionFilter,
FieldValueSelectionFilterProps,
} from './field_value_selection_filter';
import { Query } from '../query';
+const staticOptions = [
+ {
+ value: 'feature',
+ },
+ {
+ value: 'test',
+ name: 'Text',
+ },
+ {
+ value: 'bug',
+ name: 'Bug',
+ view: bug
,
+ },
+];
+
describe('FieldValueSelectionFilter', () => {
- test('render - options as a function', () => {
+ it('allows options as a function', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -26,16 +40,18 @@ describe('FieldValueSelectionFilter', () => {
type: 'field_value_selection',
field: 'tag',
name: 'Tag',
- options: () => Promise.resolve([]),
+ options: () => Promise.resolve(staticOptions),
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .first()
+ .should('have.attr', 'title', 'feature');
});
- test('render - options as an array', () => {
+ it('allows options as an array', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -45,29 +61,18 @@ describe('FieldValueSelectionFilter', () => {
type: 'field_value_selection',
field: 'tag',
name: 'Tag',
- options: [
- {
- value: 'feature',
- },
- {
- value: 'test',
- name: 'Text',
- },
- {
- value: 'bug',
- name: 'Bug',
- view: bug
,
- },
- ],
+ options: staticOptions,
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .eq(1)
+ .should('have.attr', 'title', 'Text');
});
- test('render - fields in options', () => {
+ it('allows fields in options', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -96,12 +101,14 @@ describe('FieldValueSelectionFilter', () => {
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .eq(2)
+ .should('have.attr', 'title', 'Bug');
});
- test('render - all configurations', () => {
+ it('allows all configurations', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -120,12 +127,15 @@ describe('FieldValueSelectionFilter', () => {
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableMessage"]').should(
+ 'have.text',
+ 'oops...'
+ );
});
- test('render - multi-select OR', () => {
+ it('uses multi-select OR', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -144,12 +154,15 @@ describe('FieldValueSelectionFilter', () => {
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableMessage"]').should(
+ 'have.text',
+ 'oops...'
+ );
});
- test('inactive - field is global', () => {
+ it('has inactive filters, field is global', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -159,29 +172,18 @@ describe('FieldValueSelectionFilter', () => {
type: 'field_value_selection',
field: 'tag',
name: 'Tag',
- options: [
- {
- value: 'feature',
- },
- {
- value: 'test',
- name: 'Text',
- },
- {
- value: 'bug',
- name: 'Bug',
- view: bug
,
- },
- ],
+ options: staticOptions,
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .first()
+ .should('have.attr', 'title', 'feature');
});
- test('active - field is global', () => {
+ it('has active filters, field is global', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -191,29 +193,19 @@ describe('FieldValueSelectionFilter', () => {
type: 'field_value_selection',
field: 'tag',
name: 'Tag',
- options: [
- {
- value: 'feature',
- },
- {
- value: 'test',
- name: 'Text',
- },
- {
- value: 'bug',
- name: 'Bug',
- view: bug
,
- },
- ],
+ options: staticOptions,
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('.euiNotificationBadge').should('not.be.undefined');
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .first()
+ .should('have.attr', 'title', 'Bug');
});
- test('inactive - fields in options', () => {
+ it('has inactive filters, fields in options', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -242,12 +234,14 @@ describe('FieldValueSelectionFilter', () => {
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .eq(2)
+ .should('have.attr', 'title', 'Bug');
});
- test('active - fields in options', () => {
+ it('has active filters, fields in options', () => {
const props: FieldValueSelectionFilterProps = {
...requiredProps,
index: 0,
@@ -276,8 +270,11 @@ describe('FieldValueSelectionFilter', () => {
},
};
- const component = shallow( );
-
- expect(component).toMatchSnapshot();
+ cy.mount( );
+ cy.get('button').click();
+ cy.get('.euiNotificationBadge').should('not.be.undefined');
+ cy.get('[data-test-subj="euiSelectableList"] li')
+ .eq(0)
+ .should('have.attr', 'title', 'Bug');
});
});
diff --git a/src/components/search_bar/filters/field_value_selection_filter.tsx b/src/components/search_bar/filters/field_value_selection_filter.tsx
index 2806889ef27..d908c24884f 100644
--- a/src/components/search_bar/filters/field_value_selection_filter.tsx
+++ b/src/components/search_bar/filters/field_value_selection_filter.tsx
@@ -6,15 +6,12 @@
* Side Public License, v 1.
*/
-import React, { Component, ReactElement, ReactNode } from 'react';
+import React, { Component, ReactNode } from 'react';
import { isArray, isNil } from '../../../services/predicate';
-import { keys } from '../../../services';
+import { ExclusiveUnion } from '../../common';
import { EuiPopover, EuiPopoverTitle } from '../../popover';
-import { EuiFieldSearch } from '../../form/field_search';
-import { EuiFilterButton, EuiFilterSelectItem } from '../../filter_group';
-import { EuiLoadingChart } from '../../loading';
-import { EuiSpacer } from '../../spacer';
-import { EuiIcon } from '../../icon';
+import { EuiFilterButton } from '../../filter_group';
+import { EuiSelectable, EuiSelectableProps } from '../../selectable';
import { Query } from '../query';
import { Clause, Operator, OperatorType, Value } from '../query/ast';
@@ -86,9 +83,6 @@ export class FieldValueSelectionFilter extends Component<
FieldValueSelectionFilterProps,
State
> {
- private readonly selectItems: EuiFilterSelectItem[];
- private searchInput: HTMLInputElement | null = null;
-
constructor(props: FieldValueSelectionFilterProps) {
super(props);
const { options } = props.config;
@@ -99,8 +93,6 @@ export class FieldValueSelectionFilter extends Component<
shown: options,
}
: null;
-
- this.selectItems = [];
this.state = {
popoverOpen: false,
error: null,
@@ -290,34 +282,6 @@ export class FieldValueSelectionFilter extends Component<
}
}
- onKeyDown(
- index: number,
- event:
- | React.KeyboardEvent
- | React.KeyboardEvent
- ) {
- switch (event.key) {
- case keys.ARROW_DOWN:
- if (index < this.selectItems.length - 1) {
- event.preventDefault();
- this.selectItems[index + 1].focus();
- }
- break;
-
- case keys.ARROW_UP:
- if (index < 0) {
- return; // it's coming from the search box... nothing to do... nowhere to go
- }
- if (index === 0 && this.searchInput) {
- event.preventDefault();
- this.searchInput.focus();
- } else if (index > 0) {
- event.preventDefault();
- this.selectItems[index - 1].focus();
- }
- }
- }
-
resolveMultiSelect(): MultiSelect {
const { config } = this.props;
return !isNil(config.multiSelect)
@@ -358,13 +322,59 @@ export class FieldValueSelectionFilter extends Component<
);
- const searchBox = this.renderSearchBox();
- const content = this.renderContent(
- config.field,
- query,
- config,
- multiSelect
- );
+ const items = this.state.options
+ ? this.state.options.shown.map((option) => {
+ const optionField = option.field || config.field;
+
+ if (optionField == null) {
+ throw new Error(
+ 'option.field or field should be provided in '
+ );
+ }
+
+ const clause =
+ multiSelect === 'or'
+ ? query.getOrFieldClause(optionField, option.value)
+ : query.getSimpleFieldClause(optionField, option.value);
+
+ const label = this.resolveOptionName(option);
+
+ const checked = this.resolveChecked(clause);
+ return {
+ label,
+ checked,
+ data: {
+ view: option.view ?? label,
+ value: option.value,
+ optionField,
+ },
+ };
+ })
+ : [];
+
+ const threshold = config.searchThreshold || defaults.config.searchThreshold;
+ const isOverSearchThreshold =
+ this.state.options && this.state.options.all.length >= threshold;
+
+ let searchProps: ExclusiveUnion<
+ { searchable: false },
+ {
+ searchable: true;
+ searchProps: EuiSelectableProps['searchProps'];
+ }
+ > = {
+ searchable: false,
+ };
+
+ if (isOverSearchThreshold) {
+ searchProps = {
+ searchable: true,
+ searchProps: {
+ compressed: true,
+ disabled: this.state.error != null,
+ },
+ };
+ }
return (
- {searchBox}
- {content}
+ >
+ singleSelection={!multiSelect}
+ aria-label={config.name}
+ options={items}
+ renderOption={(option) => option.view}
+ isLoading={isNil(this.state.options)}
+ loadingMessage={
+ config.loadingMessage || defaults.config.loadingMessage
+ }
+ emptyMessage={
+ config.noOptionsMessage || defaults.config.noOptionsMessage
+ }
+ errorMessage={this.state.error}
+ noMatchesMessage={
+ config.noOptionsMessage || defaults.config.noOptionsMessage
+ }
+ listProps={{
+ isVirtualized: isOverSearchThreshold || false,
+ }}
+ onChange={(options) => {
+ const diff = items.find(
+ (item, index) => item.checked !== options[index].checked
+ );
+ if (diff) {
+ this.onOptionClick(
+ diff.data.optionField,
+ diff.data.value,
+ diff.checked
+ );
+ }
+ }}
+ {...searchProps}
+ >
+ {(list, search) => (
+ <>
+ {isOverSearchThreshold && (
+ {search}
+ )}
+ {list}
+ >
+ )}
+
);
}
- renderSearchBox() {
- const threshold =
- this.props.config.searchThreshold || defaults.config.searchThreshold;
- if (this.state.options && this.state.options.all.length >= threshold) {
- const disabled = this.state.error != null;
- return (
-
- (this.searchInput = ref)}
- disabled={disabled}
- incremental={true}
- onSearch={(query) => this.filterOptions(query)}
- onKeyDown={this.onKeyDown.bind(this, -1)}
- compressed
- />
-
- );
- }
- }
-
- renderContent(
- field: string | undefined,
- query: Query,
- config: FieldValueSelectionFilterConfigType,
- multiSelect: MultiSelect
- ) {
- if (this.state.error) {
- return this.renderError(this.state.error);
- }
- if (isNil(this.state.options)) {
- return this.renderLoader();
- }
- if (this.state.options.shown.length === 0) {
- return this.renderNoOptions();
- }
-
- if (this.state.options == null) {
- return;
- }
-
- const items: ReactElement[] = [];
-
- this.state.options.shown.forEach((option, index) => {
- const optionField = option.field || field;
-
- if (optionField == null) {
- throw new Error(
- 'option.field or field should be provided in '
- );
- }
-
- const clause =
- multiSelect === 'or'
- ? query.getOrFieldClause(optionField, option.value)
- : query.getSimpleFieldClause(optionField, option.value);
-
- const checked = this.resolveChecked(clause);
- const onClick = () => {
- // clicking a checked item will uncheck it and effective remove the filter (value = undefined)
- this.onOptionClick(optionField, option.value, checked);
- };
-
- const item = (
- (this.selectItems[index] = ref!)}
- onKeyDown={this.onKeyDown.bind(this, index)}
- >
- {option.view ? option.view : this.resolveOptionName(option)}
-
- );
-
- items.push(item);
- });
-
- return {items}
;
- }
-
resolveChecked(clause: Clause | undefined): 'on' | 'off' | undefined {
if (clause) {
return Query.isMust(clause) ? 'on' : 'off';
}
}
- renderLoader() {
- const message =
- this.props.config.loadingMessage || defaults.config.loadingMessage;
- return (
-
- );
- }
-
- renderError(message: string) {
- return (
-
- );
- }
-
- renderNoOptions() {
- const message =
- this.props.config.noOptionsMessage || defaults.config.noOptionsMessage;
- return (
-
- );
- }
-
isActiveField(field: string | undefined): boolean {
if (typeof field !== 'string') {
return false;
diff --git a/src/components/selectable/__snapshots__/selectable.test.tsx.snap b/src/components/selectable/__snapshots__/selectable.test.tsx.snap
index 7c1b1c129da..a95980e42fc 100644
--- a/src/components/selectable/__snapshots__/selectable.test.tsx.snap
+++ b/src/components/selectable/__snapshots__/selectable.test.tsx.snap
@@ -12,6 +12,7 @@ exports[`EuiSelectable custom options with data 1`] = `