+
+
+
+
+
+ janet@elastic.co
+
+ was invited to the project
+
+
+
+
+
+
+
+
+ janet@elastic.co
+
+ was invited to the project
+
+
+
+
+`;
diff --git a/src/components/timeline/__snapshots__/timeline_item.test.tsx.snap b/src/components/timeline/__snapshots__/timeline_item.test.tsx.snap
new file mode 100644
index 00000000000..5d60f1d07cf
--- /dev/null
+++ b/src/components/timeline/__snapshots__/timeline_item.test.tsx.snap
@@ -0,0 +1,170 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiTimelineItem is rendered 1`] = `
+
+`;
+
+exports[`EuiTimelineItem props EuiAvatar is passed as an icon 1`] = `
+
+`;
+
+exports[`EuiTimelineItem props iconAriaLabel is rendered 1`] = `
+
+
+
+
+
+ icon aria label
+
+
+
+
+
+
+`;
+
+exports[`EuiTimelineItem props verticalAlign center is rendered 1`] = `
+
+`;
+
+exports[`EuiTimelineItem props verticalAlign top is rendered 1`] = `
+
+`;
diff --git a/src/components/timeline/index.ts b/src/components/timeline/index.ts
new file mode 100644
index 00000000000..bd23b8eb59c
--- /dev/null
+++ b/src/components/timeline/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export type { EuiTimelineProps } from './timeline';
+export { EuiTimeline } from './timeline';
+
+export type {
+ EuiTimelineItemProps,
+ EuiTimelineItemVerticalAlign,
+} from './timeline_item';
+export { EuiTimelineItem } from './timeline_item';
+
+export { EuiTimelineItemEvent } from './timeline_item_event';
+
+export { EuiTimelineItemIcon } from './timeline_item_icon';
diff --git a/src/components/timeline/timeline.test.tsx b/src/components/timeline/timeline.test.tsx
new file mode 100644
index 00000000000..224b1dcb3d9
--- /dev/null
+++ b/src/components/timeline/timeline.test.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { render } from 'enzyme';
+import { EuiAvatar } from '../avatar';
+import { EuiTimeline, EuiTimelineProps } from './timeline';
+
+describe('EuiTimeline', () => {
+ test('is rendered with items', () => {
+ const items: EuiTimelineProps['items'] = [
+ {
+ icon:
,
+ verticalAlign: 'center',
+ children: (
+
+ janet@elastic.co was invited to the project
+
+ ),
+ },
+ {
+ icon: 'bolt',
+ verticalAlign: 'top',
+ children: (
+
+ janet@elastic.co was invited to the project
+
+ ),
+ },
+ ];
+
+ const component = render(
);
+
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/components/timeline/timeline.tsx b/src/components/timeline/timeline.tsx
new file mode 100644
index 00000000000..8d6e1b87c78
--- /dev/null
+++ b/src/components/timeline/timeline.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { HTMLAttributes, FunctionComponent } from 'react';
+import { CommonProps } from '../common';
+import classNames from 'classnames';
+import { EuiTimelineItem, EuiTimelineItemProps } from './timeline_item';
+
+export interface EuiTimelineProps
+ extends HTMLAttributes
,
+ CommonProps {
+ /**
+ * List of timeline items to render. See #EuiTimelineItem
+ */
+ items?: EuiTimelineItemProps[];
+}
+
+export const EuiTimeline: FunctionComponent = ({
+ className,
+ items = [],
+ children,
+ ...rest
+}) => {
+ const classes = classNames('euiTimeline', className);
+
+ return (
+
+ {items.map((item, index) => (
+
+ ))}
+ {children}
+
+ );
+};
diff --git a/src/components/timeline/timeline_item.styles.ts b/src/components/timeline/timeline_item.styles.ts
new file mode 100644
index 00000000000..a722beeda27
--- /dev/null
+++ b/src/components/timeline/timeline_item.styles.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+import { UseEuiTheme } from '../../services';
+
+export const euiTimelineItemStyles = ({ euiTheme }: UseEuiTheme) => ({
+ euiTimelineItem: css`
+ display: flex;
+
+ &:not(:last-of-type) {
+ padding-bottom: ${euiTheme.size.xl};
+ }
+
+ &:first-of-type {
+ > [class*='euiTimelineItemIcon-center']::before {
+ top: 50%;
+ // Adding to the height the padding bottom from the parent container
+ height: calc(50% + ${euiTheme.size.xl});
+ }
+ }
+
+ &:last-of-type {
+ > [class*='euiTimelineItemIcon']::before {
+ display: none;
+ }
+
+ &:not(:only-child) > [class*='euiTimelineItemIcon-center']::before {
+ top: 0;
+ height: 50%;
+ }
+ }
+ `,
+});
diff --git a/src/components/timeline/timeline_item.test.tsx b/src/components/timeline/timeline_item.test.tsx
new file mode 100644
index 00000000000..39810be9a0e
--- /dev/null
+++ b/src/components/timeline/timeline_item.test.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { render } from 'enzyme';
+import { EuiAvatar } from '../avatar';
+import { EuiTimelineItem, VERTICAL_ALIGN } from './timeline_item';
+
+describe('EuiTimelineItem', () => {
+ test('is rendered', () => {
+ const component = render(
+
+ I'm the children
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ describe('props', () => {
+ describe('verticalAlign', () => {
+ VERTICAL_ALIGN.forEach((alignment) => {
+ test(`${alignment} is rendered`, () => {
+ const component = render(
+
+ I'm the children
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+ });
+
+ test('iconAriaLabel is rendered', () => {
+ const component = render(
+
+ I'm the children
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('EuiAvatar is passed as an icon', () => {
+ const component = render(
+ }
+ >
+ I'm the children
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/components/timeline/timeline_item.tsx b/src/components/timeline/timeline_item.tsx
new file mode 100644
index 00000000000..0aaf1a7caa3
--- /dev/null
+++ b/src/components/timeline/timeline_item.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FunctionComponent, HTMLAttributes } from 'react';
+import { CommonProps } from '../common';
+import { useEuiTheme } from '../../services';
+
+import {
+ EuiTimelineItemEvent,
+ EuiTimelineItemEventProps,
+} from './timeline_item_event';
+import {
+ EuiTimelineItemIcon,
+ EuiTimelineItemIconProps,
+} from './timeline_item_icon';
+import { euiTimelineItemStyles } from './timeline_item.styles';
+
+export const VERTICAL_ALIGN = ['top', 'center'] as const;
+export type EuiTimelineItemVerticalAlign = typeof VERTICAL_ALIGN[number];
+
+export interface EuiTimelineItemProps
+ extends Omit, 'children'>,
+ CommonProps,
+ Omit,
+ Omit {
+ /**
+ * Vertical alignment of the event with the icon
+ */
+ verticalAlign?: EuiTimelineItemVerticalAlign;
+}
+
+export const EuiTimelineItem: FunctionComponent = ({
+ children,
+ verticalAlign = 'center',
+ icon,
+ iconAriaLabel,
+ className,
+ ...rest
+}) => {
+ const euiTheme = useEuiTheme();
+ const styles = euiTimelineItemStyles(euiTheme);
+
+ const cssStyles = [styles.euiTimelineItem];
+
+ return (
+
+
+
+
+ {children}
+
+
+ );
+};
diff --git a/src/components/timeline/timeline_item_event.styles.ts b/src/components/timeline/timeline_item_event.styles.ts
new file mode 100644
index 00000000000..bdc1a6393de
--- /dev/null
+++ b/src/components/timeline/timeline_item_event.styles.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+
+export const euiTimelineItemEventStyles = () => ({
+ euiTimelineItemEvent: css`
+ flex: 1;
+ `,
+ // Vertical alignments
+ top: css`
+ align-self: flex-start;
+ `,
+ center: css`
+ align-self: center;
+ `,
+});
diff --git a/src/components/timeline/timeline_item_event.tsx b/src/components/timeline/timeline_item_event.tsx
new file mode 100644
index 00000000000..6b40a3040e5
--- /dev/null
+++ b/src/components/timeline/timeline_item_event.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FunctionComponent, ReactNode } from 'react';
+import { euiTimelineItemEventStyles } from './timeline_item_event.styles';
+import { EuiTimelineItemVerticalAlign } from './timeline_item';
+
+export interface EuiTimelineItemEventProps {
+ /**
+ * Accepts any node. But preferably `EuiPanel`
+ */
+ children: ReactNode;
+ verticalAlign?: EuiTimelineItemVerticalAlign;
+}
+
+export const EuiTimelineItemEvent: FunctionComponent = ({
+ children,
+ verticalAlign = 'center',
+}) => {
+ const styles = euiTimelineItemEventStyles();
+ const cssStyles = [styles.euiTimelineItemEvent, styles[verticalAlign]];
+
+ return {children}
;
+};
diff --git a/src/components/timeline/timeline_item_icon.styles.ts b/src/components/timeline/timeline_item_icon.styles.ts
new file mode 100644
index 00000000000..63531516b26
--- /dev/null
+++ b/src/components/timeline/timeline_item_icon.styles.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+import { UseEuiTheme } from '../../services';
+
+export const euiTimelineItemIconStyles = ({ euiTheme }: UseEuiTheme) => ({
+ euiTimelineItemIcon: css`
+ display: flex;
+ position: relative;
+ flex-grow: 0;
+ margin-right: ${euiTheme.size.base};
+
+ // timeline vertical line
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: calc(${euiTheme.size.xxl} / 2);
+ width: calc(${euiTheme.size.xs} / 2);
+ // Adding to the height the padding bottom from the parent container
+ height: calc(100% + ${euiTheme.size.xl});
+ background-color: ${euiTheme.colors.lightShade};
+ }
+ `,
+ euiTimelineItemIcon__content: css`
+ min-width: ${euiTheme.size.xxl};
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ `,
+ // Vertical alignments
+ top: css`
+ align-items: flex-start;
+ `,
+ center: css`
+ align-items: center;
+ `,
+});
diff --git a/src/components/timeline/timeline_item_icon.tsx b/src/components/timeline/timeline_item_icon.tsx
new file mode 100644
index 00000000000..af9f9b32b72
--- /dev/null
+++ b/src/components/timeline/timeline_item_icon.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FunctionComponent, ReactNode } from 'react';
+import { IconType } from '../icon';
+import { EuiAvatar } from '../avatar';
+import { useEuiTheme } from '../../services';
+import { euiTimelineItemIconStyles } from './timeline_item_icon.styles';
+import { EuiTimelineItemVerticalAlign } from './timeline_item';
+
+export interface EuiTimelineItemIconProps {
+ /**
+ * Any `ReactNode`, but preferably `EuiAvatar`, or a `string` as an `EuiIcon['type']`.
+ */
+ icon: ReactNode | IconType;
+ verticalAlign?: EuiTimelineItemVerticalAlign;
+ /**
+ * Specify an `aria-label` for the icon when passed as an `IconType`.
+ * If no `aria-label` is passed we assume the icon is purely decorative.
+ */
+ iconAriaLabel?: string;
+}
+
+export const EuiTimelineItemIcon: FunctionComponent = ({
+ icon,
+ verticalAlign = 'center',
+ iconAriaLabel,
+}) => {
+ const euiTheme = useEuiTheme();
+ const styles = euiTimelineItemIconStyles(euiTheme);
+
+ const cssStyles = [styles.euiTimelineItemIcon, styles[verticalAlign]];
+ const cssContentStyles = styles.euiTimelineItemIcon__content;
+
+ const ariaLabel = iconAriaLabel ? iconAriaLabel : '';
+
+ const iconRender =
+ typeof icon === 'string' ? (
+
+ ) : (
+ icon
+ );
+
+ return (
+
+ );
+};
diff --git a/upcoming_changelogs/5730.md b/upcoming_changelogs/5730.md
new file mode 100644
index 00000000000..48bdfa97e1e
--- /dev/null
+++ b/upcoming_changelogs/5730.md
@@ -0,0 +1,2 @@
+- Added `EuiTimeline` component
+- Added a `"subdued"` color option to `EuiAvatar`
\ No newline at end of file