├── .babelrc
├── .gitignore
├── .npmignore
├── README.md
├── example
└── src
│ ├── app.js
│ └── index.html
├── package.json
├── publish.config.js
├── src
├── index.css
├── index.js
└── send-icon.svg
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # npm
15 | /dist
16 |
17 | # ide
18 | /.idea
19 |
20 | # misc
21 | .DS_Store
22 | .env.local
23 | .env.development.local
24 | .env.test.local
25 | .env.production.local
26 |
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 | /example/dist
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # ide
15 | /.idea
16 |
17 | /src
18 |
19 | # misc
20 | .DS_Store
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 |
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
30 | /example
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-custom-chat
2 |
3 | react-custom-chat is a free and fully customizable chat window that can be easily included in any project.
4 |
5 | 
6 |
7 | ### [Link to Repository](https://github.com/ebenezerdon/react-custom-chat.git)
8 | ## [Demo](https://github.com/ebenezerdon/react-custom-chat)
9 |
10 | ## Table of Contents
11 | - [Installation](#installation)
12 | - [Example](#example)
13 | - [Components](#components)
14 |
15 | ## Installation
16 |
17 | ```
18 | $ npm install react-custom-chat
19 | ```
20 |
21 | ## Example
22 |
23 | ``` javascript
24 | import ChatBox from 'react-custom-chat'
25 | import { useState } from 'react'
26 |
27 | const App = () => {
28 | const [messageList, setMessageList] = useState([])
29 |
30 | const handleSendMessage = newMessage => {
31 | setMessageList([
32 | ...messageList,
33 | {text: newMessage, person: 'primary'}
34 | ])
35 | }
36 |
37 | return (
38 |
39 |
48 |
49 | )
50 | }
51 | ```
52 |
53 | ## Components
54 |
55 | # ChatBox
56 |
57 | ### ChatBox props:
58 |
59 | | prop | type | required | description |
60 | |------------------|--------|----------|-------------|
61 | | settings | [object](#settings-properties) | no | You can use this prop to define custom settings for your chat window. |
62 | | messageList | array | yes | an array of objects for your messages. See message object [here](#message-object) |
63 | | isOpen | boolean | yes | Forces the open/close state of the chat window. If this is not set, it will be closed by default, and open when the chat icon is clicked. |
64 | | onSendMessage | function | yes | The function's first parameter will be the message from the input field. see [example](#example) for usage.
65 |
66 |
67 | ### Settings properties:
68 |
69 | | property | type | description |
70 | |------------------|--------|-------------|
71 | | position | string | This indicates the position of your chat window. Value can be `left` or `right`. Default is `right`. |
72 | | navColor | string | You can use this to customize your NavColor |
73 | | primaryColor | string | Use this to define the primary user color. You can choose from 'green', 'red', 'blue', etc... |
74 | | secondaryColor | string | Use this to define the secondary user color. You can choose from 'green', 'red', 'blue', etc... |
75 | | navText | string | This is the text that shows up on the chat window header. The Default is 'Jane Doe'|
76 |
77 |
78 | ### Message Object
79 |
80 | Each message is styled differently depending on its type. Currently, only text is supported.
81 | Each message object has a `person` property which can have the value 'primary' or 'secondary',
82 | and a `text` property which contains the message text.
83 |
84 | ``` javascript
85 | {
86 | text: 'hello world!',
87 | person: 'primary',
88 | }
89 | ```
90 |
91 | ## Using react-custom-chat? Let's talk
92 |
93 | If you're using react-custom-chat, I'd love to see what you're building! Send me a mail
94 |
--------------------------------------------------------------------------------
/example/src/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import ChatBox from '../../src'
4 |
5 | const App = () => {
6 | const [messageList, setMessageList] = React.useState([])
7 |
8 | const handleSendMessage = newMessage => {
9 | setMessageList([
10 | ...messageList,
11 | {text: newMessage, person: 'primary'}
12 | ])
13 | }
14 |
15 | return (
16 |
17 |
26 |
27 | )
28 | }
29 |
30 | render( , document.getElementById('root'))
31 |
--------------------------------------------------------------------------------
/example/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | My First React Component
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-custom-chat",
3 | "version": "0.6.1",
4 | "description": "a free and fully customizable chat window that can be easily included in any project",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "webpack-dev-server --open development",
9 | "build": "webpack --mode production",
10 | "deploy": "webpack --config publish.config.js --mode production && npm publish"
11 | },
12 | "keywords": [
13 | "react",
14 | "chat",
15 | "chat widget",
16 | "chat window",
17 | "intercom",
18 | "react custom chat",
19 | "hooks",
20 | "functional react"
21 | ],
22 | "author": "Ebenezer Don",
23 | "license": "ISC",
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/ebenezerdon/react-custom-chat.git"
27 | },
28 | "peerDependencies": {
29 | "react": "^17.0.2",
30 | "react-dom": "^17.0.2"
31 | },
32 | "devDependencies": {
33 | "@babel/cli": "^7.5.5",
34 | "@babel/core": "^7.5.5",
35 | "@babel/preset-env": "^7.5.5",
36 | "@babel/preset-react": "^7.0.0",
37 | "babel-loader": "^8.0.6",
38 | "css-loader": "^5.2.4",
39 | "html-webpack-plugin": "^3.2.0",
40 | "react": "^17.0.2",
41 | "react-dom": "^17.0.2",
42 | "style-loader": "^2.0.0",
43 | "svg-url-loader": "^7.1.1",
44 | "webpack": "^4.39.3",
45 | "webpack-cli": "^3.3.7",
46 | "webpack-dev-server": "^3.8.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/publish.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: path.join(__dirname, "./src/index.js"),
5 | output: {
6 | path: path.join(__dirname, "./dist"),
7 | filename: "index.js",
8 | libraryTarget: 'commonjs2'
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(js|jsx)$/,
14 | use: "babel-loader",
15 | exclude: /node_modules/
16 | },
17 | {
18 | test: /\.css$/i,
19 | use: ["style-loader", "css-loader"],
20 | },
21 | {
22 | test: /\.svg$/,
23 | use: "svg-url-loader"
24 | }
25 | ]
26 | },
27 | resolve: {
28 | extensions: [".js", ".jsx"]
29 | },
30 | externals: {
31 | 'react': 'commonjs react'
32 | }
33 | };
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /*! tailwindcss v2.1.2 | MIT License | https://tailwindcss.com */
2 |
3 | /*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */:root{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}.bg-black{--tw-bg-opacity:1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgba(107,114,128,var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity:1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity:1;background-color:rgba(220,38,38,var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgba(245,158,11,var(--tw-bg-opacity))}.bg-yellow-600{--tw-bg-opacity:1;background-color:rgba(217,119,6,var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgba(16,185,129,var(--tw-bg-opacity))}.bg-green-600{--tw-bg-opacity:1;background-color:rgba(5,150,105,var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgba(59,130,246,var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.bg-purple-500{--tw-bg-opacity:1;background-color:rgba(139,92,246,var(--tw-bg-opacity))}.rounded-lg{border-radius:.5rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.table{display:table}.grid{display:grid}.float-right{float:right}.clear-both{clear:both}.h-12{height:3rem}.h-16{height:4rem}.h-4\/6{height:66.666667%}.h-5\/6{height:83.333333%}.text-2xl{font-size:1.5rem;line-height:2rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.max-w-xs{max-width:20rem}.overflow-auto{overflow:auto}.p-2{padding:.5rem}.p-4{padding:1rem}.pt-4{padding-top:1rem}.absolute{position:absolute}.right-5{right:1.25rem}.left-5{left:1.25rem}.bottom-8{bottom:2rem}.bottom-32{bottom:8rem}*{--tw-shadow:0 0 transparent}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,0.25)}.shadow-2xl,.shadow-inner{box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 rgba(0,0,0,0.06)}*{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 transparent;--tw-ring-shadow:0 0 transparent}.text-center{text-align:center}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgba(153,27,27,var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgba(146,64,14,var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgba(6,95,70,var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity:1;color:rgba(30,64,175,var(--tw-text-opacity))}.w-16{width:4rem}.w-96{width:24rem}.w-full{width:100%}.w-max{width:-webkit-max-content;width:-moz-max-content;width:max-content}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.col-span-5{grid-column:span 5/span 5}.transform{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;transform:translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-150{--tw-scale-x:1.5;--tw-scale-y:1.5}@keyframes spin{to{transform:rotate(1turn)}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.fade-in{opacity:1;animation-name:fadeInOpacity;animation-iteration-count:1;animation-timing-function:ease-in;animation-duration:.2s}@keyframes fadeInOpacity{0%{opacity:0}to{opacity:1}}
4 | /*# sourceMappingURL=main.3bce4cc5.chunk.css.map */
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useEffect, useState } from 'react'
3 | import sendIcon from './send-icon.svg'
4 | import './index.css'
5 |
6 | const ChatBox = ( { settings, messageList, onSendMessage } ) => {
7 | const { position, navColor, secondaryColor, primaryColor, navText, isOpen } = settings || {}
8 | const uniqueId = 'id' + Math.random().toString(36).substr(2, 5)
9 | const [chatBoxIsActive, setChatBoxIsActive] = useState(isOpen || false)
10 |
11 | const styles = {
12 | wrapper: `fade-in absolute rounded-lg h-4/6 w-96 shadow-2xl bottom-32 ${position === 'left' ? 'left' : 'right'}-5`,
13 | nav: `h-16 w-full bg-${navColor || 'blue'}-500 text-white pt-4 rounded-t-lg`,
14 | navText: `text-center text-2xl`,
15 | messages: `p-4 h-5/6 overflow-auto text-white`,
16 | personLeft: `fade-in bg-${secondaryColor || 'gray'}-600 p-2 w-max max-w-xs rounded-lg clear-both mb-2`,
17 | personRight: `fade-in bg-${primaryColor || 'red'}-600 p-2 w-max max-w-xs rounded-lg clear-both float-right mb-2`,
18 | formWrapper: `h-12 bg-gray-200`,
19 | form: `grid grid-cols-6`,
20 | inputField: `bg-gray-200 h-12 shadow-inner w-full p-4 col-span-5`,
21 | toggleButton: `absolute bottom-8 ${position === 'left' ? 'left' : 'right'}-5`,
22 | sendIcon: `transform scale-150 text-align mx-auto`,
23 | chatIcon: `h-16 w-16 text-${navColor || 'blue'}-800`
24 | }
25 |
26 | const handleFormSubmit = event => {
27 | event.preventDefault()
28 | const chatInput = document.getElementById(uniqueId)
29 | onSendMessage?.(chatInput.value)
30 | event.target.reset()
31 | }
32 |
33 | useEffect(() => {
34 | // scroll to last message
35 | const messageSection = document.getElementById('msg' + uniqueId)
36 | messageSection?.scrollTo(0, messageSection.scrollHeight)
37 | }, [uniqueId])
38 |
39 | return (
40 | <>
41 | {chatBoxIsActive &&
42 |
43 | {navText || 'Jane Doe'}
44 |
45 |
46 | {messageList?.map((message, index) => (
47 |
50 | {message.text}
51 |
52 | ))}
53 |
54 |
62 |
}
63 | setChatBoxIsActive(!chatBoxIsActive)}
66 | >
67 | {!chatBoxIsActive &&
69 |
71 | }
72 | {chatBoxIsActive &&
74 |
76 | }
77 |
78 | >
79 | )
80 | }
81 |
82 | export default ChatBox;
83 |
--------------------------------------------------------------------------------
/src/send-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const htmlWebpackPlugin = new HtmlWebpackPlugin({
4 | template: path.join(__dirname, "./example/src/index.html"),
5 | filename: "./index.html"
6 | });
7 |
8 | module.exports = {
9 | entry: path.join(__dirname, "./example/src/app.js"),
10 | output: {
11 | path: path.join(__dirname, "example/dist"),
12 | filename: "bundle.js"
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.(js|jsx)$/,
18 | use: "babel-loader",
19 | exclude: /node_modules/
20 | },
21 | {
22 | test: /\.css$/i,
23 | use: ["style-loader", "css-loader"],
24 | },
25 | {
26 | test: /\.svg$/,
27 | use: "svg-url-loader"
28 | }
29 | ]
30 | },
31 | plugins: [htmlWebpackPlugin],
32 | resolve: {
33 | extensions: [".js", ".jsx"]
34 | },
35 | devServer: {
36 | port: 3001
37 | }
38 | };
--------------------------------------------------------------------------------