Skip to content

Commit 9741751

Browse files
akoushkelalli-flores
authored andcommitted
feat(WebexActivity): implement component
1 parent f6d6db2 commit 9741751

File tree

13 files changed

+770
-3
lines changed

13 files changed

+770
-3
lines changed

.storybook/setupTests.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,13 @@ global.shallow = shallow;
1010
global.render = render;
1111
global.mount = mount;
1212
global.rxjs = rxjs;
13+
global.Date = class extends Date {
14+
constructor(date) {
15+
super();
16+
this.dateString = date || 'now';
17+
}
18+
19+
toString() {
20+
return this.dateString;
21+
}
22+
};

__mocks__/date-fns.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const isToday = (time) => time.dateString === 'today';
2+
export const isYesterday = (time) => time.dateString === 'yesterday';
3+
export const isSameWeek = (time1) => time1.dateString === 'sameWeek'; // ignore the second argument
4+
export const format = (time, pattern) => `${time} ${pattern}`;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Webex Activity Component
2+
3+
Webex activity component displays the activity content.
4+
5+
<p align="center">
6+
<img src="./WebexActivity.png" alt="Default Webex Activity" />
7+
</p>
8+
9+
## Preview
10+
11+
To see all the different possible states of the Webex Activity component, you can run our Storybook:
12+
13+
```shell
14+
npm start
15+
```
16+
17+
## Embed
18+
19+
1. Create a component adapters object from which the data will be retrieved (See [adapters](../../adapters)). For instance:
20+
21+
```js
22+
const jsonAdapters = {
23+
peopleJSONAdapter: new PeopleJSONAdapter(people),
24+
activitiesJSONAdapter: new ActivitiesJSONAdapter(activities),
25+
};
26+
```
27+
28+
2. Create a component instance by passing the activity ID as a string and the component data adapters which contains [PeopleAdapter](../../adapters/PeopleAdapter.js) and [ActivitiesAdapter](../../adapters/ActivitiesAdapter.js) that we created previously
29+
30+
```js
31+
<WebexActivities activityID="activityID" adapters={jsonAdapters} />
32+
```
33+
34+
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.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import {format, isToday, isSameWeek, isYesterday} from 'date-fns';
4+
5+
import WebexAvatar from '../WebexAvatar/WebexAvatar';
6+
import {useActivity, usePerson} from '../hooks';
7+
8+
import './WebexActivity.scss';
9+
10+
/**
11+
* Returns a formatted timestamp based on the given date's offset from the current time.
12+
*
13+
* @param {Date} timestamp Date instance to format
14+
* @returns {string} formattedDate
15+
*/
16+
export function formatMessageDate(timestamp) {
17+
let formattedDate;
18+
19+
if (isToday(timestamp)) {
20+
// 12:00 PM
21+
formattedDate = format(timestamp, 'p');
22+
} else if (isYesterday(timestamp)) {
23+
// Yesterday 12:00 PM
24+
formattedDate = `Yesterday ${format(timestamp, 'p')}`;
25+
} else if (isSameWeek(timestamp, new Date())) {
26+
// Monday 12:00 PM
27+
formattedDate = format(timestamp, 'iiii p');
28+
} else {
29+
// 1/1/2020 12:00 PM
30+
formattedDate = format(timestamp, 'P p');
31+
}
32+
33+
return formattedDate;
34+
}
35+
36+
export function Header(props) {
37+
const {personID, adapter, created} = props;
38+
const {displayName} = usePerson(personID, adapter);
39+
40+
return (
41+
<div className="activity-header">
42+
<WebexAvatar personID={personID} adapter={adapter} />
43+
<div className="activity-author">
44+
<span>{displayName}</span>
45+
<span className="activity-timestamp">{formatMessageDate(new Date(created))}</span>
46+
</div>
47+
</div>
48+
);
49+
}
50+
51+
Header.propTypes = {
52+
personID: PropTypes.string.isRequired,
53+
adapter: PropTypes.object.isRequired,
54+
created: PropTypes.string.isRequired,
55+
};
56+
57+
export default function WebexActivity(props) {
58+
const {activityID, adapters} = props;
59+
const {created, displayHeader, ID, personID, text} = useActivity(activityID, adapters.activitiesAdapter);
60+
const header = displayHeader ? (
61+
<Header personID={personID} adapter={adapters.peopleAdapter} created={created} />
62+
) : null;
63+
64+
return (
65+
<div className="activity" key={ID}>
66+
{header}
67+
<div className="activity-content">{text}</div>
68+
</div>
69+
);
70+
}
71+
72+
WebexActivity.propTypes = {
73+
activityID: PropTypes.string.isRequired,
74+
adapters: PropTypes.exact({
75+
activitiesAdapter: PropTypes.object.isRequired,
76+
peopleAdapter: PropTypes.object.isRequired,
77+
}).isRequired,
78+
};
95.3 KB
Loading
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
$content-left-margin: 3.5rem; //avatar width + margin
2+
.activity {
3+
display: flex;
4+
flex-direction: column;
5+
margin: 0.5rem 0;
6+
7+
.activity-header {
8+
display: flex;
9+
position: relative;
10+
11+
.activity-author {
12+
@include noselect();
13+
display: flex;
14+
min-width: 18.75rem !important;
15+
margin-left: $content-left-margin;
16+
font-size: 0.875rem;
17+
line-height: 1.375rem;
18+
text-align: left;
19+
color: $md-gray-70;
20+
word-wrap: break-word;
21+
}
22+
23+
.md-avatar {
24+
position: absolute;
25+
margin: 0 0.5rem;
26+
}
27+
28+
.activity-timestamp {
29+
margin-left: 0.5rem;
30+
}
31+
}
32+
33+
.activity-content {
34+
display: flex;
35+
align-items: flex-start;
36+
box-sizing: border-box;
37+
min-width: 15.625rem !important;
38+
margin-left: $content-left-margin;
39+
font-size: 1rem;
40+
color: $black;
41+
word-wrap: break-word;
42+
white-space: pre-wrap;
43+
}
44+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import React from 'react';
2+
import {storiesOf} from '@storybook/react';
3+
import {addDays, getDay, subDays} from 'date-fns';
4+
5+
import {ActivitiesJSONAdapter, PeopleJSONAdapter} from '../../adapters';
6+
import {activities, people} from '../../data';
7+
8+
import WebexActivity from './WebexActivity';
9+
10+
// Setup for the stories
11+
const [activityID] = Object.keys(activities);
12+
const stories = storiesOf('Webex Activity', module);
13+
const newActivities = {};
14+
15+
// Stories
16+
stories.add('default', () => (
17+
<WebexActivity
18+
activityID={activityID}
19+
adapters={{
20+
activitiesAdapter: new ActivitiesJSONAdapter(activities),
21+
peopleAdapter: new PeopleJSONAdapter(people),
22+
}}
23+
/>
24+
));
25+
26+
stories.add('no activity header', () => {
27+
newActivities[activityID] = {
28+
...activities[activityID],
29+
displayHeader: false,
30+
};
31+
32+
return (
33+
<WebexActivity
34+
activityID={activityID}
35+
adapters={{
36+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
37+
peopleAdapter: new PeopleJSONAdapter(people),
38+
}}
39+
/>
40+
);
41+
});
42+
43+
stories.add('multi-line text', () => {
44+
newActivities[activityID] = {
45+
...activities[activityID],
46+
text:
47+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
48+
};
49+
50+
return (
51+
<WebexActivity
52+
activityID={activityID}
53+
adapters={{
54+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
55+
peopleAdapter: new PeopleJSONAdapter(people),
56+
}}
57+
/>
58+
);
59+
});
60+
61+
stories.add('long text', () => {
62+
newActivities[activityID] = {
63+
...activities[activityID],
64+
text:
65+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
66+
};
67+
68+
return (
69+
<WebexActivity
70+
activityID={activityID}
71+
adapters={{
72+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
73+
peopleAdapter: new PeopleJSONAdapter(people),
74+
}}
75+
/>
76+
);
77+
});
78+
79+
stories.add('created today', () => {
80+
const today = new Date();
81+
82+
newActivities[activityID] = {...activities[activityID], text: `${today}`, created: today};
83+
84+
return (
85+
<WebexActivity
86+
activityID={activityID}
87+
adapters={{
88+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
89+
peopleAdapter: new PeopleJSONAdapter(people),
90+
}}
91+
/>
92+
);
93+
});
94+
95+
stories.add('created yesterday', () => {
96+
const yesterday = subDays(new Date(), 1);
97+
98+
newActivities[activityID] = {
99+
...activities[activityID],
100+
text: `${yesterday}`,
101+
created: yesterday,
102+
};
103+
104+
return (
105+
<WebexActivity
106+
activityID={activityID}
107+
adapters={{
108+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
109+
peopleAdapter: new PeopleJSONAdapter(people),
110+
}}
111+
/>
112+
);
113+
});
114+
115+
stories.add('created this week', () => {
116+
// if it's sunday, make it a monday, otherwise pick the day before today
117+
const thisWeek = getDay(new Date()) === 0 ? addDays(new Date(), 1) : subDays(new Date(), 2);
118+
119+
newActivities[activityID] = {...activities[activityID], text: `${thisWeek}`, created: thisWeek};
120+
121+
return (
122+
<WebexActivity
123+
activityID={activityID}
124+
adapters={{
125+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
126+
peopleAdapter: new PeopleJSONAdapter(people),
127+
}}
128+
/>
129+
);
130+
});
131+
132+
stories.add('created over a week ago', () => {
133+
const oldDate = subDays(new Date(), 7);
134+
135+
newActivities[activityID] = {...activities[activityID], text: `${oldDate}`, created: oldDate};
136+
137+
return (
138+
<WebexActivity
139+
activityID={activityID}
140+
adapters={{
141+
activitiesAdapter: new ActivitiesJSONAdapter(newActivities),
142+
peopleAdapter: new PeopleJSONAdapter(people),
143+
}}
144+
/>
145+
);
146+
});

0 commit comments

Comments
 (0)