Skip to content

Commit 59a030e

Browse files
committed
add submit button
1 parent 8080f2b commit 59a030e

17 files changed

+270
-122
lines changed

dist/react-simple-chatbot.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ChatBot.jsx

Lines changed: 126 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@ import _ from 'lodash';
22
import React, { Component } from 'react';
33
import PropTypes from 'prop-types';
44
import Random from 'random-id';
5-
import { CustomStep, OptionsStep, TextStep } from './steps/steps';
5+
import { CustomStep, OptionsStep, TextStep } from './steps';
66
import schema from './schemas/schema';
77
import ChatBotContainer from './ChatBotContainer';
88
import Content from './Content';
99
import Header from './Header';
1010
import HeaderTitle from './HeaderTitle';
1111
import HeaderIcon from './HeaderIcon';
1212
import FloatButton from './FloatButton';
13-
import ChatIcon from './ChatIcon';
14-
import CloseIcon from './CloseIcon';
1513
import Footer from './Footer';
1614
import Input from './Input';
15+
import SubmitButton from './SubmitButton';
16+
import {
17+
ChatIcon,
18+
CloseIcon,
19+
SubmitIcon,
20+
} from './icons';
1721

1822
class ChatBot extends Component {
1923
/* istanbul ignore next */
@@ -30,31 +34,29 @@ class ChatBot extends Component {
3034
opened: props.opened || !props.floating,
3135
inputValue: '',
3236
inputInvalid: false,
33-
defaultBotSettings: {},
3437
defaultUserSettings: {},
3538
};
3639

3740
this.renderStep = this.renderStep.bind(this);
3841
this.triggerNextStep = this.triggerNextStep.bind(this);
3942
this.onValueChange = this.onValueChange.bind(this);
4043
this.handleKeyPress = this.handleKeyPress.bind(this);
44+
this.handleSubmitButton = this.handleSubmitButton.bind(this);
4145
}
4246

4347
componentWillMount() {
44-
const { botDelay, botAvatar, customDelay, userDelay, userAvatar } = this.props;
48+
const {
49+
botDelay,
50+
botAvatar,
51+
customDelay,
52+
userAvatar,
53+
userDelay,
54+
} = this.props;
4555
const steps = {};
4656

47-
const defaultBotSettings = {
48-
delay: botDelay,
49-
avatar: botAvatar,
50-
};
51-
const defaultUserSettings = {
52-
delay: userDelay,
53-
avatar: userAvatar,
54-
};
55-
const defaultCustomSettings = {
56-
delay: customDelay,
57-
};
57+
const defaultBotSettings = { delay: botDelay, avatar: botAvatar };
58+
const defaultUserSettings = { delay: userDelay, avatar: userAvatar };
59+
const defaultCustomSettings = { delay: customDelay };
5860

5961
for (let i = 0, len = this.props.steps.length; i < len; i += 1) {
6062
const step = this.props.steps[i];
@@ -75,27 +77,23 @@ class ChatBot extends Component {
7577
);
7678
}
7779

80+
schema.checkInvalidIds(steps);
81+
7882
const currentStep = this.props.steps[0];
7983
const renderedSteps = [steps[currentStep.id]];
8084
const previousSteps = [steps[currentStep.id]];
8185

8286
this.setState({
83-
defaultBotSettings,
84-
defaultUserSettings,
85-
steps,
8687
currentStep,
87-
renderedSteps,
88+
defaultUserSettings,
8889
previousSteps,
90+
renderedSteps,
91+
steps,
8992
});
9093
}
9194

9295
componentDidMount() {
93-
const chatbotContent = document.querySelector('.rsc-content');
94-
95-
/* istanbul ignore next */
96-
if (chatbotContent) {
97-
chatbotContent.addEventListener('DOMNodeInserted', this.onNodeInserted);
98-
}
96+
this.content.addEventListener('DOMNodeInserted', this.onNodeInserted);
9997
}
10098

