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

Added delimiter prop to EuiComboBox #3104

Merged
merged 25 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4111a91
added delimenator prop
anishagg17 Mar 17, 2020
ac1f76d
Entering delimeator hits enter functionality added
anishagg17 Mar 17, 2020
fb452c2
added docs example, CL
anishagg17 Mar 19, 2020
406ec71
Merge branch 'master' into deim
anishagg17 Mar 19, 2020
386acbb
prettified
anishagg17 Mar 19, 2020
a133ba6
removed console.log()
anishagg17 Mar 19, 2020
c3142a4
added copy functionality
anishagg17 Mar 19, 2020
5d2cb23
added test,snap
anishagg17 Mar 19, 2020
e0a58bf
removed copy button
anishagg17 Mar 20, 2020
e89727a
removed copy button
anishagg17 Mar 20, 2020
ae3ae85
Update src-docs/src/views/combo_box/combo_box_example.js
anishagg17 Mar 20, 2020
864f5a9
Update src/components/combo_box/combo_box.tsx
anishagg17 Mar 20, 2020
ff3131a
fixed only first gets entered
anishagg17 Mar 21, 2020
4879bda
Merge branch 'deim' of https://github.com/anishagg17/eui into deim
anishagg17 Mar 21, 2020
b002be1
Merge branch 'master' into deim
anishagg17 Mar 23, 2020
fe9b420
Merge branch 'master' into deim
anishagg17 Mar 24, 2020
0c08b72
updated text
anishagg17 Mar 25, 2020
14f6499
Merge branch 'deim' of https://github.com/anishagg17/eui into deim
anishagg17 Mar 25, 2020
033499a
indent fix
anishagg17 Mar 25, 2020
e983a61
Update combo_box_options_list.tsx
anishagg17 Mar 25, 2020
5eeff79
Update src/components/combo_box/combo_box_options_list/combo_box_opti…
anishagg17 Mar 25, 2020
368c92c
Merge branch 'master' of https://github.com/elastic/eui into deim
anishagg17 Mar 25, 2020
bcd13cd
removed empty string
anishagg17 Mar 25, 2020
14ad843
removed redundant code
anishagg17 Mar 25, 2020
26ee787
Merge branch 'master' into deim
anishagg17 Mar 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 `delimiter` prop to `EuiComboBox` ([#3104](https://github.com/elastic/eui/pull/3104))
- Replaced various `lodash` functions with native functions ([#3053](https://github.com/elastic/eui/pull/3053))
- Added `whiteSpace ` prop to `EuiCodeBlock` ([#3103](https://github.com/elastic/eui/pull/3103))
- Added `sortMatchesBy` prop for `EuiComboBox` ([#3089](https://github.com/elastic/eui/pull/3089))
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;
anishagg17 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
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