diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1e96e789a8..23ee3b919d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)
+- Added `EuiListGroup` and `EuiListGroupItem` components ([#1377](https://github.com/elastic/eui/pull/1377))
- Convert the other of the services to TypeScript ([#1392](https://github.com/elastic/eui/pull/1392))
- Changed single selection to select existing option in the list ([#1391](https://github.com/elastic/eui/pull/1391))
- Added `showUpdateButton` prop to `EuiSuperDatePicker` ([#1399](https://github.com/elastic/eui/pull/1399))
@@ -19,6 +20,7 @@
- Altered a few icons and added more: `crossInACircleFilled`, `editorRedo`, `editorUndo`, `grabHorizontal`, `minusInCircleFilled`, `plusInCircleFilled`, `sortable`, `starEmptySpace`, `starFilledSpace`, `starFilled`, `starMinusEmpty`, `starMinusFilled`, `starPlusEmpty`, `pinFilled` ([#1374](https://github.com/elastic/eui/pull/1374))
- Exclude `custom_typings` from `eui.d.ts` ([#1395](https://github.com/elastic/eui/pull/1395))
+
**Bug fixes**
- Only style anchor tags in `EuiText` that have no class attribute ([#1373](https://github.com/elastic/eui/pull/1373))
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js
index 29a5a4f6ca9..2e97b630318 100644
--- a/src-docs/src/routes.js
+++ b/src-docs/src/routes.js
@@ -159,6 +159,9 @@ import { KeyPadMenuExample }
import { LinkExample }
from './views/link/link_example';
+import { ListGroupExample }
+ from './views/list_group/list_group_example';
+
import { LoadingExample }
from './views/loading/loading_example';
@@ -364,6 +367,7 @@ const navigation = [{
HealthExample,
IconExample,
ImageExample,
+ ListGroupExample,
LoadingExample,
ProgressExample,
StatExample,
diff --git a/src-docs/src/views/list_group/list_group.js b/src-docs/src/views/list_group/list_group.js
new file mode 100644
index 00000000000..4112799a0d0
--- /dev/null
+++ b/src-docs/src/views/list_group/list_group.js
@@ -0,0 +1,80 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiListGroup,
+ EuiListGroupItem,
+ EuiSpacer,
+ EuiSwitch,
+ EuiCode,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '../../../../src/components';
+
+export default class extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ flushWidth: false,
+ showBorder: false,
+ };
+ }
+
+ toggleFlushWidth = () => {
+ this.setState(prevState => ({ flushWidth: !prevState.flushWidth }));
+ };
+
+ toggleBorder = () => {
+ this.setState(prevState => ({ showBorder: !prevState.showBorder }));
+ };
+
+ render() {
+ const {
+ flushWidth,
+ showBorder,
+ } = this.state;
+
+ return (
+
+
+
+ Show as flush }
+ checked={this.state.flushWidth}
+ onChange={this.toggleFlushWidth}
+ />
+
+
+ Show as bordered }
+ checked={this.state.showBorder}
+ onChange={this.toggleBorder}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src-docs/src/views/list_group/list_group_example.js b/src-docs/src/views/list_group/list_group_example.js
new file mode 100644
index 00000000000..ff94393646a
--- /dev/null
+++ b/src-docs/src/views/list_group/list_group_example.js
@@ -0,0 +1,88 @@
+import React from 'react';
+
+import { renderToHtml } from '../../services';
+
+import {
+ GuideSectionTypes,
+} from '../../components';
+
+import {
+ EuiListGroup,
+ EuiListGroupItem,
+ EuiCode,
+} from '../../../../src/components';
+
+import ListGroup from './list_group';
+const listGroupSource = require('!!raw-loader!./list_group');
+const listGroupHtml = renderToHtml(ListGroup);
+
+import ListGroupLinks from './list_group_links';
+const listGroupLinksSource = require('!!raw-loader!./list_group_links');
+const listGroupLinksHtml = renderToHtml(ListGroupLinks);
+
+import ListGroupLinkActions from './list_group_link_actions';
+const listGroupLinkActionsSource = require('!!raw-loader!./list_group_link_actions');
+const listGroupLinkActionsHtml = renderToHtml(ListGroupLinkActions);
+
+export const ListGroupExample = {
+ title: 'List Group',
+ sections: [{
+ source: [{
+ type: GuideSectionTypes.JS,
+ code: listGroupSource,
+ }, {
+ type: GuideSectionTypes.HTML,
+ code: listGroupHtml,
+ }],
+ text: (
+
+ The ListGroup component is used to present
+ ListGroupItems in a neatly formatted list. Use the
+ flush and bordered properties
+ for full-width and bordered presentations, respectively.
+
+ ),
+ props: { EuiListGroup, EuiListGroupItem },
+ demo: ,
+ }, {
+ title: 'List of links',
+ source: [{
+ type: GuideSectionTypes.JS,
+ code: listGroupLinksSource,
+ }, {
+ type: GuideSectionTypes.HTML,
+ code: listGroupLinksHtml,
+ }],
+ text: (
+
+ Present ListGroupItems as links by providing an
+ href value and change their appearance
+ with the size , isActive , and
+ isDisabled properties. As done in this example, the
+ ListGroup component can also accept an array of
+ items via the listItems property.
+
+ ),
+ demo: ,
+ }, {
+ title: 'Secondary link actions',
+ source: [{
+ type: GuideSectionTypes.JS,
+ code: listGroupLinkActionsSource,
+ }, {
+ type: GuideSectionTypes.HTML,
+ code: listGroupLinkActionsHtml,
+ }],
+ text: (
+
+ The extraAction property adds a secondary icon
+ button to any list item. It accepts several properites of its own,
+ including color , onClick ,
+ iconType and alwaysShow ,
+ and can be used for actions such as pinning, favoriting, or deleting an
+ item.
+
+ ),
+ demo: ,
+ }],
+};
diff --git a/src-docs/src/views/list_group/list_group_link_actions.js b/src-docs/src/views/list_group/list_group_link_actions.js
new file mode 100644
index 00000000000..2ba0ad71618
--- /dev/null
+++ b/src-docs/src/views/list_group/list_group_link_actions.js
@@ -0,0 +1,156 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiListGroup,
+ EuiListGroupItem,
+ EuiSpacer,
+ EuiSwitch,
+ EuiCode,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '../../../../src/components';
+
+export default class extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ flushWidth: false,
+ showBorder: false,
+ favorite1: undefined,
+ favorite2: undefined,
+ favorite3: undefined,
+ };
+ }
+
+ toggleFlushWidth = () => {
+ this.setState(prevState => ({ flushWidth: !prevState.flushWidth }));
+ };
+
+ toggleBorder = () => {
+ this.setState(prevState => ({ showBorder: !prevState.showBorder }));
+ };
+
+ link1Clicked = () => {
+ this.setState(prevState => {
+ return {
+ favorite1: prevState.favorite1 === 'link1' ? undefined : 'link1',
+ };
+ });
+ if (this.favorite1 === undefined) { document.activeElement.blur(); }
+ };
+
+ link2Clicked = () => {
+ this.setState(prevState => {
+ return {
+ favorite2: prevState.favorite2 === 'link2' ? undefined : 'link2',
+ };
+ });
+ if (this.favorite2 === undefined) { document.activeElement.blur(); }
+ };
+
+ link3Clicked = () => {
+ this.setState(prevState => {
+ return {
+ favorite3: prevState.favorite3 === 'link3' ? undefined : 'link3',
+ };
+ });
+ if (this.favorite3 === undefined) { document.activeElement.blur(); }
+ };
+
+ render() {
+ const {
+ flushWidth,
+ showBorder,
+ favorite1,
+ favorite2,
+ favorite3,
+ } = this.state;
+
+ return (
+
+
+
+ Show as flush }
+ checked={this.state.flushWidth}
+ onChange={this.toggleFlushWidth}
+ />
+
+
+ Show as bordered }
+ checked={this.state.showBorder}
+ onChange={this.toggleBorder}
+ />
+
+
+
+
+
+
+ window.alert('Button clicked')}
+ isActive
+ extraAction={{
+ color: 'subdued',
+ onClick: this.link1Clicked,
+ iconType: favorite1 === 'link1' ? 'pinFilled' : 'pin',
+ iconSize: 's',
+ 'aria-label': 'Favorite link1',
+ alwaysShow: favorite1 === 'link1',
+ }}
+ />
+
+ window.alert('Button clicked')}
+ label="EUI button link"
+ extraAction={{
+ color: 'subdued',
+ onClick: this.link2Clicked,
+ iconType: favorite2 === 'link2' ? 'pinFilled' : 'pin',
+ iconSize: 's',
+ 'aria-label': 'Favorite link2',
+ alwaysShow: favorite2 === 'link2',
+ }}
+ />
+
+ window.alert('Button clicked')}
+ iconType="broom"
+ label="EUI button link"
+ extraAction={{
+ color: 'subdued',
+ onClick: this.link3Clicked,
+ iconType: favorite3 === 'link3' ? 'pinFilled' : 'pin',
+ iconSize: 's',
+ 'aria-label': 'Favorite link3',
+ alwaysShow: favorite3 === 'link3',
+ isDisabled: true,
+ }}
+ />
+
+ window.alert('Action clicked'),
+ iconType: 'pin',
+ iconSize: 's',
+ 'aria-label': 'Favorite link4',
+ }}
+ />
+
+
+ );
+ }
+}
diff --git a/src-docs/src/views/list_group/list_group_links.js b/src-docs/src/views/list_group/list_group_links.js
new file mode 100644
index 00000000000..19ce6c909c8
--- /dev/null
+++ b/src-docs/src/views/list_group/list_group_links.js
@@ -0,0 +1,97 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiListGroup,
+ EuiSpacer,
+ EuiSwitch,
+ EuiCode,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '../../../../src/components';
+
+const myContent = [
+ {
+ label: 'First link',
+ href: '/#/display/list-group',
+ iconType: 'calendar',
+ size: 's',
+ },
+ {
+ label: 'Second link is active',
+ href: '/#/display/list-group',
+ isActive: true,
+ iconType: 'clock',
+ size: 's',
+ },
+ {
+ label: 'Third link is disabled',
+ href: '/#/display/list-group',
+ isDisabled: true,
+ iconType: 'compute',
+ size: 's',
+ },
+ {
+ label: 'Fourth link',
+ href: '/#/display/list-group',
+ iconType: 'copyClipboard',
+ size: 's',
+ },
+ {
+ label: 'Fifth link',
+ href: '/#/display/list-group',
+ iconType: 'crosshairs',
+ size: 's',
+ },
+];
+
+
+export default class extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ flushWidth: false,
+ showBorder: false,
+ };
+ }
+
+ toggleFlushWidth = () => {
+ this.setState(prevState => ({ flushWidth: !prevState.flushWidth }));
+ };
+
+ toggleBorder = () => {
+ this.setState(prevState => ({ showBorder: !prevState.showBorder }));
+ };
+
+ render() {
+ const {
+ flushWidth,
+ showBorder,
+ } = this.state;
+
+ return (
+
+
+
+ Show as flush }
+ checked={this.state.flushWidth}
+ onChange={this.toggleFlushWidth}
+ />
+
+
+ Show as bordered }
+ checked={this.state.showBorder}
+ onChange={this.toggleBorder}
+ />
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/components/index.js b/src/components/index.js
index e71d9a4f250..31bc138669d 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -197,6 +197,11 @@ export {
EuiLink,
} from './link';
+export {
+ EuiListGroup,
+ EuiListGroupItem,
+} from './list_group';
+
export {
EUI_MODAL_CANCEL_BUTTON,
EUI_MODAL_CONFIRM_BUTTON,
diff --git a/src/components/index.scss b/src/components/index.scss
index 1f059ce94af..333447b1ca0 100644
--- a/src/components/index.scss
+++ b/src/components/index.scss
@@ -33,6 +33,7 @@
@import 'image/index';
@import 'key_pad_menu/index';
@import 'link/index';
+@import 'list_group/index';
@import 'loading/index';
@import 'modal/index';
@import 'overlay_mask/index';
diff --git a/src/components/list_group/__snapshots__/list_group.test.js.snap b/src/components/list_group/__snapshots__/list_group.test.js.snap
new file mode 100644
index 00000000000..fbd22fd489e
--- /dev/null
+++ b/src/components/list_group/__snapshots__/list_group.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiListGroup is rendered 1`] = `
+
+`;
diff --git a/src/components/list_group/__snapshots__/list_group_item.test.js.snap b/src/components/list_group/__snapshots__/list_group_item.test.js.snap
new file mode 100644
index 00000000000..1b34c68b527
--- /dev/null
+++ b/src/components/list_group/__snapshots__/list_group_item.test.js.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiListGroupItem is rendered 1`] = `
+
+
+
+ Label
+
+
+
+`;
+
+exports[`EuiListGroupItem renders href 1`] = `
+
+
+
+ Label
+
+
+
+`;
diff --git a/src/components/list_group/_index.scss b/src/components/list_group/_index.scss
new file mode 100644
index 00000000000..ab5c9e1dd4c
--- /dev/null
+++ b/src/components/list_group/_index.scss
@@ -0,0 +1,4 @@
+// List group provides a way to neatly present a set of text-based items
+
+@import 'list_group';
+@import 'list_group_item';
diff --git a/src/components/list_group/_list_group.scss b/src/components/list_group/_list_group.scss
new file mode 100644
index 00000000000..9e8fa0373e3
--- /dev/null
+++ b/src/components/list_group/_list_group.scss
@@ -0,0 +1,25 @@
+/**
+ * The List Group component provides neatly styled lists containing plain text
+ * or links. The outer container can be bordered, with padding, or borderless
+ * with links flush to the sides.
+ *(
+ * List items can be displayed with active and disabled states.
+ */
+
+.euiListGroup {
+ padding: $euiSizeS;
+
+ &.euiListGroup-flush {
+ padding: 0;
+ border: none;
+ }
+
+ &.euiListGroup-bordered {
+ border-radius: $euiBorderRadius;
+ border: $euiBorderThin;
+ }
+}
+
+.euiListGroup-maxWidthDefault {
+ max-width: $euiFormMaxWidth;
+}
diff --git a/src/components/list_group/_list_group_item.scss b/src/components/list_group/_list_group_item.scss
new file mode 100644
index 00000000000..1177f140f59
--- /dev/null
+++ b/src/components/list_group/_list_group_item.scss
@@ -0,0 +1,97 @@
+.euiListGroupItem {
+ padding: 0;
+ margin-top: $euiSizeXS;
+ border-radius: $euiBorderRadius;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ transition: background-color $euiAnimSpeedFast;
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &.euiListGroupItem-isActive,
+ &.euiListGroupItem-isClickable:hover,
+ &.euiListGroupItem-isClickable:focus {
+ background-color: tintOrShade($euiColorLightestShade, 0%, 30%);
+ }
+
+ &.euiListGroupItem-isClickable:hover,
+ &.euiListGroupItem-isClickable:focus {
+ text-decoration: underline;
+ }
+
+ // Style all disabled list items whether or not they are links or buttons
+ &.euiListGroupItem-isDisabled,
+ &.euiListGroupItem-isDisabled:hover,
+ &.euiListGroupItem-isDisabled:focus,
+ &.euiListGroupItem-isDisabled .euiListGroupItem__button:hover,
+ &.euiListGroupItem-isDisabled .euiListGroupItem__button:focus {
+ color: $euiButtonColorDisabled;
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+ }
+}
+
+.euiListGroupItem__text,
+.euiListGroupItem__button {
+ padding: $euiSizeM $euiSizeS;
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+ text-align: left;
+}
+
+.euiListGroupItem__button:focus {
+ text-decoration: underline;
+}
+
+.euiListGroupItem__extraAction {
+ opacity: 0;
+ margin-right: $euiSizeS;
+ transition: opacity $euiAnimSpeedFast;
+
+ .euiListGroupItem:not(.euiListGroupItem-isDisabled):focus &,
+ .euiListGroupItem:not(.euiListGroupItem-isDisabled):hover &,
+ &.euiListGroupItem__extraAction-alwaysShow,
+ &:focus {
+ opacity: 1;
+ }
+}
+
+.euiListGroupItem__icon {
+ flex-grow: 0;
+ margin-right: $euiSizeM;
+}
+
+.euiListGroupItem--xSmall {
+ font-size: $euiFontSizeXS;
+}
+
+.euiListGroupItem--small {
+ font-size: $euiFontSizeS;
+}
+
+.euiListGroupItem--large {
+ font-size: $euiFontSizeL;
+}
+
+// Layout alts from euiListGroup
+
+.euiListGroup-flush .euiListGroupItem {
+ border-radius: 0;
+}
+
+.euiListGroup-bordered .euiListGroupItem {
+ &:first-child {
+ border-top-left-radius: $euiBorderRadius;
+ border-top-right-radius: $euiBorderRadius;
+ }
+
+ &:last-child {
+ border-bottom-left-radius: $euiBorderRadius;
+ border-bottom-right-radius: $euiBorderRadius;
+ }
+}
diff --git a/src/components/list_group/index.d.ts b/src/components/list_group/index.d.ts
new file mode 100644
index 00000000000..f000d1953ed
--- /dev/null
+++ b/src/components/list_group/index.d.ts
@@ -0,0 +1,16 @@
+import { CommonProps } from '../common';
+import { SFC } from 'react';
+
+declare module '@elastic/eui' {
+ /**
+ * list group type defs
+ *
+ * @see './list_group.js'
+ */
+
+ type EuiListGroupProps = CommonProps & {
+
+ };
+
+ export const EuiListGroup: SFC;
+}
diff --git a/src/components/list_group/index.js b/src/components/list_group/index.js
new file mode 100644
index 00000000000..6a4e6a4ef5c
--- /dev/null
+++ b/src/components/list_group/index.js
@@ -0,0 +1,7 @@
+export {
+ EuiListGroup,
+} from './list_group';
+
+export {
+ EuiListGroupItem,
+} from './list_group_item';
diff --git a/src/components/list_group/list_group.js b/src/components/list_group/list_group.js
new file mode 100644
index 00000000000..d35c62ff1f6
--- /dev/null
+++ b/src/components/list_group/list_group.js
@@ -0,0 +1,106 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import {
+ EuiListGroupItem,
+} from './list_group_item';
+
+export const EuiListGroup = ({
+ children,
+ className,
+ flush,
+ bordered,
+ listItems,
+ maxWidth,
+ style,
+ ...rest,
+}) => {
+
+ let newStyle;
+ let widthClassName;
+ if (maxWidth !== true) {
+ const value = typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth;
+ newStyle = { ...style, maxWidth: value };
+ } else if (maxWidth === true) {
+ widthClassName = 'euiListGroup-maxWidthDefault';
+ }
+
+ const classes = classNames(
+ 'euiListGroup',
+ {
+ 'euiListGroup-flush': flush,
+ 'euiListGroup-bordered': bordered,
+ },
+ widthClassName,
+ className
+ );
+
+ let childrenOrListItems = null;
+ if (listItems) {
+ childrenOrListItems = (
+ listItems.map((item, index) => {
+ return [
+
+ ];
+ })
+ );
+ } else {
+ childrenOrListItems = children;
+ }
+
+ return (
+
+ {childrenOrListItems}
+
+ );
+};
+
+EuiListGroup.propTypes = {
+ listItems: PropTypes.arrayOf(PropTypes.shape({
+ label: PropTypes.node,
+ href: PropTypes.string,
+ extraAction: PropTypes.node,
+ iconType: PropTypes.string,
+ isActive: PropTypes.boolean,
+ isDisabled: PropTypes.boolean,
+ })),
+ children: PropTypes.node,
+ className: PropTypes.string,
+
+ /**
+ * Remove container padding, stretching list items to the edges
+ */
+ flush: PropTypes.bool,
+
+ /**
+ * Add a border to the list container
+ */
+ bordered: PropTypes.bool,
+
+ /**
+ * Sets the max-width of the page,
+ * set to `true` to use the default size,
+ * set to `false` to not restrict the width,
+ * set to a number for a custom width in px,
+ * set to a string for a custom width in custom measurement.
+ */
+ maxWidth: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+};
+
+EuiListGroup.defaultProps = {
+ flush: false,
+ bordered: false,
+ maxWidth: true,
+};
diff --git a/src/components/list_group/list_group.test.js b/src/components/list_group/list_group.test.js
new file mode 100644
index 00000000000..4e0130b35c5
--- /dev/null
+++ b/src/components/list_group/list_group.test.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiListGroup } from './list_group';
+
+describe('EuiListGroup', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+});
diff --git a/src/components/list_group/list_group_item.js b/src/components/list_group/list_group_item.js
new file mode 100644
index 00000000000..f3167d5d097
--- /dev/null
+++ b/src/components/list_group/list_group_item.js
@@ -0,0 +1,155 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import { EuiButtonIcon } from '../button';
+import { ICON_TYPES, EuiIcon } from '../icon';
+
+const sizeToClassNameMap = {
+ xs: 'euiListGroupItem--xSmall',
+ s: 'euiListGroupItem--small',
+ m: 'euiListGroupItem--medium',
+ l: 'euiListGroupItem--large',
+};
+
+export const SIZES = Object.keys(sizeToClassNameMap);
+
+export const EuiListGroupItem = ({
+ label,
+ isActive,
+ isDisabled,
+ href,
+ className,
+ iconType,
+ extraAction,
+ onClick,
+ size,
+ ...rest
+}) => {
+ const classes = classNames(
+ 'euiListGroupItem',
+ sizeToClassNameMap[size],
+ {
+ 'euiListGroupItem-isActive': isActive,
+ 'euiListGroupItem-isDisabled': isDisabled,
+ 'euiListGroupItem-isClickable': href || onClick,
+ },
+ className
+ );
+
+ let iconNode;
+
+ if (iconType) {
+ iconNode = (
+
+ );
+ }
+
+ let extraActionNode;
+
+ if (extraAction) {
+ const {
+ iconType,
+ alwaysShow,
+ ...rest
+ } = extraAction;
+
+ const extraActionClasses = classNames(
+ 'euiListGroupItem__extraAction',
+ { 'euiListGroupItem__extraAction-alwaysShow': alwaysShow }
+ );
+
+ extraActionNode = (
+
+ );
+ }
+
+ // Handle the variety of interaction behavior
+ let itemContent;
+
+ if (href && !isDisabled) {
+ itemContent = (
+
+ {iconNode}
+ {label}
+
+ );
+ } else if ((href && isDisabled) || onClick) {
+ itemContent = (
+
+ {iconNode}
+ {label}
+
+ );
+ } else {
+ itemContent = (
+
+ {iconNode}
+ {label}
+
+ );
+ }
+
+ return (
+
+ {itemContent}
+ {extraActionNode}
+
+ );
+};
+
+EuiListGroupItem.propTypes = {
+ className: PropTypes.string,
+
+ /**
+ * Set the size of the label text
+ */
+ size: PropTypes.oneOf(SIZES),
+
+ /**
+ * Content to be displyed in the list item
+ */
+ label: PropTypes.node.isRequired,
+
+ /**
+ * Apply styles indicating an item is active
+ */
+ isActive: PropTypes.bool,
+
+ /**
+ * Apply styles indicating an item is disabled
+ */
+ isDisabled: PropTypes.bool,
+
+ /**
+ * Make the list item label a link
+ */
+ href: PropTypes.string,
+
+ /**
+ * See `EuiIcon`
+ */
+ iconType: PropTypes.oneOf(ICON_TYPES),
+
+ /**
+ * Adds an `EuiButtonIcon` to the right side of the item; `iconType` is required;
+ * pass `alwaysShow` if you don't want the default behavior of only showing on hover
+ */
+ extraAction: PropTypes.shape({
+ iconType: PropTypes.oneOf(ICON_TYPES).isRequired,
+ alwaysShow: PropTypes.bool,
+ }),
+
+ onClick: PropTypes.func,
+};
+
+EuiListGroupItem.defaultProps = {
+ isActive: false,
+ isDisabled: false,
+ size: 'm',
+};
diff --git a/src/components/list_group/list_group_item.test.js b/src/components/list_group/list_group_item.test.js
new file mode 100644
index 00000000000..5d9fbd47a21
--- /dev/null
+++ b/src/components/list_group/list_group_item.test.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { render } from 'enzyme';
+
+import {
+ EuiListGroupItem,
+} from './list_group_item';
+
+describe('EuiListGroupItem', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+
+ test('renders href', () => {
+ const component = render(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+});