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

[EuiComboBox] Added sortMatchesBy prop #3089

Merged
merged 13 commits into from
Mar 19, 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 `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))
Expand Down
26 changes: 26 additions & 0 deletions src-docs/src/views/combo_box/combo_box_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
Expand Down Expand Up @@ -347,5 +351,27 @@ export const ComboBoxExample = {
props: { EuiComboBox },
demo: <Async />,
},
{
title: 'Starting With',
source: [
{
type: GuideSectionTypes.JS,
code: startingWithSource,
},
{
type: GuideSectionTypes.HTML,
code: startingWithHtml,
},
],
text: (
<p>
Use the
<EuiCode>startingWith</EuiCode> prop to let the options that start
with the query be displayed on the top of the list.
</p>
),
props: { EuiComboBox },
demo: <StartingWith />,
},
],
};
97 changes: 97 additions & 0 deletions src-docs/src/views/combo_box/startingWith.js
Original file line number Diff line number Diff line change
@@ -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 (
<EuiComboBox
startingWith={true}
placeholder="Select or create options"
options={this.options}
selectedOptions={selectedOptions}
onChange={this.onChange}
onCreateOption={this.onCreateOption}
isClearable={true}
data-test-subj="demoComboBox"
/>
);
}
}
31 changes: 30 additions & 1 deletion src/components/combo_box/combo_box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ interface _EuiComboBoxProps<T>
* 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
Expand Down Expand Up @@ -170,6 +174,7 @@ export class EuiComboBox<T> extends Component<
singleSelection: false,
prepend: null,
append: null,
startingWith: false,
};

state: EuiComboBoxState<T> = {
Expand Down Expand Up @@ -779,6 +784,7 @@ export class EuiComboBox<T> extends Component<
);
}
}

this.setState({
matchingOptions: newMatchingOptions,
activeOptionIndex: nextActiveOptionIndex,
Expand Down Expand Up @@ -841,6 +847,7 @@ export class EuiComboBox<T> extends Component<
singleSelection,
prepend,
append,
startingWith,
...rest
} = this.props;
const {
Expand All @@ -850,8 +857,30 @@ export class EuiComboBox<T> extends Component<
listPosition,
searchValue,
width,
matchingOptions,
} = this.state;

let newMatchingOptions = matchingOptions;

if (startingWith) {
const refObj: {
startWith: Array<EuiComboBoxOptionOption<T>>;
others: Array<EuiComboBoxOptionOption<T>>;
} = { 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.
Expand Down Expand Up @@ -887,7 +916,7 @@ export class EuiComboBox<T> extends Component<
fullWidth={fullWidth}
isLoading={isLoading}
listRef={this.listRefCallback}
matchingOptions={this.state.matchingOptions}
matchingOptions={newMatchingOptions}
onCloseList={this.closeList}
onCreateOption={onCreateOption}
onOptionClick={this.onOptionClick}
Expand Down