10199
componentWillUpdate(nextProps, nextState) {
@@ -107,15 +105,9 @@ class ChatBot extends Component {
107105
}
108106

109107
componentWillUnmount() {
110-
const chatbotContent = document.querySelector('.rsc-content');
111-
112-
/* istanbul ignore next */
113-
if (chatbotContent) {
114-
chatbotContent.removeEventListener('DOMNodeInserted', this.onNodeInserted);
115-
}
108+
this.content.removeEventListener('DOMNodeInserted', this.onNodeInserted);
116109
}
117110

118-
/* istanbul ignore next */
119111
onNodeInserted(event) {
120112
event.currentTarget.scrollTop = event.currentTarget.scrollHeight;
121113
}
@@ -126,10 +118,10 @@ class ChatBot extends Component {
126118

127119
triggerNextStep(data) {
128120
const {
129-
renderedSteps,
121+
defaultUserSettings,
130122
previousSteps,
123+
renderedSteps,
131124
steps,
132-
defaultUserSettings,
133125
} = this.state;
134126
let { currentStep, previousStep } = this.state;
135127
const isEnd = currentStep.end;
@@ -199,11 +191,7 @@ class ChatBot extends Component {
199191
this.setState({ renderedSteps, currentStep, previousStep }, () => {
200192
if (nextStep.user) {
201193
this.setState({ disabled: false }, () => {
202-
const chatInput = document.querySelector('.rsc-input');
203-
/* istanbul ignore next */
204-
if (chatInput) {
205-
chatInput.focus();
206-
}
194+
this.input.focus();
207195
});
208196
} else {
209197
renderedSteps.push(nextStep);
@@ -278,40 +266,48 @@ class ChatBot extends Component {
278266

279267
handleKeyPress(event) {
280268
if (event.key === 'Enter') {
281-
const {
269+
this.submitUserMessage();
270+
}
271+
}
272+
273+
handleSubmitButton() {
274+
this.submitUserMessage();
275+
}
276+
277+
submitUserMessage() {
278+
const {
279+
defaultUserSettings,
280+
inputValue,
281+
previousSteps,
282+
renderedSteps,
283+
} = this.state;
284+
let { currentStep } = this.state;
285+
286+
const isInvalid = currentStep.validator && this.checkInvalidInput();
287+
288+
if (!isInvalid) {
289+
const step = {
290+
message: inputValue,
291+
value: inputValue,
292+
};
293+
294+
currentStep = Object.assign(
295+
{},
296+
defaultUserSettings,
297+
currentStep,
298+
step,
299+
);
300+
301+
renderedSteps.push(currentStep);
302+
previousSteps.push(currentStep);
303+
304+
this.setState({
305+
currentStep,
282306
renderedSteps,
283307
previousSteps,
284-
inputValue,
285-
defaultUserSettings,
286-
} = this.state;
287-
let { currentStep } = this.state;
288-
289-
const isInvalid = currentStep.validator && this.checkInvalidInput();
290-
291-
if (!isInvalid) {
292-
const step = {
293-
message: inputValue,
294-
value: inputValue,
295-
};
296-
297-
currentStep = Object.assign(
298-
{},
299-
defaultUserSettings,
300-
currentStep,
301-
step,
302-
);
303-
304-
renderedSteps.push(currentStep);
305-
previousSteps.push(currentStep);
306-
307-
this.setState({
308-
currentStep,
309-
renderedSteps,
310-
previousSteps,
311-
disabled: true,
312-
inputValue: '',
313-
});
314-
}
308+
disabled: true,
309+
inputValue: '',
310+
});
315311
}
316312
}
317313

@@ -332,11 +328,7 @@ class ChatBot extends Component {
332328
inputInvalid: false,
333329
disabled: false,
334330
}, () => {
335-
const chatInput = document.querySelector('.rsc-input');
336-
/* istanbul ignore next */
337-
if (chatInput) {
338-
chatInput.focus();
339-
}
331+
this.input.focus();
340332
});
341333
}, 2000);
342334
});
@@ -430,23 +422,25 @@ class ChatBot extends Component {
430422

431423
render() {
432424
const {
433-
opened,
434425
disabled,
435-
inputValue,
436426
inputInvalid,
427+
inputValue,
428+
opened,
437429
renderedSteps,
438430
} = this.state;
439431
const {
432+
className,
433+
contentStyle,
434+
floating,
435+
footerStyle,
440436
headerComponent,
441437
headerTitle,
442-
floating,
443438
hideHeader,
444-
style,
445-
contentStyle,
446-
footerStyle,
439+
hideSubmitButton,
447440
inputStyle,
448-
className,
449441
placeholder,
442+
style,
443+
submitButtonStyle,
450444
} = this.props;
451445

452446
const header = headerComponent || (
@@ -489,6 +483,7 @@ class ChatBot extends Component {
489483
{!hideHeader && header}
490484
<Content
491485
className="rsc-content"
486+
innerRef={contentRef => this.content = contentRef}
492487
floating={floating}
493488
style={contentStyle}
494489
>
@@ -501,14 +496,29 @@ class ChatBot extends Component {
501496
<Input
502497
type="textarea"
503498
style={inputStyle}
499+
innerRef={inputRef => this.input = inputRef}
504500
className="rsc-input"
505-
placeholder={placeholder}
501+
placeholder={inputInvalid ? '' : placeholder}
506502
onKeyPress={this.handleKeyPress}
507503
onChange={this.onValueChange}
508504
value={inputValue}
505+
floating={floating}
509506
invalid={inputInvalid}
510507
disabled={disabled}
508+
hasButton={!hideSubmitButton}
511509
/>
510+
{
511+
!hideSubmitButton &&
512+
<SubmitButton
513+
className="rsc-submit-button"
514+
style={submitButtonStyle}
515+
onClick={this.handleSubmitButton}
516+
invalid={inputInvalid}
517+
disabled={disabled}
518+
>
519+
<SubmitIcon />
520+
</SubmitButton>
521+
}
512522
</Footer>
513523
</ChatBotContainer>
514524
</div>
@@ -518,52 +528,56 @@ class ChatBot extends Component {
518528

519529
ChatBot.propTypes = {
520530
steps: PropTypes.array.isRequired,
531+
avatarStyle: PropTypes.object,
532+
botAvatar: PropTypes.string,
533+
botDelay: PropTypes.number,
534+
bubbleStyle: PropTypes.object,
535+
className: PropTypes.string,
536+
contentStyle: PropTypes.object,
537+
customDelay: PropTypes.number,
538+
customStyle: PropTypes.object,
539+
floating: PropTypes.bool,
540+
footerStyle: PropTypes.object,
541+
handleEnd: PropTypes.func,
521542
headerComponent: PropTypes.element,
522543
headerTitle: PropTypes.string,
523-
hideHeader: PropTypes.bool,
524544
hideBotAvatar: PropTypes.bool,
545+
hideHeader: PropTypes.bool,
546+
hideSubmitButton: PropTypes.bool,
525547
hideUserAvatar: PropTypes.bool,
526-
floating: PropTypes.bool,
548+
inputStyle: PropTypes.object,
527549
opened: PropTypes.bool,
528550
toggleFloating: PropTypes.func,
551+
placeholder: PropTypes.string,
529552
style: PropTypes.object,
530-
contentStyle: PropTypes.object,
531-
footerStyle: PropTypes.object,
532-
inputStyle: PropTypes.object,
533-
avatarStyle: PropTypes.object,
534-
bubbleStyle: PropTypes.object,
535-
customStyle: PropTypes.object,
536-
customDelay: PropTypes.number,
537-
className: PropTypes.string,
538-
botAvatar: PropTypes.string,
539-
botDelay: PropTypes.number,
553+
submitButtonStyle: PropTypes.object,
540554
userAvatar: PropTypes.string,
541555
userDelay: PropTypes.number,
542-
handleEnd: PropTypes.func,
543-
placeholder: PropTypes.string,
544556
};
545557

546558
ChatBot.defaultProps = {
547-
placeholder: 'Type the message ...',
559+
avatarStyle: {},
560+
botDelay: 1000,
561+
bubbleStyle: {},
562+
className: '',
563+
contentStyle: {},
564+
customStyle: {},
565+
customDelay: 1000,
566+
floating: false,
567+
footerStyle: {},
548568
handleEnd: undefined,
549569
headerComponent: undefined,
550570
headerTitle: 'Chat',
551-
hideHeader: false,
552571
hideBotAvatar: false,
572+
hideHeader: false,
573+
hideSubmitButton: false,
553574
hideUserAvatar: false,
554-
floating: false,
575+
inputStyle: {},
555576
opened: undefined,
556-
toggleFloating: undefined,
577+
placeholder: 'Type the message ...',
557578
style: {},
558-
contentStyle: {},
559-
footerStyle: {},
560-
inputStyle: {},
561-
avatarStyle: {},
562-
bubbleStyle: {},
563-
customStyle: {},
564-
customDelay: 1000,
565-
className: '',
566-
botDelay: 1000,
579+
submitButtonStyle: {},
580+
toggleFloating: undefined,
567581
userDelay: 1000,
568582
botAvatar: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIHN0eWxlPSJmaWxsOiM5M0M3RUY7IiBkPSJNMzAyLjU0NSw2OS44MThjMC0yNS43MDctMjAuODQtNDYuNTQ1LTQ2LjU0NS00Ni41NDVzLTQ2LjU0NSwyMC44MzgtNDYuNTQ1LDQ2LjU0NQ0KCWMwLDE3LjIyNSw5LjM2NSwzMi4yNTQsMjMuMjczLDQwLjMwNHY4My44MThoNDYuNTQ1di04My44MThDMjkzLjE4MSwxMDIuMDczLDMwMi41NDUsODcuMDQzLDMwMi41NDUsNjkuODE4eiIvPg0KPHBhdGggc3R5bGU9ImZpbGw6IzVBOEJCMDsiIGQ9Ik0yNTYsMjMuMjczdjE3MC42NjdoMjMuMjczdi04My44MThjMTMuOTA4LTguMDQ5LDIzLjI3My0yMy4wNzcsMjMuMjczLTQwLjMwNA0KCUMzMDIuNTQ1LDQ0LjExMSwyODEuNzA1LDIzLjI3MywyNTYsMjMuMjczeiIvPg0KPHJlY3QgeT0iMjQwLjQ4NSIgc3R5bGU9ImZpbGw6IzkzQzdFRjsiIHdpZHRoPSIyNDguMjQyIiBoZWlnaHQ9IjEyNC4xMjEiLz4NCjxyZWN0IHg9IjI2My43NTgiIHk9IjI0MC40ODUiIHN0eWxlPSJmaWxsOiM1QThCQjA7IiB3aWR0aD0iMjQ4LjI0MiIgaGVpZ2h0PSIxMjQuMTIxIi8+DQo8cmVjdCB4PSIxODYuMTgyIiB5PSIzNjQuNjA2IiBzdHlsZT0iZmlsbDojOTNDN0VGOyIgd2lkdGg9IjEzOS42MzYiIGhlaWdodD0iMTI0LjEyMSIvPg0KPHJlY3QgeD0iMjU2IiB5PSIzNjQuNjA2IiBzdHlsZT0iZmlsbDojNUE4QkIwOyIgd2lkdGg9IjY5LjgxOCIgaGVpZ2h0PSIxMjQuMTIxIi8+DQo8cmVjdCB4PSI0Ni41NDUiIHk9IjE2Mi45MDkiIHN0eWxlPSJmaWxsOiNDQ0U5Rjk7IiB3aWR0aD0iNDE4LjkwOSIgaGVpZ2h0PSIyNzkuMjczIi8+DQo8cmVjdCB4PSIyNTYiIHk9IjE2Mi45MDkiIHN0eWxlPSJmaWxsOiM5M0M3RUY7IiB3aWR0aD0iMjA5LjQ1NSIgaGVpZ2h0PSIyNzkuMjczIi8+DQo8cGF0aCBzdHlsZT0iZmlsbDojM0M1RDc2OyIgZD0iTTE5My45MzksMjcxLjUxNWMwLDE3LjEzOC0xMy44OTQsMzEuMDMtMzEuMDMsMzEuMDNsMCwwYy0xNy4xMzYsMC0zMS4wMy0xMy44OTItMzEuMDMtMzEuMDNsMCwwDQoJYzAtMTcuMTM4LDEzLjg5NC0zMS4wMywzMS4wMy0zMS4wM2wwLDBDMTgwLjA0NiwyNDAuNDg1LDE5My45MzksMjU0LjM3NywxOTMuOTM5LDI3MS41MTVMMTkzLjkzOSwyNzEuNTE1eiIvPg0KPHBhdGggc3R5bGU9ImZpbGw6IzFFMkUzQjsiIGQ9Ik0zODAuMTIxLDI3MS41MTVjMCwxNy4xMzgtMTMuODk0LDMxLjAzLTMxLjAzLDMxLjAzbDAsMGMtMTcuMTM3LDAtMzEuMDMtMTMuODkyLTMxLjAzLTMxLjAzbDAsMA0KCWMwLTE3LjEzOCwxMy44OTQtMzEuMDMsMzEuMDMtMzEuMDNsMCwwQzM2Ni4yMjcsMjQwLjQ4NSwzODAuMTIxLDI1NC4zNzcsMzgwLjEyMSwyNzEuNTE1TDM4MC4xMjEsMjcxLjUxNXoiLz4NCjxwYXRoIHN0eWxlPSJmaWxsOiMzQzVENzY7IiBkPSJNMTg2LjE4MiwzNDkuMDkxYzAsMzguNTU4LDMxLjI1OCw2OS44MTgsNjkuODE4LDY5LjgxOGwwLDBjMzguNTU4LDAsNjkuODE4LTMxLjI2LDY5LjgxOC02OS44MTgNCglIMTg2LjE4MnoiLz4NCjxwYXRoIHN0eWxlPSJmaWxsOiMxRTJFM0I7IiBkPSJNMjU2LDM0OS4wOTFjMCwzOC41NTgsMCw0Ni41NDUsMCw2OS44MThsMCwwYzM4LjU1OCwwLDY5LjgxOC0zMS4yNiw2OS44MTgtNjkuODE4SDI1NnoiLz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjwvc3ZnPg0K',
569583
userAvatar: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTIwOC41IDIxIDEwMCAxMDAiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9Ii0yMDguNSAyMSAxMDAgMTAwIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGc+PGNpcmNsZSBjeD0iLTE1OC41IiBjeT0iNzEiIGZpbGw9IiNGNUVFRTUiIGlkPSJNYXNrIiByPSI1MCIvPjxnPjxkZWZzPjxjaXJjbGUgY3g9Ii0xNTguNSIgY3k9IjcxIiBpZD0iTWFza18yXyIgcj0iNTAiLz48L2RlZnM+PGNsaXBQYXRoIGlkPSJNYXNrXzRfIj48dXNlIG92ZXJmbG93PSJ2aXNpYmxlIiB4bGluazpocmVmPSIjTWFza18yXyIvPjwvY2xpcFBhdGg+PHBhdGggY2xpcC1wYXRoPSJ1cmwoI01hc2tfNF8pIiBkPSJNLTEwOC41LDEyMXYtMTRjMCwwLTIxLjItNC45LTI4LTYuN2MtMi41LTAuNy03LTMuMy03LTEyICAgICBjMC0xLjcsMC02LjMsMC02LjNoLTE1aC0xNWMwLDAsMCw0LjYsMCw2LjNjMCw4LjctNC41LDExLjMtNywxMmMtNi44LDEuOS0yOC4xLDcuMy0yOC4xLDYuN3YxNGg1MC4xSC0xMDguNXoiIGZpbGw9IiNFNkMxOUMiIGlkPSJNYXNrXzNfIi8+PGcgY2xpcC1wYXRoPSJ1cmwoI01hc2tfNF8pIj48ZGVmcz48cGF0aCBkPSJNLTEwOC41LDEyMXYtMTRjMCwwLTIxLjItNC45LTI4LTYuN2MtMi41LTAuNy03LTMuMy03LTEyYzAtMS43LDAtNi4zLDAtNi4zaC0xNWgtMTVjMCwwLDAsNC42LDAsNi4zICAgICAgIGMwLDguNy00LjUsMTEuMy03LDEyYy02LjgsMS45LTI4LjEsNy4zLTI4LjEsNi43djE0aDUwLjFILTEwOC41eiIgaWQ9Ik1hc2tfMV8iLz48L2RlZnM+PGNsaXBQYXRoIGlkPSJNYXNrXzVfIj48dXNlIG92ZXJmbG93PSJ2aXNpYmxlIiB4bGluazpocmVmPSIjTWFza18xXyIvPjwvY2xpcFBhdGg+PHBhdGggY2xpcC1wYXRoPSJ1cmwoI01hc2tfNV8pIiBkPSJNLTE1OC41LDEwMC4xYzEyLjcsMCwyMy0xOC42LDIzLTM0LjQgICAgICBjMC0xNi4yLTEwLjMtMjQuNy0yMy0yNC43cy0yMyw4LjUtMjMsMjQuN0MtMTgxLjUsODEuNS0xNzEuMiwxMDAuMS0xNTguNSwxMDAuMXoiIGZpbGw9IiNENEIwOEMiIGlkPSJoZWFkLXNoYWRvdyIvPjwvZz48L2c+PHBhdGggZD0iTS0xNTguNSw5NmMxMi43LDAsMjMtMTYuMywyMy0zMWMwLTE1LjEtMTAuMy0yMy0yMy0yM3MtMjMsNy45LTIzLDIzICAgIEMtMTgxLjUsNzkuNy0xNzEuMiw5Ni0xNTguNSw5NnoiIGZpbGw9IiNGMkNFQTUiIGlkPSJoZWFkIi8+PC9nPjwvc3ZnPg==',

0 commit comments

Comments
 (0)