Add Messages component

This commit is contained in:
Kimmo Puputti 2017-11-09 13:29:29 +02:00
parent f88293da07
commit b362e30054
9 changed files with 316 additions and 2 deletions

View file

@ -0,0 +1,80 @@
@import '../../marketplace.css';
.root {
margin: 0;
/* Clearfix */
width: 100%;
overflow: auto;
}
.messageItem {
margin-bottom: 35px;
@media (--viewportMedium) {
margin-bottom: 40px;
}
&:last-of-type {
margin-bottom: 0;
}
}
.message,
.ownMessage {
/* Preserve newlines, but collapse other whitespace */
white-space: pre-line;
max-width: 433px;
}
.message {
display: flex;
flex-direction: row;
}
.ownMessage {
float: right;
}
.avatar {
flex-shrink: 0;
margin: 9px 12px 0 0;
}
.messageContent,
.ownMessageContent {
@apply --marketplaceMessageFontStyles;
margin: 0;
padding: 15px 14px 11px 14px;
border-radius: 8px;
box-shadow: var(--boxShadow);
@media (--viewportMedium) {
margin: 0;
}
}
.messageContent {
flex: 1;
}
.ownMessageContent {
background-color: var(--marketplaceColor);
color: var(--matterColorLight);
}
.messageDate,
.ownMessageDate {
@apply --marketplaceMessageDateFontStyles;
margin: 11px 0 0 0;
color: var(--matterColorAnti);
@media (--viewportMedium) {
margin: 14px 0 0 0;
}
}
.ownMessageDate {
text-align: right;
}

View file

@ -0,0 +1,35 @@
import { createUser, createCurrentUser, createMessage } from '../../util/test-data';
import Messages from './Messages';
export const Empty = {
component: Messages,
props: {
messages: [],
currentUser: null,
},
group: 'messages',
};
export const WithoutCurrentUser = {
component: Messages,
props: {
messages: [
createMessage('msg1', {}, { sender: createUser('user1') }),
createMessage('msg2', {}, { sender: createUser('user2') }),
],
currentUser: null,
},
group: 'messages',
};
export const WithCurrentUser = {
component: Messages,
props: {
messages: [
createMessage('msg1', {}, { sender: createUser('user1') }),
createMessage('msg2', {}, { sender: createUser('user2') }),
],
currentUser: createCurrentUser('user2'),
},
group: 'messages',
};

View file

@ -0,0 +1,92 @@
import React from 'react';
import { string, arrayOf } from 'prop-types';
import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { Avatar } from '../../components';
import { formatDate } from '../../util/dates';
import * as propTypes from '../../util/propTypes';
import css from './Messages.css';
const Message = props => {
const { message, intl } = props;
const todayString = intl.formatMessage({ id: 'Messages.today' });
return (
<div className={css.message}>
<Avatar className={css.avatar} user={message.sender} />
<div>
<p className={css.messageContent}>{message.attributes.content}</p>
<p className={css.messageDate}>{formatDate(intl, todayString, message.attributes.at)}</p>
</div>
</div>
);
};
Message.propTypes = {
message: propTypes.message.isRequired,
intl: intlShape.isRequired,
};
const OwnMessage = props => {
const { message, intl } = props;
const todayString = intl.formatMessage({ id: 'Messages.today' });
return (
<div className={css.ownMessage}>
<p className={css.ownMessageContent}>{message.attributes.content}</p>
<p className={css.ownMessageDate}>{formatDate(intl, todayString, message.attributes.at)}</p>
</div>
);
};
OwnMessage.propTypes = {
message: propTypes.message.isRequired,
intl: intlShape.isRequired,
};
export const MessagesComponent = props => {
const { rootClassName, className, messages, currentUser, intl } = props;
const classes = classNames(rootClassName || css.root, className);
const msg = message => {
const isOwnMessage =
message.sender &&
message.sender.id &&
currentUser &&
currentUser.id &&
message.sender.id.uuid === currentUser.id.uuid;
if (isOwnMessage) {
return <OwnMessage message={message} intl={intl} />;
}
return <Message message={message} intl={intl} />;
};
return (
<ul className={classes}>
{messages.map(m => (
<li key={m.id.uuid} className={css.messageItem}>
{msg(m)}
</li>
))}
</ul>
);
};
MessagesComponent.defaultProps = {
rootClassName: null,
className: null,
};
MessagesComponent.propTypes = {
rootClassName: string,
className: string,
messages: arrayOf(propTypes.message),
currentUser: propTypes.currentUser,
// from injectIntl
intl: intlShape.isRequired,
};
const Messages = injectIntl(MessagesComponent);
export default Messages;

View file

