├── .gitignore ├── es ├── assets │ ├── close-icon.png │ ├── logo-no-bg.svg │ ├── chat-icon.svg │ ├── close.svg │ └── file.svg ├── index.js ├── styles │ ├── index.js │ ├── header.css │ ├── index.css │ ├── emojiPicker.css │ ├── launcher.css │ ├── message.css │ └── userInput.css ├── components │ ├── Messages │ │ ├── EmojiMessage.js │ │ ├── TextMessage.js │ │ ├── FileMessage.js │ │ └── index.js │ ├── Header.js │ ├── MessageList.js │ ├── icons │ │ ├── SendIcon.js │ │ ├── FileIcon.js │ │ └── EmojiIcon.js │ ├── ChatWindow.js │ ├── emoji-picker │ │ ├── EmojiPicker.js │ │ └── emojiData.js │ ├── Launcher.js │ └── UserInput.js ├── messageTypes.js └── services │ └── messageBroker.js ├── lib ├── assets │ ├── close-icon.png │ ├── logo-no-bg.svg │ ├── chat-icon.svg │ ├── close.svg │ └── file.svg ├── styles │ ├── index.js │ ├── header.css │ ├── index.css │ ├── emojiPicker.css │ ├── launcher.css │ ├── message.css │ └── userInput.css ├── messageTypes.js ├── index.js ├── components │ ├── Messages │ │ ├── EmojiMessage.js │ │ ├── TextMessage.js │ │ ├── FileMessage.js │ │ └── index.js │ ├── MessageList.js │ ├── Header.js │ ├── icons │ │ ├── SendIcon.js │ │ ├── FileIcon.js │ │ └── EmojiIcon.js │ ├── ChatWindow.js │ ├── emoji-picker │ │ ├── EmojiPicker.js │ │ └── emojiData.js │ ├── Launcher.js │ └── UserInput.js └── services │ └── messageBroker.js ├── src ├── assets │ ├── close-icon.png │ ├── logo-no-bg.svg │ ├── chat-icon.svg │ ├── close.svg │ └── file.svg ├── index.js ├── styles │ ├── index.js │ ├── header.css │ ├── index.css │ ├── emojiPicker.css │ ├── launcher.css │ ├── message.css │ └── userInput.css ├── components │ ├── Messages │ │ ├── EmojiMessage.js │ │ ├── TextMessage.js │ │ ├── index.js │ │ └── FileMessage.js │ ├── MessageList.js │ ├── Header.js │ ├── icons │ │ ├── SendIcon.js │ │ ├── FileIcon.js │ │ └── EmojiIcon.js │ ├── ChatWindow.js │ ├── emoji-picker │ │ └── EmojiPicker.js │ ├── Launcher.js │ └── UserInput.js ├── messageTypes.js └── services │ └── messageBroker.js ├── umd ├── close-icon.c30463a5.png ├── main.2176b2e1.css.map ├── logo-no-bg.7718b3e3.svg ├── chat-icon.e0d2b748.svg ├── close.c4c396d3.svg └── file.037acab7.svg ├── demo ├── dist │ ├── demo.a318e8d5.css.map │ ├── close-icon.c30463a5.png │ ├── logo-no-bg.7718b3e3.svg │ ├── chat-icon.e0d2b748.svg │ ├── close.c4c396d3.svg │ ├── manifest.4a1ee271.js │ ├── index.html │ └── file.037acab7.svg ├── assets │ └── styles │ │ ├── index.js │ │ ├── footer.css │ │ ├── header.css │ │ ├── base.css │ │ └── test-area.css └── src │ ├── Header.js │ ├── Footer.js │ ├── TestArea.js │ ├── messageHistory.js │ └── index.js ├── nwb.config.js ├── .travis.yml ├── CONTRIBUTING.md ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /node_modules 3 | npm-debug.log* 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /es/assets/close-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmezza/react-beautiful-chat/HEAD/es/assets/close-icon.png -------------------------------------------------------------------------------- /lib/assets/close-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmezza/react-beautiful-chat/HEAD/lib/assets/close-icon.png -------------------------------------------------------------------------------- /src/assets/close-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmezza/react-beautiful-chat/HEAD/src/assets/close-icon.png -------------------------------------------------------------------------------- /umd/close-icon.c30463a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmezza/react-beautiful-chat/HEAD/umd/close-icon.c30463a5.png -------------------------------------------------------------------------------- /umd/main.2176b2e1.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"main.2176b2e1.css","sourceRoot":""} -------------------------------------------------------------------------------- /demo/dist/demo.a318e8d5.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"demo.a318e8d5.css","sourceRoot":""} -------------------------------------------------------------------------------- /demo/assets/styles/index.js: -------------------------------------------------------------------------------- 1 | import "./base.css"; 2 | import "./header.css"; 3 | import "./test-area.css"; 4 | import "./footer.css"; -------------------------------------------------------------------------------- /demo/dist/close-icon.c30463a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattmezza/react-beautiful-chat/HEAD/demo/dist/close-icon.c30463a5.png -------------------------------------------------------------------------------- /es/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './styles'; 3 | import Launcher from './components/Launcher'; 4 | 5 | export { Launcher }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import './styles'; 3 | import Launcher from './components/Launcher'; 4 | 5 | 6 | export { Launcher }; 7 | -------------------------------------------------------------------------------- /es/styles/index.js: -------------------------------------------------------------------------------- 1 | import './emojiPicker.css'; 2 | import './index.css'; 3 | import './launcher.css'; 4 | import './header.css'; 5 | import './message.css'; 6 | import './userInput.css'; -------------------------------------------------------------------------------- /src/styles/index.js: -------------------------------------------------------------------------------- 1 | import './emojiPicker.css'; 2 | import './index.css'; 3 | import './launcher.css'; 4 | import './header.css'; 5 | import './message.css'; 6 | import './userInput.css'; -------------------------------------------------------------------------------- /src/components/Messages/EmojiMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | 4 | const EmojiMessage = (props) => { 5 | return
{props.data.emoji}
6 | } 7 | 8 | export default EmojiMessage -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | esModules: true, 5 | umd: { 6 | global: 'liveChat', 7 | externals: { 8 | react: 'React' 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/styles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./emojiPicker.css'); 4 | 5 | require('./index.css'); 6 | 7 | require('./launcher.css'); 8 | 9 | require('./header.css'); 10 | 11 | require('./message.css'); 12 | 13 | require('./userInput.css'); -------------------------------------------------------------------------------- /demo/assets/styles/footer.css: -------------------------------------------------------------------------------- 1 | .demo-footer { 2 | width: 1100px; 3 | display: flex; 4 | margin: auto; 5 | justify-content: space-between; 6 | padding: 20px 0px; 7 | border-top: solid 2px #F0F4FA; 8 | color: #8694AB; 9 | font-weight: 100; 10 | font-size: 14px; 11 | } -------------------------------------------------------------------------------- /es/components/Messages/EmojiMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | var EmojiMessage = function EmojiMessage(props) { 4 | return React.createElement( 5 | "div", 6 | { className: "sc-message--emoji" }, 7 | props.data.emoji 8 | ); 9 | }; 10 | 11 | export default EmojiMessage; -------------------------------------------------------------------------------- /es/messageTypes.js: -------------------------------------------------------------------------------- 1 | var MESSAGE_TYPES = { 2 | CLIENT: { 3 | NEW_VISITOR: 'client.new_visitor', 4 | MESSAGE: 'client.message', 5 | RETURNING_VISITOR: 'client.returning_visitor' 6 | }, 7 | BROKER: { 8 | VISITOR_ID: 'broker.visitor_id', 9 | MESSAGE: 'broker.message' 10 | } 11 | }; 12 | 13 | module.exports = MESSAGE_TYPES; -------------------------------------------------------------------------------- /src/messageTypes.js: -------------------------------------------------------------------------------- 1 | const MESSAGE_TYPES = { 2 | CLIENT: { 3 | NEW_VISITOR: 'client.new_visitor', 4 | MESSAGE: 'client.message', 5 | RETURNING_VISITOR: 'client.returning_visitor', 6 | }, 7 | BROKER: { 8 | VISITOR_ID: 'broker.visitor_id', 9 | MESSAGE: 'broker.message', 10 | }, 11 | }; 12 | 13 | module.exports = MESSAGE_TYPES; 14 | -------------------------------------------------------------------------------- /lib/messageTypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MESSAGE_TYPES = { 4 | CLIENT: { 5 | NEW_VISITOR: 'client.new_visitor', 6 | MESSAGE: 'client.message', 7 | RETURNING_VISITOR: 'client.returning_visitor' 8 | }, 9 | BROKER: { 10 | VISITOR_ID: 'broker.visitor_id', 11 | MESSAGE: 'broker.message' 12 | } 13 | }; 14 | 15 | module.exports = MESSAGE_TYPES; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 6 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /demo/src/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class Header extends Component { 4 | render () { 5 | return ( 6 |
7 |
react-beautiful-chat
8 |
9 | GitHub 10 |
11 |
12 | ) 13 | } 14 | } 15 | 16 | export default Header -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | exports.Launcher = undefined; 5 | 6 | var _react = require('react'); 7 | 8 | var _react2 = _interopRequireDefault(_react); 9 | 10 | require('./styles'); 11 | 12 | var _Launcher = require('./components/Launcher'); 13 | 14 | var _Launcher2 = _interopRequireDefault(_Launcher); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | exports.Launcher = _Launcher2.default; -------------------------------------------------------------------------------- /lib/components/Messages/EmojiMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.__esModule = true; 4 | 5 | var _react = require("react"); 6 | 7 | var _react2 = _interopRequireDefault(_react); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | var EmojiMessage = function EmojiMessage(props) { 12 | return _react2.default.createElement( 13 | "div", 14 | { className: "sc-message--emoji" }, 15 | props.data.emoji 16 | ); 17 | }; 18 | 19 | exports.default = EmojiMessage; 20 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /src/components/MessageList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Message from './Messages' 3 | 4 | class MessageList extends Component { 5 | 6 | componentDidUpdate(prevProps, prevState) { 7 | this.scrollList.scrollTop = this.scrollList.scrollHeight; 8 | } 9 | 10 | render () { 11 | return ( 12 |
this.scrollList = el}> 13 | {this.props.messages.map((message, i) => { 14 | return 15 | })} 16 |
) 17 | } 18 | } 19 | 20 | export default MessageList -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import closeIcon from './../assets/close-icon.png'; 3 | 4 | 5 | class Header extends Component { 6 | 7 | render() { 8 | return ( 9 |
10 | 11 |
{this.props.teamName}
12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | } 19 | 20 | export default Header; 21 | -------------------------------------------------------------------------------- /demo/assets/styles/header.css: -------------------------------------------------------------------------------- 1 | .demo-header { 2 | width: 100%; 3 | color: #4e8cff; 4 | display: flex; 5 | justify-content: space-between; 6 | max-width: 1100px; 7 | margin: auto; 8 | padding: 40px 0px; 9 | } 10 | 11 | .demo-header--title { 12 | font-size: 24px; 13 | font-weight: 600; 14 | } 15 | 16 | .demo-header--links { 17 | width: 200px; 18 | display: flex; 19 | justify-content: space-between; 20 | font-size: 18px; 21 | font-weight: 500; 22 | } 23 | 24 | .demo-header--links a { 25 | color: #4e8cff; 26 | text-decoration: none; 27 | cursor: pointer; 28 | } 29 | 30 | .demo-header--links a:hover { 31 | color: #4983ee; 32 | } -------------------------------------------------------------------------------- /demo/src/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class Footer extends Component { 4 | render () { 5 | return ( 6 |
7 |
8 |
© {new Date().getFullYear()} mattmezza/react-beautiful-chat
9 |
Improved and customized by @mattmezza, @IlyasAbdighni and @joke2k
10 |
11 |
12 |
Improved version of react-chat-window
13 |
14 |
15 | ) 16 | } 17 | } 18 | 19 | export default Footer -------------------------------------------------------------------------------- /src/components/Messages/TextMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import chatIconUrl from './../../assets/chat-icon.svg'; 3 | 4 | const TextMessage = (props) => { 5 | const meta = props.message.data.meta || null 6 | const text = props.message.data.text || '' 7 | const author = props.message.author 8 | return ( 9 |
10 | { 11 | props.message && 12 | author === "me" && 13 | props.onDelete && 14 | 17 | } 18 | {text} 19 | {meta &&

{meta}

} 20 |
21 | ) 22 | } 23 | 24 | export default TextMessage -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= v4 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the components's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /es/components/Messages/TextMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import chatIconUrl from './../../assets/chat-icon.svg'; 3 | 4 | var TextMessage = function TextMessage(props) { 5 | var meta = props.message.data.meta || null; 6 | var text = props.message.data.text || ''; 7 | var author = props.message.author; 8 | return React.createElement( 9 | 'div', 10 | { className: 'sc-message--text' }, 11 | props.message && author === "me" && props.onDelete && React.createElement( 12 | 'button', 13 | { className: 'delete-message', onClick: function onClick() { 14 | return props.onDelete(props.message); 15 | } }, 16 | 'x' 17 | ), 18 | text, 19 | meta && React.createElement( 20 | 'p', 21 | { className: 'sc-message--meta' }, 22 | meta 23 | ) 24 | ); 25 | }; 26 | 27 | export default TextMessage; -------------------------------------------------------------------------------- /demo/assets/styles/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0px; 3 | margin: 0px; 4 | } 5 | 6 | * { 7 | font-family: Avenir Next, Helvetica Neue, Helvetica,sans-serif; 8 | } 9 | 10 | .demo-description { 11 | max-width: 500px; 12 | } 13 | 14 | .demo-description img { 15 | max-width: 500px; 16 | } 17 | 18 | .demo-test-area { 19 | width: 300px; 20 | box-sizing: border-box; 21 | } 22 | 23 | .demo-test-area--text { 24 | box-sizing: border-box; 25 | width: 100%; 26 | margin: 0px; 27 | padding: 0px; 28 | resize: none; 29 | font-family: Avenir Next, Helvetica Neue, Helvetica,sans-serif; 30 | background: #fafbfc; 31 | color: #8da2b5; 32 | border: 1px solid #dde5ed; 33 | font-size: 16px; 34 | padding: 16px 15px 14px; 35 | margin: 0; 36 | border-radius: 6px; 37 | outline: none; 38 | height: 150px; 39 | margin-bottom: 10px; 40 | } 41 | 42 | .demo-monster-img { 43 | width: 400px; 44 | display: block; 45 | margin: 60px auto; 46 | } -------------------------------------------------------------------------------- /es/assets/logo-no-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/logo-no-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo-no-bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /umd/logo-no-bg.7718b3e3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/dist/logo-no-bg.7718b3e3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /es/assets/chat-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/chat-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/chat-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /umd/chat-icon.e0d2b748.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/dist/chat-icon.e0d2b748.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /es/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /umd/close.c4c396d3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/dist/close.c4c396d3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /es/styles/header.css: -------------------------------------------------------------------------------- 1 | .sc-header { 2 | background: #4e8cff; 3 | min-height: 75px; 4 | border-top-left-radius: 9px; 5 | border-top-right-radius: 9px; 6 | color: white; 7 | padding: 10px; 8 | box-shadow: 0 1px 4px rgba(0,0,0,.2); 9 | position: relative; 10 | box-sizing: border-box; 11 | display: flex; 12 | } 13 | 14 | .sc-header--img { 15 | border-radius: 50%; 16 | align-self: center; 17 | padding: 10px; 18 | } 19 | 20 | .sc-header--team-name { 21 | align-self: center; 22 | padding: 10px; 23 | flex: 1; 24 | user-select: none; 25 | cursor: pointer; 26 | border-radius: 5px; 27 | } 28 | 29 | .sc-header--team-name:hover { 30 | background: #4882ed; 31 | } 32 | 33 | .sc-header--close-button { 34 | width: 40px; 35 | align-self: center; 36 | height: 40px; 37 | margin-right: 10px; 38 | box-sizing: border-box; 39 | cursor: pointer; 40 | border-radius: 5px; 41 | } 42 | 43 | .sc-header--close-button:hover { 44 | background: #4882ed; 45 | } 46 | 47 | .sc-header--close-button img { 48 | width: 100%; 49 | height: 100%; 50 | padding: 13px; 51 | box-sizing: border-box; 52 | } 53 | 54 | @media (max-width: 450px) { 55 | .sc-header { 56 | border-radius: 0px; 57 | } 58 | } -------------------------------------------------------------------------------- /lib/styles/header.css: -------------------------------------------------------------------------------- 1 | .sc-header { 2 | background: #4e8cff; 3 | min-height: 75px; 4 | border-top-left-radius: 9px; 5 | border-top-right-radius: 9px; 6 | color: white; 7 | padding: 10px; 8 | box-shadow: 0 1px 4px rgba(0,0,0,.2); 9 | position: relative; 10 | box-sizing: border-box; 11 | display: flex; 12 | } 13 | 14 | .sc-header--img { 15 | border-radius: 50%; 16 | align-self: center; 17 | padding: 10px; 18 | } 19 | 20 | .sc-header--team-name { 21 | align-self: center; 22 | padding: 10px; 23 | flex: 1; 24 | user-select: none; 25 | cursor: pointer; 26 | border-radius: 5px; 27 | } 28 | 29 | .sc-header--team-name:hover { 30 | background: #4882ed; 31 | } 32 | 33 | .sc-header--close-button { 34 | width: 40px; 35 | align-self: center; 36 | height: 40px; 37 | margin-right: 10px; 38 | box-sizing: border-box; 39 | cursor: pointer; 40 | border-radius: 5px; 41 | } 42 | 43 | .sc-header--close-button:hover { 44 | background: #4882ed; 45 | } 46 | 47 | .sc-header--close-button img { 48 | width: 100%; 49 | height: 100%; 50 | padding: 13px; 51 | box-sizing: border-box; 52 | } 53 | 54 | @media (max-width: 450px) { 55 | .sc-header { 56 | border-radius: 0px; 57 | } 58 | } -------------------------------------------------------------------------------- /src/styles/header.css: -------------------------------------------------------------------------------- 1 | .sc-header { 2 | background: #4e8cff; 3 | min-height: 75px; 4 | border-top-left-radius: 9px; 5 | border-top-right-radius: 9px; 6 | color: white; 7 | padding: 10px; 8 | box-shadow: 0 1px 4px rgba(0,0,0,.2); 9 | position: relative; 10 | box-sizing: border-box; 11 | display: flex; 12 | } 13 | 14 | .sc-header--img { 15 | border-radius: 50%; 16 | align-self: center; 17 | padding: 10px; 18 | } 19 | 20 | .sc-header--team-name { 21 | align-self: center; 22 | padding: 10px; 23 | flex: 1; 24 | user-select: none; 25 | cursor: pointer; 26 | border-radius: 5px; 27 | } 28 | 29 | .sc-header--team-name:hover { 30 | background: #4882ed; 31 | } 32 | 33 | .sc-header--close-button { 34 | width: 40px; 35 | align-self: center; 36 | height: 40px; 37 | margin-right: 10px; 38 | box-sizing: border-box; 39 | cursor: pointer; 40 | border-radius: 5px; 41 | } 42 | 43 | .sc-header--close-button:hover { 44 | background: #4882ed; 45 | } 46 | 47 | .sc-header--close-button img { 48 | width: 100%; 49 | height: 100%; 50 | padding: 13px; 51 | box-sizing: border-box; 52 | } 53 | 54 | @media (max-width: 450px) { 55 | .sc-header { 56 | border-radius: 0px; 57 | } 58 | } -------------------------------------------------------------------------------- /lib/components/Messages/TextMessage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | 5 | var _react = require('react'); 6 | 7 | var _react2 = _interopRequireDefault(_react); 8 | 9 | var _chatIcon = require('./../../assets/chat-icon.svg'); 10 | 11 | var _chatIcon2 = _interopRequireDefault(_chatIcon); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | var TextMessage = function TextMessage(props) { 16 | var meta = props.message.data.meta || null; 17 | var text = props.message.data.text || ''; 18 | var author = props.message.author; 19 | return _react2.default.createElement( 20 | 'div', 21 | { className: 'sc-message--text' }, 22 | props.message && author === "me" && props.onDelete && _react2.default.createElement( 23 | 'button', 24 | { className: 'delete-message', onClick: function onClick() { 25 | return props.onDelete(props.message); 26 | } }, 27 | 'x' 28 | ), 29 | text, 30 | meta && _react2.default.createElement( 31 | 'p', 32 | { className: 'sc-message--meta' }, 33 | meta 34 | ) 35 | ); 36 | }; 37 | 38 | exports.default = TextMessage; 39 | module.exports = exports['default']; -------------------------------------------------------------------------------- /es/styles/index.css: -------------------------------------------------------------------------------- 1 | .sc-chat-window { 2 | width: 370px; 3 | height: calc(100% - 120px); 4 | max-height: 590px; 5 | position: fixed; 6 | right: 25px; 7 | bottom: 100px; 8 | box-sizing: border-box; 9 | box-shadow: 0px 7px 40px 2px rgba(148, 149, 150, 0.3); 10 | background: white; 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: space-between; 14 | transition: 0.3s ease-in-out; 15 | border-radius: 10px; 16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 17 | } 18 | 19 | .sc-chat-window.closed { 20 | opacity: 0; 21 | visibility: hidden; 22 | bottom: 90px; 23 | } 24 | 25 | .sc-message-list { 26 | height: 80%; 27 | overflow-y: auto; 28 | background-color: white; 29 | background-size: 100%; 30 | padding: 40px 0px; 31 | } 32 | 33 | .sc-message--me { 34 | text-align: right; 35 | } 36 | .sc-message--them { 37 | text-align: left; 38 | } 39 | 40 | @media (max-width: 450px) { 41 | .sc-chat-window { 42 | width: 100%; 43 | height: 100%; 44 | max-height: 100%; 45 | right: 0px; 46 | bottom: 0px; 47 | border-radius: 0px; 48 | } 49 | .sc-chat-window { 50 | transition: 0.1s ease-in-out; 51 | } 52 | .sc-chat-window.closed { 53 | bottom: 0px; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/styles/index.css: -------------------------------------------------------------------------------- 1 | .sc-chat-window { 2 | width: 370px; 3 | height: calc(100% - 120px); 4 | max-height: 590px; 5 | position: fixed; 6 | right: 25px; 7 | bottom: 100px; 8 | box-sizing: border-box; 9 | box-shadow: 0px 7px 40px 2px rgba(148, 149, 150, 0.3); 10 | background: white; 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: space-between; 14 | transition: 0.3s ease-in-out; 15 | border-radius: 10px; 16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 17 | } 18 | 19 | .sc-chat-window.closed { 20 | opacity: 0; 21 | visibility: hidden; 22 | bottom: 90px; 23 | } 24 | 25 | .sc-message-list { 26 | height: 80%; 27 | overflow-y: auto; 28 | background-color: white; 29 | background-size: 100%; 30 | padding: 40px 0px; 31 | } 32 | 33 | .sc-message--me { 34 | text-align: right; 35 | } 36 | .sc-message--them { 37 | text-align: left; 38 | } 39 | 40 | @media (max-width: 450px) { 41 | .sc-chat-window { 42 | width: 100%; 43 | height: 100%; 44 | max-height: 100%; 45 | right: 0px; 46 | bottom: 0px; 47 | border-radius: 0px; 48 | } 49 | .sc-chat-window { 50 | transition: 0.1s ease-in-out; 51 | } 52 | .sc-chat-window.closed { 53 | bottom: 0px; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | .sc-chat-window { 2 | width: 370px; 3 | height: calc(100% - 120px); 4 | max-height: 590px; 5 | position: fixed; 6 | right: 25px; 7 | bottom: 100px; 8 | box-sizing: border-box; 9 | box-shadow: 0px 7px 40px 2px rgba(148, 149, 150, 0.3); 10 | background: white; 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: space-between; 14 | transition: 0.3s ease-in-out; 15 | border-radius: 10px; 16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 17 | } 18 | 19 | .sc-chat-window.closed { 20 | opacity: 0; 21 | visibility: hidden; 22 | bottom: 90px; 23 | } 24 | 25 | .sc-message-list { 26 | height: 80%; 27 | overflow-y: auto; 28 | background-color: white; 29 | background-size: 100%; 30 | padding: 40px 0px; 31 | } 32 | 33 | .sc-message--me { 34 | text-align: right; 35 | } 36 | .sc-message--them { 37 | text-align: left; 38 | } 39 | 40 | @media (max-width: 450px) { 41 | .sc-chat-window { 42 | width: 100%; 43 | height: 100%; 44 | max-height: 100%; 45 | right: 0px; 46 | bottom: 0px; 47 | border-radius: 0px; 48 | } 49 | .sc-chat-window { 50 | transition: 0.1s ease-in-out; 51 | } 52 | .sc-chat-window.closed { 53 | bottom: 0px; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Messages/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import TextMessage from './TextMessage' 3 | import EmojiMessage from './EmojiMessage' 4 | import FileMessage from './FileMessage' 5 | import chatIconUrl from './../../assets/chat-icon.svg' 6 | 7 | 8 | class Message extends Component { 9 | 10 | _renderMessageOfType(type) { 11 | switch (type) { 12 | case 'text': 13 | return 14 | case 'emoji': 15 | return 16 | case 'file': 17 | return 18 | } 19 | } 20 | 21 | render() { 22 | let contentClassList = [ 23 | "sc-message--content", 24 | (this.props.message.author === "me" ? "sent" : "received") 25 | ]; 26 | return ( 27 |
28 |
29 |
32 | {this._renderMessageOfType(this.props.message.type)} 33 |
34 |
) 35 | } 36 | } 37 | 38 | export default Message -------------------------------------------------------------------------------- /src/components/Messages/FileMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import genericFileIcon from '../../assets/file.svg'; 3 | import closeIcon from '../../assets/close.svg' 4 | 5 | const FileMessage = (props) => { 6 | const meta = props.message.data.meta || null 7 | const text = props.message.data.text || '' 8 | const file = props.message.data.file 9 | const author = props.message.author 10 | return ( 11 |
12 | { 13 | props.message && 14 | author === "me" && 15 | props.onDelete && 16 | 19 | } 20 |
21 | 22 | generic file icon 23 | 24 |
25 |
26 | {file.name} 27 |
28 |
29 | {text} 30 |
31 | {meta &&

{meta}

} 32 |
33 | ) 34 | } 35 | 36 | export default FileMessage -------------------------------------------------------------------------------- /src/components/icons/SendIcon.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class SendIcon extends Component { 4 | 5 | render() { 6 | return ( 7 | 31 | ); 32 | } 33 | } 34 | 35 | export default SendIcon; 36 | -------------------------------------------------------------------------------- /es/styles/emojiPicker.css: -------------------------------------------------------------------------------- 1 | .sc-emoji-picker { 2 | position: absolute; 3 | bottom: 50px; 4 | right: 0px; 5 | width: 330px; 6 | max-height: 215px; 7 | box-shadow: 0px 7px 40px 2px rgba(148, 149, 150, 0.3); 8 | background: white; 9 | border-radius: 10px; 10 | outline: none; 11 | } 12 | 13 | .sc-emoji-picker:after { 14 | content: ""; 15 | width: 14px; 16 | height: 14px; 17 | background: white; 18 | position: absolute; 19 | bottom: -6px; 20 | right: 30px; 21 | transform: rotate(45deg); 22 | border-radius: 2px; 23 | } 24 | 25 | .sc-emoji-picker--content { 26 | padding: 10px; 27 | overflow: auto; 28 | width: 100%; 29 | max-height: 195px; 30 | margin-top: 7px; 31 | box-sizing: border-box; 32 | } 33 | 34 | .sc-emoji-picker--category { 35 | display: flex; 36 | flex-direction: row; 37 | flex-wrap: wrap; 38 | } 39 | 40 | .sc-emoji-picker--category-title { 41 | min-width: 100%; 42 | color: #b8c3ca; 43 | font-weight: 200; 44 | font-size: 13px; 45 | margin: 5px; 46 | letter-spacing: 1px; 47 | } 48 | 49 | .sc-emoji-picker--emoji { 50 | margin: 5px; 51 | width: 30px; 52 | line-height: 30px; 53 | text-align: center; 54 | cursor: pointer; 55 | vertical-align: middle; 56 | font-size: 28px; 57 | transition: transform 60ms ease-out,-webkit-transform 60ms ease-out; 58 | } 59 | 60 | .sc-emoji-picker--emoji:hover { 61 | transform: scale(1.4); 62 | } -------------------------------------------------------------------------------- /lib/styles/emojiPicker.css: -------------------------------------------------------------------------------- 1 | .sc-emoji-picker { 2 | position: absolute; 3 | bottom: 50px; 4 | right: 0px; 5 | width: 330px; 6 | max-height: 215px; 7 | box-shadow: 0px 7px 40px 2px rgba(148, 149, 150, 0.3); 8 | background: white; 9 | border-radius: 10px; 10 | outline: none; 11 | } 12 | 13 | .sc-emoji-picker:after { 14 | content: ""; 15 | width: 14px; 16 | height: 14px; 17 | background: white; 18 | position: absolute; 19 | bottom: -6px; 20 | right: 30px; 21 | transform: rotate(45deg); 22 | border-radius: 2px; 23 | } 24 | 25 | .sc-emoji-picker--content { 26 | padding: 10px; 27 | overflow: auto; 28 | width: 100%; 29 | max-height: 195px; 30 | margin-top: 7px; 31 | box-sizing: border-box; 32 | } 33 | 34 | .sc-emoji-picker--category { 35 | display: flex; 36 | flex-direction: row; 37 | flex-wrap: wrap; 38 | } 39 | 40 | .sc-emoji-picker--category-title { 41 | min-width: 100%; 42 | color: #b8c3ca; 43 | font-weight: 200; 44 | font-size: 13px; 45 | margin: 5px; 46 | letter-spacing: 1px; 47 | } 48 | 49 | .sc-emoji-picker--emoji { 50 | margin: 5px; 51 | width: 30px; 52 | line-height: 30px; 53 | text-align: center; 54 | cursor: pointer; 55 | vertical-align: middle; 56 | font-size: 28px; 57 | transition: transform 60ms ease-out,-webkit-transform 60ms ease-out; 58 | } 59 | 60 | .sc-emoji-picker--emoji:hover { 61 | transform: scale(1.4); 62 | } -------------------------------------------------------------------------------- /src/styles/emojiPicker.css: -------------------------------------------------------------------------------- 1 | .sc-emoji-picker { 2 | position: absolute; 3 | bottom: 50px; 4 | right: 0px; 5 | width: 330px; 6 | max-height: 215px; 7 | box-shadow: 0px 7px 40px 2px rgba(148, 149, 150, 0.3); 8 | background: white; 9 | border-radius: 10px; 10 | outline: none; 11 | } 12 | 13 | .sc-emoji-picker:after { 14 | content: ""; 15 | width: 14px; 16 | height: 14px; 17 | background: white; 18 | position: absolute; 19 | bottom: -6px; 20 | right: 30px; 21 | transform: rotate(45deg); 22 | border-radius: 2px; 23 | } 24 | 25 | .sc-emoji-picker--content { 26 | padding: 10px; 27 | overflow: auto; 28 | width: 100%; 29 | max-height: 195px; 30 | margin-top: 7px; 31 | box-sizing: border-box; 32 | } 33 | 34 | .sc-emoji-picker--category { 35 | display: flex; 36 | flex-direction: row; 37 | flex-wrap: wrap; 38 | } 39 | 40 | .sc-emoji-picker--category-title { 41 | min-width: 100%; 42 | color: #b8c3ca; 43 | font-weight: 200; 44 | font-size: 13px; 45 | margin: 5px; 46 | letter-spacing: 1px; 47 | } 48 | 49 | .sc-emoji-picker--emoji { 50 | margin: 5px; 51 | width: 30px; 52 | line-height: 30px; 53 | text-align: center; 54 | cursor: pointer; 55 | vertical-align: middle; 56 | font-size: 28px; 57 | transition: transform 60ms ease-out,-webkit-transform 60ms ease-out; 58 | } 59 | 60 | .sc-emoji-picker--emoji:hover { 61 | transform: scale(1.4); 62 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-beautiful-chat", 3 | "version": "1.1.0", 4 | "description": "A simple and beautiful React chat component backend agnostic and with Emoji and File support.", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "files": [ 8 | "css", 9 | "es", 10 | "lib", 11 | "umd" 12 | ], 13 | "scripts": { 14 | "build": "nwb build-react-component --copy-files", 15 | "clean": "nwb clean-module && nwb clean-demo", 16 | "start": "nwb serve-react-demo", 17 | "test": "nwb test-react", 18 | "test:coverage": "nwb test-react --coverage", 19 | "test:watch": "nwb test-react --server", 20 | "gh:publish": "nwb build-demo && gh-pages -d demo/dist" 21 | }, 22 | "dependencies": { 23 | "emoji-js": "3.2.2", 24 | "gh-pages": "^1.1.0", 25 | "lodash": "^4.17.5", 26 | "prop-types": "15.5.10", 27 | "react-highlight.js": "1.0.5", 28 | "socket.io-client": "2.0.3" 29 | }, 30 | "peerDependencies": { 31 | "react": "15.x" 32 | }, 33 | "devDependencies": { 34 | "nwb": "0.17.x", 35 | "react": "15.6.1", 36 | "react-dom": "15.6.1" 37 | }, 38 | "author": "", 39 | "homepage": "https://mattmezza.github.io/react-beautiful-chat/", 40 | "license": "MIT", 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/mattmezza/react-beautiful-chat.git" 44 | }, 45 | "keywords": [ 46 | "react-component" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /demo/dist/manifest.4a1ee271.js: -------------------------------------------------------------------------------- 1 | !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l handle(msg)); 32 | }, 33 | 34 | onMessageReceived(handler) { 35 | this.messageRecievedHandlers.push(handler); 36 | }, 37 | 38 | getVisitorId() { 39 | return 1; 40 | }, 41 | 42 | getTeamId() { 43 | return 1; 44 | }, 45 | 46 | getTeamName() { 47 | return 1; 48 | }, 49 | 50 | getImageUrl() { 51 | return 1; 52 | }, 53 | 54 | setVisitorId(data) { 55 | localStorage.setItem('SLACKCHAT.VISITOR_ID', data.visitorId); 56 | } 57 | }; 58 | 59 | export default messageBroker; -------------------------------------------------------------------------------- /src/components/ChatWindow.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React, { Component } from 'react' 3 | import MessageList from './MessageList' 4 | import UserInput from './UserInput' 5 | import Header from './Header' 6 | 7 | 8 | class ChatWindow extends Component { 9 | constructor(props) { 10 | super(props) 11 | } 12 | 13 | onUserInputSubmit = (message) => { 14 | this.props.onUserInputSubmit(message) 15 | } 16 | 17 | onMessageReceived(message) { 18 | this.setState({ messages: [...this.state.messages, message] }) 19 | } 20 | 21 | render() { 22 | let messageList = this.props.messageList || [] 23 | let classList = [ 24 | "sc-chat-window", 25 | (this.props.isOpen ? "opened" : "closed") 26 | ] 27 | return ( 28 |
29 |
34 | 39 | 44 |
45 | ) 46 | } 47 | } 48 | 49 | ChatWindow.propTypes = { 50 | showEmoji: PropTypes.bool, 51 | showFile: PropTypes.bool, 52 | onKeyPress: PropTypes.func 53 | } 54 | 55 | export default ChatWindow 56 | -------------------------------------------------------------------------------- /es/components/Messages/FileMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import genericFileIcon from '../../assets/file.svg'; 3 | import closeIcon from '../../assets/close.svg'; 4 | 5 | var FileMessage = function FileMessage(props) { 6 | var meta = props.message.data.meta || null; 7 | var text = props.message.data.text || ''; 8 | var file = props.message.data.file; 9 | var author = props.message.author; 10 | return React.createElement( 11 | 'div', 12 | { className: 'sc-message--file' }, 13 | props.message && author === "me" && props.onDelete && React.createElement( 14 | 'button', 15 | { className: 'delete-message', onClick: function onClick() { 16 | return props.onDelete(props.message); 17 | } }, 18 | 'x' 19 | ), 20 | React.createElement( 21 | 'div', 22 | { className: 'sc-message--file-icon' }, 23 | React.createElement( 24 | 'a', 25 | { href: file.url || '#', target: '_blank' }, 26 | React.createElement('img', { src: genericFileIcon, alt: 'generic file icon', height: 60 }) 27 | ) 28 | ), 29 | React.createElement( 30 | 'div', 31 | { className: 'sc-message--file-name' }, 32 | React.createElement( 33 | 'a', 34 | { href: file.url ? file.url : '#', target: '_blank' }, 35 | file.name 36 | ) 37 | ), 38 | React.createElement( 39 | 'div', 40 | { className: 'sc-message--file-text' }, 41 | text 42 | ), 43 | meta && React.createElement( 44 | 'p', 45 | { className: 'sc-message--meta' }, 46 | meta 47 | ) 48 | ); 49 | }; 50 | 51 | export default FileMessage; -------------------------------------------------------------------------------- /src/components/icons/FileIcon.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class FileIcon extends Component { 4 | 5 | render() { 6 | return ( 7 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | export default FileIcon; 36 | -------------------------------------------------------------------------------- /es/services/messageBroker.js: -------------------------------------------------------------------------------- 1 | import MESSAGE_TYPES from './../messageTypes'; 2 | var CLIENT = MESSAGE_TYPES.CLIENT; 3 | var BROKER = MESSAGE_TYPES.BROKER; 4 | var SOCKET_URL = process.env.SC_SOCKET_URL; 5 | var io = require('socket.io-client'); 6 | 7 | var messageBroker = { 8 | init: function init() { 9 | var socket = io(SOCKET_URL); 10 | this.socket = socket; 11 | this.messageRecievedHandlers = []; 12 | socket.on(BROKER.VISITOR_ID, this.setVisitorId); 13 | socket.on(BROKER.MESSAGE, this.handleIncomingMessage.bind(this)); 14 | var visitorId = this.getVisitorId(); 15 | var teamId = this.getTeamId(); 16 | 17 | if (!visitorId) { 18 | socket.emit(CLIENT.NEW_VISITOR, { teamId: teamId }); 19 | } else { 20 | socket.emit(CLIENT.RETURNING_VISITOR, { visitorId: visitorId, teamId: teamId }); 21 | } 22 | }, 23 | sendMessage: function sendMessage(msg) { 24 | msg.visitorId = this.getVisitorId(); 25 | msg.teamId = this.getTeamId(); 26 | this.socket.emit(CLIENT.MESSAGE, msg); 27 | }, 28 | handleIncomingMessage: function handleIncomingMessage(msg) { 29 | this.messageRecievedHandlers.forEach(function (handle) { 30 | return handle(msg); 31 | }); 32 | }, 33 | onMessageReceived: function onMessageReceived(handler) { 34 | this.messageRecievedHandlers.push(handler); 35 | }, 36 | getVisitorId: function getVisitorId() { 37 | return 1; 38 | }, 39 | getTeamId: function getTeamId() { 40 | return 1; 41 | }, 42 | getTeamName: function getTeamName() { 43 | return 1; 44 | }, 45 | getImageUrl: function getImageUrl() { 46 | return 1; 47 | }, 48 | setVisitorId: function setVisitorId(data) { 49 | localStorage.setItem('SLACKCHAT.VISITOR_ID', data.visitorId); 50 | } 51 | }; 52 | 53 | export default messageBroker; -------------------------------------------------------------------------------- /src/components/emoji-picker/EmojiPicker.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import EmojiConvertor from 'emoji-js'; 3 | import emojiData from './emojiData'; 4 | 5 | 6 | class EmojiPicker extends Component { 7 | 8 | constructor() { 9 | super(); 10 | this.emojiConvertor = new EmojiConvertor(); 11 | this.emojiConvertor.init_env(); 12 | } 13 | 14 | componentDidMount() { 15 | const elem = this.domNode; 16 | elem.style.opacity = 0; 17 | window.requestAnimationFrame(() => { 18 | elem.style.transition = 'opacity 350ms'; 19 | elem.style.opacity = 1; 20 | }); 21 | this.domNode.focus(); 22 | } 23 | 24 | render() { 25 | return ( 26 |
{ this.domNode = e; }} 31 | > 32 |
33 | {emojiData.map((category) => { 34 | return ( 35 |
36 |
{category.name}
37 | {category.emojis.map((emoji) => { 38 | return ( 39 | { 43 | this.props.onEmojiPicked(emoji); 44 | this.domNode.blur(); 45 | }} 46 | > 47 | {emoji} 48 | 49 | ); 50 | })} 51 |
52 | ); 53 | })} 54 |
55 |
56 | ); 57 | } 58 | } 59 | 60 | export default EmojiPicker; 61 | -------------------------------------------------------------------------------- /demo/src/TestArea.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class TestArea extends Component { 4 | render () { 5 | return ( 6 |
7 |
8 |
react-beautiful-chat demo
9 | 10 |
11 |
{ 12 | e.preventDefault(); 13 | this.props.onMessage(this.textArea.value); 14 | this.textArea.value = ''; 15 | }}> 16 |
Test the chat window by sending a message:
17 |