Skip to content

Commit

Permalink
Added delimiter prop to EuiComboBox (#3104)
Browse files Browse the repository at this point in the history
* added delimenator prop

* Entering delimeator hits enter functionality added

* added docs example, CL

* prettified

* removed console.log()

* added copy functionality

* added test,snap

* removed copy button

* removed copy button

* Update src-docs/src/views/combo_box/combo_box_example.js

Co-Authored-By: Caroline Horn <[email protected]>

* Update src/components/combo_box/combo_box.tsx

Co-Authored-By: Caroline Horn <[email protected]>

* fixed only first gets entered

* updated text

* indent fix

* Update combo_box_options_list.tsx

* Update src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx

Co-Authored-By: Caroline Horn <[email protected]>

* removed empty string

* removed redundant code

Co-authored-by: Caroline Horn <[email protected]>
  • Loading branch information
anishagg17 and cchaos authored Mar 25, 2020
1 parent c91fb39 commit 2bc9917
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 16 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)

No public interface changes since `22.0.0`.
- Added `delimiter` prop to `EuiComboBox` ([#3104](https://github.com/elastic/eui/pull/3104))

## [`22.0.0`](https://github.com/elastic/eui/tree/v22.0.0)

Expand Down
97 changes: 97 additions & 0 deletions src-docs/src/views/combo_box/combo_box_delimiter.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
placeholder="Select or create options"
options={this.options}
delimiter=","
selectedOptions={selectedOptions}
onChange={this.onChange}
onCreateOption={this.onCreateOption}
isClearable={true}
data-test-subj="demoComboBox"
/>
);
}
}
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 Delimiter from './combo_box_delimiter';
const delimiterSource = require('!!raw-loader!./combo_box_delimiter');
const delimiterHtml = renderToHtml(Delimiter);