@ -0,0 +1,19 @@
import React from 'react';
import { renderDeep } from '../../util/test-helpers';
import { fakeIntl, createUser, createCurrentUser, createMessage } from '../../util/test-data';
import { MessagesComponent } from './Messages';
describe('Messages', () => {
it('matches snapshot', () => {
const props = {
messages: [
createMessage('msg1', {}, { sender: createUser('user1') }),
createMessage('msg2', {}, { sender: createUser('user2') }),
],
currentUser: createCurrentUser('user2'),
intl: fakeIntl,
};
const tree = renderDeep(<MessagesComponent {...props} />);
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Messages matches snapshot 1`] = `
<ul
className=""
>
<li
className={undefined}
>
<div
className={undefined}
>
<a
className=""
href="/u/user1"
onClick={[Function]}
style={Object {}}
title="user1 display name"
>
<span
className={undefined}
>
TT
</span>
</a>
<div>
<p
className={undefined}
>
Message msg1
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
<p
className={undefined}
>
2017-11-09, 08:12
</p>
</div>
</div>
</li>
<li
className={undefined}
>
<div
className={undefined}
>
<p
className={undefined}
>
Message msg2
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
<p
className={undefined}
>
2017-11-09, 08:12
</p>
</div>
</li>
</ul>
`;

View file

@ -49,10 +49,10 @@ export { default as ImageCarousel } from './ImageCarousel/ImageCarousel';
export { default as ImageFromFile } from './ImageFromFile/ImageFromFile';
export { default as LayoutSideNavigation } from './LayoutSideNavigation/LayoutSideNavigation';
export { default as LayoutSingleColumn } from './LayoutSingleColumn/LayoutSingleColumn';
export { default as LayoutWrapperTopbar } from './LayoutWrapperTopbar/LayoutWrapperTopbar';
export { default as LayoutWrapperFooter } from './LayoutWrapperFooter/LayoutWrapperFooter';
export { default as LayoutWrapperMain } from './LayoutWrapperMain/LayoutWrapperMain';
export { default as LayoutWrapperSideNav } from './LayoutWrapperSideNav/LayoutWrapperSideNav';
export { default as LayoutWrapperFooter } from './LayoutWrapperFooter/LayoutWrapperFooter';
export { default as LayoutWrapperTopbar } from './LayoutWrapperTopbar/LayoutWrapperTopbar';
export { default as ListingCard } from './ListingCard/ListingCard';
export {
default as LocationAutocompleteInput,
@ -66,6 +66,7 @@ export { default as Menu } from './Menu/Menu';
export { default as MenuContent } from './MenuContent/MenuContent';
export { default as MenuItem } from './MenuItem/MenuItem';
export { default as MenuLabel } from './MenuLabel/MenuLabel';
export { default as Messages } from './Messages/Messages';
export { default as Modal } from './Modal/Modal';
export { default as ModalInMobile } from './ModalInMobile/ModalInMobile';
export { default as NamedLink } from './NamedLink/NamedLink';

View file

@ -29,6 +29,7 @@ import * as LocationAutocompleteInput from './components/LocationAutocompleteInp
import * as ManageListingCard from './components/ManageListingCard/ManageListingCard.example';
import * as Map from './components/Map/Map.example';
import * as Menu from './components/Menu/Menu.example';
import * as Messages from './components/Messages/Messages.example';
import * as Modal from './components/Modal/Modal.example';
import * as ModalInMobile from './components/ModalInMobile/ModalInMobile.example';
import * as NamedLink from './components/NamedLink/NamedLink.example';
@ -97,6 +98,7 @@ export {
ManageListingCard,
Map,
Menu,
Messages,
Modal,
ModalInMobile,
NamedLink,

View file

@ -210,6 +210,27 @@
}
}
--marketplaceMessageFontStyles {
font-family: 'sofiapro', Helvetica, Arial, sans-serif;
font-weight: var(--fontWeightRegular);
font-size: 16px;
line-height: 24px;
}
--marketplaceMessageDateFontStyles {
font-family: 'sofiapro', Helvetica, Arial, sans-serif;
font-weight: var(--fontWeightRegular);
font-size: 13px;
line-height: 18px;
letter-spacing: -0.1px;
@media (--viewportMedium) {
font-weight: var(--fontWeightMedium);
font-size: 14px;
line-height: 24px;
}
}
/* ================ Tabbed navigation font styles ================ */
--marketplaceTabNavFontStyles {

View file

@ -248,6 +248,7 @@
"ManageListingsPage.youHaveListings": "You have {count} {count, plural, one {listing} other {listings}}",
"ManageListingsPage.yourListings": "Your listings",
"MapPriceMarker.unsupportedPrice": "({currency})",
"Messages.today": "Today",
"Modal.close": "CLOSE",
"Modal.closeModal": "Close modal",
"NotFoundPage.description": "We can't find the page or the sauna you're looking for. Make sure you've typed the URL correctly, of try searching Saunatime.",