diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12500a307e1..6169f16426b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
- Added support for `href`, `onClick`, and related props in `EuiBasicTable` default actions ([#3115](https://github.com/elastic/eui/pull/3115))
- Added support for `EuiCodeEditor` to set `readonly` and `id` on `` ([#3212](https://github.com/elastic/eui/pull/3212))
+- Added `EuiComment` component ([#3179](https://github.com/elastic/eui/pull/3179))
**Deprecation**
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js
index e73038cd1ac..dca74ea2328 100644
--- a/src-docs/src/routes.js
+++ b/src-docs/src/routes.js
@@ -69,6 +69,8 @@ import { ColorPickerExample } from './views/color_picker/color_picker_example';
import { ComboBoxExample } from './views/combo_box/combo_box_example';
+import { CommentExample } from './views/comment/comment_example';
+
import { ContextMenuExample } from './views/context_menu/context_menu_example';
import { ControlBarExample } from './views/control_bar/control_bar_example';
@@ -364,6 +366,7 @@ const navigation = [
CallOutExample,
CardExample,
CodeExample,
+ CommentExample,
DescriptionListExample,
DragAndDropExample,
EmptyPromptExample,
diff --git a/src-docs/src/views/comment/comment.tsx b/src-docs/src/views/comment/comment.tsx
new file mode 100644
index 00000000000..e4260300150
--- /dev/null
+++ b/src-docs/src/views/comment/comment.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { EuiComment } from '../../../../src/components/comment';
+import { EuiAvatar } from '../../../../src/components/avatar';
+import { EuiBadge } from '../../../../src/components/badge';
+import { EuiFlexGroup, EuiFlexItem } from '../../../../src/components/flex';
+import { EuiButtonIcon } from '../../../../src/components/button';
+import { EuiText } from '../../../../src/components/text';
+
+const body = (
+
+
+ Far out in the uncharted backwaters of the unfashionable end of the
+ western spiral arm of the Galaxy lies a small unregarded yellow sun.
+
+
+);
+
+const longBody = (
+
+
+ This planet has - or rather had - a problem, which was this: most of the
+ people living on it were unhappy for pretty much of the time. Many
+ solutions were suggested for this problem, but most of these were largely
+ concerned with the movements of small green pieces of paper, which is odd
+ because on the whole it was not the small green pieces of paper that were
+ unhappy.
+
+
+);
+
+const copyAction = (
+
+);
+
+export default () => (
+
+
+ {body}
+
+
+ }
+ />
+
+
+
+
+
+ pedror
+
+ }
+ type="update"
+ event={
+
+ added tags
+
+ sample
+
+
+ review
+
+
+ }
+ timestamp="on Jan 11, 2020"
+ timelineIcon="tag"
+ />
+ }>
+ {longBody}
+
+
+);
diff --git a/src-docs/src/views/comment/comment_actions.tsx b/src-docs/src/views/comment/comment_actions.tsx
new file mode 100644
index 00000000000..5ebf1cb178d
--- /dev/null
+++ b/src-docs/src/views/comment/comment_actions.tsx
@@ -0,0 +1,103 @@
+import React, { Component, HTMLAttributes } from 'react';
+import { EuiComment } from '../../../../src/components/comment';
+import { EuiButtonIcon } from '../../../../src/components/button';
+import { EuiText } from '../../../../src/components/text';
+import { EuiPopover } from '../../../../src/components/popover';
+import {
+ EuiContextMenuPanel,
+ EuiContextMenuItem,
+} from '../../../../src/components/context_menu';
+import { CommonProps } from '../../../../src/components/common';
+
+const body = (
+
+
+ This comment has custom actions available. See the upper right corner.
+
+
+);
+
+export type CustomActionsProps = HTMLAttributes &
+ CommonProps & {};
+
+interface CustomActionsState {
+ isPopoverOpen: boolean;
+}
+
+export default class extends Component {
+ state = {
+ isPopoverOpen: false,
+ };
+
+ togglePopover = () => {
+ this.setState(prevState => ({
+ isPopoverOpen: !prevState.isPopoverOpen,
+ }));
+ };
+
+ closePopover = () => {
+ this.setState({
+ isPopoverOpen: false,
+ });
+ };
+
+ render() {
+ const { isPopoverOpen } = this.state;
+ const customActions = (
+ this.togglePopover()}
+ />
+ }
+ isOpen={isPopoverOpen}
+ closePopover={() => this.closePopover()}
+ panelPaddingSize="none"
+ anchorPosition="leftCenter">
+ {
+ this.closePopover();
+ }}>
+ Edit
+ ,
+ {
+ this.closePopover();
+ }}>
+ Share
+ ,
+ {
+ this.closePopover();
+ }}>
+ Copy
+ ,
+ ]}
+ />
+
+ );
+ return (
+
+
+ {body}
+
+
+ );
+ }
+}
diff --git a/src-docs/src/views/comment/comment_example.js b/src-docs/src/views/comment/comment_example.js
new file mode 100644
index 00000000000..c112a2b91cd
--- /dev/null
+++ b/src-docs/src/views/comment/comment_example.js
@@ -0,0 +1,216 @@
+import React from 'react';
+
+import { Link } from 'react-router';
+
+import { renderToHtml } from '../../services';
+
+import { GuideSectionTypes } from '../../components';
+
+import { EuiCode, EuiComment } from '../../../../src/components';
+
+import Comment from './comment';
+const commentSource = require('!!raw-loader!./comment');
+const commentHtml = renderToHtml(Comment);
+
+import CommentTypes from './comment_types';
+const commentTypesSource = require('!!raw-loader!./comment_types');
+const commentTypesHtml = renderToHtml(CommentTypes);
+
+import CommentTimelineIcons from './comment_timelineIcons';
+const commentTimelineIconsSource = require('!!raw-loader!./comment_timelineIcons');
+const commentTimelineIconsHtml = renderToHtml(CommentTimelineIcons);
+
+import CommentActions from './comment_actions';
+const commentActionsSource = require('!!raw-loader!./comment_actions');
+const commentActionsHtml = renderToHtml(CommentActions);
+
+const commentSnippet = `
+ {body}
+ `;
+
+const commentTypesSnippet = [
+ `
+ {body}
+
+`,
+ `
+`,
+ `
+ {body}
+
+`,
+];
+
+const commentTimelineIconsSnippet = [
+ `
+ {body}
+
+`,
+ `
+`,
+ `
+ } username="janed">
+ {body}
+
+`,
+];
+
+const commentActionsSnippet = `
+ {body}
+ `;
+
+export const CommentExample = {
+ title: 'Comment',
+ sections: [
+ {
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: commentSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: commentHtml,
+ },
+ ],
+ text: (
+
+
+ Use EuiComment to display comments. Each{' '}
+ EuiComment has two parts: a{' '}
+ timelineIcon on the left and content on the
+ right. The timelineIcon provides a visual
+ indication of the type of comment it is. For
+ example, it can be an icon that represents what action was performed
+ or it can be a user avatar. The content has a header with all the
+ relevant metadata and a body.
+
+
+ ),
+ props: { EuiComment },
+ snippet: commentSnippet,
+ demo: ,
+ },
+ {
+ title: 'Comment types',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: commentTypesSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: commentTypesHtml,
+ },
+ ],
+ text: (
+
+
+ The default type of comment is
+ regular and displays a comment that a user has
+ written.
+
+
+ Change the type to update to display comments
+ that generally do not have a body and are logging actions that
+ either the user or the system has performed (e.g. “jsmith
+ edited a case” or “kibanamachine added the review
+ label”).
+
+
+ ),
+ props: { EuiComment },
+ snippet: commentTypesSnippet,
+ demo: ,
+ },
+ {
+ title: 'Custom timeline icon',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: commentTimelineIconsSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: commentTimelineIconsHtml,
+ },
+ ],
+ text: (
+
+
+ There are three ways to use timelineIcon :
+
+
+
+ Use the defaults; a user icon inside a large container for
+ regular comments; or a dot icon inside a small
+ container for update comments.
+
+
+ Pass a string with any of the icon types that{' '}
+ EuiIcon supports and it will receive the default
+ styling.
+
+
+ Pass any other element (e.g.{' '}
+
+ EuiAvatar
+
+ ). It is recommended not to use an element larger than 40x40.
+
+
+
+ ),
+ props: { EuiComment },
+ snippet: commentTimelineIconsSnippet,
+ demo: ,
+ },
+ {
+ title: 'Actions',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: commentActionsSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: commentActionsHtml,
+ },
+ ],
+ text: (
+
+
+ There are scenarios where you might want to allow the user to
+ perform actions related to each comment. Some
+ common actions include: editing, deleting,
+ sharing and copying. To add custom actions to a
+ comment, use the actions
+ prop. These will be placed to the right of the metadata in the
+ comment's header. You can use any element to display{' '}
+ actions . For example, for something simple you
+ can use{' '}
+
+ EuiButtonIcon
+ {' '}
+ and for something more complex you can combine that with{' '}
+
+ EuiPopover
+ {' '}
+ and{' '}
+
+ EuiContextMenu
+
+ .
+
+
+ ),
+ props: { EuiComment },
+ snippet: commentActionsSnippet,
+ demo: ,
+ },
+ ],
+};
diff --git a/src-docs/src/views/comment/comment_timelineIcons.tsx b/src-docs/src/views/comment/comment_timelineIcons.tsx
new file mode 100644
index 00000000000..99ce2e0c4e6
--- /dev/null
+++ b/src-docs/src/views/comment/comment_timelineIcons.tsx
@@ -0,0 +1,68 @@
+import React, { Fragment } from 'react';
+import { EuiComment } from '../../../../src/components/comment';
+import { EuiText } from '../../../../src/components/text';
+import { EuiAvatar } from '../../../../src/components/avatar';
+import { EuiCode } from '../../../../src/components/code';
+
+const defaultBody = (
+
+
+ This comment and the one below are using the default{' '}
+ timelineIcon .
+
+
+);
+
+const iconStringBody = (
+
+
+ This comment passed the string “tag” to the{' '}
+ timelineIcon prop.
+
+
+);
+
+const customIconBody = (
+
+
+ This comment has a custom element as its timelineIcon .
+
+
+);
+
+export default () => (
+
+
+ {defaultBody}
+
+
+
+ {iconStringBody}
+
+
+ }>
+ {customIconBody}
+
+
+);
diff --git a/src-docs/src/views/comment/comment_types.tsx b/src-docs/src/views/comment/comment_types.tsx
new file mode 100644
index 00000000000..1ebd29824e6
--- /dev/null
+++ b/src-docs/src/views/comment/comment_types.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { EuiComment } from '../../../../src/components/comment';
+import { EuiText } from '../../../../src/components/text';
+import { EuiCode } from '../../../../src/components/code';
+
+const body = (
+
+
+ This is the body of a comment of type regular
+
+
+);
+
+const bodyUpdate = (
+
+
+ Comments of type update can also have a body
+
+
+);
+
+export default () => (
+
+
+ {body}
+
+
+
+ {bodyUpdate}
+
+
+);
diff --git a/src/components/comment/__snapshots__/comment.test.tsx.snap b/src/components/comment/__snapshots__/comment.test.tsx.snap
new file mode 100644
index 00000000000..a2293959a2c
--- /dev/null
+++ b/src/components/comment/__snapshots__/comment.test.tsx.snap
@@ -0,0 +1,280 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiComment is rendered 1`] = `
+
+`;
+
+exports[`EuiComment props event is rendered 1`] = `
+
+`;
+
+exports[`EuiComment props timelineIcon is rendered 1`] = `
+
+`;
+
+exports[`EuiComment props timestamp is rendered 1`] = `
+
+`;
+
+exports[`EuiComment props type is rendered 1`] = `
+
+`;
+
+exports[`EuiComment renders a body 1`] = `
+
+
+
+
+`;
diff --git a/src/components/comment/__snapshots__/comment_event.test.tsx.snap b/src/components/comment/__snapshots__/comment_event.test.tsx.snap
new file mode 100644
index 00000000000..8c90835a202
--- /dev/null
+++ b/src/components/comment/__snapshots__/comment_event.test.tsx.snap
@@ -0,0 +1,102 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiCommentEvent is rendered 1`] = `
+
+`;
+
+exports[`EuiCommentEvent props event is rendered 1`] = `
+
+`;
+
+exports[`EuiCommentEvent props timestamp is rendered 1`] = `
+
+`;
+
+exports[`EuiCommentEvent props type is rendered 1`] = `
+
+`;
diff --git a/src/components/comment/__snapshots__/comment_timeline.test.tsx.snap b/src/components/comment/__snapshots__/comment_timeline.test.tsx.snap
new file mode 100644
index 00000000000..7da312647b8
--- /dev/null
+++ b/src/components/comment/__snapshots__/comment_timeline.test.tsx.snap
@@ -0,0 +1,64 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiCommentTimeline is rendered 1`] = `
+
+`;
+
+exports[`EuiCommentTimeline props timelineIcon is rendered 1`] = `
+
+`;
+
+exports[`EuiCommentTimeline props type is rendered 1`] = `
+
+`;
diff --git a/src/components/comment/_comment.scss b/src/components/comment/_comment.scss
new file mode 100644
index 00000000000..7b94de4fb61
--- /dev/null
+++ b/src/components/comment/_comment.scss
@@ -0,0 +1,38 @@
+.euiComment {
+ font-size: $euiFontSizeS;
+ display: flex;
+ padding-bottom: $euiSize;
+ min-height: $euiSize * 3.5;
+
+ .euiCommentEvent {
+ flex-grow: 1;
+ }
+
+ .euiCommentTimeline {
+ position: relative;
+ flex-grow: 0;
+ margin-right: $euiSize;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: $euiSizeXXL / 2;
+ top: $euiSizeL;
+ width: $euiSizeXS / 2;
+ background-color: $euiColorLightShade;
+ height: calc(100% + #{$euiSizeL});
+ }
+ }
+}
+
+.euiComment:last-of-type {
+ .euiCommentTimeline {
+ &::before {
+ display: none;
+ }
+ }
+}
+
+.euiComment--update:not(.euiComment--hasBody) {
+ align-items: center;
+}
\ No newline at end of file
diff --git a/src/components/comment/_comment_event.scss b/src/components/comment/_comment_event.scss
new file mode 100644
index 00000000000..e6ccfd851d8
--- /dev/null
+++ b/src/components/comment/_comment_event.scss
@@ -0,0 +1,75 @@
+@include euiPanel($selector: '.euiCommentEvent--regular');
+
+.euiCommentEvent {
+ overflow: hidden;
+}
+
+.euiCommentEvent__header {
+ line-height: $euiLineHeight;
+ display: flex;
+ align-items: center;
+}
+
+.euiCommentEvent__headerData {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+
+ > div {
+ padding-right: $euiSizeXS;
+ }
+}
+
+.euiCommentEvent__headerUsername {
+ font-weight: $euiFontWeightSemiBold;
+}
+
+.euiCommentEvent--regular {
+ border: $euiBorderThin;
+
+ .euiCommentEvent__header {
+ min-height: $euiSizeXXL;
+ background-color: $euiColorLightestShade;
+ border-bottom: $euiBorderThin;
+ padding: $euiSizeXS $euiSizeS;
+
+ /**
+ * Fix for IE when using align-items:center in an item that has min-height
+ (https://github.com/philipwalton/flexbugs/issues/231#issuecomment-362790042)
+ */
+ // sass-lint:disable-block mixins-before-declarations
+ @include internetExplorerOnly {
+ &::after {
+ content: '';
+ // Calculates the minimum height based on full header's min-height minus the vertical padding
+ min-height: $euiSizeXXL - $euiSizeS;
+ font-size: 0;
+ display: block;
+ }
+ }
+ }
+
+ .euiCommentEvent__headerData {
+ // Push the actions far right
+ flex-grow: 1;
+ }
+
+ .euiCommentEvent__body {
+ padding: $euiSizeS;
+ }
+}
+
+.euiCommentEvent--update {
+ .euiCommentEvent__header {
+ justify-content: flex-start;
+ padding: $euiSizeXS 0;
+ }
+
+ .euiCommentEvent__headerData {
+ padding-right: $euiSizeS;
+ }
+
+ .euiCommentEvent__body {
+ padding-top: $euiSizeXS;
+ }
+}
diff --git a/src/components/comment/_comment_timeline.scss b/src/components/comment/_comment_timeline.scss
new file mode 100644
index 00000000000..18ea3d40acf
--- /dev/null
+++ b/src/components/comment/_comment_timeline.scss
@@ -0,0 +1,27 @@
+.euiCommentTimeline__content {
+ min-width: $euiSizeXXL;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+}
+
+.euiCommentTimeline__icon--default {
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow-x: hidden;
+ border-radius: 50%;
+ background-color: $euiColorLightestShade;
+
+ &.euiCommentTimeline__icon--regular {
+ width: $euiSizeXXL;
+ height: $euiSizeXXL;
+ }
+
+ &.euiCommentTimeline__icon--update {
+ width: $euiSizeL;
+ height: $euiSizeL;
+ }
+}
\ No newline at end of file
diff --git a/src/components/comment/_index.scss b/src/components/comment/_index.scss
new file mode 100644
index 00000000000..78e82990cb1
--- /dev/null
+++ b/src/components/comment/_index.scss
@@ -0,0 +1,3 @@
+@import 'comment';
+@import 'comment_event';
+@import 'comment_timeline';
diff --git a/src/components/comment/comment.test.tsx b/src/components/comment/comment.test.tsx
new file mode 100644
index 00000000000..346383117db
--- /dev/null
+++ b/src/components/comment/comment.test.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiComment } from './comment';
+import { EuiAvatar } from '../avatar';
+
+describe('EuiComment', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ describe('props', () => {
+ describe('type', () => {
+ it('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('timelineIcon', () => {
+ it('is rendered', () => {
+ const component = render(
+ }
+ />
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('timestamp', () => {
+ it('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('event', () => {
+ it('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+ });
+
+ test('renders a body', () => {
+ const component = render(
+
+ This is the body.
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/components/comment/comment.tsx b/src/components/comment/comment.tsx
new file mode 100644
index 00000000000..15e12d8dccb
--- /dev/null
+++ b/src/components/comment/comment.tsx
@@ -0,0 +1,51 @@
+import React, { FunctionComponent, HTMLAttributes } from 'react';
+import classNames from 'classnames';
+
+import { EuiCommentEvent, EuiCommentEventProps } from './comment_event';
+import {
+ EuiCommentTimeline,
+ EuiCommentTimelineProps,
+} from './comment_timeline';
+
+export interface EuiCommentProps
+ extends HTMLAttributes,
+ EuiCommentEventProps,
+ EuiCommentTimelineProps {}
+
+const typeToClassNameMap = {
+ regular: '',
+ update: 'euiComment--update',
+};
+
+export const EuiComment: FunctionComponent = ({
+ children,
+ className,
+ username,
+ event,
+ actions,
+ timelineIcon,
+ type = 'regular',
+ timestamp,
+ ...rest
+}) => {
+ const classes = classNames(
+ 'euiComment',
+ typeToClassNameMap[type],
+ { 'euiComment--hasBody': children },
+ className
+ );
+
+ return (
+
+
+
+ {children}
+
+
+ );
+};
diff --git a/src/components/comment/comment_event.test.tsx b/src/components/comment/comment_event.test.tsx
new file mode 100644
index 00000000000..96dd8f7dca0
--- /dev/null
+++ b/src/components/comment/comment_event.test.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiCommentEvent } from './comment_event';
+
+describe('EuiCommentEvent', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ describe('props', () => {
+ describe('type', () => {
+ it('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('timestamp', () => {
+ it('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('event', () => {
+ it('is rendered', () => {
+ const component = render(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/src/components/comment/comment_event.tsx b/src/components/comment/comment_event.tsx
new file mode 100644
index 00000000000..9feb2f0d36e
--- /dev/null
+++ b/src/components/comment/comment_event.tsx
@@ -0,0 +1,85 @@
+import React, { FunctionComponent, ReactNode } from 'react';
+import { CommonProps, keysOf } from '../common';
+import classNames from 'classnames';
+
+export interface EuiCommentEventProps extends CommonProps {
+ /**
+ * Author of the comment. Display a small icon or avatar with it if needed.
+ */
+ username: ReactNode;
+ /**
+ * Time of occurrence of the event. Its format is set on the consumer's side
+ */
+ timestamp?: ReactNode;
+ /**
+ * Describes the event that took place
+ */
+ event?: ReactNode;
+ /**
+ * Custom actions that the user can perform from the comment's header
+ */
+ actions?: ReactNode;
+ /**
+ * Use "update" when the comment is primarily showing info about actions that the user or the system has performed (e.g. "user1 edited a case").
+ */
+ type?: EuiCommentType;
+}
+
+const typeToClassNameMap = {
+ regular: 'euiCommentEvent--regular',
+ update: 'euiCommentEvent--update',
+};
+
+export const TYPES = keysOf(typeToClassNameMap);
+export type EuiCommentType = keyof typeof typeToClassNameMap;
+
+export const EuiCommentEvent: FunctionComponent = ({
+ children,
+ className,
+ username,
+ timestamp,
+ type = 'regular',
+ event,
+ actions,
+}) => {
+ const classes = classNames(
+ 'euiCommentEvent',
+ typeToClassNameMap[type],
+ className
+ );
+
+ const isFigure =
+ type === 'regular' ||
+ (type === 'update' && typeof children !== 'undefined');
+
+ const Element = isFigure ? 'figure' : 'div';
+ const HeaderElement = isFigure ? 'figcaption' : 'div';
+
+ return (
+
+
+
+
{username}
+
{event}
+ {timestamp ? (
+
+ {timestamp}
+
+ ) : (
+ undefined
+ )}
+
+ {actions ? (
+ {actions}
+ ) : (
+ undefined
+ )}
+
+ {children ? (
+ {children}
+ ) : (
+ undefined
+ )}
+
+ );
+};
diff --git a/src/components/comment/comment_timeline.test.tsx b/src/components/comment/comment_timeline.test.tsx
new file mode 100644
index 00000000000..0d508c16452
--- /dev/null
+++ b/src/components/comment/comment_timeline.test.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { render } from 'enzyme';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiCommentTimeline } from './comment_timeline';
+import { EuiAvatar } from '../avatar';
+
+describe('EuiCommentTimeline', () => {
+ test('is rendered', () => {
+ const component = render( );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ describe('props', () => {
+ describe('type', () => {
+ it('is rendered', () => {
+ const component = render( );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('timelineIcon', () => {
+ it('is rendered', () => {
+ const component = render(
+ }
+ />
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/src/components/comment/comment_timeline.tsx b/src/components/comment/comment_timeline.tsx
new file mode 100644
index 00000000000..e902315fe7b
--- /dev/null
+++ b/src/components/comment/comment_timeline.tsx
@@ -0,0 +1,59 @@
+import React, { FunctionComponent, ReactNode } from 'react';
+import { CommonProps, keysOf } from '../common';
+import classNames from 'classnames';
+import { EuiIcon, IconType } from '../icon';
+
+export interface EuiCommentTimelineProps extends CommonProps {
+ /**
+ * Main icon that accompanies the comment. The default is `user` for regular comments and `dot` for update comments. To customize, pass a `string` as an `EuiIcon['type']` or any `ReactNode`.
+ */
+ timelineIcon?: ReactNode | IconType;
+ type?: EuiCommentType;
+}
+
+const typeToClassNameMap = {
+ regular: 'euiCommentTimeline__icon--regular',
+ update: 'euiCommentTimeline__icon--update',
+};
+
+export const TYPES = keysOf(typeToClassNameMap);
+export type EuiCommentType = keyof typeof typeToClassNameMap;
+
+export const EuiCommentTimeline: FunctionComponent = ({
+ className,
+ timelineIcon,
+ type = 'regular',
+}) => {
+ const classes = classNames('euiCommentTimeline', className);
+ const iconClasses = classNames(
+ {
+ 'euiCommentTimeline__icon--default':
+ !timelineIcon || typeof timelineIcon === 'string',
+ },
+ typeToClassNameMap[type]
+ );
+
+ let iconRender;
+ if (typeof timelineIcon === 'string') {
+ iconRender = (
+
+ );
+ } else if (timelineIcon) {
+ iconRender = timelineIcon;
+ } else {
+ iconRender = (
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/src/components/comment/index.ts b/src/components/comment/index.ts
new file mode 100644
index 00000000000..23f3dc8bdb5
--- /dev/null
+++ b/src/components/comment/index.ts
@@ -0,0 +1,5 @@
+export { EuiComment, EuiCommentProps } from './comment';
+
+export { EuiCommentEvent, EuiCommentType } from './comment_event';
+
+export { EuiCommentTimeline } from './comment_timeline';
diff --git a/src/components/index.js b/src/components/index.js
index 9dcc3991767..c67f4bb976b 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -49,6 +49,8 @@ export {
export { EuiComboBox } from './combo_box';
+export { EuiComment } from './comment';
+
export { EuiContext, EuiI18nConsumer } from './context';
export {
diff --git a/src/components/index.scss b/src/components/index.scss
index dce5687119d..68c1942e319 100644
--- a/src/components/index.scss
+++ b/src/components/index.scss
@@ -17,6 +17,7 @@
@import 'collapsible_nav/index';
@import 'color_picker/index';
@import 'combo_box/index';
+@import 'comment/index';
@import 'context_menu/index';
@import 'control_bar/index';
@import 'date_picker/index';