import StartingWith from './startingWith';
const startingWithSource = require('!!raw-loader!./startingWith');
const startingWithHtml = renderToHtml(StartingWith);
Expand Down Expand Up @@ -351,6 +355,28 @@ export const ComboBoxExample = {
props: { EuiComboBox },
demo: <Async />,
},
{
title: 'With delimiter',
source: [
{
type: GuideSectionTypes.JS,
code: delimiterSource,
},
{
type: GuideSectionTypes.HTML,
code: delimiterHtml,
},
],
text: (
<p>
Pass a unique character to the <EuiCode>delimiter</EuiCode> prop to
aid in option creation. This is best used when knowing that content
may be pasted from elsewhere such as a comma separated list.
</p>
),
props: { EuiComboBox },
demo: <Delimiter />,
},
{
title: 'Sorting matches',
source: [
Expand Down
43 changes: 43 additions & 0 deletions src/components/combo_box/__snapshots__/combo_box.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,49 @@ exports[`EuiComboBox is rendered 1`] = `
</div>
`;

exports[`props delimiter is rendered 1`] = `
<div
aria-expanded={false}
aria-haspopup="listbox"
className="euiComboBox"
onKeyDown={[Function]}
role="combobox"
>
<EuiComboBoxInput
autoSizeInputRef={[Function]}
compressed={false}
fullWidth={false}
hasSelectedOptions={true}
inputRef={[Function]}
isListOpen={false}
noIcon={false}
onChange={[Function]}
onClear={[Function]}
onClick={[Function]}
onCloseListClick={[Function]}
onFocus={[Function]}
onOpenListClick={[Function]}
onRemoveOption={[Function]}
rootId={[Function]}
searchValue=""
selectedOptions={
Array [
Object {
"label": "Mimas",
},
Object {
"label": "Dione",
},
]
}
singleSelection={false}
toggleButtonRef={[Function]}
updatePosition={[Function]}
value="Mimas, Dione"
/>
</div>
`;

exports[`props full width is rendered 1`] = `
<div
aria-expanded={false}
Expand Down
12 changes: 12 additions & 0 deletions src/components/combo_box/combo_box.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ describe('props', () => {

expect(component).toMatchSnapshot();
});

test('delimiter is rendered', () => {
const component = shallow(
<EuiComboBox
options={options}
selectedOptions={[options[2], options[3]]}
delimiter=","
/>
);

expect(component).toMatchSnapshot();
});
});

describe('behavior', () => {
Expand Down
36 changes: 30 additions & 6 deletions src/components/combo_box/combo_box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ interface _EuiComboBoxProps<T>
* `string` | `ReactElement` or an array of these
*/
append?: EuiFormControlLayoutProps['append'];
/**
* A special character to use as a value separator. Typically a comma `,`
*/
delimiter?: string;
}

/**
Expand Down Expand Up @@ -400,15 +404,15 @@ export class EuiComboBox<T> extends Component<
}
};

addCustomOption = (isContainerBlur: boolean) => {
addCustomOption = (isContainerBlur: boolean, searchValue: string) => {
const {
onCreateOption,
options,
selectedOptions,
singleSelection,
} = this.props;

const { searchValue, matchingOptions } = this.state;
const { matchingOptions } = this.state;

if (this.doesSearchMatchOnlyOption()) {
this.onAddOption(matchingOptions[0], isContainerBlur);
Expand Down Expand Up @@ -497,6 +501,18 @@ export class EuiComboBox<T> extends Component<
this.setState({ hasFocus: true });
};

setCustomOptions = (isContainerBlur: boolean) => {
const { searchValue } = this.state;
const { delimiter } = this.props;
if (delimiter) {
searchValue.split(delimiter).forEach((option: string) => {
if (option.length > 0) this.addCustomOption(true, option);
});
} else {
this.addCustomOption(isContainerBlur, searchValue);
}
};

onContainerBlur: EventListener = event => {
// close the options list, unless the use clicked on an option

Expand Down Expand Up @@ -531,7 +547,7 @@ export class EuiComboBox<T> extends Component<
// If the user tabs away or changes focus to another element, take whatever input they've
// typed and convert it into a pill, to prevent the combo box from looking like a text input.
if (!this.hasActiveOption()) {
this.addCustomOption(true);
this.setCustomOptions(true);
}
}
};
Expand Down Expand Up @@ -576,7 +592,7 @@ export class EuiComboBox<T> extends Component<
this.state.matchingOptions[this.state.activeOptionIndex]
);
} else {
this.addCustomOption(false);
this.setCustomOptions(false);
}
break;

Expand Down Expand Up @@ -708,7 +724,8 @@ export class EuiComboBox<T> extends Component<
onSearchChange: NonNullable<
EuiComboBoxInputProps<T>['onChange']
> = searchValue => {
const { onSearchChange } = this.props;
const { onSearchChange, delimiter } = this.props;

if (onSearchChange) {
const hasMatchingOptions = this.state.matchingOptions.length > 0;
onSearchChange(searchValue, hasMatchingOptions);
Expand All @@ -717,6 +734,11 @@ export class EuiComboBox<T> extends Component<
this.setState({ searchValue }, () => {
if (searchValue && this.state.isListOpen === false) this.openList();
});
if (delimiter && searchValue.endsWith(delimiter)) {
searchValue.split(delimiter).forEach(value => {
if (value.length > 0) this.addCustomOption(false, value);
});
}
};

componentDidMount() {
Expand Down Expand Up @@ -848,8 +870,9 @@ export class EuiComboBox<T> extends Component<
selectedOptions,
singleSelection,
prepend,
append,
sortMatchesBy,
delimiter,
append,
...rest
} = this.props;
const {
Expand Down Expand Up @@ -936,6 +959,7 @@ export class EuiComboBox<T> extends Component<
selectedOptions={selectedOptions}
updatePosition={this.updatePosition}
width={width}
delimiter={delimiter}
/>
</EuiPortal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type EuiComboBoxOptionsListProps<T> = CommonProps &
updatePosition: UpdatePositionHandler;
width: number;
singleSelection?: boolean | EuiComboBoxSingleSelectionShape;
delimiter?: string;
};

export class EuiComboBoxOptionsList<T> extends Component<
Expand Down Expand Up @@ -179,6 +180,7 @@ export class EuiComboBoxOptionsList<T> extends Component<
singleSelection,
updatePosition,
width,
delimiter,
...rest
} = this.props;

Expand Down Expand Up @@ -232,15 +234,27 @@ export class EuiComboBoxOptionsList<T> extends Component<
);
}
} else {
emptyStateContent = (
<p>
<EuiI18n
token="euiComboBoxOptionsList.noMatchingOptions"
default="{searchValue} doesn't match any options"
values={{ searchValue: <strong>{searchValue}</strong> }}
/>
</p>
);
if (delimiter && searchValue.includes(delimiter)) {
emptyStateContent = (
<p>
<EuiI18n
token="euiComboBoxOptionsList.delimiterMessage"
default="Hit enter to add each item separated by {delimiter}"
values={{ delimiter: <strong>{delimiter}</strong> }}
/>
</p>
);
} else {
emptyStateContent = (
<p>
<EuiI18n
token="euiComboBoxOptionsList.noMatchingOptions"
default="{searchValue} doesn't match any options"
values={{ searchValue: <strong>{searchValue}</strong> }}
/>
</p>
);
}
}
} else if (!options.length) {
emptyStateContent = (
Expand Down

0 comments on commit 2bc9917

Please sign in to comment.