diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2111dfacce9..ad6479a8f99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)
+- Added `EuiSuggestItem` component ([#2090](https://github.com/elastic/eui/pull/2090))
- Added support for negated or clauses to `EuiSearchBar` ([#2140](https://github.com/elastic/eui/pull/2140))
**Bug fixes**
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js
index a9fc78a5964..f3c8c27dfbc 100644
--- a/src-docs/src/routes.js
+++ b/src-docs/src/routes.js
@@ -160,6 +160,8 @@ import { StatExample } from './views/stat/stat_example';
import { StepsExample } from './views/steps/steps_example';
+// import { SuggestExample } from './views/suggest/suggest_example';
+
import { TableExample } from './views/tables/tables_example';
import { TabsExample } from './views/tabs/tabs_example';
@@ -337,6 +339,7 @@ const navigation = [
RangeControlExample,
SearchBarExample,
SelectableExample,
+ // SuggestExample,
].map(example => createExample(example)),
},
{
diff --git a/src-docs/src/views/suggest/suggest.js b/src-docs/src/views/suggest/suggest.js
new file mode 100644
index 00000000000..3c4c903427b
--- /dev/null
+++ b/src-docs/src/views/suggest/suggest.js
@@ -0,0 +1,3 @@
+import React from 'react';
+
+export default () =>
;
diff --git a/src-docs/src/views/suggest/suggest_example.js b/src-docs/src/views/suggest/suggest_example.js
new file mode 100644
index 00000000000..b884f78846b
--- /dev/null
+++ b/src-docs/src/views/suggest/suggest_example.js
@@ -0,0 +1,81 @@
+import React from 'react';
+
+import { renderToHtml } from '../../services';
+
+import { GuideSectionTypes } from '../../components';
+
+import { EuiCode, EuiSuggestItem } from '../../../../src/components';
+
+import Suggest from './suggest';
+const suggestSource = require('!!raw-loader!./suggest');
+const suggestHtml = renderToHtml(Suggest);
+
+import SuggestItem from './suggest_item';
+const suggestItemSource = require('!!raw-loader!./suggest_item');
+const suggestItemHtml = renderToHtml(SuggestItem);
+const suggestItemSnippet = [
+ `
+`,
+ ` `,
+];
+
+export const SuggestExample = {
+ title: 'Suggest',
+ sections: [
+ {
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: suggestSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: suggestHtml,
+ },
+ ],
+ text: (
+
+
+ EuiSuggest description goes here.
+
+
+ ),
+ },
+ {
+ title: 'Suggest Item',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: suggestItemSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: suggestItemHtml,
+ },
+ ],
+ text: (
+
+
+ EuiSuggestItem is a list item component to
+ display suggestions when typing queries in{' '}
+ EuiSuggestInput . Use{' '}
+ labelDisplay to set whether the{' '}
+ label has a fixed width or not.
+
+
+ ),
+ props: { EuiSuggestItem },
+ snippet: suggestItemSnippet,
+ demo: ,
+ },
+ ],
+};
diff --git a/src-docs/src/views/suggest/suggest_item.js b/src-docs/src/views/suggest/suggest_item.js
new file mode 100644
index 00000000000..b3c90b18753
--- /dev/null
+++ b/src-docs/src/views/suggest/suggest_item.js
@@ -0,0 +1,71 @@
+import React from 'react';
+
+import { EuiSuggestItem, EuiSpacer } from '../../../../src/components';
+
+const shortDescription = 'This is the description';
+
+const sampleItems = [
+ {
+ type: { iconType: 'kqlField', color: 'tint4' },
+ label: 'Field sample',
+ description: shortDescription,
+ },
+ {
+ type: { iconType: 'kqlValue', color: 'tint0' },
+ label: 'Value sample',
+ description: shortDescription,
+ },
+ {
+ type: { iconType: 'kqlSelector', color: 'tint3' },
+ label: 'Conjunction sample',
+ description: shortDescription,
+ },
+ {
+ type: { iconType: 'kqlOperand', color: 'tint1' },
+ label: 'Operator sample',
+ description: shortDescription,
+ },
+ {
+ type: { iconType: 'search', color: 'tint8' },
+ label: 'Recent search',
+ },
+ {
+ type: { iconType: 'save', color: 'tint5' },
+ label: 'Saved query',
+ },
+];
+
+const typeObj = { iconType: 'kqlValue', color: 'tint0' };
+
+const longLabel =
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ut quam eget augue pulvinar.';
+
+export default () => (
+
+ {sampleItems.map((item, index) => (
+
+ ))}
+
+
+
+
+
+
+);
diff --git a/src/components/index.js b/src/components/index.js
index 9218bd62cdc..32923f19c17 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -234,6 +234,8 @@ export { EuiStat } from './stat';
export { EuiStep, EuiSteps, EuiSubSteps, EuiStepsHorizontal } from './steps';
+export { EuiSuggestItem } from './suggest_item';
+
export {
EuiTable,
EuiTableBody,
diff --git a/src/components/index.scss b/src/components/index.scss
index a46031166fa..c39303f5798 100644
--- a/src/components/index.scss
+++ b/src/components/index.scss
@@ -51,6 +51,7 @@
@import 'selectable/index';
@import 'stat/index';
@import 'steps/index';
+@import 'suggest_item/index';
@import 'table/index';
@import 'tabs/index';
@import 'title/index';
diff --git a/src/components/suggest_item/__snapshots__/suggest_item.test.js.snap b/src/components/suggest_item/__snapshots__/suggest_item.test.js.snap
new file mode 100644
index 00000000000..a3068051a5f
--- /dev/null
+++ b/src/components/suggest_item/__snapshots__/suggest_item.test.js.snap
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiSuggestItem is rendered 1`] = `
+
+
+
+
+
+ Test label
+
+
+
+`;
+
+exports[`props item with no description has expanded label is rendered 1`] = `
+
+
+
+
+
+ This is the description
+
+
+
+`;
+
+exports[`props labelDisplay as expand is rendered 1`] = `
+
+
+
+
+
+ This is the description
+
+
+ This is the description
+
+
+`;
diff --git a/src/components/suggest_item/_index.scss b/src/components/suggest_item/_index.scss
new file mode 100644
index 00000000000..c4dd928bb4e
--- /dev/null
+++ b/src/components/suggest_item/_index.scss
@@ -0,0 +1,3 @@
+@import 'variables';
+
+@import 'suggest_item';
diff --git a/src/components/suggest_item/_suggest_item.scss b/src/components/suggest_item/_suggest_item.scss
new file mode 100644
index 00000000000..9f063b25889
--- /dev/null
+++ b/src/components/suggest_item/_suggest_item.scss
@@ -0,0 +1,68 @@
+.euiSuggestItem {
+ display: flex;
+ flex-grow: 1;
+ align-items: center;
+ font-size: $euiFontSizeXS;
+ white-space: nowrap;
+
+ @each $name, $color in $itemColors {
+ .euiSuggestItem__type--#{$name} {
+ $backgroundColor: tintOrShade($color, 90%, 50%);
+ background-color: $backgroundColor;
+ color: makeHighContrastColor($color, $backgroundColor);
+ }
+ }
+
+ .euiSuggestItem__label,
+ .euiSuggestItem__type,
+ .euiSuggestItem__description {
+ flex-grow: 0;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .euiSuggestItem__type {
+ position: relative;
+ flex-shrink: 0;
+ flex-basis: auto;
+ width: $euiSizeXL;
+ height: $euiSizeXL;
+ text-align: center;
+ overflow: hidden;
+ padding: $euiSizeXS;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .euiSuggestItem__label {
+ flex-basis: 50%;
+ min-width: 50%;
+ font-family: $euiCodeFontFamily;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: $euiSizeXS $euiSizeS;
+ color: $euiTextColor;
+
+ &.euiSuggestItem__labelDisplay--expand {
+ flex-basis: auto;
+ flex-shrink: 1;
+ }
+ }
+
+ .euiSuggestItem__description,
+ .euiSuggestItem__label {
+ @include euiTextTruncate;
+ display: block;
+ }
+
+ .euiSuggestItem__description {
+ color: $euiColorDarkShade;
+ flex-basis: auto;
+ padding-top: $euiSizeXS * .5;
+
+ &:empty {
+ flex-grow: 0;
+ margin-left: 0;
+ }
+ }
+}
diff --git a/src/components/suggest_item/_variables.scss b/src/components/suggest_item/_variables.scss
new file mode 100644
index 00000000000..d90fa21260d
--- /dev/null
+++ b/src/components/suggest_item/_variables.scss
@@ -0,0 +1,11 @@
+$itemColors: (
+ tint0: $euiColorVis0,
+ tint1: $euiColorVis1,
+ tint2: $euiColorVis2,
+ tint3: $euiColorVis3,
+ tint4: $euiColorVis5,
+ tint5: $euiColorVis7,
+ tint6: $euiColorVis8,
+ tint7: $euiColorVis9,
+ tint8: $euiColorDarkShade,
+);
diff --git a/src/components/suggest_item/index.js b/src/components/suggest_item/index.js
new file mode 100644
index 00000000000..fde82176579
--- /dev/null
+++ b/src/components/suggest_item/index.js
@@ -0,0 +1 @@
+export { EuiSuggestItem } from './suggest_item';
diff --git a/src/components/suggest_item/suggest_item.js b/src/components/suggest_item/suggest_item.js
new file mode 100644
index 00000000000..0d396023414
--- /dev/null
+++ b/src/components/suggest_item/suggest_item.js
@@ -0,0 +1,91 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { EuiIcon, IconPropType } from '../icon';
+
+const colorToClassNameMap = {
+ tint0: 'euiSuggestItem__type--tint0',
+ tint1: 'euiSuggestItem__type--tint1',
+ tint2: 'euiSuggestItem__type--tint2',
+ tint3: 'euiSuggestItem__type--tint3',
+ tint4: 'euiSuggestItem__type--tint4',
+ tint5: 'euiSuggestItem__type--tint5',
+ tint6: 'euiSuggestItem__type--tint6',
+ tint7: 'euiSuggestItem__type--tint7',
+ tint8: 'euiSuggestItem__type--tint8',
+ tint9: 'euiSuggestItem__type--tint9',
+};
+
+export const COLORS = Object.keys(colorToClassNameMap);
+
+const labelDisplayToClassMap = {
+ fixed: 'euiSuggestItem__labelDisplay--fixed',
+ expand: 'euiSuggestItem__labelDisplay--expand',
+};
+
+export const DISPLAYS = Object.keys(labelDisplayToClassMap);
+
+export const EuiSuggestItem = ({
+ className,
+ label,
+ type,
+ labelDisplay,
+ description,
+ ...rest
+}) => {
+ const classes = classNames('euiSuggestItem', className);
+
+ let colorClass = '';
+
+ const labelDisplayClass = classNames(
+ 'euiSuggestItem__label',
+ labelDisplayToClassMap[labelDisplay],
+ {
+ 'euiSuggestItem__labelDisplay--expand': !description,
+ }
+ );
+
+ if (type && type.color) {
+ if (COLORS.indexOf(type.color) > -1) {
+ colorClass = colorToClassNameMap[type.color];
+ }
+ }
+
+ return (
+
+
+
+
+ {label}
+ {description}
+
+ );
+};
+
+EuiSuggestItem.propTypes = {
+ className: PropTypes.string,
+ /**
+ * Takes 'iconType' for EuiIcon and 'color'. 'color' can be tint1 through tint9.
+ */
+ type: PropTypes.shape({
+ iconType: IconPropType.isRequired,
+ color: PropTypes.oneOfType([PropTypes.oneOf(COLORS), PropTypes.string])
+ .isRequired,
+ }).isRequired,
+ /**
+ * Label or primary text.
+ */
+ label: PropTypes.string.isRequired,
+ /**
+ * Description or secondary text (optional).
+ */
+ description: PropTypes.string,
+ /**
+ * Label display is 'fixed' by default. Label will increase its width beyond 50% if needed with 'expand'.
+ */
+ labelDisplay: PropTypes.oneOf(DISPLAYS),
+};
+
+EuiSuggestItem.defaultProps = {
+ labelDisplay: 'fixed',
+};
diff --git a/src/components/suggest_item/suggest_item.test.js b/src/components/suggest_item/suggest_item.test.js
new file mode 100644
index 00000000000..e6fe2664991
--- /dev/null
+++ b/src/components/suggest_item/suggest_item.test.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiSuggestItem } from './suggest_item';
+
+const TYPE = {
+ iconType: 'search',
+ color: 'tint1',
+};
+
+describe('EuiSuggestItem', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('props', () => {
+ const sampleItem = {
+ type: { iconType: 'kqlValue', color: 'tint2' },
+ label: 'Charles de Gaulle International Airport',
+ description: 'This is the description',
+ };
+
+ describe('labelDisplay as expand', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('item with no description has expanded label', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+ });
+});