From 5bcab2d0b85775d8746e1511643bd815ec4a6c00 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal Date: Mon, 16 Mar 2020 14:55:00 +0530 Subject: [PATCH 01/10] added startingWith prop to combobox --- CHANGELOG.md | 1 + .../src/views/combo_box/combo_box_example.js | 26 +++++ src-docs/src/views/combo_box/startingWith.js | 97 +++++++++++++++++++ src/components/combo_box/combo_box.tsx | 31 +++++- 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src-docs/src/views/combo_box/startingWith.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fadf630d538..8bf04166168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added `startingWith` prop for `EuiComboBox` ([#3007](https://github.com/elastic/eui/pull/3007)) - Added props descriptions for `EuiComboBox` ([#3007](https://github.com/elastic/eui/pull/3007)) - Exported `dateFormatAliases` as a part of the public API ([#3043](https://github.com/elastic/eui/pull/3043)) - Exported `EuiTextProps` type definition ([#3039](https://github.com/elastic/eui/pull/3039)) diff --git a/src-docs/src/views/combo_box/combo_box_example.js b/src-docs/src/views/combo_box/combo_box_example.js index a3479d6ff26..ed84e061db5 100644 --- a/src-docs/src/views/combo_box/combo_box_example.js +++ b/src-docs/src/views/combo_box/combo_box_example.js @@ -59,6 +59,10 @@ import Disabled from './disabled'; const disabledSource = require('!!raw-loader!./disabled'); const disabledHtml = renderToHtml(Disabled); +import StartingWith from './startingWith'; +const startingWithSource = require('!!raw-loader!./startingWith'); +const startingWithHtml = renderToHtml(StartingWith); + export const ComboBoxExample = { title: 'Combo Box', intro: ( @@ -347,5 +351,27 @@ export const ComboBoxExample = { props: { EuiComboBox }, demo: , }, + { + title: 'Starting With', + source: [ + { + type: GuideSectionTypes.JS, + code: startingWithSource, + }, + { + type: GuideSectionTypes.HTML, + code: startingWithHtml, + }, + ], + text: ( +

+ Use the + startingWith prop to let the options that start + with the query be displayed on the top of the list. +

+ ), + props: { EuiComboBox }, + demo: , + }, ], }; diff --git a/src-docs/src/views/combo_box/startingWith.js b/src-docs/src/views/combo_box/startingWith.js new file mode 100644 index 00000000000..2d288a87432 --- /dev/null +++ b/src-docs/src/views/combo_box/startingWith.js @@ -0,0 +1,97 @@ +import React, { Component } from 'react'; + +import { EuiComboBox } from '../../../../src/components'; + +export default class extends Component { + constructor(props) { + super(props); + + this.options = [ + { + label: 'Titan', + 'data-test-subj': 'titanOption', + }, + { + label: 'Enceladus is disabled', + disabled: true, + }, + { + label: 'Mimas', + }, + { + label: 'Dione', + }, + { + label: 'Iapetus', + }, + { + label: 'Phoebe', + }, + { + label: 'Rhea', + }, + { + label: + "Pandora is one of Saturn's moons, named for a Titaness of Greek mythology", + }, + { + label: 'Tethys', + }, + { + label: 'Hyperion', + }, + ]; + + this.state = { + selectedOptions: [this.options[2], this.options[4]], + }; + } + + onChange = selectedOptions => { + this.setState({ + selectedOptions, + }); + }; + + onCreateOption = (searchValue, flattenedOptions) => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + + if (!normalizedSearchValue) { + return; + } + + const newOption = { + label: searchValue, + }; + + // Create the option if it doesn't exist. + if ( + flattenedOptions.findIndex( + option => option.label.trim().toLowerCase() === normalizedSearchValue + ) === -1 + ) { + this.options.push(newOption); + } + + // Select the option. + this.setState(prevState => ({ + selectedOptions: prevState.selectedOptions.concat(newOption), + })); + }; + + render() { + const { selectedOptions } = this.state; + return ( + + ); + } +} diff --git a/src/components/combo_box/combo_box.tsx b/src/components/combo_box/combo_box.tsx index 740fff697f0..cfab17695dc 100644 --- a/src/components/combo_box/combo_box.tsx +++ b/src/components/combo_box/combo_box.tsx @@ -110,6 +110,10 @@ interface _EuiComboBoxProps * When `true` only allows the user to select a single option. Set to `{ asPlainText: true }` to not render input selection as pills */ singleSelection: boolean | EuiComboBoxSingleSelectionShape; + /** + * Display matching options that start with search value on top of the list + */ + startingWith?: boolean; /** * Creates an input group with element(s) coming before input. It won't show if `singleSelection` is set to `false`. * `string` | `ReactElement` or an array of these @@ -170,6 +174,7 @@ export class EuiComboBox extends Component< singleSelection: false, prepend: null, append: null, + startingWith: false, }; state: EuiComboBoxState = { @@ -779,6 +784,7 @@ export class EuiComboBox extends Component< ); } } + this.setState({ matchingOptions: newMatchingOptions, activeOptionIndex: nextActiveOptionIndex, @@ -841,6 +847,7 @@ export class EuiComboBox extends Component< singleSelection, prepend, append, + startingWith, ...rest } = this.props; const { @@ -850,8 +857,30 @@ export class EuiComboBox extends Component< listPosition, searchValue, width, + matchingOptions, } = this.state; + let newMatchingOptions = matchingOptions; + + if (startingWith) { + const refObj: { + startWith: Array>; + others: Array>; + } = { startWith: [], others: [] }; + + newMatchingOptions.forEach(object => { + if ( + object.label + .toLowerCase() + .startsWith(searchValue.trim().toLowerCase()) + ) { + refObj.startWith.push(object); + } else { + refObj.others.push(object); + } + }); + newMatchingOptions = [...refObj.startWith, ...refObj.others]; + } // Visually indicate the combobox is in an invalid state if it has lost focus but there is text entered in the input. // When custom options are disabled and the user leaves the combo box after entering text that does not match any // options, this tells the user that they've entered invalid input. @@ -887,7 +916,7 @@ export class EuiComboBox extends Component< fullWidth={fullWidth} isLoading={isLoading} listRef={this.listRefCallback} - matchingOptions={this.state.matchingOptions} + matchingOptions={newMatchingOptions} onCloseList={this.closeList} onCreateOption={onCreateOption} onOptionClick={this.onOptionClick} From d694a0140518b415d14718a8e45c66d19973e8dd Mon Sep 17 00:00:00 2001 From: Anish Aggarwal <43617894+anishagg17@users.noreply.github.com> Date: Mon, 16 Mar 2020 14:59:43 +0530 Subject: [PATCH 02/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf04166168..8518b69f453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -- Added `startingWith` prop for `EuiComboBox` ([#3007](https://github.com/elastic/eui/pull/3007)) +- Added `startingWith` prop for `EuiComboBox` ([#3089](https://github.com/elastic/eui/pull/3089)) - Added props descriptions for `EuiComboBox` ([#3007](https://github.com/elastic/eui/pull/3007)) - Exported `dateFormatAliases` as a part of the public API ([#3043](https://github.com/elastic/eui/pull/3043)) - Exported `EuiTextProps` type definition ([#3039](https://github.com/elastic/eui/pull/3039)) From 7dd4c4c31f5c4342e77e32d40dbfb18fa1f3c7b3 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal <43617894+anishagg17@users.noreply.github.com> Date: Mon, 16 Mar 2020 20:19:18 +0530 Subject: [PATCH 03/10] Update src/components/combo_box/combo_box.tsx Co-Authored-By: Caroline Horn <549577+cchaos@users.noreply.github.com> --- src/components/combo_box/combo_box.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/combo_box/combo_box.tsx b/src/components/combo_box/combo_box.tsx index cfab17695dc..f502db6c555 100644 --- a/src/components/combo_box/combo_box.tsx +++ b/src/components/combo_box/combo_box.tsx @@ -111,9 +111,11 @@ interface _EuiComboBoxProps */ singleSelection: boolean | EuiComboBoxSingleSelectionShape; /** - * Display matching options that start with search value on top of the list + * Display matching options by: + * `startsWith`: moves items that start with search value to top of the list; + * `none`: don't change the sort order of initial object */ - startingWith?: boolean; + sortMatchesBy?: 'none' | 'startsWith'; /** * Creates an input group with element(s) coming before input. It won't show if `singleSelection` is set to `false`. * `string` | `ReactElement` or an array of these From 2c3b02e086ad0d0fab1c89efe412ef2c9e7ab650 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal Date: Mon, 16 Mar 2020 20:23:46 +0530 Subject: [PATCH 04/10] updated prop --- CHANGELOG.md | 2 +- src-docs/src/views/combo_box/startingWith.js | 2 +- src/components/combo_box/combo_box.tsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8518b69f453..00a8fc5c274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -- Added `startingWith` prop for `EuiComboBox` ([#3089](https://github.com/elastic/eui/pull/3089)) +- Added `sortMatchesBy` prop for `EuiComboBox` ([#3089](https://github.com/elastic/eui/pull/3089)) - Added props descriptions for `EuiComboBox` ([#3007](https://github.com/elastic/eui/pull/3007)) - Exported `dateFormatAliases` as a part of the public API ([#3043](https://github.com/elastic/eui/pull/3043)) - Exported `EuiTextProps` type definition ([#3039](https://github.com/elastic/eui/pull/3039)) diff --git a/src-docs/src/views/combo_box/startingWith.js b/src-docs/src/views/combo_box/startingWith.js index 2d288a87432..95de94a4738 100644 --- a/src-docs/src/views/combo_box/startingWith.js +++ b/src-docs/src/views/combo_box/startingWith.js @@ -83,7 +83,7 @@ export default class extends Component { const { selectedOptions } = this.state; return ( extends Component< singleSelection: false, prepend: null, append: null, - startingWith: false, + sortMatchesBy: 'none', }; state: EuiComboBoxState = { @@ -849,7 +849,7 @@ export class EuiComboBox extends Component< singleSelection, prepend, append, - startingWith, + sortMatchesBy, ...rest } = this.props; const { @@ -864,7 +864,7 @@ export class EuiComboBox extends Component< let newMatchingOptions = matchingOptions; - if (startingWith) { + if (sortMatchesBy === 'startsWith') { const refObj: { startWith: Array>; others: Array>; From 6c8e6abdf901335eaa943cd3f6f9adfe714f2529 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal Date: Wed, 18 Mar 2020 14:24:30 +0530 Subject: [PATCH 05/10] added test --- src/components/combo_box/combo_box.test.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index dadb0e77c85..8211fba205a 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -8,6 +8,7 @@ import { import { comboBoxKeyCodes } from '../../services'; import { EuiComboBox } from './combo_box'; +import T from 'tabbable'; jest.mock('../portal', () => ({ EuiPortal: ({ children }: { children: ReactNode }) => children, @@ -325,4 +326,20 @@ describe('behavior', () => { ).toBe(document.activeElement); }); }); + + describe('sortMatchesBy', () => { + test('options startsWith', () => { + const component = mount( + + ) as EuiComboBox; + + findTestSubject(component, 'comboBoxSearchInput').simulate('change', { + target: { value: 'e' }, + }); + const instance = component.instance(); + + expect(instance.state.matchingOptions[0].label).toBe('Enceladus'); + expect(component.state('matchingOptions')[0].label).toBe('Enceladus'); + }); + }); }); From b58d3a4ebc1c5d689f20e26e19dc70ea4bc21c78 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal <43617894+anishagg17@users.noreply.github.com> Date: Wed, 18 Mar 2020 19:22:51 +0530 Subject: [PATCH 06/10] Update src-docs/src/views/combo_box/combo_box_example.js Co-Authored-By: Caroline Horn <549577+cchaos@users.noreply.github.com> --- src-docs/src/views/combo_box/combo_box_example.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src-docs/src/views/combo_box/combo_box_example.js b/src-docs/src/views/combo_box/combo_box_example.js index ed84e061db5..bdda676e55a 100644 --- a/src-docs/src/views/combo_box/combo_box_example.js +++ b/src-docs/src/views/combo_box/combo_box_example.js @@ -365,9 +365,10 @@ export const ComboBoxExample = { ], text: (

- Use the - startingWith prop to let the options that start - with the query be displayed on the top of the list. + By default, the matched options will keep their original sort order. If you would like + to prioritize those options that start with the searched string, + pass sortMatchesBy="startsWith" + to display those options at the top of the list.

), props: { EuiComboBox }, From c52d5d98a80b027423d7d86622bba230a85d20c7 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal <43617894+anishagg17@users.noreply.github.com> Date: Wed, 18 Mar 2020 19:23:00 +0530 Subject: [PATCH 07/10] Update src-docs/src/views/combo_box/combo_box_example.js Co-Authored-By: Caroline Horn <549577+cchaos@users.noreply.github.com> --- src-docs/src/views/combo_box/combo_box_example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/combo_box/combo_box_example.js b/src-docs/src/views/combo_box/combo_box_example.js index bdda676e55a..87e19016bc9 100644 --- a/src-docs/src/views/combo_box/combo_box_example.js +++ b/src-docs/src/views/combo_box/combo_box_example.js @@ -352,7 +352,7 @@ export const ComboBoxExample = { demo: , }, { - title: 'Starting With', + title: 'Sorting matches', source: [ { type: GuideSectionTypes.JS, From a43e56432390228da7c115971906c5f7c2ba4013 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal <43617894+anishagg17@users.noreply.github.com> Date: Wed, 18 Mar 2020 21:15:12 +0530 Subject: [PATCH 08/10] Update src/components/combo_box/combo_box.test.tsx Co-Authored-By: Greg Thompson --- src/components/combo_box/combo_box.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index 8211fba205a..454256c6fa0 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -8,7 +8,6 @@ import { import { comboBoxKeyCodes } from '../../services'; import { EuiComboBox } from './combo_box'; -import T from 'tabbable'; jest.mock('../portal', () => ({ EuiPortal: ({ children }: { children: ReactNode }) => children, From b8c30616388522f249b9eff6431b12b694b5a4e7 Mon Sep 17 00:00:00 2001 From: Anish Aggarwal <43617894+anishagg17@users.noreply.github.com> Date: Wed, 18 Mar 2020 21:15:22 +0530 Subject: [PATCH 09/10] Update src/components/combo_box/combo_box.test.tsx Co-Authored-By: Greg Thompson --- src/components/combo_box/combo_box.test.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index 454256c6fa0..84b63b0fc22 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -328,9 +328,11 @@ describe('behavior', () => { describe('sortMatchesBy', () => { test('options startsWith', () => { - const component = mount( - - ) as EuiComboBox; +const component = mount< + EuiComboBox, + EuiComboBoxProps, + { matchingOptions: TitanOption[] } +>(); findTestSubject(component, 'comboBoxSearchInput').simulate('change', { target: { value: 'e' }, From 88a10e9cb513eb620fe15b4047fbcfa59670041c Mon Sep 17 00:00:00 2001 From: Anish Aggarwal Date: Wed, 18 Mar 2020 21:19:36 +0530 Subject: [PATCH 10/10] resolved eslint errors --- src-docs/src/views/combo_box/combo_box_example.js | 7 ++++--- src/components/combo_box/combo_box.test.tsx | 8 +++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src-docs/src/views/combo_box/combo_box_example.js b/src-docs/src/views/combo_box/combo_box_example.js index 87e19016bc9..0f70d299c8b 100644 --- a/src-docs/src/views/combo_box/combo_box_example.js +++ b/src-docs/src/views/combo_box/combo_box_example.js @@ -365,9 +365,10 @@ export const ComboBoxExample = { ], text: (

- By default, the matched options will keep their original sort order. If you would like - to prioritize those options that start with the searched string, - pass sortMatchesBy="startsWith" + By default, the matched options will keep their original sort order. + If you would like to prioritize those options that{' '} + start with the searched string, pass{' '} + sortMatchesBy="startsWith" to display those options at the top of the list.

), diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index 84b63b0fc22..6856b23c2bc 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -7,7 +7,7 @@ import { } from '../../test'; import { comboBoxKeyCodes } from '../../services'; -import { EuiComboBox } from './combo_box'; +import { EuiComboBox, EuiComboBoxProps } from './combo_box'; jest.mock('../portal', () => ({ EuiPortal: ({ children }: { children: ReactNode }) => children, @@ -328,18 +328,16 @@ describe('behavior', () => { describe('sortMatchesBy', () => { test('options startsWith', () => { -const component = mount< + const component = mount< EuiComboBox, EuiComboBoxProps, { matchingOptions: TitanOption[] } ->(); + >(); findTestSubject(component, 'comboBoxSearchInput').simulate('change', { target: { value: 'e' }, }); - const instance = component.instance(); - expect(instance.state.matchingOptions[0].label).toBe('Enceladus'); expect(component.state('matchingOptions')[0].label).toBe('Enceladus'); }); });