├── .editorconfig
├── .gitignore
├── .travis.yml
├── README.md
├── example
├── README.md
├── package.json
├── public
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.js
│ ├── assets
│ │ └── chat_avatar.png
│ ├── index.css
│ └── index.js
└── yarn.lock
├── lab_signum.png
├── lab_signum_configurator.png
├── package.json
├── rollup.config.js
├── src
├── common.d.ts
├── components
│ ├── chat-body
│ │ ├── index.tsx
│ │ └── style.scss
│ ├── chat-context
│ │ ├── ChatContext.ts
│ │ └── index.tsx
│ └── chat-icon
│ │ ├── assets
│ │ ├── chat_icon.svg
│ │ └── chat_icon_close.svg
│ │ ├── index.tsx
│ │ └── style.scss
├── constants
│ └── index.ts
├── index.tsx
└── types.ts
├── tsconfig.json
├── tsconfig.test.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 | .rpt2_cache
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 | env:
5 | - SKIP_PREFLIGHT_CHECK=true
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Signum
2 | 
3 |
4 | [](https://www.npmjs.com/package/@nightborn/signum) [](https://standardjs.com)
5 |
6 | ## Demo
7 |
8 | - You can try the communication tool here: https://nightborn-be.github.io/signum
9 | - Or on our website for a production-version: https://nightborn.be
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install --save @nightborn/signum
15 | yarn add @nightborn/signum
16 | ```
17 |
18 | ## Configuration
19 |
20 | ### Theme
21 | 
22 | For the configuration of the UI we created a little interface to generate a config.js file to customize the theme.
23 | https://nightborn.be/lab/signum
24 |
25 | ### Options
26 | For the options it's a pretty straightforward structure.
27 |
28 | 1. First create the container object:
29 |
30 | ```jsx
31 | option: {
32 | title: 'Hello there !',
33 | subTitle: 'How can I help you?',
34 | message: 'How can I help you?',
35 | name: 'Hello there'
36 | }
37 | ```
38 |
39 | 2. Add the array of options (If you want options)
40 | ```jsx
41 | option: {
42 | title: 'Hello there !',
43 | subTitle: 'How can I help you?',
44 | message: 'How can I help you?',
45 | name: 'Hello there',
46 | options: [
47 | {
48 | title: 'Hello there !',
49 | message: 'You chose option A?',
50 | name: 'Option A',
51 | options: [
52 | {
53 | title: 'Hello there !',
54 | message: 'You chose option AA?',
55 | name: 'Option AA',
56 | },
57 | {
58 | title: 'Hello there !',
59 | message: 'You chose option AB?',
60 | name: 'Option AB',
61 | }
62 | ]
63 | },
64 | {
65 | title: 'Hello there !',
66 | message: 'You chose option B?',
67 | name: 'Option B',
68 | }
69 | ]
70 | }
71 | ```
72 |
73 |
74 | ### Important information
75 |
76 | 1. In order for the back-button to appear, you should leave blanc the subTitle attribute of the object.
77 | 2. If you're at a leaf of your option-tree, please leave empty the options attribute and the messaging body of Signum will appear.
78 |
79 |
80 | ## Usage
81 |
82 | ```jsx
83 | import React from 'react'
84 | import Chat from '@nightborn/signum'
85 | import '@nightborn/signum/dist/index.css';
86 |
87 | const defaultProps = {
88 | option: {
89 | title: 'Hello there !',
90 | subTitle: 'How can I help you?',
91 | message: 'How can I help you?',
92 | name: 'Hello there',
93 | options: [
94 | {
95 | title: 'Hello there !',
96 | message: 'You chose option A?',
97 | name: 'Option A',
98 | options: [
99 | {
100 | title: 'Hello there !',
101 | message: 'You chose option AA?',
102 | name: 'Option AA',
103 | },
104 | {
105 | title: 'Hello there !',
106 | message: 'You chose option AB?',
107 | name: 'Option AB',
108 | }
109 | ]
110 | },
111 | {
112 | title: 'Hello there !',
113 | message: 'You chose option B?',
114 | name: 'Option B',
115 | }
116 | ]
117 | },
118 | config: {
119 | openByDefault: false,
120 | avatarIcon: require('./assets/chat_avatar.png'),
121 | mainColor: 'linear-gradient(90deg, #406321 0%, #283E15 100%)',
122 | secondaryColor: 'linear-gradient(90deg, #406321 0%, #283E15 100%)',
123 | sendButtonColor: '#0074CE',
124 | finalButtonColor: "linear-gradient(90deg, #406321 0%, #283E15 100%)",
125 | emailPlaceholder: 'Please fill in your e-mail',
126 | messagePlaceholder: 'Please provide us some information',
127 | finalTitle: 'Thank you.',
128 | finalSubTitle: "We'll be in touch!",
129 | finalButtonText: "Continue",
130 | handleFinalButtonClicked: () => { },
131 | handleSendClicked: (information) => console.log(information),
132 | }
133 | }
134 |
135 | const App = () => {
136 | return (
137 |
138 | )
139 | }
140 |
141 | export default App;
142 | ```
143 |
144 | ## Result
145 |
146 | In the handleSendClicked function you'll receive following structure
147 | ```jsx
148 | interface IChatBodyInformation {
149 | email: string,
150 | message: string,
151 | steps: IOption[],
152 | option: IOption
153 | }
154 | ```
155 | You can then use the received information to send it to EMAILJS, your backend, ... any solution in which you want to track the information given by your website visitors.
156 |
157 | ## License
158 |
159 | MIT © [m-Nightly](https://github.com/m-Nightly)
160 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | Example project of Signum usage.
2 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "signum-example",
3 | "homepage": "https://m-Nightly.github.io/signum",
4 | "version": "0.0.0",
5 | "license": "MIT",
6 | "private": true,
7 | "dependencies": {
8 | "signum": "link:..",
9 | "react": "link:../node_modules/react",
10 | "react-dom": "^16.9.0",
11 | "react-scripts": "^3.0.1"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test --env=jsdom",
17 | "eject": "react-scripts eject"
18 | },
19 | "browserslist": [
20 | ">0.2%",
21 | "not dead",
22 | "not ie <= 11",
23 | "not op_mini all"
24 | ]
25 | }
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | signum
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "signum",
3 | "name": "signum",
4 | "start_url": "./index.html",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Chat from 'signum'
3 | import 'signum/dist/index.css';
4 |
5 | const defaultProps = {
6 | option: {
7 | title: 'Hello there !',
8 | subTitle: 'How can I help you?',
9 | message: 'How can I help you?',
10 | name: 'Hello there',
11 | options: [
12 | {
13 | title: 'Hello there !',
14 | message: 'You chose option A?',
15 | name: 'Option A',
16 | options: [
17 | {
18 | title: 'Hello there !',
19 | message: 'You chose option AA?',
20 | name: 'Option AA',
21 | },
22 | {
23 | title: 'Hello there !',
24 | message: 'You chose option AB?',
25 | name: 'Option AB',
26 | }
27 | ]
28 | },
29 | {
30 | title: 'Hello there !',
31 | message: 'You chose option B?',
32 | name: 'Option B',
33 | }
34 | ]
35 | },
36 | config: {
37 | avatarIcon: require('./assets/chat_avatar.png'),
38 | mainColor: 'linear-gradient(90deg, #406321 0%, #283E15 100%)',
39 | secondaryColor: 'linear-gradient(90deg, #406321 0%, #283E15 100%)',
40 | sendButtonColor: '#0074CE',
41 | finalButtonColor: "linear-gradient(90deg, #406321 0%, #283E15 100%)",
42 | emailPlaceholder: 'Please fill in your e-mail',
43 | messagePlaceholder: 'Please provide us some information',
44 | finalTitle: 'Thank you.',
45 | finalSubTitle: "We'll be in touch!",
46 | finalButtonText: "Continue",
47 | handleFinalButtonClicked: () => { },
48 | handleSendClicked: (information) => console.log(information),
49 | }
50 | }
51 |
52 | const App = () => {
53 | return (
54 |
55 | )
56 | }
57 |
58 | export default App;
59 |
--------------------------------------------------------------------------------
/example/src/assets/chat_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nightborn-be/signum/eb55362b7157f9a8ebce43fcc3457de38e975a8c/example/src/assets/chat_avatar.png
--------------------------------------------------------------------------------
/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
7 | .app-container {
8 | height: 100%;
9 | width: 100%;
10 | background: red;
11 | }
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import './index.css'
5 | import App from './App'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 |
--------------------------------------------------------------------------------
/lab_signum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nightborn-be/signum/eb55362b7157f9a8ebce43fcc3457de38e975a8c/lab_signum.png
--------------------------------------------------------------------------------
/lab_signum_configurator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nightborn-be/signum/eb55362b7157f9a8ebce43fcc3457de38e975a8c/lab_signum_configurator.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nightborn/signum",
3 | "version": "1.0.6",
4 | "description": "A lead-generating communication component",
5 | "author": "Maxime Denuit",
6 | "license": "MIT",
7 | "private": false,
8 | "repository": "nightborn-be/signum",
9 | "main": "dist/index.js",
10 | "types": "./dist/index.d.ts",
11 | "module": "dist/index.es.js",
12 | "jsnext:main": "dist/index.es.js",
13 | "files": [
14 | "dist"
15 | ],
16 | "engines": {
17 | "node": ">=8",
18 | "npm": ">=5"
19 | },
20 | "scripts": {
21 | "build": "rollup -c",
22 | "start": "rollup -c -w",
23 | "prepare": "yarn run build",
24 | "predeploy": "cd example && yarn install && yarn run build",
25 | "deploy": "gh-pages -d example/build"
26 | },
27 | "dependencies": {
28 | "@babel/preset-react": "^7.9.4",
29 | "@rollup/plugin-image": "^2.0.4",
30 | "@rollup/plugin-typescript": "^4.0.0",
31 | "@wessberg/rollup-plugin-ts": "^1.2.24",
32 | "framer-motion": "^1.10.3",
33 | "rollup": "^2.4.0",
34 | "rollup-plugin-babel": "^4.4.0",
35 | "rollup-plugin-scss": "^2.1.0"
36 | },
37 | "peerDependencies": {
38 | "react": "^16.9.0"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "^7.2.2",
42 | "@babel/runtime": "^7.3.1",
43 | "@testing-library/react-hooks": "^3.2.1",
44 | "@types/jest": "^23.3.13",
45 | "@types/react": "^16.7.22",
46 | "cross-env": "^5.2.0",
47 | "gh-pages": "^2.0.1",
48 | "typescript": "^3.2.4"
49 | }
50 | }
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import ts from "@wessberg/rollup-plugin-ts";
2 | import pkg from './package.json';
3 | import babel from 'rollup-plugin-babel';
4 | import scss from 'rollup-plugin-scss'
5 | import image from '@rollup/plugin-image';
6 |
7 | const external = Object.keys(pkg.dependencies).concat(['path', 'fs', 'typescript']);
8 |
9 | export default {
10 | input: 'src/index.tsx',
11 | plugins: [ts(), babel({
12 | exclude: 'node_modules/**',
13 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
14 | presets: ['@babel/env', '@babel/preset-react']
15 |
16 | }), scss(), image()],
17 | external,
18 | output: [
19 | { format: 'cjs', file: pkg.main },
20 | { format: 'esm', file: pkg.module }
21 | ]
22 | };
--------------------------------------------------------------------------------
/src/common.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | import React = require("react");
3 | export const ReactComponent: React.FunctionComponent>;
6 | const src: string;
7 | export default src;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/chat-body/index.tsx:
--------------------------------------------------------------------------------
1 | import './style.scss';
2 | import React, { useState, useEffect } from 'react';
3 | import { motion, useAnimation } from 'framer-motion';
4 | import { useChat } from '../chat-context/ChatContext';
5 | import { IOption } from '../../types';
6 |
7 | export default function ChatBody() {
8 |
9 | // Attributes
10 | const { isOpen, closeChat, config, defaultOption } = useChat();
11 |
12 | const apparitionAnimation = useAnimation();
13 | const apparitionHeaderAnimation = useAnimation();
14 | const apparitionBodyAnimation = useAnimation();
15 | const apparitionFooterAnimation = useAnimation();
16 | const [option, setOption] = useState(defaultOption);
17 | const [steps, setSteps] = useState([]);
18 | const [email, setEmail] = useState('');
19 | const [message, setMessage] = useState('');
20 | const [isDone, setIsDone] = useState(false);
21 |
22 | // Effects
23 | useEffect(() => {
24 | if (!isOpen) {
25 | outSequence();
26 | } else {
27 | inSequence();
28 | }
29 | }, [isOpen])
30 |
31 | // Methods
32 | async function inSequence() {
33 | await apparitionAnimation.start({ scaleY: 0, opacity: 0, x: 0, transition: { duration: 0 } });
34 | apparitionAnimation.start({ opacity: 1, scaleY: 1, transition: { duration: 0.2 } });
35 | apparitionHeaderAnimation.start({ opacity: 1, y: 0, transition: { delay: 0.2 } });
36 | apparitionBodyAnimation.start({ y: 0, opacity: 1, transition: { delay: 0.2 } });
37 | apparitionFooterAnimation.start({ y: 0, opacity: 1, scale: 1, transition: { delay: 0.2, ease: "linear" } });
38 | }
39 |
40 | async function outSequence() {
41 | await apparitionAnimation.start({ scaleY: 1, opacity: 0, x: 450, transition: { duration: 0.5 } });
42 | apparitionHeaderAnimation.start({ opacity: 0, y: 10, transition: { duration: 0.3 } });
43 | apparitionBodyAnimation.start({ y: -10, opacity: 0, transition: { duration: 0.3 } });
44 | apparitionFooterAnimation.start({ y: 10, opacity: 0, transition: { duration: 0.3, ease: "linear" } });
45 | }
46 |
47 | function handleOptionClicked(option: IOption) {
48 | setSteps((steps) => { steps.push(option); return steps; });
49 | setOption(option);
50 | }
51 |
52 | function handleBackClicked() {
53 | // Resets the current values
54 | setMessage('');
55 | setEmail('');
56 |
57 | // Sets the current option to the previous one
58 | if (steps.length >= 2) {
59 | setOption(steps[steps.length - 2]);
60 | }
61 | else {
62 | setOption(defaultOption);
63 | }
64 |
65 | // Removes the previous step
66 | setSteps((steps) => {
67 | steps.pop();
68 | return steps;
69 | });
70 | }
71 |
72 | async function handleSendClicked() {
73 | if (isFormFilledIn()) {
74 | await config.handleSendClicked({ email, message, steps, option });
75 | setIsDone(true);
76 | }
77 | }
78 |
79 | async function handleFinishClicked() {
80 | await outSequence();
81 | reset();
82 | if (config.handleFinalButtonClicked) {
83 | config.handleFinalButtonClicked();
84 | }
85 | }
86 |
87 | /**
88 | * Returns if the message and e-mail are filled in
89 | */
90 | function isFormFilledIn() {
91 | if (message && email) {
92 | return true
93 | }
94 | return false;
95 | }
96 |
97 | /**
98 | * Resets the entire state of the chat
99 | */
100 | function reset() {
101 | closeChat();
102 | setOption(defaultOption);
103 | setSteps([]);
104 | setIsDone(false);
105 | setEmail('');
106 | setMessage('');
107 | }
108 |
109 | if (!isOpen) {
110 | return (
111 | <>
112 | >
113 | )
114 | }
115 |
116 | // Render
117 | if (isDone) {
118 | return (
119 |
120 |
121 | {config.finalTitle}
122 |
123 |
124 | {config.finalSubTitle}
125 |
126 |
127 | {config.finalButtonText}
128 |
129 |
130 | );
131 | }
132 |
133 | return (
134 |
135 |
136 |
137 | {
138 | option.subTitle ?
139 | (
140 |
141 |
142 |
143 | {option.title}
144 |
145 |
150 |
151 |
152 | {option.subTitle}
153 |
154 |
155 | )
156 | :
157 | (
158 |
159 |
160 |
161 |
164 |
165 |
166 | {option.title}
167 |
168 |
169 |
174 |
175 | )
176 | }
177 |
178 |
179 |
180 |
181 |

182 |
183 | {option.message}
184 |
185 |
186 |
187 |
188 | {option.options && option.options.map((option) => (
189 | handleOptionClicked(option)} className="chat-body-content-option" style={{ background: config.mainColor }}>
190 | {option.name}
191 |
192 | ))}
193 |
194 |
195 |
196 | {
197 | option.options === undefined ?
198 | (
199 |
200 |
201 | setEmail(event.target.value)} placeholder={config.emailPlaceholder} />
202 |
204 |
205 |
206 |
209 |
210 |
211 |
212 | )
213 | :
214 | (
215 |
216 | {config.footer}
217 |
218 | )
219 | }
220 |
221 | )
222 | }
--------------------------------------------------------------------------------
/src/components/chat-body/style.scss:
--------------------------------------------------------------------------------
1 | .chat-body-container {
2 | height: calc(100% - 200px);
3 | max-height: 600px;
4 | width: 375px;
5 | position: fixed;
6 | z-index: 10000;
7 | right: 30px;
8 | bottom: 130px;
9 | background-color: #F8F9FC;
10 | border-radius: 14px;
11 | overflow: auto;
12 | display: flex;
13 | flex-direction: column;
14 | box-shadow: 0px 26px 41px rgba(0, 0, 0, 0.15);
15 | }
16 |
17 | .chat-body-small-container {
18 | height: fit-content;
19 | width: 375px;
20 | position: fixed;
21 | z-index: 10000;
22 | right: 30px;
23 | bottom: 130px;
24 | background-color: #F8F9FC;
25 | border-radius: 14px;
26 | overflow: auto;
27 | display: flex;
28 | flex-direction: column;
29 | box-shadow: 0px 26px 41px rgba(0, 0, 0, 0.15);
30 | }
31 |
32 | .chat-body-header {
33 | background-color: #270E41;
34 | padding: 30px;
35 | }
36 |
37 | .chat-body-header-title {
38 | font-weight: 600;
39 | font-size: 24.6154px;
40 | color: #FFFFFF;
41 | }
42 |
43 | .chat-body-header-title-container {
44 | display: flex;
45 | flex-direction: row;
46 | justify-content: space-between;
47 | }
48 |
49 | .chat-body-header-title-close-button {
50 | display: none;
51 | margin-top: auto;
52 | margin-bottom: auto;
53 | }
54 |
55 |
56 | .chat-body-header-small {
57 | display: flex;
58 | flex-direction: row;
59 | justify-content: space-between;
60 | }
61 |
62 | .chat-body-header-small-left {
63 | display: flex;
64 | flex-direction: row;
65 | }
66 |
67 | .chat-body-header-small-image {
68 | height: 20px;
69 | width: 20px;
70 | cursor: pointer;
71 | }
72 |
73 | .chat-body-header-small-title {
74 | margin-left: 15px;
75 | font-weight: 600;
76 | font-size: 14px;
77 | line-height: 21px;
78 | color: #FFFFFF;
79 | }
80 |
81 | .chat-body-header-body {
82 | margin-top: 9px;
83 | font-size: 13px;
84 | line-height: 24px;
85 | max-width: 280px;
86 | color: #FFFFFF;
87 | }
88 |
89 | .chat-body-content
90 | {
91 | padding: 30px;
92 | }
93 |
94 | .chat-body-content-message {
95 | display: flex;
96 | flex-direction: row;
97 | }
98 |
99 | .chat-body-content-message-image {
100 | height: 41px;
101 | width: 41px;
102 | }
103 |
104 | .chat-body-content-message-body {
105 | margin-left: 9px;
106 | background: #FFFFFF;
107 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.0631283);
108 | border-radius: 22px 22px 22px 5px;
109 | padding: 15px;
110 | font-weight: 300;
111 | font-size: 12px;
112 | line-height: 151.9%;
113 | color: #080038;
114 | width: 200px;
115 | }
116 |
117 | .chat-body-content-options {
118 | margin-left: auto;
119 | margin-top: 40px;
120 | }
121 |
122 | .chat-body-content-option {
123 | margin-top: 9px;
124 | margin-left: auto;
125 | background-color: #161F41;
126 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.0631283);
127 | border-radius: 6px;
128 | padding: 10px;
129 | font-weight: 500;
130 | font-size: 12px;
131 | line-height: 18px;
132 | text-align: center;
133 | color: #FFFFFF;
134 | width: fit-content;
135 | cursor: pointer;
136 | }
137 |
138 | .chat-body-footer {
139 | margin-top: auto;
140 | font-weight: 300;
141 | font-size: 12px;
142 | line-height: 18px;
143 | text-align: center;
144 | text-decoration-line: underline;
145 | color: #C4C4C4;
146 | padding: 30px;
147 | padding-bottom: 25px;
148 | }
149 |
150 | .chat-body-messenger {
151 | margin-top: auto;
152 | border-top: 1px solid #EBEBEB;
153 | }
154 |
155 | .chat-body-messenger-body {
156 | background-color: white;
157 | padding: 25px;
158 | display: flex;
159 | flex-direction: column;
160 | }
161 |
162 | .chat-body-messenger-body > input {
163 | font-weight: 300;
164 | font-size: 12px;
165 | line-height: 100%;
166 | width: 100%;
167 | color: #080038;
168 | border: none;
169 | border-bottom: 1px solid #E3E3E3;
170 | outline: none;
171 | }
172 |
173 | .chat-body-messenger-body > input::placeholder {
174 | font-weight: 300;
175 | font-size: 12px;
176 | color: #BBBBBB;
177 | }
178 |
179 | .chat-body-messenger-body > textarea {
180 | margin-top: 25px;
181 | border: none;
182 | font-weight: 300;
183 | font-size: 12px;
184 | line-height: 100%;
185 | width: 100%;
186 | height: 120px;
187 | color: #080038;
188 | outline: none;
189 | resize: none;
190 | }
191 |
192 | .chat-body-messenger-body > textarea::placeholder {
193 | font-weight: 300;
194 | font-size: 12px;
195 | color: #BBBBBB;
196 | }
197 |
198 |
199 | .chat-body-messenger-footer {
200 | text-align: right;
201 | padding: 20px;
202 | display: flex;
203 | flex-direction: row;
204 | align-content: center;
205 | align-items: center;
206 | justify-content: flex-end;
207 | }
208 |
209 | .chat-body-messenger-footer-image {
210 | height: 20px;
211 | width: 20px;
212 | scale: 1;
213 | }
214 |
215 | .chat-body-done-title{
216 | margin-top: 40px;
217 | font-weight: 300;
218 | font-size: 30px;
219 | display: flex;
220 | flex-direction: row;
221 | justify-content: center;
222 | align-items: center;
223 | text-align: center;
224 | color: #080038;
225 | }
226 |
227 | .chat-body-done-sub-title{
228 | font-weight: 600;
229 | font-size: 30px;
230 | display: flex;
231 | flex-direction: row;
232 | justify-content: center;
233 | align-items: center;
234 | text-align: center;
235 | color: #080038;
236 | }
237 |
238 | .chat-body-done-button
239 | {
240 | scale: 1;
241 | cursor: pointer;
242 | margin: 40px;
243 | display: flex;
244 | flex-direction: row;
245 | align-content: center;
246 | justify-content: center;
247 | padding: 15px;
248 | font-weight: 600;
249 | font-size: 16px;
250 | line-height: 35px;
251 | display: flex;
252 | align-items: center;
253 | text-align: center;
254 | color: #FFFFFF;
255 | background: linear-gradient(163.56deg, #7A22AF -5.81%, #F40078 113.99%);
256 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.0631283);
257 | border-radius: 100px;
258 | }
259 |
260 | @media screen and (max-width: 550px) {
261 | .chat-body-container {
262 | height: 100%;
263 | width: 100%;
264 | max-height: 100%;
265 | position: fixed;
266 | z-index: 10000;
267 | right: 0px;
268 | bottom: 0px;
269 | background-color: #F8F9FC;
270 | border-radius: 0px;
271 | overflow: auto;
272 | display: flex;
273 | flex-direction: column;
274 | box-shadow: 0px 26px 41px rgba(0, 0, 0, 0.15);
275 | }
276 |
277 | .chat-body-small-container {
278 | height: 100%;
279 | width: 100%;
280 | max-height: 100%;
281 | position: fixed;
282 | z-index: 10000;
283 | right: 0px;
284 | bottom: 0px;
285 | background-color: #F8F9FC;
286 | border-radius: 0px;
287 | overflow: auto;
288 | display: flex;
289 | flex-direction: column;
290 | justify-content: center;
291 | box-shadow: 0px 26px 41px rgba(0, 0, 0, 0.15);
292 | }
293 |
294 | .chat-body-header-title-close-button {
295 | display: inherit;
296 | margin-top: auto;
297 | margin-bottom: auto;
298 | }
299 | }
--------------------------------------------------------------------------------
/src/components/chat-context/ChatContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from "react";
2 | import { defaultConfig, defaultOption } from "../../constants";
3 | import { IChatConfig, IOption } from "../../types";
4 |
5 | interface IChatContext {
6 | isOpen: boolean,
7 | config: IChatConfig,
8 | openChat: () => void,
9 | closeChat: () => void,
10 | defaultOption: IOption
11 | }
12 |
13 | const ChatContext = createContext({
14 | isOpen: false,
15 | config: defaultConfig,
16 | openChat: () => { },
17 | closeChat: () => { },
18 | defaultOption: defaultOption
19 | });
20 |
21 | function useChat() {
22 | return useContext(ChatContext);
23 | }
24 |
25 | export { ChatContext, useChat };
--------------------------------------------------------------------------------
/src/components/chat-context/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { ChatContext } from "./ChatContext";
3 | import { defaultConfig, defaultOption } from "../../constants";
4 | import { IOption, IChatConfig } from '../../types';
5 |
6 | export interface IChatProviderProps {
7 | option: IOption,
8 | config: IChatConfig,
9 | children: any
10 | }
11 |
12 | export default function ChatContextProvider(props: IChatProviderProps) {
13 |
14 | // Attributes
15 | const { option, config } = props;
16 | const [isOpen, setIsOpen] = useState(config.openByDefault);
17 |
18 | // Methods
19 | function computeValue() {
20 | return {
21 | isOpen: isOpen,
22 | config: { ...defaultConfig, ...config },
23 | defaultOption: { ...defaultOption, ...option },
24 | openChat: () => setIsOpen(true),
25 | closeChat: () => setIsOpen(false)
26 | }
27 | }
28 |
29 | // Render
30 | return (
31 |
32 | {props.children}
33 |
34 | )
35 | }
--------------------------------------------------------------------------------
/src/components/chat-icon/assets/chat_icon.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/components/chat-icon/assets/chat_icon_close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/chat-icon/index.tsx:
--------------------------------------------------------------------------------
1 | import './style.scss';
2 | import React from 'react';
3 | import { motion } from 'framer-motion';
4 | import ChatIconImage from './assets/chat_icon.svg';
5 | import ChatIconClosedImage from './assets/chat_icon_close.svg';
6 | import { useChat } from '../chat-context/ChatContext';
7 |
8 | export default function ChatIcon() {
9 |
10 | // Attributes
11 | const { isOpen, closeChat, openChat, config } = useChat();
12 |
13 | // Methods
14 | function getImage() {
15 | if (isOpen) {
16 | return ChatIconClosedImage;
17 | }
18 | return ChatIconImage;
19 | }
20 |
21 | function getImageClass() {
22 | if (isOpen) {
23 | return "chat-button-image-close";
24 | }
25 | return "chat-button-image";
26 | }
27 |
28 | // Render
29 | return (
30 | isOpen ? closeChat() : openChat()} whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} className={`chat-button${isOpen ? "-active" : ""}`} style={{ background: config.secondaryColor }}>
31 |
32 |
33 | )
34 | }
--------------------------------------------------------------------------------
/src/components/chat-icon/style.scss:
--------------------------------------------------------------------------------
1 | .chat-button {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 32.5px;;
5 | position: fixed;
6 | z-index: 10000;
7 | right: 30px;
8 | bottom: 30px;
9 | display: flex;
10 | background-color: #2DDC9F;
11 | align-items: center;
12 | align-content: center;
13 | justify-content: center;
14 | cursor: pointer;
15 | scale: 1;
16 | }
17 |
18 | .chat-button-image {
19 | margin-top: 2px;
20 | margin-left: -1px;
21 | width: 30px;
22 | height: 30px;
23 | }
24 |
25 | .chat-button-image-close {
26 | margin-top: 0px;
27 | margin-left: -1px;
28 | width: 23px;
29 | height: 23px;
30 | }
31 |
32 | .chat-button-active {
33 | width: 60px;
34 | height: 60px;
35 | border-radius: 32.5px;;
36 | position: fixed;
37 | z-index: 10000;
38 | right: 30px;
39 | bottom: 30px;
40 | display: flex;
41 | background-color: #2DDC9F;
42 | align-items: center;
43 | align-content: center;
44 | justify-content: center;
45 | cursor: pointer;
46 | scale: 1;
47 | display: flex;
48 | }
49 |
50 | @media screen and (max-width: 550px) {
51 | .chat-button-active {
52 | width: 60px;
53 | height: 60px;
54 | border-radius: 32.5px;;
55 | position: fixed;
56 | z-index: 10000;
57 | right: 30px;
58 | bottom: 30px;
59 | display: flex;
60 | background-color: #2DDC9F;
61 | align-items: center;
62 | align-content: center;
63 | justify-content: center;
64 | cursor: pointer;
65 | scale: 1;
66 | display: none;
67 | }
68 | }
--------------------------------------------------------------------------------
/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | import { IChatConfig, IOption } from "../types"
2 |
3 | export const defaultConfig: IChatConfig = {
4 | openByDefault: false,
5 | footer: 'Signum - created by Nightborn.be',
6 | avatarIcon: '',
7 | mainColor: 'linear-gradient(158.5deg, #270E41 -32.82%, #112441 120.45%)',
8 | secondaryColor: 'linear-gradient(137.56deg, #2DDC9F 1.03%, #1DC6AF 99.9%)',
9 | sendButtonColor: '#112441',
10 | finalButtonColor: "linear-gradient(163.56deg, #7A22AF -5.81%, #F40078 113.99%)",
11 | emailPlaceholder: 'Leave us your e-mail address',
12 | messagePlaceholder: 'Write your message',
13 | finalTitle: 'Thank you.',
14 | finalSubTitle: "We'll be in touch!",
15 | finalButtonText: "Continue",
16 | handleFinalButtonClicked: () => { },
17 | handleSendClicked: (information) => console.log(information),
18 | }
19 |
20 | export const defaultOption: IOption = {
21 | title: 'Hello there 👋',
22 | subTitle: "We're glad to see you on our website.",
23 | message: 'How can I help you?',
24 | name: 'Hello there 👋',
25 | }
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ChatBody from './components/chat-body';
3 | import ChatIcon from './components/chat-icon';
4 | import ChatContextProvider from './components/chat-context';
5 | import { IOption, IChatConfig } from './types';
6 |
7 | interface IChatProps {
8 | option: IOption,
9 | config: IChatConfig
10 | }
11 |
12 | export * from './types';
13 |
14 | export default function Chat(props: IChatProps) {
15 | return (
16 |
17 |
18 |
19 |
20 | );
21 | };
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | export interface IOption {
4 | title: string,
5 | subTitle?: string,
6 | message: ReactNode,
7 | name: string,
8 | handleOnClick?: Function,
9 | options?: IOption[]
10 | }
11 |
12 | export interface IChatBodyInformation {
13 | email: string,
14 | message: string,
15 | steps: IOption[],
16 | option: IOption
17 | }
18 |
19 | export interface IChatConfig {
20 | openByDefault: boolean,
21 | footer?: string,
22 | avatarIcon: any,
23 | mainColor: string,
24 | emailPlaceholder: string,
25 | messagePlaceholder: string,
26 | finalTitle: string,
27 | finalSubTitle: string,
28 | finalButtonText: string,
29 | finalButtonColor: string,
30 | secondaryColor: string,
31 | sendButtonColor: string,
32 | handleFinalButtonClicked?: () => void,
33 | handleSendClicked: (information: IChatBodyInformation) => void
34 | }
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build",
4 | "module": "esnext",
5 | "target": "es5",
6 | "lib": [
7 | "es6",
8 | "dom",
9 | "es2016",
10 | "es2017"
11 | ],
12 | "sourceMap": true,
13 | "allowJs": false,
14 | "jsx": "preserve",
15 | "declaration": true,
16 | "moduleResolution": "node",
17 | "forceConsistentCasingInFileNames": true,
18 | "noImplicitReturns": true,
19 | "noImplicitThis": true,
20 | "noImplicitAny": true,
21 | "strictNullChecks": true,
22 | "suppressImplicitAnyIndexErrors": true,
23 | "noUnusedLocals": true,
24 | "noUnusedParameters": true,
25 | "skipLibCheck": true,
26 | "esModuleInterop": true,
27 | "allowSyntheticDefaultImports": true,
28 | "strict": true,
29 | "resolveJsonModule": true,
30 | "isolatedModules": true,
31 | "noEmit": true
32 | },
33 | "include": [
34 | "src"
35 | ],
36 | "exclude": [
37 | "node_modules",
38 | "build",
39 | "dist",
40 | "example",
41 | "rollup.config.js"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------