diff --git a/README.md b/README.md index 71885a77..d9eaea3f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ import { MessageBox } from 'react-chat-elements/native'; 12. [Avatar](#avatar-component) 13. [LocationMessage](#locationmessage-component) 14. [SpotifyMessage](#spotifymessage-component) +15. [MeetingItem](#meetingitem-component) +16. [MeetingList](#meetinglist-component) ## ChatItem Component @@ -555,3 +557,72 @@ import { SpotifyMessage } from 'react-chat-elements' | data | {} | object | message data | | width | 300 | int | spotify embed width | | height | 380 | int | spotify embed height | + + +## MeetingItem Component + +![meeting-photo](https://user-images.githubusercontent.com/15075759/90499887-cd878500-e152-11ea-9e13-80118bf2c94f.png) + +```javascript +import { MeetingItem } from 'react-chat-elements' + + +``` +#### MeetingItem props + +| prop | default | type | description | +| ---- | ---- | ---- | ---- | +| subject | none | string | MeetingItem subject | +| subjectLimit | 60 | int | MeetingItem subject text limit | +| date | none | date | MeetingItem date | +| dateString | none | string | MeetingItem represents dateString or timeagojs(now, date) | +| lazyLoadingImage | none | image path | lazy loading image | +| onClick | none | function | MeetingItem on click | +| onMeetingClick | none | function | MeetingItem on meeting click | +| onShareClick | none | function | MeetingItem on share click | +| avatars | none | date | MeetingItem avatars | +| avatarLimit | 5 | date | MeetingItem avatars limit | + + +## MeetingList Component + +![meetingList-photo](https://user-images.githubusercontent.com/15075759/90499889-ce201b80-e152-11ea-9cdb-7c3ef0e04b4e.png) + +```javascript +import { MeetingList } from 'react-chat-elements' + + +``` + +#### MeetingList props + +| prop | default | type | description | +| ---- | ---- | ---- | ---- | +| className | none | string | optional meeting list className | +| dataSource | [] | array | meeting list array | +| onClick | none | function | meeting list item on click (meeting(object) is returned) | +| onMeetingClick | none | function | meeting list item on meeting click (meeting(object) is returned) | +| onShareClick | none | function | meeting list item on share click (meeting(object) is returned) | +| onContextMenu | none | function | meeting list item on context menu (meeting(object) is returned) | +| onAvatarError | none | function | meeting list item on error avatar img | +| lazyLoadingImage | none | image path | lazy loading image | \ No newline at end of file diff --git a/example/App.js b/example/App.js index bca71da5..69859f25 100644 --- a/example/App.js +++ b/example/App.js @@ -12,6 +12,7 @@ import { SideBar, Dropdown, Popup, + MeetingList, } from '../src'; import FaSearch from 'react-icons/lib/fa/search'; @@ -34,6 +35,7 @@ export class App extends Component { this.state = { show: true, + list: 'chat', messageList: [], }; } @@ -186,6 +188,17 @@ export class App extends Component { ]} /> ), }; + case 'meeting': + return { + id: String(Math.random()), + lazyLoadingImage: `data:image/png;base64,${this.photo()}`, + avatarFlexible: true, + subject: loremIpsum({ count: 1, units: 'sentences' }), + date: new Date(), + avatars: Array(this.token() + 2).fill(1).map(x => ({ + src: `data:image/png;base64,${this.photo()}`, + })), + }; } } @@ -203,6 +216,7 @@ export class App extends Component { arr.push(i); var chatSource = arr.map(x => this.random('chat')); + var meetingSource = arr.map(x => this.random('meeting')); return (
@@ -210,34 +224,52 @@ export class App extends Component { className='chat-list'> { - this.setState({ show: false }) - }, - icon: { - component: , - size: 18 - } - }]} - text='Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatem animi veniam voluptas eius!' - footerButtons={[{ - color: 'white', - backgroundColor: '#ff5e3e', - text: "Vazgeç", - }, { - color: 'white', - backgroundColor: 'lightgreen', - text: "Tamam", - }]} /> +
+ { + this.setState({ show: false }) + }, + icon: { + component: , + size: 18 + } + }]} + text='Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatem animi veniam voluptas eius!' + footerButtons={[{ + color: 'white', + backgroundColor: '#ff5e3e', + text: "Vazgeç", + }, { + color: 'white', + backgroundColor: 'lightgreen', + text: "Tamam", + }]} /> + +
} center={ + this.state.list === 'chat' ? + : + } bottom={ diff --git a/package.json b/package.json index b05bcc41..661e57a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-chat-elements", - "version": "10.9.3", + "version": "10.10.0", "description": "Reactjs chat components", "author": "Avare Kodcu ", "main": "dist/main.js", diff --git a/src/Input/__tests__/__snapshots__/Input.js.snap b/src/Input/__tests__/__snapshots__/Input.js.snap index d376b65d..21544e68 100644 --- a/src/Input/__tests__/__snapshots__/Input.js.snap +++ b/src/Input/__tests__/__snapshots__/Input.js.snap @@ -6,11 +6,11 @@ exports[`Input component should render without issues 1`] = ` >
`; diff --git a/src/MeetingItem/MeetingItem.css b/src/MeetingItem/MeetingItem.css new file mode 100644 index 00000000..70d20249 --- /dev/null +++ b/src/MeetingItem/MeetingItem.css @@ -0,0 +1,117 @@ +.rce-container-mtitem { + flex-direction: column; + display: block; + overflow: hidden; + min-width: 240px; +} + +.rce-mtitem { + position: relative; + background: white; + display: flex; + flex-direction: column; + user-select: none; + max-width: 100%; + overflow: hidden; + min-width: 240px; + border-bottom: 1px solid rgba(0,0,0,.05); +} + +.rce-mtitem:hover { + background: #fbfbfb; +} + + +.rce-mtitem-subject { + padding: 0 10px; + margin-top: 5px; + font-size: 15px; + overflow: hidden; + color: #333; + max-height: 35px; + text-overflow: ellipsis; +} + +.rce-mtitem-body { + display: flex; + flex: 1; + flex-direction: row; + display: flex; + justify-content: center; + padding: 0 10px; + overflow: hidden; + align-items: center; +} + +.rce-mtitem-body--avatars { + display: flex; + flex: 1; + overflow: hidden; + opacity: 0.7; +} + +.rce-mtitem-body--functions { + width: 60px; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.rce-mtitem-footer { + padding: 0 10px; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 5px; +} + +.rce-mtitem-body--avatars .rce-avatar-container { + margin-left: -15px; + border: 2px solid #fff; +} + +.rce-mtitem-body--avatars .rce-avatar-container:first-child { + margin: 0; +} + +.rce-mtitem-letter { + color: #fff; + background: #e48989; + display: flex; + align-items: center; + justify-content: center; +} + +.rce-mtitem-button { + font-size: 25px; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + background: #5ba7c5; + border-radius: 100%; + padding: 3px; + transition: 300ms; +} + +.rce-mtitem-share { + font-size: 30px; + display: flex; + align-items: center; + justify-content: center; + color: #5ba7c5; + margin: -10px 0; + transition: 300ms; +} + + +.rce-mtitem-button:hover, +.rce-mtitem-share:hover { + opacity: 0.8; + cursor: pointer; +} + +.rce-mtitem-date { + color: #9f9f9f; + font-size: 13px; +} diff --git a/src/MeetingItem/MeetingItem.js b/src/MeetingItem/MeetingItem.js new file mode 100644 index 00000000..4514fb60 --- /dev/null +++ b/src/MeetingItem/MeetingItem.js @@ -0,0 +1,118 @@ +import React, { Component } from 'react'; +import './MeetingItem.css'; + +import MdVideoCall from 'react-icons/lib/md/video-call'; +import MdLink from 'react-icons/lib/md/link'; + +import Avatar from '../Avatar/Avatar'; + +import { + format, +} from'timeago.js'; + +import classNames from 'classnames'; + +export class MeetingItem extends Component { + + render() { + const statusColorType = this.props.statusColorType; + const AVATAR_LIMIT = this.props.avatarLimit; + + const dateText = this.props.date && !isNaN(this.props.date) && ( + this.props.dateString || + format(this.props.date) + ); + + const subject = this.props.subject.substring(0, this.props.subjectLimit) + (this.props.subject.length > this.props.subjectLimit ? '...' : ''); + + return ( +
+
+
+ {subject} +
+
+
+ { + this.props.avatars.slice(0, 5).map((x, i) => x instanceof Avatar ? x : ( + + {x.statusText} + + } + onError={this.props.onAvatarError} + lazyLoadingImage={this.props.lazyLoadingImage} + type={classNames('circle', {'flexible': this.props.avatarFlexible})}/> + )) + } + + { + this.props.avatars.length > AVATAR_LIMIT && +
+ + {'+' + AVATAR_LIMIT} + +
+ } +
+
+
+ +
+
+
+
+
+ +
+ + {dateText} + +
+
+
+ ); + } +} + +MeetingItem.defaultProps = { + id: '', + subject: '', + subjectLimit: 60, + onClick: null, + avatarFlexible: false, + alt: '', + title: '', + subtitle: '', + date: new Date(), + dateString: '', + lazyLoadingImage: undefined, + avatarLimit: 5, + avatars: [], + onAvatarError: () => void(0), + onMeetingClick: () => void(0), + onShareClick: () => void(0), +} + +export default MeetingItem; diff --git a/src/MeetingItem/__tests__/MeetingItem.js b/src/MeetingItem/__tests__/MeetingItem.js new file mode 100644 index 00000000..75803f56 --- /dev/null +++ b/src/MeetingItem/__tests__/MeetingItem.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import MeetingItem from '../MeetingItem'; + +describe('MeetingItem component', () => { + it('should render without issues', () => { + const component = shallow(); + + expect(component.length).toBe(1); + expect(toJson(component)).toMatchSnapshot(); + }); +}); diff --git a/src/MeetingItem/__tests__/__snapshots__/MeetingItem.js.snap b/src/MeetingItem/__tests__/__snapshots__/MeetingItem.js.snap new file mode 100644 index 00000000..35f86c22 --- /dev/null +++ b/src/MeetingItem/__tests__/__snapshots__/MeetingItem.js.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MeetingItem component should render without issues 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+ + just now + +
+
+
+`; diff --git a/src/MeetingList/MeetingList.css b/src/MeetingList/MeetingList.css new file mode 100644 index 00000000..5545ae04 --- /dev/null +++ b/src/MeetingList/MeetingList.css @@ -0,0 +1,4 @@ +.rce-container-mtlist { + display: block; + overflow: auto; +} diff --git a/src/MeetingList/MeetingList.js b/src/MeetingList/MeetingList.js new file mode 100644 index 00000000..185affd0 --- /dev/null +++ b/src/MeetingList/MeetingList.js @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +import './MeetingList.css'; + +import MeetingItem from '../MeetingItem/MeetingItem'; + +const classNames = require('classnames'); + +export class MeetingList extends Component { + + onClick(item, i, e) { + if (this.props.onClick instanceof Function) + this.props.onClick(item, i, e); + } + + onContextMenu(item, i, e) { + e.preventDefault(); + if (this.props.onContextMenu instanceof Function) + this.props.onContextMenu(item, i, e); + } + + onAvatarError(item, i, e) { + if (this.props.onAvatarError instanceof Function) + this.props.onAvatarError(item, i, e); + } + + onMeetingClick(item, i, e) { + if (this.props.onMeetingClick instanceof Function) + this.props.onMeetingClick(item, i, e); + } + + onShareClick(item, i, e) { + if (this.props.onShareClick instanceof Function) + this.props.onShareClick(item, i, e); + } + + render() { + return ( +
+ { + this.props.dataSource.map((x, i) => ( + this.onAvatarError(x,i,e)} + onContextMenu={(e) => this.onContextMenu(x,i,e)} + onClick={() => this.onClick(x, i)} + onMeetingClick={() => this.onMeetingClick(x, i)} + onShareClick={() => this.onShareClick(x, i)}/> + )) + } +
+ ); + } +} + +MeetingList.defaultProps = { + dataSource: [], + onClick: null, + lazyLoadingImage: undefined, +}; + +export default MeetingList; diff --git a/src/MeetingList/__tests__/MeetList.js b/src/MeetingList/__tests__/MeetList.js new file mode 100644 index 00000000..3bef3117 --- /dev/null +++ b/src/MeetingList/__tests__/MeetList.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import MeetingList from '../MeetingList'; + +describe('MeetingList component', () => { + it('should render without issues', () => { + const component = shallow(); + + expect(component.length).toBe(1); + expect(toJson(component)).toMatchSnapshot(); + }); +}); diff --git a/src/MeetingList/__tests__/__snapshots__/MeetList.js.snap b/src/MeetingList/__tests__/__snapshots__/MeetList.js.snap new file mode 100644 index 00000000..a6f82368 --- /dev/null +++ b/src/MeetingList/__tests__/__snapshots__/MeetList.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MeetingList component should render without issues 1`] = ` +
+`; diff --git a/src/index.js b/src/index.js index 56707496..c216204c 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,8 @@ import Dropdown from './Dropdown/Dropdown'; import SideBar from './SideBar/SideBar'; import Popup from './Popup/Popup'; import ReplyMessage from './ReplyMessage/ReplyMessage'; +import MeetingItem from './MeetingItem/MeetingItem'; +import MeetingList from './MeetingList/MeetingList'; export { @@ -27,4 +29,6 @@ export { SideBar, Popup, ReplyMessage, + MeetingItem, + MeetingList, };