From 53007afad9e1379a8dbcb258a805e15bec7793b5 Mon Sep 17 00:00:00 2001 From: Lasha Kakhidze Date: Tue, 23 Jul 2019 18:36:25 +0400 Subject: [PATCH 1/2] [Contribution: @kaxi1993] Add typing indicator (#127) * Add typing indicator * Update readme to include typing feature * fix sample code issue in readme * Update demo to include typing example * Remove vendor prefixes from typing indicator style * Update typing indicator prop name * Add missing comma --- README.md | 23 ++++++++------ demo/src/TestArea.js | 22 +++++++++++++ demo/src/index.js | 18 ++++++++++- src/components/ChatWindow.js | 4 ++- src/components/Launcher.js | 5 ++- src/components/MessageList.js | 2 ++ src/components/TypingIndicator.js | 16 ++++++++++ src/styles/index.js | 1 + src/styles/typing-indicator.css | 52 +++++++++++++++++++++++++++++++ 9 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 src/components/TypingIndicator.js create mode 100644 src/styles/typing-indicator.css diff --git a/README.md b/README.md index 46734ed59..14a8388aa 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ class Demo extends Component { constructor() { super(); this.state = { - messageList: [] + messageList: [], + isTyping: true }; } @@ -74,6 +75,7 @@ class Demo extends Component { onMessageWasSent={this._onMessageWasSent.bind(this)} messageList={this.state.messageList} showEmoji + showTypingIndicator={this.state.isTyping} /> ) } @@ -92,15 +94,16 @@ Launcher props: | prop | type | required | description | |------------------|--------|----------|-------------| -| agentProfile | [object](#agent-profile-objects) | yes | Represents your product or service's customer service agent. Fields: imageUrl (string), teamName (string). | -| handleClick | function | yes | Intercept the click event on the launcher. No argument sent when function is called. | -| isOpen | boolean | yes | Force the open/close state of the chat window. If this is not set, it will open and close when clicked. | -| messageList | [[message](#message-objects)] | yes | An array of message objects to be rendered as a conversation. | -| mute | boolean | no | Don't play sound for incoming messages. Defaults to `false`. | -| newMessagesCount | number | no | The number of new messages. If greater than 0, this number will be displayed in a badge on the launcher. Defaults to `0`. | -| onFilesSelected | function([fileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList)) | no | Called after file has been selected from dialogue in chat window. | -| onMessageWasSent | function([message](#message-objects)) | yes | Called when a message is sent, with a message object as an argument. | -| showEmoji | boolean | no | Whether or not to show the emoji button in the input bar. Defaults to `true`. +| agentProfile | [object](#agent-profile-objects) | yes | Represents your product or service's customer service agent. Fields: imageUrl (string), teamName (string). | +| handleClick | function | yes | Intercept the click event on the launcher. No argument sent when function is called. | +| isOpen | boolean | yes | Force the open/close state of the chat window. If this is not set, it will open and close when clicked. | +| messageList | [[message](#message-objects)] | yes | An array of message objects to be rendered as a conversation. | +| mute | boolean | no | Don't play sound for incoming messages. Defaults to `false`. | +| newMessagesCount | number | no | The number of new messages. If greater than 0, this number will be displayed in a badge on the launcher. Defaults to `0`. | +| onFilesSelected | function([fileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList)) | no | Called after file has been selected from dialogue in chat window. | +| onMessageWasSent | function([message](#message-objects)) | yes | Called when a message is sent, with a message object as an argument. | +| showEmoji | boolean | no | Whether or not to show the emoji button in the input bar. Defaults to `true`. +| showTypingIndicator | boolean | no | Whether or not to show typing indicator in the message box. Defaults to `false`. ### Message Objects diff --git a/demo/src/TestArea.js b/demo/src/TestArea.js index 59ee265ff..fb42728d7 100644 --- a/demo/src/TestArea.js +++ b/demo/src/TestArea.js @@ -1,6 +1,26 @@ import React, { Component } from 'react'; class TestArea extends Component { + constructor (props) { + super(props); + + this.timeout = null; + this._onKeyUp = this._onKeyUp.bind(this); + } + + _onKeyUp() { + if (this.timeout) { + clearTimeout(this.timeout); + } + + this.props.startTyping(); + + // stop typing after 1 second of inactivity + this.timeout = setTimeout(() => { + this.props.stopTyping(); + }, 1000); + } + render () { return (
@@ -13,6 +33,7 @@ class TestArea extends Component {
{ e.preventDefault(); this.props.onMessage(this.textArea.value); + this.props.stopTyping(); this.textArea.value = ''; }}>
Test the chat window by sending a message:
@@ -20,6 +41,7 @@ class TestArea extends Component { ref={(e) => { this.textArea = e; }} className="demo-test-area--text" placeholder="Write a test message...." + onKeyUp={this._onKeyUp} />
diff --git a/demo/src/index.js b/demo/src/index.js index dedcf9090..82bd7bade 100644 --- a/demo/src/index.js +++ b/demo/src/index.js @@ -17,7 +17,8 @@ class Demo extends Component { this.state = { messageList: messageHistory, newMessagesCount: 0, - isOpen: false + isOpen: false, + isTyping: false }; } @@ -54,6 +55,18 @@ class Demo extends Component { } } + _startTyping() { + this.setState({ + isTyping: true + }); + } + + _stopTyping() { + this.setState({ + isTyping: false + }); + } + _handleClick() { this.setState({ isOpen: !this.state.isOpen, @@ -66,6 +79,8 @@ class Demo extends Component {
diff --git a/src/components/ChatWindow.js b/src/components/ChatWindow.js index 445bf1247..2573733c1 100644 --- a/src/components/ChatWindow.js +++ b/src/components/ChatWindow.js @@ -34,6 +34,7 @@ class ChatWindow extends Component {
); @@ -84,11 +85,13 @@ Launcher.propTypes = { messageList: PropTypes.arrayOf(PropTypes.object), mute: PropTypes.bool, showEmoji: PropTypes.bool, + showTypingIndicator: PropTypes.bool }; Launcher.defaultProps = { newMessagesCount: 0, - showEmoji: true + showEmoji: true, + showTypingIndicator: false }; export default Launcher; diff --git a/src/components/MessageList.js b/src/components/MessageList.js index e72cc123b..91aeb631c 100644 --- a/src/components/MessageList.js +++ b/src/components/MessageList.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import Message from './Messages'; +import TypingIndicator from './TypingIndicator'; class MessageList extends Component { @@ -13,6 +14,7 @@ class MessageList extends Component { {this.props.messages.map((message, i) => { return ; })} + {this.props.showTypingIndicator && } ); } } diff --git a/src/components/TypingIndicator.js b/src/components/TypingIndicator.js new file mode 100644 index 000000000..5c7f0818f --- /dev/null +++ b/src/components/TypingIndicator.js @@ -0,0 +1,16 @@ +import React from 'react' + +function TypingIndicator () { + return ( +
+
+
+
+
+
+
+ ) +} + + +export default TypingIndicator diff --git a/src/styles/index.js b/src/styles/index.js index a740bb776..5ff47e25c 100644 --- a/src/styles/index.js +++ b/src/styles/index.js @@ -5,3 +5,4 @@ import './header.css'; import './message.css'; import './user-input.css'; import './popup-window.css'; +import './typing-indicator.css'; diff --git a/src/styles/typing-indicator.css b/src/styles/typing-indicator.css new file mode 100644 index 000000000..348eba9c2 --- /dev/null +++ b/src/styles/typing-indicator.css @@ -0,0 +1,52 @@ +.sc-typing-indicator { + padding-top: 20px; + width: 300px; + margin: auto; +} + +.sc-typing-indicator--circle { + width: 28px; + height: 22px; + padding: 5px 8px; + border-radius: 15px; + background-color: #f4f7f9; + display: flex; + justify-content: center; + align-items: center; +} + +.sc-typing-indicator--dot { + background-color: #90949c; + animation: typing-indicator 1.5s infinite ease-in-out; + display: inline-block; + border-radius: 2px; + margin-right: 2px; + height: 4px; + width: 4px; +} + +@keyframes typing-indicator { + 0% { + transform: translateY(0px) + } + + 28% { + transform: translateY(-5px) + } + + 44% { + transform: translateY(0px) + } +} + +.sc-typing-indicator--dot:nth-child(1) { + animation-delay: 200ms; +} + +.sc-typing-indicator--dot:nth-child(2) { + animation-delay: 300ms; +} + +.sc-typing-indicator--dot:nth-child(3) { + animation-delay: 400ms; +} From a520c1a8131891eb3e4ff4d9803a2729201f2ccd Mon Sep 17 00:00:00 2001 From: Heather Booker Date: Tue, 8 Oct 2019 16:09:12 -0400 Subject: [PATCH 2/2] Fix lints and css class names --- src/components/TypingIndicator.js | 6 +++--- src/styles/typing-indicator.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/TypingIndicator.js b/src/components/TypingIndicator.js index 5c7f0818f..77ce90e72 100644 --- a/src/components/TypingIndicator.js +++ b/src/components/TypingIndicator.js @@ -1,4 +1,4 @@ -import React from 'react' +import React from 'react'; function TypingIndicator () { return ( @@ -9,8 +9,8 @@ function TypingIndicator () {
- ) + ); } -export default TypingIndicator +export default TypingIndicator; diff --git a/src/styles/typing-indicator.css b/src/styles/typing-indicator.css index 348eba9c2..3f60ac920 100644 --- a/src/styles/typing-indicator.css +++ b/src/styles/typing-indicator.css @@ -4,7 +4,7 @@ margin: auto; } -.sc-typing-indicator--circle { +.sc-typing-indicator--message { width: 28px; height: 22px; padding: 5px 8px;