diff --git a/src/components/WebexActivityStream/README.md b/src/components/WebexActivityStream/README.md
new file mode 100644
index 000000000..f0c36b5b5
--- /dev/null
+++ b/src/components/WebexActivityStream/README.md
@@ -0,0 +1,31 @@
+# Webex Activity Stream Component
+
+Webex activity stream component displays a list of activities of a room.
+
+
+ picture coming up
+
+
+## Preview
+
+To see all the different possible states of the Webex Activity Stream component, you can run our Storybook:
+
+```shell
+ npm start
+```
+
+## Embed
+
+1. Create a component adapter from which the data will be retrieved (See [adapters](../../adapters)). For instance:
+
+ ```js
+ const jsonAdapter = new RoomsJSONAdapter(rooms);
+ ```
+
+2. Create a component instance by passing the room ID as a string and the [component data adapter](../../adapters/RoomsAdapter.js) that we created previously
+
+ ```js
+
+ ```
+
+The component knows how to manage its data. If anything changes in the data source that the adapter manages, the component will also update on its own.
diff --git a/src/components/WebexActivityStream/WebexActivityStream.js b/src/components/WebexActivityStream/WebexActivityStream.js
new file mode 100644
index 000000000..022d7c046
--- /dev/null
+++ b/src/components/WebexActivityStream/WebexActivityStream.js
@@ -0,0 +1,151 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {RoomType} from '../../adapters/RoomsAdapter';
+import './WebexActivityStream.scss';
+import {useRoom, useActivityStream} from '../hooks';
+
+export function GreetingDirectSVG() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function GreetingSpaceSVG() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function Greeting(props) {
+ let svg = ;
+ let description = `This is a shared space between you and other group members. Here's where you'll see shared messages, files, and a call history with this space.`;
+
+ if (props.personName) {
+ svg = ;
+ description = `This is your private conversation with ${props.personName}. Here's where you'll see shared messages, files, and a call history with this person.`;
+ }
+
+ return (
+
+
+ {svg}
+
{description}
+
+
+ );
+}
+
+Greeting.propTypes = {
+ personName: PropTypes.string.isRequired,
+};
+
+export default function WebexActivityStream(props) {
+ const {roomID, adapter} = props;
+ const {title, roomType} = useRoom(roomID, adapter);
+ const activityIDs = useActivityStream(roomID, adapter);
+ const personName = roomType === RoomType.DIRECT ? title : '';
+
+ return {!activityIDs.length && }
;
+}
+
+WebexActivityStream.propTypes = {
+ roomID: PropTypes.string.isRequired,
+ adapter: PropTypes.object.isRequired,
+};
diff --git a/src/components/WebexActivityStream/WebexActivityStream.scss b/src/components/WebexActivityStream/WebexActivityStream.scss
new file mode 100644
index 000000000..aa7e8c18b
--- /dev/null
+++ b/src/components/WebexActivityStream/WebexActivityStream.scss
@@ -0,0 +1,18 @@
+.greeting {
+ display: block;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ max-width: 37.5rem;
+ text-align: center;
+ color: $gray-dark-3;
+
+ .greeting-header {
+ @include header-fonts;
+ }
+
+ .greeting-description {
+ @include body-fonts;
+ padding: 0 3.125rem;
+ }
+}
diff --git a/src/components/WebexActivityStream/WebexActivityStream.stories.js b/src/components/WebexActivityStream/WebexActivityStream.stories.js
new file mode 100644
index 000000000..3b7eabd10
--- /dev/null
+++ b/src/components/WebexActivityStream/WebexActivityStream.stories.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+
+import RoomsJSONAdapter from '../../adapters/RoomsJSONAdapter';
+import {RoomType} from '../../adapters/RoomsAdapter';
+import rooms from '../../data/rooms';
+
+import WebexActivityStream from './WebexActivityStream';
+
+// Setup for the stories
+const [roomID] = Object.keys(rooms);
+const stories = storiesOf('Webex Activity Stream', module);
+const newRooms = {};
+
+// Stories
+stories.add('empty group stream', () => );
+stories.add('empty 1:1 stream', () => {
+ newRooms[roomID] = {
+ ...rooms[roomID],
+ roomType: RoomType.DIRECT,
+ };
+
+ return ;
+});
diff --git a/src/components/WebexActivityStream/WebexActivityStream.test.js b/src/components/WebexActivityStream/WebexActivityStream.test.js
new file mode 100644
index 000000000..13073e0f5
--- /dev/null
+++ b/src/components/WebexActivityStream/WebexActivityStream.test.js
@@ -0,0 +1,64 @@
+import React from 'react';
+
+import RoomsJSONAdapter from '../../adapters/RoomsJSONAdapter';
+import {RoomType} from '../../adapters/RoomsAdapter';
+import rooms from '../../data/rooms';
+
+import WebexActivityStream, {Greeting, GreetingSpaceSVG, GreetingDirectSVG} from './WebexActivityStream';
+
+jest.mock('../hooks/useRoom');
+jest.mock('../hooks/useActivityStream');
+
+describe('Webex Activity Stream component', () => {
+ let roomID, newRooms, roomsAdapter;
+
+ beforeEach(() => {
+ [roomID] = Object.keys(rooms);
+ newRooms = rooms;
+ roomsAdapter = new RoomsJSONAdapter(rooms);
+ });
+
+ describe('Greeting Space SVG snapshot', () => {
+ test('matches with greeting space SVG', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+ });
+
+ describe('Greeting 1:1 SVG snapshot', () => {
+ test('matches with greeting 1:1 SVG', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+ });
+
+ describe('Greeting component snapshot', () => {
+ test('matches with empty space', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+
+ test('matches with empty 1:1', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+ });
+
+ describe('Webex Activity Stream snapshots', () => {
+ test('matches with empty group stream', () => {
+ expect(shallow( )).toMatchSnapshot();
+ });
+
+ test('matches with empty direct stream', () => {
+ newRooms[roomID] = {
+ ...rooms[roomID],
+ roomType: RoomType.DIRECT,
+ };
+ roomsAdapter = new RoomsJSONAdapter(newRooms);
+
+ expect(shallow( )).toMatchSnapshot();
+ });
+ });
+
+ afterEach(() => {
+ roomsAdapter = null;
+ newRooms = null;
+ roomID = null;
+ });
+});
diff --git a/src/components/WebexActivityStream/__snapshots__/WebexActivityStream.test.js.snap b/src/components/WebexActivityStream/__snapshots__/WebexActivityStream.test.js.snap
new file mode 100644
index 000000000..978883b95
--- /dev/null
+++ b/src/components/WebexActivityStream/__snapshots__/WebexActivityStream.test.js.snap
@@ -0,0 +1,191 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Webex Activity Stream component Greeting 1:1 SVG snapshot matches with greeting 1:1 SVG 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Webex Activity Stream component Greeting Space SVG snapshot matches with greeting space SVG 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Webex Activity Stream component Greeting component snapshot matches with empty 1:1 1`] = `
+
+
+
+
+ This is your private conversation with personName. Here's where you'll see shared messages, files, and a call history with this person.
+
+
+
+`;
+
+exports[`Webex Activity Stream component Greeting component snapshot matches with empty space 1`] = `
+
+
+
+
+ This is a shared space between you and other group members. Here's where you'll see shared messages, files, and a call history with this space.
+
+
+
+`;
+
+exports[`Webex Activity Stream component Webex Activity Stream snapshots matches with empty direct stream 1`] = `
+
+
+
+`;
+
+exports[`Webex Activity Stream component Webex Activity Stream snapshots matches with empty group stream 1`] = `
+
+
+
+`;
diff --git a/src/components/hooks/__mocks__/useActivityStream.js b/src/components/hooks/__mocks__/useActivityStream.js
new file mode 100644
index 000000000..0ae4df912
--- /dev/null
+++ b/src/components/hooks/__mocks__/useActivityStream.js
@@ -0,0 +1,6 @@
+export default function useActivityStream(ID, adapter) {
+ const [roomID] = Object.keys(adapter.datasource);
+ const rooms = adapter.datasource;
+
+ return ID === roomID ? rooms[`${ID}-activities`] : [];
+}
diff --git a/src/components/hooks/__mocks__/useRoom.js b/src/components/hooks/__mocks__/useRoom.js
new file mode 100644
index 000000000..b299fc662
--- /dev/null
+++ b/src/components/hooks/__mocks__/useRoom.js
@@ -0,0 +1,13 @@
+export default function useRoom(ID, adapter) {
+ const [roomID] = Object.keys(adapter.datasource);
+ const rooms = adapter.datasource;
+ let room = null;
+
+ if (ID === roomID) {
+ room = rooms[ID];
+ } else {
+ throw new Error(`Could not find room with ID "${ID}"`);
+ }
+
+ return room;
+}
diff --git a/src/components/hooks/index.js b/src/components/hooks/index.js
index b0c6ca426..6339fc883 100644
--- a/src/components/hooks/index.js
+++ b/src/components/hooks/index.js
@@ -1,2 +1,4 @@
export {default as useActivity} from './useActivity';
export {default as usePerson} from './usePerson';
+export {default as useRoom} from './useRoom';
+export {default as useActivityStream} from './useActivityStream';
diff --git a/src/components/hooks/useActivityStream.js b/src/components/hooks/useActivityStream.js
new file mode 100644
index 000000000..2574ac6ae
--- /dev/null
+++ b/src/components/hooks/useActivityStream.js
@@ -0,0 +1,28 @@
+import {useState, useEffect} from 'react';
+import {merge} from 'rxjs';
+
+/**
+ * Custom hook that returns activity data associated to the room of the given ID.
+ *
+ * @param {string} roomID ID of the room for which to return data.
+ * @param {obj} roomsAdapter Component data adapter from which to retrieve data.
+ * @returns {Room} Activity ID associated to the room
+ */
+export default function useActivityStream(roomID, roomsAdapter) {
+ const [activityIDs, setActivityIDs] = useState([]);
+
+ useEffect(() => {
+ const activityStream = merge(
+ roomsAdapter.getPreviousRoomActivities(roomID),
+ roomsAdapter.getRoomActivities(roomID)
+ );
+ const subscription = activityStream.subscribe(setActivityIDs);
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return activityIDs;
+}
diff --git a/src/components/hooks/useRoom.js b/src/components/hooks/useRoom.js
new file mode 100644
index 000000000..b6dfd3423
--- /dev/null
+++ b/src/components/hooks/useRoom.js
@@ -0,0 +1,26 @@
+import {useState, useEffect} from 'react';
+
+/**
+ * Custom hook that returns room data of the given ID.
+ *
+ * @param {string} roomID ID of the room for which to return data.
+ * @param {obj} roomsAdapter Component data adapter from which to retrieve data.
+ * @returns {Room} Data of the room
+ */
+export default function useRoom(roomID, roomsAdapter) {
+ const [room, setRoom] = useState({});
+
+ useEffect(() => {
+ const onError = (error) => {
+ throw error;
+ };
+ const subscription = roomsAdapter.getRoom(roomID).subscribe(setRoom, onError);
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return room;
+}
diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss
index 6e3667222..abf37b03f 100644
--- a/src/styles/_mixins.scss
+++ b/src/styles/_mixins.scss
@@ -9,13 +9,14 @@
cursor: auto;
}
-@mixin font-size-26 {
+@mixin header-fonts {
font-family: $brand-font-extra-light;
font-size: 1.625rem;
line-height: 2rem;
letter-spacing: 0.0125rem;
}
-@mixin font-size-16 {
+
+@mixin body-fonts {
font-family: $brand-font-light;
font-size: 1rem;
line-height: 1.5rem;