├── .editorconfig
├── .gitignore
├── .npmignore
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── App.css
├── App.tsx
├── components
│ ├── AvatarExample.tsx
│ ├── ButtonExample.tsx
│ ├── ChatListExample.tsx
│ ├── DropdownExample.tsx
│ ├── MeetingListExample.tsx
│ ├── MessageListExample.tsx
│ ├── NavbarExample.tsx
│ └── PopupExample.tsx
├── index.tsx
└── utils
│ ├── MessageTypes.ts
│ └── common.ts
├── package.json
├── prettier.config.js
├── rollup.config.js
├── src
├── AudioMessage
│ ├── AudioMessage.css
│ ├── AudioMessage.tsx
│ └── __tests__
│ │ ├── AudioMessage.js
│ │ └── __snapshots__
│ │ └── AudioMessage.js.snap
├── Avatar
│ ├── Avatar.css
│ ├── Avatar.tsx
│ └── __tests__
│ │ ├── Avatar.js
│ │ └── __snapshots__
│ │ └── Avatar.js.snap
├── Button
│ ├── Button.css
│ ├── Button.tsx
│ └── __tests__
│ │ ├── Button.js
│ │ └── __snapshots__
│ │ └── Button.js.snap
├── ChatItem
│ ├── ChatItem.css
│ ├── ChatItem.tsx
│ └── __tests__
│ │ ├── ChatItem.js
│ │ └── __snapshots__
│ │ └── ChatItem.js.snap
├── ChatList
│ ├── ChatList.css
│ ├── ChatList.tsx
│ └── __tests__
│ │ ├── ChatList.js
│ │ └── __snapshots__
│ │ └── ChatList.js.snap
├── Circle
│ └── Circle.tsx
├── Dropdown
│ ├── Dropdown.css
│ ├── Dropdown.tsx
│ └── __tests__
│ │ ├── Dropdown.js
│ │ └── __snapshots__
│ │ └── Dropdown.js.snap
├── FileMessage
│ ├── FileMessage.css
│ ├── FileMessage.tsx
│ └── __tests__
│ │ ├── FileMessage.js
│ │ └── __snapshots__
│ │ └── FileMessage.js.snap
├── Input
│ ├── Input.css
│ ├── Input.tsx
│ └── __tests__
│ │ ├── Input.js
│ │ └── __snapshots__
│ │ └── Input.js.snap
├── LocationMessage
│ ├── LocationMessage.css
│ ├── LocationMessage.tsx
│ └── __tests__
│ │ ├── LocationMessage.js
│ │ └── __snapshots__
│ │ └── LocationMessage.js.snap
├── MeetingItem
│ ├── MeetingItem.css
│ ├── MeetingItem.tsx
│ └── __tests__
│ │ ├── MeetingItem.js
│ │ └── __snapshots__
│ │ └── MeetingItem.js.snap
├── MeetingLink
│ ├── MeetingLink.css
│ ├── MeetingLink.tsx
│ └── __tests__
│ │ ├── MeetingLink.js
│ │ └── __snapshots__
│ │ └── MeetingLink.js.snap
├── MeetingList
│ ├── MeetingList.css
│ ├── MeetingList.tsx
│ └── __tests__
│ │ ├── MeetList.js
│ │ └── __snapshots__
│ │ └── MeetList.js.snap
├── MeetingMessage
│ ├── MeetingMessage.css
│ ├── MeetingMessage.tsx
│ └── __tests__
│ │ ├── MeetingMessage.js
│ │ └── __snapshots__
│ │ └── MeetingMessage.js.snap
├── MessageBox
│ ├── MessageBox.css
│ ├── MessageBox.tsx
│ └── __tests__
│ │ ├── MessageBox.js
│ │ └── __snapshots__
│ │ └── MessageBox.js.snap
├── MessageList
│ ├── MessageList.css
│ ├── MessageList.tsx
│ └── __tests__
│ │ ├── MessageList.js
│ │ └── __snapshots__
│ │ └── MessageList.js.snap
├── Navbar
│ ├── Navbar.css
│ ├── Navbar.tsx
│ └── __tests__
│ │ ├── Navbar.js
│ │ └── __snapshots__
│ │ └── Navbar.js.snap
├── PhotoMessage
│ ├── PhotoMessage.css
│ ├── PhotoMessage.tsx
│ └── __tests__
│ │ ├── PhotoMessage.js
│ │ └── __snapshots__
│ │ └── PhotoMessage.js.snap
├── Popup
│ ├── Popup.css
│ ├── Popup.tsx
│ └── __tests__
│ │ ├── Popup.js
│ │ └── __snapshots__
│ │ └── Popup.js.snap
├── ReplyMessage
│ ├── ReplyMessage.css
│ ├── ReplyMessage.tsx
│ └── __tests__
│ │ ├── ReplyMessage.js
│ │ └── __snapshots__
│ │ └── ReplyMessage.js.snap
├── SideBar
│ ├── SideBar.css
│ ├── SideBar.tsx
│ └── __tests__
│ │ ├── SideBar.js
│ │ └── __snapshots__
│ │ └── SideBar.js.snap
├── SpotifyMessage
│ ├── SpotifyMessage.css
│ ├── SpotifyMessage.tsx
│ └── __tests__
│ │ ├── SpotifyMessage.js
│ │ └── __snapshots__
│ │ └── SpotifyMessage.js.snap
├── SystemMessage
│ ├── SystemMessage.css
│ ├── SystemMessage.tsx
│ └── __tests__
│ │ ├── SystemMessage.js
│ │ └── __snapshots__
│ │ └── SystemMessage.js.snap
├── VideoMessage
│ ├── VideoMessage.css
│ ├── VideoMessage.tsx
│ └── __tests__
│ │ ├── VideoMessage.js
│ │ └── __snapshots__
│ │ └── VideoMessage.js.snap
├── assets
│ └── img
│ │ ├── leftArrow.svg
│ │ └── rightArrow.svg
├── declaration.d.ts
├── index.ts
├── index.tsx
├── loremIpsum.d.ts
├── setupTests.ts
└── type.d.ts
├── tsconfig.json
├── tsconfig.prod.json
├── webpack.dev.config.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dependencies
2 | node_modules
3 | .vscode
4 |
5 | # testing
6 | coverage
7 |
8 | # misc
9 | .DS_Store
10 | .env
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | dist
15 | package-lock.json
16 |
17 | # -------- React Native -------- #
18 |
19 | # Xcode
20 | #
21 | build/
22 | *.pbxuser
23 | !default.pbxuser
24 | *.mode1v3
25 | !default.mode1v3
26 | *.mode2v3
27 | !default.mode2v3
28 | *.perspectivev3
29 | !default.perspectivev3
30 | xcuserdata
31 | *.xccheckout
32 | *.moved-aside
33 | DerivedData
34 | *.hmap
35 | *.ipa
36 | *.xcuserstate
37 | project.xcworkspace
38 |
39 | # Android/IntelliJ
40 | #
41 | build/
42 | .idea
43 | .gradle
44 | local.properties
45 | *.iml
46 |
47 | # BUCK
48 | buck-out/
49 | \.buckd/
50 | *.keystore
51 |
52 | # fastlane
53 | #
54 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
55 | # screenshots whenever they are needed.
56 | # For more information about the recommended setup visit:
57 | # https://docs.fastlane.tools/best-practices/source-control/
58 |
59 | */fastlane/report.xml
60 | */fastlane/Preview.html
61 | */fastlane/screenshots
62 |
63 | # -------- React Native -------- #
64 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dependencies
2 | node_modules
3 |
4 | # testing
5 | coverage
6 |
7 | # misc
8 | .DS_Store
9 | .env
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # react-native
15 | /ios
16 | /android
17 |
18 | # examples
19 | /example
20 | /example-native
21 |
22 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.json
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | 12.0.4 Updates
3 |
4 |
5 | # Meeting Link changes
6 |
7 | ## You can render custom meeting link action buttons
8 |
9 | ### New usage;
10 |
11 | ```js
12 |
13 | import { MeetlingLink } from 'react-chat-elements'
14 |
15 | {
19 | console.log(test)
20 | },
21 | Component: () => Test
,
22 | },
23 | {
24 | onClickButton: test => {
25 | console.log(test)
26 | },
27 | Component: () => Test
,
28 | },
29 | ]}
30 | .
31 | .
32 | .
33 | />
34 | ```
35 |
36 | ### In MessageList component usage;
37 |
38 | ```js
39 | import { MessageList } from 'react-chat-elements'
40 |
41 | {
45 | console.log(test)
46 | },
47 | Component: () => Test
,
48 | },
49 | {
50 | onClickButton: test => {
51 | console.log(test)
52 | },
53 | Component: () => Test
,
54 | },
55 | ]}
56 | .
57 | .
58 | .
59 | />
60 | ```
61 |
62 |
63 |
64 |
65 |
66 |
67 | 12.0.0 Updates
68 |
69 |
70 | # typescript support
71 |
72 |
73 |
74 |
75 |
76 | 11.0.0 Updates
77 |
78 |
79 | This update target to fix component ref broken problems
80 |
81 | Fixed issues:
82 |
83 | - https://github.com/Detaysoft/react-chat-elements/issues/158
84 | - https://github.com/Detaysoft/react-chat-elements/issues/157
85 | - https://github.com/Detaysoft/react-chat-elements/issues/142
86 |
87 | 1. All react-chat-elements components turneded to function component for "ref" property problems.
88 |
89 | 2. In the [Input](https://github.com/Detaysoft/react-chat-elements#input-component) component `referance={...}` instead of use `ref={...}`
90 |
91 | 3. **10.16.2** and before vesion usage
92 |
93 | **Before usage**: this.refs.input.clear();
94 |
95 | After **11.0.0** version usage is:
96 |
97 | ```js
98 | import { Input } from 'react-chat-elements'
99 |
100 |
108 | ```
109 |
110 | New usage in **class component**: clearRef();
111 |
112 | ```js
113 |
114 | import { Input } from 'react-chat-elements'
115 | let clearRef = () => {};
116 | this.inputReferance = React.createRef();
117 |
118 | clearRef = clear}
121 | placeholder="Type here..."
122 | multiline={true}
123 | .
124 | .
125 | .
126 | />
127 |
128 | ```
129 |
130 | New usage in **function component**: clearRef();
131 |
132 | ```js
133 |
134 | import { Input } from 'react-chat-elements'
135 | let clearRef = () => {};
136 | const inputReferance = React.useRef();
137 |
138 | clearRef = clear}
141 | placeholder="Type here..."
142 | multiline={true}
143 | .
144 | .
145 | .
146 | />
147 |
148 | ```
149 |
150 | 4. In the [MessageList](https://github.com/Detaysoft/react-chat-elements#messagelist-component) component usage `referance={...}` instead of use `ref={...}`
151 |
152 | **Class Component:**
153 |
154 | ```js
155 | import { MessageList } from 'react-chat-elements'
156 | this.messageList = React.createRef();
157 |
158 |
174 | ```
175 |
176 | **Function Component:**
177 |
178 | ```js
179 | import { MessageList } from 'react-chat-elements'
180 | const messageListReferance = React.useRef();
181 |
182 |
198 | ```
199 |
200 |
201 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Detaysoft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, ubuntu;
3 | background: white;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | html,
9 | body {
10 | height: 100%;
11 | overflow: hidden;
12 | }
13 |
14 | input,
15 | button {
16 | font-family: verdana, ubuntu;
17 | }
18 |
19 | .container {
20 | display: flex;
21 | flex-direction: row;
22 | min-height: 100%;
23 | overflow: auto;
24 | position: absolute;
25 | top: 0;
26 | bottom: 0;
27 | left: 0;
28 | right: 0;
29 | }
30 |
31 | .chat-list {
32 | min-width: 240px;
33 | max-width: 380px;
34 | overflow: auto;
35 | }
36 |
37 | .right-panel {
38 | flex: 1;
39 | display: flex;
40 | flex-direction: column;
41 | }
42 |
43 | .message-list {
44 | flex: 1;
45 | min-width: 140px;
46 | overflow: auto;
47 | }
48 |
49 | .input-area {
50 | height: 50px;
51 | }
52 |
53 | .on-drag-mlist {
54 | width: 100%;
55 | height: 100%;
56 | background-color: #e2f3f5;
57 | display: flex;
58 | justify-content: center;
59 | align-items: center;
60 | z-index: 999;
61 | }
62 |
63 | .rce-example {
64 | display: flex;
65 | flex-direction: column;
66 | align-items: center;
67 | }
68 |
69 | .rce-example .rce-example-btn {
70 | display: flex;
71 | flex-wrap: wrap;
72 | justify-content: center;
73 | margin: 2rem 0;
74 | border-bottom: 1px solid rgb(214, 213, 213);
75 | width: 100%;
76 | }
77 |
78 | .rce-example .rce-example-btn button {
79 | margin: 0 0.5rem 0.5rem 0.5rem;
80 | padding: 0.5rem;
81 | border-radius: 0.5rem;
82 | cursor: pointer;
83 | font-weight: 600;
84 | border: none;
85 | }
86 |
87 | .rce-example .rce-example-btn button:first-child {
88 | margin-left: 0;
89 | }
90 |
91 | .rce-example .rce-example-btn button:last-child {
92 | margin-right: 0;
93 | }
94 |
95 | .rce-example-component {
96 | display: flex;
97 | align-items: center;
98 | justify-content: center;
99 | width: 60%;
100 | }
101 |
102 | .rce-example-messageList {
103 | background: rgb(246 250 250);
104 | border: 1px solid rgb(238, 235, 235);
105 | height: 70vh;
106 | }
107 |
108 | .rce-example-input textarea {
109 | border: 1px solid rgb(214, 213, 213);
110 | border-radius: 0.5rem;
111 | overflow: hidden;
112 | }
113 |
114 | .rce-mlist {
115 | padding: 1rem 0;
116 | }
117 |
118 | .chat-item-sub-text {
119 | display: flex;
120 | align-items: center;
121 | font-size: 12px;
122 | column-gap: 3px;
123 | margin-top: 3px;
124 | opacity: 0.7;
125 | color: teal;
126 | }
127 |
--------------------------------------------------------------------------------
/example/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import './App.css'
3 | import AvatarExample from './components/AvatarExample'
4 | import ButtonExample from './components/ButtonExample'
5 | import ChatListExample from './components/ChatListExample'
6 | import DropdownExample from './components/DropdownExample'
7 | import MeetingListExample from './components/MeetingListExample'
8 | import MessageListExample from './components/MessageListExample'
9 | import NavbarExample from './components/NavbarExample'
10 | import PopupExample from './components/PopupExample'
11 |
12 | const App: React.FC = () => {
13 | const [showComponent, setShowComponent] = useState('')
14 |
15 | const btnClick = (component: String) => {
16 | switch (component) {
17 | case 'chatList':
18 | return
19 | case 'messageList':
20 | return
21 | case 'avatar':
22 | return
23 | case 'button':
24 | return
25 | case 'dropdown':
26 | return
27 | case 'meetingList':
28 | return
29 | case 'navbar':
30 | return
31 | case 'popup':
32 | return
33 | default:
34 | break
35 | }
36 | }
37 |
38 | return (
39 |
40 |
41 |
59 |
77 |
95 |
113 |
131 |
149 |
167 |
185 |
186 |
{btnClick(showComponent)}
187 |
188 | )
189 | }
190 |
191 | export default App
192 |
--------------------------------------------------------------------------------
/example/components/AvatarExample.tsx:
--------------------------------------------------------------------------------
1 | import Avatar from '../../src/Avatar/Avatar'
2 | import { photo } from '../utils/common'
3 |
4 | function AvatarExample() {
5 | return (
6 |
9 | )
10 | }
11 |
12 | export default AvatarExample
13 |
--------------------------------------------------------------------------------
/example/components/ButtonExample.tsx:
--------------------------------------------------------------------------------
1 | import Button from '../../src/Button/Button'
2 |
3 | import { HugeiconsIcon } from '@hugeicons/react';
4 | // @ts-ignore
5 | import { Chatting01Icon } from '@hugeicons/core-free-icons';
6 |
7 | function ButtonExample() {
8 | return (
9 |
10 |
17 | Icon Button
18 | ,
23 | size: 18,
24 | }}
25 | />
26 |
27 |
35 | Text Button
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | export default ButtonExample
43 |
--------------------------------------------------------------------------------
/example/components/ChatListExample.tsx:
--------------------------------------------------------------------------------
1 | import Identicon from 'identicon.js'
2 | import loremIpsum from 'lorem-ipsum'
3 | import React, { useEffect, useState } from 'react'
4 | import ChatList from '../../src/ChatList/ChatList'
5 | import SideBar from '../../src/SideBar/SideBar'
6 | import { IChatItemProps } from '../../src/type'
7 | import { FaUsers } from 'react-icons/fa'
8 |
9 | import { HugeiconsIcon } from '@hugeicons/react';
10 | // @ts-ignore
11 | import { LeftToRightListBulletIcon } from '@hugeicons/core-free-icons';
12 |
13 | function Test(params: any) {
14 | return (
15 | console.log('clicked')}>
16 |
30 | {Math.ceil(Math.random() * 9) + 1}
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | function ChatListExample() {
38 | const photo = (size: number) => {
39 | return new Identicon(String(Math.random()) + String(Math.random()), {
40 | margin: 0,
41 | size: size || 20,
42 | }).toString()
43 | }
44 |
45 | const [chatListAray, setChatListAray] = useState([
46 | {
47 | id: String(Math.random()),
48 | avatar: `data:image/png;base64,${photo(20)}`,
49 | avatarFlexible: true,
50 | statusColor: 'lightgreen',
51 | statusColorType: Math.floor((Math.random() * 100) % 2) === 1 ? 'encircle' : undefined,
52 | alt: loremIpsum({ count: 2, units: 'words' }),
53 | title: loremIpsum({ count: 2, units: 'words' }),
54 | date: new Date(),
55 | subtitle: loremIpsum({ count: 1, units: 'sentences' }),
56 | unread: Math.floor((Math.random() * 10) % 3),
57 | muted: Math.floor((Math.random() * 10) % 2) === 1,
58 | showMute: Math.floor((Math.random() * 10) % 2) === 1,
59 | showVideoCall: Math.floor((Math.random() * 10) % 2) === 1,
60 | customStatusComponents: [Test],
61 | },
62 | ])
63 |
64 | useEffect(() => {
65 | if (chatListAray.length === 5) return
66 | setChatListAray([
67 | ...chatListAray,
68 | getRandomChat(),
69 | ])
70 | }, [chatListAray])
71 |
72 | const getRandomChat: any = (nested = true) => {
73 | return {
74 | id: String(Math.random()),
75 | avatar: `data:image/png;base64,${photo(20)}`,
76 | avatarFlexible: true,
77 | statusColor: 'lightgreen',
78 | statusColorType: Math.floor((Math.random() * 100) % 2) === 1 ? 'encircle' : undefined,
79 | alt: loremIpsum({ count: 2, units: 'words' }),
80 | title: loremIpsum({ count: 2, units: 'words' }),
81 | date: new Date(),
82 | subtitle: loremIpsum({ count: 1, units: 'sentences' }),
83 | unread: Math.floor((Math.random() * 10) % 3),
84 | muted: Math.floor((Math.random() * 10) % 2) === 1,
85 | showMute: Math.floor((Math.random() * 10) % 2) === 1,
86 | showVideoCall: Math.floor((Math.random() * 10) % 2) === 1,
87 | customStatusComponents: [Test],
88 | subTextElement: Math.floor((Math.random() * 100) % 2) === 1 ? getSubTextElement() : <>>,
89 | };
90 | }
91 |
92 | const getSubTextElement = () => {
93 | return (
94 |
95 |
96 | {loremIpsum({ count: 2, units: 'words' })}
97 |
98 | )
99 | }
100 |
101 | return (
102 |
103 |
console.log(props)}
109 | onClickVideoCall={({ ...props }) => console.log(props)}
110 | id={''}
111 | lazyLoadingImage={''}
112 | onDragEnter={(e: React.DragEventHandler, id: number) => console.log(e, id, 'onDragEnter')}
113 | onDragLeave={(e: React.DragEventHandler, id: number) => console.log(e, id, 'onDragLeave')}
114 | onDrop={(e: React.DragEventHandler, id: number) => console.log(e, id, 'onDrop')}
115 | onDragComponent={() => {loremIpsum({ count: 4, units: 'words' })}
}
116 | />
117 | ),
118 | }}
119 | />
120 |
121 | )
122 | }
123 |
124 | export default ChatListExample
125 |
--------------------------------------------------------------------------------
/example/components/DropdownExample.tsx:
--------------------------------------------------------------------------------
1 | import Dropdown from '../../src/Dropdown/Dropdown'
2 |
3 | import { HugeiconsIcon } from '@hugeicons/react';
4 | // @ts-ignore
5 | import { MoreVerticalIcon, SquareIcon } from '@hugeicons/core-free-icons';
6 |
7 | function DropdownExample() {
8 | return (
9 |
10 | {
12 | console.log(e)
13 | }}
14 | animationPosition='norteast'
15 | title='Dropdown Title'
16 | buttonProps={{
17 | type: 'transparent',
18 | color: '#929292',
19 | icon: {
20 | component: ,
21 | size: 24,
22 | },
23 | }}
24 | items={[
25 | {
26 | icon: {
27 | component: ,
28 | float: 'left',
29 | size: 22,
30 | },
31 | text: 'Menu Item',
32 | },
33 | {
34 | icon: {
35 | component: ,
36 | float: 'left',
37 | color: 'purple',
38 | size: 22,
39 | },
40 | text: 'Menu Item',
41 | },
42 | {
43 | icon: {
44 | component: ,
45 | float: 'left',
46 | color: 'yellow',
47 | size: 22,
48 | },
49 | text: 'Menu Item',
50 | },
51 | ]}
52 | />
53 |
54 | )
55 | }
56 |
57 | export default DropdownExample
58 |
--------------------------------------------------------------------------------
/example/components/MeetingListExample.tsx:
--------------------------------------------------------------------------------
1 | import loremIpsum from 'lorem-ipsum'
2 | import { useEffect, useState } from 'react'
3 | import MeetingList from '../../src/MeetingList/MeetingList'
4 | import { photo, token } from '../utils/common'
5 |
6 | function MeetingListExample() {
7 | const [meetingListArray, setMeetingListArray] = useState([])
8 |
9 | useEffect(() => {
10 | if (meetingListArray.length === 5) return
11 | setMeetingListArray([
12 | ...meetingListArray,
13 | {
14 | id: String(Math.random()),
15 | closable: true,
16 | avatars: Array(token() + 2)
17 | .fill(1)
18 | .map(x => ({
19 | src: `data:image/png;base64,${photo(20)}`,
20 | title: 'react, rce',
21 | })),
22 | avatarFlexible: true,
23 | date: +new Date(),
24 | subject: loremIpsum({ count: 2, units: 'words' }),
25 | subjectLimit: 25,
26 | avatarLimit: 5,
27 | },
28 | ])
29 | }, [meetingListArray])
30 |
31 | return (
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default MeetingListExample
39 |
--------------------------------------------------------------------------------
/example/components/MessageListExample.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 | import Button from '../../src/Button/Button'
3 | import Input from '../../src/Input/Input'
4 | import MessageList from '../../src/MessageList/MessageList'
5 | import { token } from '../utils/common'
6 | import {
7 | audioMessage,
8 | fileMessage,
9 | locationMessage,
10 | meetingLinkMessage,
11 | meetingMessage,
12 | photoMessage,
13 | spotifyMessage,
14 | systemMessage,
15 | textMessage,
16 | videoMessage,
17 | } from '../utils/MessageTypes'
18 |
19 | let clearRef = () => {}
20 |
21 | function useForceUpdate() {
22 | const [value, setValue] = useState(0)
23 | return () => setValue(() => value + 1)
24 | }
25 |
26 | function MessageListExample() {
27 | const [messageListArray, setMessageListArray] = useState([])
28 | const [status, setStatus] = useState('')
29 | const messageListReferance = useRef()
30 | const inputReferance = useRef()
31 |
32 | const forceUpdate = useForceUpdate()
33 |
34 | const addMessage = (data: number) => {
35 | let Addmtype: string | number = data || token()
36 | switch (data) {
37 | case 0:
38 | Addmtype = 'photo'
39 | setStatus('waiting')
40 | break
41 | case 1:
42 | Addmtype = 'file'
43 | setStatus('sent')
44 | break
45 | case 2:
46 | Addmtype = 'system'
47 | break
48 | case 3:
49 | Addmtype = 'location'
50 | setStatus('received')
51 | break
52 | case 4:
53 | Addmtype = 'spotify'
54 | setStatus('waiting')
55 | break
56 | case 5:
57 | Addmtype = 'meeting'
58 | setStatus('sent')
59 | break
60 | case 6:
61 | Addmtype = 'video'
62 | setStatus('read')
63 | break
64 | case 7:
65 | Addmtype = 'audio'
66 | break
67 | case 8:
68 | Addmtype = 'meetingLink'
69 | break
70 | default:
71 | Addmtype = 'text'
72 | setStatus('read')
73 | break
74 | }
75 |
76 | setMessageListArray([...messageListArray, randomMessage(Addmtype)])
77 | clearRef()
78 | forceUpdate()
79 | }
80 |
81 | const randomMessage = (type: string) => {
82 | switch (type) {
83 | case 'photo':
84 | return photoMessage
85 | case 'file':
86 | return fileMessage
87 | case 'system':
88 | return systemMessage
89 | case 'location':
90 | return locationMessage
91 | case 'spotify':
92 | return spotifyMessage
93 | case 'meeting':
94 | return meetingMessage
95 | case 'video':
96 | return videoMessage
97 | case 'audio':
98 | return audioMessage
99 | case 'meetingLink':
100 | return meetingLinkMessage
101 | case 'text':
102 | return textMessage
103 | default:
104 | break
105 | }
106 | }
107 |
108 | return (
109 |
110 |
119 |
120 |
130 | console.log('onMaxLengthExceed')}
137 | referance={inputReferance}
138 | clear={(clear: any) => (clearRef = clear)}
139 | maxHeight={50}
140 | onKeyPress={(e: any) => {
141 | if (e.shiftKey && e.charCode === 13) {
142 | return true
143 | }
144 | if (e.charCode === 13) {
145 | clearRef()
146 | addMessage(token())
147 | }
148 | }}
149 | rightButtons={
152 |
153 | )
154 | }
155 |
156 | export default MessageListExample
157 |
--------------------------------------------------------------------------------
/example/components/NavbarExample.tsx:
--------------------------------------------------------------------------------
1 | import Avatar from '../../src/Avatar/Avatar'
2 | import Button from '../../src/Button/Button'
3 | import Dropdown from '../../src/Dropdown/Dropdown'
4 | import Navbar from '../../src/Navbar/Navbar'
5 | import { photo } from '../utils/common'
6 |
7 | import { HugeiconsIcon } from '@hugeicons/react';
8 | // @ts-ignore
9 | import { CircleIcon, Location01Icon, Video01Icon, MoreVerticalIcon } from '@hugeicons/core-free-icons';
10 |
11 | function NavbarExample() {
12 | return (
13 |
14 |
18 |
19 |
console.log(e)}
22 | style={{ display: 'flex', alignItems: 'center' }}
23 | >
24 |
25 |
{}}
28 | style={{
29 | display: 'flex',
30 | flexDirection: 'column',
31 | alignItems: 'flex-start',
32 | marginLeft: '0.5rem',
33 | }}
34 | >
35 |
36 | Bilinmiyor
37 |
38 | {
39 | {}} className='user-location' style={{ fontSize: '10px', color: '#575757' }}>
40 |
41 | Konum bilinmiyor
42 |
43 | }
44 |
45 |
46 |
47 |
48 | }
49 | right={
50 |
51 |
52 |
94 |
95 | }
96 | />
97 |
98 | )
99 | }
100 |
101 | export default NavbarExample
102 |
--------------------------------------------------------------------------------
/example/components/PopupExample.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import Button from '../../src/Button/Button'
3 | import Popup from '../../src/Popup/Popup'
4 |
5 | import { HugeiconsIcon } from '@hugeicons/react';
6 | // @ts-ignore
7 | import { Cancel01Icon } from '@hugeicons/core-free-icons';
8 |
9 | function PopupExample() {
10 | const [show, setShow] = useState(false)
11 | return (
12 |
13 | {' '}
14 |
{
23 | setShow(false)
24 | },
25 | icon: {
26 | component: ,
27 | size: 18,
28 | },
29 | },
30 | ],
31 | text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatem animi veniam voluptas eius!',
32 | footerButtons: [
33 | {
34 | color: 'white',
35 | backgroundColor: '#ff5e3e',
36 | text: 'Vazgeç',
37 | onClick: () => {
38 | setShow(false)
39 | },
40 | },
41 | {
42 | color: 'white',
43 | backgroundColor: 'lightgreen',
44 | text: 'Tamam',
45 | onClick: () => {
46 | setShow(false)
47 | },
48 | },
49 | ],
50 | }}
51 | />
52 |
59 | )
60 | }
61 |
62 | export default PopupExample
63 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.body
10 | )
11 |
--------------------------------------------------------------------------------
/example/utils/MessageTypes.ts:
--------------------------------------------------------------------------------
1 | import loremIpsum from 'lorem-ipsum'
2 | import { MessageType } from '../../src/type'
3 | import { getRandomColor, photo, token } from './common'
4 |
5 | import { HugeiconsIcon } from '@hugeicons/react';
6 | // @ts-ignore
7 | import { Video01Icon } from '@hugeicons/core-free-icons';
8 |
9 | export const photoMessage: MessageType = {
10 | type: 'photo',
11 | id: String(Math.random()),
12 | position: token() >= 1 ? 'right' : 'left',
13 | title: loremIpsum({ count: 2, units: 'words' }),
14 | focus: true,
15 | date: +new Date(),
16 | forwarded: true,
17 | replyButton: true,
18 | removeButton: true,
19 | notch: true,
20 | retracted: false,
21 | text: loremIpsum({ count: 1, units: 'sentences' }),
22 | titleColor: getRandomColor(),
23 | status: 'waiting',
24 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
25 | data: {
26 | uri: `data:image/png;base64,${photo(150)}`,
27 | status: {
28 | click: true,
29 | loading: 0.5,
30 | download: false, //type === "video",
31 | error: false,
32 | },
33 | width: 300,
34 | height: 300,
35 | },
36 | }
37 |
38 | export const locationMessage: MessageType = {
39 | type: 'location',
40 | markerColor: '',
41 | zoom: '',
42 | apiKey: '',
43 | status: 'received',
44 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
45 | id: String(Math.random()),
46 | position: token() >= 1 ? 'right' : 'left',
47 | text: loremIpsum({ count: 1, units: 'sentences' }),
48 | title: loremIpsum({ count: 2, units: 'words' }),
49 | focus: true,
50 | date: +new Date(),
51 | dateString: 'now',
52 | avatar: `data:image/png;base64,${photo(20)}`,
53 | titleColor: getRandomColor(),
54 | forwarded: true,
55 | replyButton: true,
56 | removeButton: true,
57 | notch: true,
58 | copiableDate: true,
59 | retracted: false,
60 | forwardedMessageText: 'Forwarded',
61 | className: '',
62 | data: {
63 | latitude: '37.773972',
64 | longitude: '-122.431297',
65 | staticURL:
66 | 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s-circle+FF0000(LONGITUDE,LATITUDE)/LONGITUDE,LATITUDE,ZOOM/270x200@2x?access_token=KEY',
67 | },
68 | reply:
69 | token() >= 1
70 | ? {
71 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
72 | title: loremIpsum({ count: 2, units: 'words' }),
73 | titleColor: getRandomColor(),
74 | message: loremIpsum({ count: 1, units: 'sentences' }),
75 | }
76 | : undefined,
77 | }
78 |
79 | export const fileMessage: MessageType = {
80 | type: 'file',
81 | data: {
82 | status: {
83 | click: () => {},
84 | loading: 0.5,
85 | download: () => {}, //item === "video",
86 | error: false,
87 | },
88 | size: '100MB',
89 | },
90 | status: 'sent',
91 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
92 | id: String(Math.random()),
93 | position: token() >= 1 ? 'right' : 'left',
94 | text: loremIpsum({ count: 1, units: 'sentences' }),
95 | title: loremIpsum({ count: 2, units: 'words' }),
96 | focus: false,
97 | date: +new Date(),
98 | dateString: 'now',
99 | avatar: `data:image/png;base64,${photo(20)}`,
100 | titleColor: getRandomColor(),
101 | forwarded: true,
102 | replyButton: true,
103 | removeButton: true,
104 | notch: true,
105 | copiableDate: true,
106 | retracted: false,
107 | forwardedMessageText: 'Forwarded',
108 | className: '',
109 | reply:
110 | token() >= 1
111 | ? {
112 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
113 | title: loremIpsum({ count: 2, units: 'words' }),
114 | titleColor: getRandomColor(),
115 | message: loremIpsum({ count: 1, units: 'sentences' }),
116 | }
117 | : undefined,
118 | }
119 |
120 | export const systemMessage: MessageType = {
121 | type: 'system',
122 | id: String(Math.random()),
123 | position: token() >= 1 ? 'right' : 'left',
124 | text: loremIpsum({ count: 2, units: 'words' }),
125 | title: loremIpsum({ count: 2, units: 'words' }),
126 | focus: true,
127 | date: +new Date(),
128 | dateString: 'now',
129 | avatar: `data:image/png;base64,${photo(20)}`,
130 | titleColor: getRandomColor(),
131 | forwarded: true,
132 | replyButton: true,
133 | removeButton: true,
134 | status: 'received',
135 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
136 | notch: true,
137 | copiableDate: true,
138 | retracted: false,
139 | className: '',
140 | }
141 |
142 | export const spotifyMessage: MessageType = {
143 | type: 'spotify',
144 | id: String(Math.random()),
145 | position: token() >= 1 ? 'right' : 'left',
146 | text: loremIpsum({ count: 1, units: 'sentences' }),
147 | title: loremIpsum({ count: 2, units: 'words' }),
148 | focus: true,
149 | date: +new Date(),
150 | dateString: 'now',
151 | avatar: `data:image/png;base64,${photo(20)}`,
152 | titleColor: getRandomColor(),
153 | forwarded: true,
154 | replyButton: true,
155 | removeButton: true,
156 | notch: true,
157 | copiableDate: true,
158 | retracted: false,
159 | className: '',
160 | status: 'read',
161 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
162 | theme: 'white',
163 | view: 'list',
164 | width: 300,
165 | height: 300,
166 | uri: 'spotify:track:0QjjaCaXE45mvhCnV3C0TA',
167 | reply:
168 | token() >= 1
169 | ? {
170 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
171 | title: loremIpsum({ count: 2, units: 'words' }),
172 | titleColor: getRandomColor(),
173 | message: loremIpsum({ count: 1, units: 'sentences' }),
174 | }
175 | : undefined,
176 | }
177 |
178 | export const videoMessage: MessageType = {
179 | type: 'video',
180 | id: String(Math.random()),
181 | position: token() >= 1 ? 'right' : 'left',
182 | text: loremIpsum({ count: 1, units: 'sentences' }),
183 | title: loremIpsum({ count: 2, units: 'words' }),
184 | focus: true,
185 | date: +new Date(),
186 | dateString: 'now',
187 | avatar: `data:image/png;base64,${photo(20)}`,
188 | titleColor: getRandomColor(),
189 | forwarded: true,
190 | replyButton: true,
191 | removeButton: true,
192 | controlsList: '',
193 | status: 'read',
194 | forwardedMessageText: 'Forwarded',
195 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
196 | data: {
197 | uri: `data:image/png;base64,${photo(150)}`,
198 | videoURL: token() >= 1 ? 'https://www.w3schools.com/html/mov_bbb.mp4' : 'http://www.exit109.com/~dnn/clips/RW20seconds_1.mp4',
199 | status: {
200 | click: true,
201 | loading: 0.5,
202 | download: true, //item === "video",
203 | error: false,
204 | },
205 | width: 300,
206 | height: 200,
207 | },
208 | notch: true,
209 | copiableDate: true,
210 | retracted: false,
211 | className: '',
212 | reply:
213 | token() >= 1
214 | ? {
215 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
216 | title: loremIpsum({ count: 2, units: 'words' }),
217 | titleColor: getRandomColor(),
218 | message: loremIpsum({ count: 1, units: 'sentences' }),
219 | }
220 | : undefined,
221 | }
222 |
223 | export const audioMessage: MessageType = {
224 | type: 'audio',
225 | id: String(Math.random()),
226 | position: token() >= 1 ? 'right' : 'left',
227 | text: loremIpsum({ count: 1, units: 'sentences' }),
228 | title: loremIpsum({ count: 2, units: 'words' }),
229 | focus: true,
230 | date: +new Date(),
231 | dateString: 'now',
232 | avatar: `data:image/png;base64,${photo(20)}`,
233 | titleColor: getRandomColor(),
234 | forwarded: true,
235 | replyButton: true,
236 | removeButton: true,
237 | status: 'received',
238 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
239 | notch: true,
240 | copiableDate: true,
241 | retracted: false,
242 | className: '',
243 | data: {
244 | audioURL: 'https://www.w3schools.com/html/horse.mp3',
245 | audioType: 'audio/mp3',
246 | controlsList: 'nodownload',
247 | },
248 | reply:
249 | token() >= 1
250 | ? {
251 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
252 | title: loremIpsum({ count: 2, units: 'words' }),
253 | titleColor: getRandomColor(),
254 | message: loremIpsum({ count: 1, units: 'sentences' }),
255 | }
256 | : undefined,
257 | }
258 |
259 | export const meetingMessage: MessageType = {
260 | type: 'meeting',
261 | message: 'asd',
262 | id: String(Math.random()),
263 | position: token() >= 1 ? 'right' : 'left',
264 | text: 'spotify:track:0QjjaCaXE45mvhCnV3C0TA',
265 | title: loremIpsum({ count: 2, units: 'words' }),
266 | focus: true,
267 | date: +new Date(),
268 | dateString: 'now',
269 | avatar: `data:image/png;base64,${photo(20)}`,
270 | titleColor: getRandomColor(),
271 | forwarded: true,
272 | replyButton: true,
273 | removeButton: true,
274 | status: 'received',
275 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
276 | notch: true,
277 | copiableDate: true,
278 | retracted: false,
279 | className: '',
280 | forwardedMessageText: 'Forwarded',
281 | reply:
282 | token() >= 1
283 | ? {
284 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
285 | title: loremIpsum({ count: 2, units: 'words' }),
286 | titleColor: getRandomColor(),
287 | message: loremIpsum({ count: 1, units: 'sentences' }),
288 | }
289 | : undefined,
290 | subject: loremIpsum({ count: 2, units: 'words' }),
291 | collapseTitle: loremIpsum({ count: 2, units: 'words' }),
292 | participants: Array(token() + 6)
293 | .fill(1)
294 | .map(x => ({
295 | id: Math.floor((Math.random() * 10) % 7),
296 | title: loremIpsum({ count: 1, units: 'words' }),
297 | })),
298 | dataSource: Array(token() + 5)
299 | .fill(1)
300 | .map(x => ({
301 | type: 'meeting',
302 | position: token() > 1 ? 'right' : 'left',
303 | text: loremIpsum({ count: 1, units: 'sentences' }),
304 | focus: false,
305 | titleColor: getRandomColor(),
306 | forwarded: true,
307 | replyButton: true,
308 | removeButton: true,
309 | status: 'received',
310 | notch: true,
311 | retracted: false,
312 | id: String(Math.random()),
313 | avatar: `data:image/png;base64,${photo(20)}`,
314 | message: loremIpsum({ count: 1, units: 'sentences' }),
315 | title: loremIpsum({ count: 2, units: 'words' }),
316 | avatarFlexible: true,
317 | date: +new Date(),
318 | event: {
319 | title: loremIpsum({ count: 2, units: 'words' }),
320 | avatars: Array(token() + 2)
321 | .fill(1)
322 | .map(x => ({
323 | src: `data:image/png;base64,${photo(20)}`,
324 | title: 'react, rce',
325 | })),
326 | avatarsLimit: 5,
327 | },
328 | record: {
329 | avatar: `data:image/png;base64,${photo(20)}`,
330 | title: loremIpsum({ count: 1, units: 'words' }),
331 | savedBy: 'Kaydeden: ' + loremIpsum({ count: 2, units: 'words' }),
332 | time: new Date().toLocaleString(),
333 | },
334 | })),
335 | }
336 |
337 | export const meetingLinkMessage: MessageType = {
338 | type: 'meetingLink',
339 | actionButtons: [
340 | {
341 | onClickButton(id) {
342 | console.log(id)
343 | },
344 | // Component: () => MdOutlineVideoCall({ size: '25px' }),
345 | Component: () => HugeiconsIcon({ icon: Video01Icon, size: 25 })
346 | },
347 | {
348 | onClickButton(id) {
349 | console.log(id)
350 | },
351 | Component: () => HugeiconsIcon({ icon: Video01Icon, size: 25 })
352 | },
353 | ],
354 | meetingID: String(Math.random()),
355 | id: String(Math.random()),
356 | position: token() >= 1 ? 'right' : 'left',
357 | text: loremIpsum({ count: 1, units: 'sentences' }),
358 | title: loremIpsum({ count: 2, units: 'words' }),
359 | focus: true,
360 | date: +new Date(),
361 | dateString: 'now',
362 | avatar: `data:image/png;base64,${photo(20)}`,
363 | titleColor: getRandomColor(),
364 | forwarded: true,
365 | replyButton: true,
366 | removeButton: true,
367 | status: 'received',
368 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
369 | notch: true,
370 | copiableDate: true,
371 | retracted: false,
372 | className: '',
373 | reply:
374 | token() >= 1
375 | ? {
376 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
377 | title: loremIpsum({ count: 2, units: 'words' }),
378 | titleColor: getRandomColor(),
379 | message: loremIpsum({ count: 1, units: 'sentences' }),
380 | }
381 | : undefined,
382 | }
383 |
384 | export const textMessage: MessageType = {
385 | type: 'text',
386 | id: String(Math.random()),
387 | position: token() >= 1 ? 'right' : 'left',
388 | text: loremIpsum({ count: 1, units: 'sentences' }),
389 | title: loremIpsum({ count: 2, units: 'words' }),
390 | focus: true,
391 | date: +new Date(),
392 | dateString: 'now',
393 | avatar: `data:image/png;base64,${photo(20)}`,
394 | titleColor: getRandomColor(),
395 | forwarded: true,
396 | replyButton: true,
397 | removeButton: true,
398 | status: 'received',
399 | statusTitle: token() >= 5 ? 'Desktop' : 'Mobile',
400 | notch: true,
401 | copiableDate: true,
402 | retracted: false,
403 | forwardedMessageText: 'Forwarded',
404 | className: '',
405 | reply:
406 | token() >= 1
407 | ? {
408 | photoURL: token() >= 1 ? `data:image/png;base64,${photo(150)}` : null,
409 | title: loremIpsum({ count: 2, units: 'words' }),
410 | titleColor: getRandomColor(),
411 | message: loremIpsum({ count: 1, units: 'sentences' }),
412 | }
413 | : undefined,
414 | }
415 |
--------------------------------------------------------------------------------
/example/utils/common.ts:
--------------------------------------------------------------------------------
1 | import Identicon from 'identicon.js'
2 | import { useState } from 'react'
3 |
4 | export const photo = (size: number) => {
5 | return new Identicon(String(Math.random()) + String(Math.random()), {
6 | margin: 0,
7 | size: size || 20,
8 | }).toString()
9 | }
10 |
11 | export const getRandomColor = () => {
12 | var letters = '0123456789ABCDEF'
13 | var color = '#'
14 | for (var i = 0; i < 6; i++) {
15 | color += letters[Math.floor(Math.random() * 16)]
16 | }
17 | return color
18 | }
19 |
20 | export const token = () => {
21 | return Math.floor((Math.random() * 10) % 10)
22 | }
23 |
24 | export function useForceUpdate() {
25 | const [value, setValue] = useState(0)
26 | return () => setValue(() => value + 1)
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-chat-elements",
3 | "version": "12.0.18",
4 | "description": "Reactjs chat components",
5 | "author": "Avare Kodcu ",
6 | "main": "dist/main.js",
7 | "types": "dist/type.d.ts",
8 | "module": "dist/main.es.js",
9 | "devDependencies": {
10 | "@babel/core": "^7.18.5",
11 | "@babel/preset-react": "^7.17.12",
12 | "@rollup/plugin-commonjs": "^22.0.0",
13 | "@rollup/plugin-node-resolve": "^13.3.0",
14 | "@types/identicon.js": "^2.3.1",
15 | "@types/jest": "^27.5.1",
16 | "@types/node": "^17.0.34",
17 | "@types/progressbar.js": "^1.1.2",
18 | "@types/react": "^18.0.9",
19 | "@types/react-dom": "^18.0.4",
20 | "babel-jest": "21.2.0",
21 | "babel-loader": "^8.2.5",
22 | "copyfiles": "^2.4.1",
23 | "css-loader": "^6.7.1",
24 | "enzyme": "^2.9.1",
25 | "enzyme-to-json": "^1.5.1",
26 | "identicon.js": "^2.3.1",
27 | "jest": "21.2.1",
28 | "lorem-ipsum": "^1.0.4",
29 | "mini-css-extract-plugin": "^2.6.0",
30 | "react": "^18.2.0",
31 | "react-dom": "^18.2.0",
32 | "react-scripts": "^5.0.1",
33 | "react-test-renderer": "^18.2.0",
34 | "rimraf": "^3.0.2",
35 | "rollup": "^2.75.6",
36 | "rollup-plugin-babel": "^4.4.0",
37 | "rollup-plugin-peer-deps-external": "^2.2.4",
38 | "rollup-plugin-postcss": "^4.0.2",
39 | "rollup-plugin-terser": "^7.0.2",
40 | "ts-loader": "^9.3.0",
41 | "typescript": "^4.6.4",
42 | "webpack": "^5.73.0",
43 | "webpack-cli": "^4.9.2",
44 | "webpack-dev-server": "^4.9.0"
45 | },
46 | "repository": {
47 | "type": "git",
48 | "url": "git+https://github.com/detaysoft/react-chat-elements.git"
49 | },
50 | "keywords": [
51 | "react",
52 | "reactjs",
53 | "chat",
54 | "css",
55 | "chat",
56 | "components",
57 | "detaysoft"
58 | ],
59 | "license": "MIT",
60 | "jest": {
61 | "snapshotSerializers": [
62 | "/node_modules/enzyme-to-json/serializer"
63 | ]
64 | },
65 | "browser": {
66 | "[module-name]": false
67 | },
68 | "bugs": {
69 | "url": "https://github.com/detaysoft/react-chat-elements/issues"
70 | },
71 | "homepage": "https://github.com/detaysoft/react-chat-elements#readme",
72 | "scripts": {
73 | "dev-server": "webpack-dev-server --config webpack.dev.config.js",
74 | "build": "yarn run clear:build && node node_modules/typescript/bin/tsc -p tsconfig.prod.json && yarn run copy-files",
75 | "copy-files": "copyfiles -u 1 src/**/*.css build/",
76 | "clear:build": "rimraf dist build",
77 | "move:types": "copyfiles -u 1 src/type.d.ts dist/",
78 | "test": "react-scripts test --env=jsdom",
79 | "test:coverage": "npm run test -- --coverage --collectCoverageFrom=src/**/*.js --collectCoverageFrom=!src/index.js",
80 | "build:lib": "yarn run build && rollup -c && yarn run move:types",
81 | "prepare": "npm run build:lib"
82 | },
83 | "peerDependencies": {
84 | "react": "^18.2.0",
85 | "react-dom": "18.2.0"
86 | },
87 | "dependencies": {
88 | "@hugeicons/core-free-icons": "^1.0.13",
89 | "@hugeicons/react": "^1.0.5",
90 | "classnames": "^2.2.5",
91 | "progressbar.js": "^1.1.0",
92 | "react-spinkit": "^3.0.0",
93 | "timeago.js": "^4.0.2"
94 | },
95 | "browserslist": {
96 | "production": [
97 | ">0.2%",
98 | "not dead",
99 | "not op_mini all"
100 | ],
101 | "development": [
102 | "last 1 chrome version",
103 | "last 1 firefox version",
104 | "last 1 safari version"
105 | ]
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | const options = {
2 | arrowParens: 'avoid',
3 | bracketSameLine: false,
4 | bracketSpacing: true,
5 | embeddedLanguageFormatting: 'auto',
6 | htmlWhitespaceSensitivity: 'css',
7 | insertPragma: false,
8 | jsxSingleQuote: true,
9 | proseWrap: 'preserve',
10 | quoteProps: 'preserve',
11 | requirePragma: false,
12 | semi: false,
13 | singleQuote: true,
14 | printWidth: 130,
15 | tabWidth: 2,
16 | trailingComma: 'es5',
17 | useTabs: false,
18 | vueIndentScriptAndStyle: false,
19 | }
20 |
21 | module.exports = options
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel'
2 | import resolve from '@rollup/plugin-node-resolve'
3 | import external from 'rollup-plugin-peer-deps-external'
4 | import postcss from 'rollup-plugin-postcss'
5 | import { terser } from 'rollup-plugin-terser'
6 | import commonjs from '@rollup/plugin-commonjs'
7 | import path from 'path'
8 |
9 | export default [
10 | {
11 | input: './build/index.js',
12 | output: [
13 | {
14 | file: 'dist/main.js',
15 | format: 'cjs',
16 | },
17 | {
18 | file: 'dist/main.es.js',
19 | format: 'es',
20 | exports: 'named',
21 | },
22 | ],
23 | plugins: [
24 | postcss({
25 | plugins: [],
26 | minimize: true,
27 | extract: path.resolve('dist/main.css'),
28 | }),
29 | babel({
30 | exclude: 'node_modules/**',
31 | presets: ['@babel/preset-react'],
32 | }),
33 | commonjs(),
34 | external(),
35 | resolve(),
36 | terser(),
37 | ],
38 | },
39 | ]
40 |
--------------------------------------------------------------------------------
/src/AudioMessage/AudioMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-audio {
2 | padding-bottom: 10px;
3 | max-width: 300px;
4 | }
5 |
6 | audio:focus {
7 | outline: none;
8 | }
9 |
--------------------------------------------------------------------------------
/src/AudioMessage/AudioMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { IAudioMessageProps } from '../type'
3 | import './AudioMessage.css'
4 |
5 | const AudioMessage: React.FC = props => {
6 | const controlsList = props.data.controlsList
7 |
8 | return (
9 |
10 |
14 | {props.text &&
{props.text}
}
15 |
16 | )
17 | }
18 |
19 | export default AudioMessage
20 |
--------------------------------------------------------------------------------
/src/AudioMessage/__tests__/AudioMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import AudioMessage from '../AudioMessage'
5 |
6 | describe('AudioMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/AudioMessage/__tests__/__snapshots__/AudioMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`AudioMessage component should render without issues 1`] = `
4 |
7 |
16 |
17 | `;
18 |
--------------------------------------------------------------------------------
/src/Avatar/Avatar.css:
--------------------------------------------------------------------------------
1 | .rce-avatar-container {
2 | overflow: hidden;
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | }
7 |
8 | .rce-avatar-container .rce-avatar {
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | .rce-avatar-container.flexible .rce-avatar {
14 | height: auto !important;
15 | width: 100% !important;
16 | border-radius: unset !important;
17 | overflow: unset !important;
18 | }
19 |
20 | .rce-avatar-container.default {
21 | width: 25px;
22 | height: 25px;
23 | }
24 |
25 | .rce-avatar-container.rounded {
26 | border-radius: 5px;
27 | }
28 |
29 | .rce-avatar-container.circle {
30 | border-radius: 100%;
31 | }
32 |
33 | .rce-avatar-container.xsmall {
34 | width: 30px;
35 | height: 30px;
36 | }
37 |
38 | .rce-avatar-container.small {
39 | width: 35px;
40 | height: 35px;
41 | }
42 |
43 | .rce-avatar-container.medium {
44 | width: 40px;
45 | height: 40px;
46 | }
47 |
48 | .rce-avatar-container.large {
49 | width: 45px;
50 | height: 45px;
51 | }
52 |
53 | .rce-avatar-container.xlarge {
54 | width: 55px;
55 | height: 55px;
56 | }
57 |
58 | @keyframes avatarLazy {
59 | 0% {
60 | opacity: 1;
61 | }
62 | 50% {
63 | opacity: 0.5;
64 | }
65 | 100% {
66 | opacity: 1;
67 | }
68 | }
69 |
70 | .rce-avatar-lazy {
71 | animation: avatarLazy normal 2s infinite ease-in-out;
72 | }
73 |
74 | .rce-avatar-container.rce-citem-avatar-encircle-status {
75 | box-sizing: border-box;
76 | position: relative;
77 | }
78 |
79 | .rce-avatar-letter {
80 | margin-top: 5px;
81 | font-size: 20px;
82 | color: #fff;
83 | display: flex;
84 | justify-content: center;
85 | align-items: center;
86 | }
87 |
88 | .rce-avatar-letter-background {
89 | height: 35px;
90 | width: 35px;
91 | border-radius: 20px;
92 | }
93 |
94 | .rce-mini-avatar-container {
95 | position: absolute;
96 | right: 5px;
97 | bottom: 2.5px;
98 | }
99 |
100 | .rce-citem-avatar .rce-mini-avatar-container img {
101 | width: 35px !important;
102 | height: 35px !important;
103 | border: 2px solid #FFF !important;
104 | }
105 |
--------------------------------------------------------------------------------
/src/Avatar/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import './Avatar.css'
3 | import classNames from 'classnames'
4 | import { IAvatarProps } from '../type'
5 |
6 | const Avatar: React.FC = ({ type = 'default', size = 'default', lazyLoadingImage = undefined, ...props }) => {
7 | let loadedAvatars: string[] = []
8 | let loading: boolean = false
9 | let src = props.src
10 | let isLazyImage: boolean = false
11 |
12 | useEffect(() => {
13 | if (lazyLoadingImage) {
14 | isLazyImage = true
15 |
16 | if (!isLoaded(src)) {
17 | src = lazyLoadingImage
18 |
19 | if (!loading) {
20 | requestImage(props.src)
21 | }
22 | } else {
23 | isLazyImage = false
24 | }
25 | }
26 | }, [])
27 |
28 | const isLoaded = (src: string) => {
29 | return loadedAvatars.indexOf(src) !== -1
30 | }
31 |
32 | const requestImage = (src: string) => {
33 | loading = true
34 |
35 | var loaded = () => {
36 | loadedAvatars.push(src)
37 | loading = false
38 | }
39 |
40 | var img: HTMLImageElement = document.createElement('img')
41 | img.src = src
42 | img.onload = loaded
43 | img.onerror = loaded
44 | }
45 |
46 | const stringToColour = (str: string) => {
47 | var hash: number = 0
48 | for (let i: number = 0; i < str.length; i++) {
49 | hash = str.charCodeAt(i) + ((hash << 5) - hash)
50 | }
51 | var colour: string = '#'
52 | for (let i: number = 0; i < 3; i++) {
53 | var value: number = (hash >> (i * 8)) & 0xff
54 | value = (value % 150) + 50
55 | colour += ('00' + value.toString(16)).substr(-2)
56 | }
57 | return colour
58 | }
59 |
60 | return (
61 | <>
62 | {/* @ts-ignore */}
63 |
64 | {props.letterItem ? (
65 |
66 | {props.letterItem.letter}
67 |
68 | ) : (
69 |

75 | )}
76 | {props.sideElement}
77 |
78 |
79 | {props.miniImage && (
80 |
81 |

84 |
85 | )}
86 | >
87 | )
88 | }
89 | export default Avatar
90 |
--------------------------------------------------------------------------------
/src/Avatar/__tests__/Avatar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import Avatar from '../Avatar'
5 |
6 | describe('Avatar component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/Avatar/__tests__/__snapshots__/Avatar.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Avatar component should render without issues 1`] = `
4 |
7 |
![]()
13 |
14 | `;
15 |
--------------------------------------------------------------------------------
/src/Button/Button.css:
--------------------------------------------------------------------------------
1 | .rce-button {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 | justify-content: center;
6 | font-size: 12px;
7 | border-radius: 5px;
8 | cursor: pointer;
9 | padding: 8px;
10 | text-align: center;
11 | box-sizing: border-box;
12 | background: #3979aa;
13 | color: white;
14 | transition: all 0.15s ease;
15 | user-select: none;
16 | border: none;
17 | outline: none;
18 | border: none;
19 | position: relative;
20 | }
21 |
22 | .rce-button-icon--container {
23 | display: flex;
24 | align-items: center;
25 | }
26 |
27 | .rce-button:hover {
28 | opacity: 0.8;
29 | }
30 |
31 | .rce-button:active {
32 | opacity: 0.6;
33 | }
34 |
35 | .rce-button.outline {
36 | background: rgba(0, 0, 0, 0) !important;
37 | border: 1px solid #3979aa;
38 | color: #3979aa;
39 | }
40 |
41 | .rce-button.outline:hover {
42 | opacity: 0.6;
43 | }
44 |
45 | .rce-button.outline:active {
46 | opacity: 0.3;
47 | }
48 |
49 | .rce-button.transparent {
50 | background: rgba(0, 0, 0, 0) !important;
51 | }
52 |
53 | .rce-button.transparent:hover {
54 | opacity: 0.6;
55 | }
56 |
57 | .rce-button.transparent:active {
58 | opacity: 0.3;
59 | }
60 |
61 | .rce-button-icon {
62 | position: relative;
63 | font-size: 18px;
64 | display: flex;
65 | padding: 0 3px;
66 | }
67 |
68 | .rce-button-badge {
69 | border-radius: 4px;
70 | padding: 4px;
71 | background: #f64b34;
72 | display: flex;
73 | flex-direction: row;
74 | align-items: center;
75 | justify-content: center;
76 | position: absolute;
77 | right: -7px;
78 | top: -7px;
79 | font-size: 10px;
80 | }
81 |
82 | .rce-button.circle {
83 | min-width: 35px;
84 | min-height: 35px;
85 | border: 1px solid #3979aa;
86 | border-radius: 100%;
87 | }
88 |
--------------------------------------------------------------------------------
/src/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import './Button.css'
2 | import classNames from 'classnames'
3 | import { IButtonProps } from '../type'
4 |
5 | const Button: React.FC = ({ disabled = false, backgroundColor = '#3979aa', color = 'white', ...props }) => {
6 | return (
7 |
33 | )
34 | }
35 | export default Button
36 |
--------------------------------------------------------------------------------
/src/Button/__tests__/Button.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import Button from '../Button'
5 |
6 | describe('Button component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/Button/__tests__/__snapshots__/Button.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Button component should render without issues 1`] = `
4 |
18 | `;
19 |
--------------------------------------------------------------------------------
/src/ChatItem/ChatItem.css:
--------------------------------------------------------------------------------
1 | .rce-container-citem {
2 | flex-direction: column;
3 | display: block;
4 | overflow: hidden;
5 | min-width: 240px;
6 | }
7 |
8 | .rce-container-citem.rce-citem-status-encircle {
9 | position: relative;
10 | }
11 |
12 | .rce-citem {
13 | position: relative;
14 | background: white;
15 | display: flex;
16 | flex-direction: row;
17 | height: 72px;
18 | cursor: pointer;
19 | user-select: none;
20 | max-width: 100%;
21 | overflow: hidden;
22 | min-width: 240px;
23 | }
24 |
25 | .rce-citem:hover {
26 | background: #f9f9f9;
27 | }
28 |
29 | .rce-citem-avatar {
30 | position: relative;
31 | padding: 0 15px 0 13px;
32 | justify-content: center;
33 | display: flex;
34 | align-items: center;
35 | flex-direction: column;
36 | }
37 |
38 | .rce-citem-status {
39 | width: 20px;
40 | height: 20px;
41 | bottom: 10px;
42 | right: 10px;
43 | position: absolute;
44 | border-radius: 100%;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 | background: #ccc;
49 | }
50 |
51 | .rce-citem-avatar.rce-citem-status-encircle .rce-citem-status {
52 | left: 0;
53 | right: 0;
54 | top: 0;
55 | bottom: 0;
56 | width: 100%;
57 | height: 100%;
58 | background: transparent;
59 | margin: auto;
60 | border-radius: 100%;
61 | }
62 |
63 | .rce-citem-avatar img {
64 | width: 50px;
65 | height: 50px;
66 | border: none !important;
67 | background: #ccc;
68 | border-radius: 100%;
69 | overflow: hidden;
70 | font-size: 10px;
71 | text-align: center;
72 | line-height: 50px;
73 | text-overflow: ellipsis;
74 | white-space: nowrap;
75 | }
76 |
77 | .rce-citem-body {
78 | display: flex;
79 | flex: 1;
80 | flex-direction: column;
81 | justify-content: center;
82 | padding-right: 15px;
83 | border-bottom: 1px solid rgba(0, 0, 0, 0.05);
84 | overflow: hidden;
85 | }
86 |
87 | .rce-citem-body--top {
88 | display: flex;
89 | }
90 |
91 | .rce-citem-body--bottom {
92 | margin-top: 4px;
93 | display: flex;
94 | }
95 |
96 | .rce-citem-body--bottom-title,
97 | .rce-citem-body--top-title {
98 | flex: 1;
99 | white-space: nowrap;
100 | text-overflow: ellipsis;
101 | overflow: hidden;
102 | }
103 |
104 | .rce-citem-body--top-title {
105 | font-size: 16px;
106 | }
107 |
108 | .rce-citem-body--bottom-title {
109 | color: #555;
110 | font-size: 15px;
111 | }
112 |
113 | .rce-citem-body--top-time {
114 | font-size: 12px;
115 | color: rgba(0, 0, 0, 0.4);
116 | }
117 |
118 | .rce-citem-body--bottom-status {
119 | margin-left: 3px;
120 | }
121 |
122 | .rce-citem-body--bottom-status span {
123 | width: 18px;
124 | height: 18px;
125 | font-size: 12px;
126 | color: white;
127 | font-weight: bold;
128 | text-align: center;
129 | align-items: center;
130 | justify-content: center;
131 | display: flex;
132 | border-radius: 100%;
133 | background: red;
134 | }
135 |
136 | .rce-citem-body--bottom-status-icon {
137 | position: relative;
138 | margin-left: 3px;
139 | width: 18px;
140 | height: 18px;
141 | align-items: center;
142 | justify-content: center;
143 | display: flex;
144 | }
145 |
146 | .rce-citem-body--bottom-tools {
147 | align-items: center;
148 | justify-content: center;
149 | flex-direction: row;
150 | display: flex;
151 | height: 0;
152 | opacity: 0;
153 | position: absolute;
154 | left: -999px;
155 | transition: height 0.5s ease, opacity 1s ease;
156 | }
157 |
158 | .rce-citem:hover .rce-citem-body--bottom-tools {
159 | height: 100%;
160 | opacity: 1;
161 | position: relative;
162 | left: 0;
163 | }
164 |
165 | .rce-citem-body--bottom-tools-item-hidden-hover {
166 | height: 100%;
167 | opacity: 0.3;
168 | transition: 0.5s ease;
169 | }
170 |
171 | .rce-citem:hover .rce-citem-body--bottom-tools-item-hidden-hover {
172 | height: 0;
173 | opacity: 0;
174 | position: absolute;
175 | left: -999px;
176 | transition: 0.5s ease;
177 | }
178 |
179 | .rce-citem-body--bottom-tools-item {
180 | width: 18px;
181 | height: 18px;
182 | }
183 |
184 | .rce-citem-body--bottom-tools-item svg {
185 | width: 18px;
186 | height: 18px;
187 | }
188 |
189 | .rce-citem-body--subinfo {
190 | display: flex;
191 | }
192 |
193 | .rce-container-citem.subitem .rce-citem {
194 | height: 40px;
195 | padding-left: 30px;
196 | }
197 |
198 | .rce-container-citem.subitem .rce-citem-body--top .rce-citem-body--top-title {
199 | font-size: 12px !important;
200 | }
201 |
202 | .rce-container-citem.subitem .rce-citem-body--top .rce-citem-body--top-time {
203 | font-size: 10px !important;
204 | }
205 |
206 | .rce-container-citem.subitem .rce-citem-body--bottom * {
207 | font-size: 12px !important;
208 | }
209 |
210 | .rce-container-citem.subitem .rce-citem-body--bottom-status span {
211 | width: 14px;
212 | height: 14px;
213 | font-size: 10px !important;
214 | color: white;
215 | font-weight: bold;
216 | text-align: center;
217 | align-items: center;
218 | justify-content: center;
219 | display: flex;
220 | border-radius: 100%;
221 | background: red;
222 | }
223 |
224 | .rce-container-citem.subitem .rce-citem-body--bottom {
225 | margin-top: unset;
226 | }
227 |
228 | .rce-citem-expand-button {
229 | background: transparent;
230 | border: none;
231 | cursor: pointer;
232 | padding: 2px 15px;
233 | color: teal;
234 | }
235 |
236 | .rce-citem-expand-button:hover {
237 | background-color: #eee;
238 | }
239 |
240 | .rce-sublist-container {
241 | position: relative;
242 | }
243 |
--------------------------------------------------------------------------------
/src/ChatItem/ChatItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { Key, useEffect, useState } from 'react'
2 | import './ChatItem.css'
3 |
4 | import Avatar from '../Avatar/Avatar'
5 |
6 | import { format } from 'timeago.js'
7 |
8 | import classNames from 'classnames'
9 |
10 | import { IChatItemProps } from '../type'
11 |
12 | import { HugeiconsIcon } from '@hugeicons/react';
13 | import {
14 | Video01Icon,
15 | VolumeOffIcon,
16 | VolumeHighIcon,
17 | ArrowDown01Icon,
18 | ArrowUp01Icon,
19 | // @ts-ignore
20 | } from '@hugeicons/core-free-icons';
21 |
22 | const ChatItem: React.FC = ({
23 | avatarFlexible = false,
24 | date = new Date(),
25 | unread = 0,
26 | statusColorType = 'badge',
27 | lazyLoadingImage = undefined,
28 | onAvatarError = () => void 0,
29 | ...props
30 | }) => {
31 | const [onHoverTool, setOnHoverTool] = useState(false)
32 | const [onDrag, setOnDrag] = useState(false)
33 |
34 | useEffect(() => {
35 | props.setDragStates?.(setOnDrag)
36 | }, [])
37 |
38 | const handleOnMouseEnter = () => {
39 | setOnHoverTool(true)
40 | }
41 |
42 | const handleOnMouseLeave = () => {
43 | setOnHoverTool(false)
44 | }
45 |
46 | const handleOnClick = (e: React.MouseEvent) => {
47 | e.preventDefault()
48 |
49 | if (onHoverTool === true) return
50 |
51 | props.onClick?.(e)
52 | }
53 |
54 | const onDragOver = (e: React.MouseEvent) => {
55 | e.preventDefault()
56 | if (props.onDragOver instanceof Function) props.onDragOver(e, props.id)
57 | }
58 |
59 | const onDragEnter = (e: React.MouseEvent) => {
60 | e.preventDefault()
61 | if (props.onDragEnter instanceof Function) props.onDragEnter(e, props.id)
62 | if (!onDrag) setOnDrag(true)
63 | }
64 |
65 | const onDragLeave = (e: React.MouseEvent) => {
66 | e.preventDefault()
67 | if (props.onDragLeave instanceof Function) props.onDragLeave(e, props.id)
68 | if (onDrag) setOnDrag(false)
69 | }
70 |
71 | const onDrop = (e: React.MouseEvent) => {
72 | e.preventDefault()
73 | if (props.onDrop instanceof Function) props.onDrop(e, props.id)
74 | if (onDrag) setOnDrag(false)
75 | }
76 |
77 | const onExpandItem = (e: React.MouseEvent, id: string | number) => {
78 | e.preventDefault();
79 | e.stopPropagation();
80 | if (props.onExpandItem instanceof Function) props.onExpandItem(id);
81 | }
82 |
83 | return (
84 | <>
85 |
91 |
92 | {!!props.onDragComponent && onDrag && props.onDragComponent(props.id)}
93 | {((onDrag && !props.onDragComponent) || !onDrag) && [
94 |
98 |
118 | {props.statusText}
119 |
120 | ) : (
121 | <>>
122 | )
123 | }
124 | onError={onAvatarError}
125 | lazyLoadingImage={lazyLoadingImage}
126 | type={classNames('circle', { 'flexible': avatarFlexible })}
127 | />
128 | {props.subList && props.subList.length > 0 && (
129 |
132 | )}
133 | ,
134 |
135 |
136 |
{props.title}
137 |
{date && (props.dateString || format(date))}
138 |
139 |
140 |
141 |
{props.subtitle}
142 |
143 | {props.showMute && (
144 |
145 | {props.muted === true && }
146 | {props.muted === false && }
147 |
148 | )}
149 | {props.showVideoCall && (
150 |
151 |
152 |
153 | )}
154 |
155 |
156 | {props.showMute && props.muted && (
157 |
158 |
159 |
160 | )}
161 |
162 |
{unread && unread > 0 ? {unread} : null}
163 | {props.customStatusComponents !== undefined ? props.customStatusComponents.map(Item =>
) : null}
164 |
165 |
,
166 | ]}
167 |
168 |
169 | >
170 | )
171 | }
172 |
173 | export default ChatItem
174 |
--------------------------------------------------------------------------------
/src/ChatItem/__tests__/ChatItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import ChatItem from '../ChatItem'
5 |
6 | describe('ChatItem component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/ChatItem/__tests__/__snapshots__/ChatItem.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ChatItem component should render without issues 1`] = `
4 |
8 |
11 |
24 |
27 |
30 |
33 |
36 | just now
37 |
38 |
39 |
57 |
58 |
59 |
60 | `;
61 |
--------------------------------------------------------------------------------
/src/ChatList/ChatList.css:
--------------------------------------------------------------------------------
1 | .rce-container-clist {
2 | display: block;
3 | overflow: auto;
4 | }
5 |
--------------------------------------------------------------------------------
/src/ChatList/ChatList.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch, Key, SetStateAction } from 'react'
2 | import classNames from 'classnames'
3 | import './ChatList.css'
4 |
5 | import ChatItem from '../ChatItem/ChatItem'
6 | import { IChatListProps, ChatListEvent } from '../type'
7 |
8 | let list: Dispatch>[] = []
9 |
10 | const ChatList: React.FC = props => {
11 | const onClick: ChatListEvent = (item, index, event) => {
12 | if (props.onClick instanceof Function) props.onClick(item, index, event)
13 | }
14 |
15 | const onContextMenu: ChatListEvent = (item, index, event) => {
16 | event.preventDefault()
17 | if (props.onContextMenu instanceof Function) props.onContextMenu(item, index, event)
18 | }
19 |
20 | const onAvatarError: ChatListEvent = (item, index, event) => {
21 | if (props.onAvatarError instanceof Function) props.onAvatarError(item, index, event)
22 | }
23 |
24 | const setDragStates = (state: Dispatch>) => {
25 | list.push(state)
26 | }
27 |
28 | const onDragLeaveMW = (e: React.MouseEvent, id: number | string) => {
29 | if (list.length > 0) list.forEach(item => item(false))
30 | props.onDragLeave?.(e, id)
31 | }
32 |
33 | return (
34 |
35 | {props.dataSource.map((x, i: number) => (
36 | <>
37 | ) => onAvatarError(x, i, e)}
42 | onContextMenu={(e: React.MouseEvent) => onContextMenu(x, i, e)}
43 | onClick={(e: React.MouseEvent) => onClick(x, i, e)}
44 | onClickMute={(e: React.MouseEvent) => props.onClickMute?.(x, i, e)}
45 | onClickVideoCall={(e: React.MouseEvent) => props.onClickVideoCall?.(x, i, e)}
46 | onDragOver={props?.onDragOver}
47 | onDragEnter={props?.onDragEnter}
48 | onDrop={props.onDrop}
49 | onDragLeave={onDragLeaveMW}
50 | onDragComponent={props.onDragComponent}
51 | setDragStates={setDragStates}
52 | />
53 | >
54 | ))}
55 |
56 | )
57 | }
58 |
59 | export default ChatList
60 |
--------------------------------------------------------------------------------
/src/ChatList/__tests__/ChatList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import ChatList from '../ChatList'
5 |
6 | describe('ChatList component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/ChatList/__tests__/__snapshots__/ChatList.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ChatList component should render without issues 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/Circle/Circle.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useCallback } from 'react'
2 | import { Circle } from 'progressbar.js'
3 | import { ICircleProps } from '../type'
4 |
5 | let wrapper: HTMLDivElement
6 | const ProgressCircle: React.FC = ({ animate, progressOptions, className }) => {
7 | const bar = useMemo(() => {
8 | wrapper = document.createElement('div')
9 | return new Circle(wrapper, progressOptions)
10 | }, [])
11 |
12 | const node = useCallback((node: any) => {
13 | if (node) {
14 | node.appendChild(wrapper)
15 | }
16 | }, [])
17 |
18 | useEffect(() => {
19 | bar.animate(animate)
20 | }, [animate, bar])
21 |
22 | return
23 | }
24 |
25 | export default ProgressCircle
26 |
--------------------------------------------------------------------------------
/src/Dropdown/Dropdown.css:
--------------------------------------------------------------------------------
1 | .rce-dropdown-container {
2 | position: relative;
3 | }
4 | .rce-dropdown {
5 | min-width: 100%;
6 | box-sizing: border-box;
7 | padding: 8px 15px;
8 | background: #fff;
9 | border-radius: 5px;
10 | display: none;
11 | flex-direction: row;
12 | align-items: center;
13 | justify-content: center;
14 | transform: scale(0);
15 | position: absolute;
16 | box-shadow: 0px 0px 5px 0px rgba(163, 163, 163, 1);
17 | transform-origin: left top;
18 | z-index: 99999;
19 | }
20 |
21 | .rce-dropdown-title {
22 | font-size: 14px;
23 | color: '#000';
24 | font-weight: 500;
25 | }
26 |
27 | .rce-dropdown.dropdown-show {
28 | animation: dropdown-scaling 0.2s ease forwards;
29 | display: flex;
30 | }
31 |
32 | @keyframes dropdown-scaling {
33 | 0% {
34 | opacity: 0;
35 | }
36 | 50% {
37 | opacity: 0.5;
38 | transform: scale(1.1);
39 | }
40 | 100% {
41 | opacity: 1;
42 | transform: scale(1);
43 | }
44 | }
45 |
46 | .rce-dropdown.dropdown-hide {
47 | animation: dropdown-reverse-scaling 0.2s ease forwards;
48 | display: flex;
49 | }
50 |
51 | @keyframes dropdown-reverse-scaling {
52 | 0% {
53 | opacity: 1;
54 | transform: scale(1);
55 | }
56 | 50% {
57 | opacity: 0.5;
58 | transform: scale(1.1);
59 | }
60 | 100% {
61 | opacity: 0;
62 | transform: scale(0);
63 | }
64 | }
65 |
66 | .rce-dropdown-open__nortwest {
67 | transform-origin: left top;
68 | left: 0;
69 | top: 100%;
70 | margin-top: 5px;
71 | }
72 |
73 | .rce-dropdown-open__norteast {
74 | transform-origin: right top;
75 | right: 0;
76 | top: 100%;
77 | margin-top: 5px;
78 | }
79 |
80 | .rce-dropdown-open__southwest {
81 | transform-origin: left bottom;
82 | left: 0;
83 | bottom: 100%;
84 | margin-bottom: 5px;
85 | }
86 |
87 | .rce-dropdown-open__southeast {
88 | transform-origin: right bottom;
89 | right: 0;
90 | bottom: 100%;
91 | margin-bottom: 5px;
92 | }
93 |
94 | .rce-dropdown ul {
95 | list-style: none;
96 | margin: 0;
97 | padding: 0;
98 | width: 100%;
99 | display: flex;
100 | flex-direction: column;
101 | align-items: center;
102 | justify-content: center;
103 | }
104 |
105 | .rce-dropdown ul li {
106 | white-space: nowrap;
107 | color: #767676;
108 | padding: 8px;
109 | cursor: pointer;
110 | font-size: 16px;
111 | width: 100%;
112 | border-bottom: 1px solid #e9e9e9;
113 | box-sizing: border-box;
114 | user-select: none;
115 | }
116 |
117 | .rce-dropdown ul li:last-child {
118 | border: none;
119 | }
120 |
121 | .rce-dropdown ul li:hover a {
122 | color: #3a6d8c;
123 | }
124 |
125 | .rce-dropdown.fade {
126 | opacity: 0;
127 | transform: scale(1);
128 | animation: dropdown-fade 0.5s ease forwards;
129 | }
130 |
131 | @keyframes dropdown-fade {
132 | 0% {
133 | opacity: 0;
134 | }
135 | 100% {
136 | opacity: 1;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Dropdown/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import './Dropdown.css'
3 | import classNames from 'classnames'
4 | import Button from '../Button/Button'
5 | import { IDropdownProps, IDropdownItemType } from '../type'
6 |
7 | const Dropdown: React.FC = ({ animationPosition = 'nortwest', animationType = 'default', ...props }) => {
8 | const [show, setShow] = useState(undefined)
9 |
10 | const onBlur = () => {
11 | if (show === true) setShow(false)
12 | }
13 |
14 | return (
15 |
16 | {
55 | )
56 | }
57 |
58 | export default Dropdown
59 |
--------------------------------------------------------------------------------
/src/Dropdown/__tests__/Dropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import Dropdown from '../Dropdown'
5 |
6 | describe('Dropdown component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/Dropdown/__tests__/__snapshots__/Dropdown.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Dropdown component should render without issues 1`] = `
4 |
26 | `;
27 |
--------------------------------------------------------------------------------
/src/FileMessage/FileMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-file {
2 | padding-bottom: 13px;
3 | }
4 |
5 | .rce-mbox-file > button {
6 | background: #e9e9e9;
7 | display: flex;
8 | border-radius: 5px;
9 | margin-top: -3px;
10 | margin-right: -6px;
11 | margin-left: -6px;
12 | align-items: center;
13 | min-height: 52px;
14 | max-width: 500px;
15 | padding: 5px 0;
16 | cursor: pointer;
17 | user-select: none;
18 | outline: none;
19 | border: none;
20 | }
21 |
22 | .rce-mbox-file > button > * {
23 | padding: 0px 10px;
24 | }
25 |
26 | .rce-mbox-file--icon {
27 | font-size: 30px;
28 | align-items: center;
29 | display: flex;
30 | flex-direction: column;
31 | }
32 |
33 | .rce-mbox-file--size {
34 | font-size: 10px;
35 | margin-top: 3px;
36 | max-width: 52px;
37 | white-space: nowrap;
38 | overflow: hidden;
39 | text-overflow: ellipsis;
40 | }
41 |
42 | .rce-mbox-file--text {
43 | font-size: 13.6px;
44 | white-space: nowrap;
45 | overflow: hidden;
46 | text-overflow: ellipsis;
47 | }
48 |
49 | .rce-mbox-file--buttons {
50 | font-size: 30px;
51 | align-items: center;
52 | display: flex;
53 | }
54 | .rce-mbox-file--buttons .rce-error-button {
55 | display: inherit;
56 | }
57 |
58 | .rce-mbox-file--loading {
59 | font-size: 15px;
60 | width: 40px;
61 | height: 40px;
62 | }
63 |
--------------------------------------------------------------------------------
/src/FileMessage/FileMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ProgressCircle from '../Circle/Circle'
3 | import { IFileMessageProps } from '../type'
4 | import './FileMessage.css'
5 |
6 | import { HugeiconsIcon } from '@hugeicons/react';
7 | // @ts-ignore
8 | import { File01Icon, CloudDownloadIcon, Alert02Icon } from '@hugeicons/core-free-icons';
9 |
10 | const FileMessage: React.FC = props => {
11 | var progressOptions = {
12 | strokeWidth: 5,
13 | color: '#333',
14 | trailColor: '#aaa',
15 | trailWidth: 5,
16 | step: (
17 | state: { color: string; width: string },
18 | circle: {
19 | path: { setAttribute: (arg0: string, arg1: string) => void }
20 | value: () => number
21 | setText: (arg0: string | number) => void
22 | }
23 | ) => {
24 | circle.path.setAttribute('trail', state.color)
25 | circle.path.setAttribute('trailwidth-width', state.width)
26 |
27 | var value = Math.round(circle.value() * 100)
28 | if (value === 0) circle.setText('')
29 | else circle.setText(value)
30 | },
31 | }
32 |
33 | const error = props?.data?.status && props?.data?.status.error === true
34 |
35 | const onClick = (e: React.MouseEvent) => {
36 | if (!props?.data?.status) return
37 |
38 | if (!props?.data?.status.download && props.onDownload instanceof Function) props.onDownload(e)
39 | else if (props?.data?.status.download && props.onOpen instanceof Function) props.onOpen(e)
40 | }
41 |
42 | return (
43 |
44 |
71 |
72 | )
73 | }
74 |
75 | export default FileMessage
76 |
--------------------------------------------------------------------------------
/src/FileMessage/__tests__/FileMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import FileMessage from '../FileMessage'
5 |
6 | describe('FileMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/FileMessage/__tests__/__snapshots__/FileMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`FileMessage component should render without issues 1`] = `
4 |
7 |
27 |
28 | `;
29 |
--------------------------------------------------------------------------------
/src/Input/Input.css:
--------------------------------------------------------------------------------
1 | .rce-container-input {
2 | display: flex;
3 | min-width: 100%;
4 | box-sizing: border-box;
5 | flex-direction: row;
6 | background: #fff;
7 | align-items: center;
8 | }
9 |
10 | .rce-input {
11 | flex: 1;
12 | height: 40px;
13 | padding: 0 5px;
14 | border: none;
15 | border-radius: 5px;
16 | color: #333;
17 | font-size: 14px;
18 | box-sizing: border-box;
19 | outline: none;
20 | }
21 |
22 | .rce-input-textarea {
23 | height: 37px;
24 | padding: 10px 5px;
25 | resize: none;
26 | }
27 |
28 | .rce-input-buttons {
29 | display: flex;
30 | flex-direction: row;
31 | margin: 5px;
32 | }
33 |
34 | .rce-input-buttons > * {
35 | display: flex;
36 | flex-direction: row;
37 | }
38 |
39 | .rce-input-buttons .rce-button:nth-child(even) {
40 | margin-left: 5px;
41 | margin-right: 5px;
42 | }
43 |
44 | .rce-input-buttons .rce-button:last-child {
45 | margin-right: 0;
46 | }
47 |
48 | .rce-left-padding {
49 | padding-left: 10px;
50 | padding-right: 0px !important;
51 | }
52 |
53 | .rce-right-padding {
54 | padding-right: 10px;
55 | padding-left: 0px !important;
56 | }
57 |
58 | .rce-input::placeholder {
59 | color: #afafaf;
60 | }
61 |
--------------------------------------------------------------------------------
/src/Input/Input.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import './Input.css'
3 | import classNames from 'classnames'
4 | import { IInputProps } from '../type'
5 |
6 | const Input: React.FC = ({
7 | type = 'text',
8 | multiline = false,
9 | minHeight = 25,
10 | maxHeight = 200,
11 | autoHeight = true,
12 | autofocus = false,
13 | ...props
14 | }) => {
15 | useEffect(() => {
16 | if (autofocus === true) props.referance?.current?.focus()
17 |
18 | if (props.clear instanceof Function) {
19 | props.clear(clear)
20 | }
21 | }, [])
22 |
23 | const onChangeEvent = (e: any) => {
24 | if (multiline === true) {
25 | if (autoHeight === true) {
26 | if (e.target.style.height !== minHeight + 'px') {
27 | e.target.style.height = minHeight + 'px'
28 | }
29 |
30 | let height
31 | if (e.target.scrollHeight <= maxHeight) height = e.target.scrollHeight + 'px'
32 | else height = maxHeight + 'px'
33 |
34 | if (e.target.style.height !== height) {
35 | e.target.style.height = height
36 | }
37 | }
38 | }
39 |
40 | if (props.maxlength && (e.target.value || '').length > props.maxlength) {
41 | if (props.onMaxLengthExceed instanceof Function) props.onMaxLengthExceed()
42 |
43 | if (props.referance) {
44 | props.referance.current.value = (e.target.value || '').substring(0, props.maxlength)
45 | }
46 | return
47 | }
48 |
49 | if (props.onChange instanceof Function) props.onChange(e)
50 | }
51 |
52 | const clear = () => {
53 | var _event = {
54 | FAKE_EVENT: true,
55 | target: props.referance?.current,
56 | }
57 |
58 | if (props.referance?.current?.value) {
59 | props.referance.current.value = ''
60 | }
61 |
62 | onChangeEvent(_event)
63 | }
64 |
65 | return (
66 |
67 | {props.leftButtons &&
{props.leftButtons}
}
68 | {multiline === false ? (
69 |
90 | ) : (
91 |
112 | )}
113 | {props.rightButtons &&
{props.rightButtons}
}
114 |
115 | )
116 | }
117 |
118 | export default Input
119 |
--------------------------------------------------------------------------------
/src/Input/__tests__/Input.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import Input from '../Input'
5 |
6 | describe('Input component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/Input/__tests__/__snapshots__/Input.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Input component should render without issues 1`] = `
4 |
7 |
15 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/LocationMessage/LocationMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-location {
2 | position: relative;
3 | width: 250px;
4 | height: 150px;
5 | overflow: hidden;
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | margin-top: -3px;
10 | margin-right: -6px;
11 | margin-left: -6px;
12 | border-radius: 5px;
13 | }
14 |
15 | .rce-mbox-location-img {
16 | width: 100%;
17 | }
18 |
19 | .rce-mbox-location-text {
20 | padding: 5px 0;
21 | width: 250px;
22 | margin-left: -6px;
23 | margin-right: -6px;
24 | }
25 |
--------------------------------------------------------------------------------
/src/LocationMessage/LocationMessage.tsx:
--------------------------------------------------------------------------------
1 | import './LocationMessage.css'
2 | import classNames from 'classnames'
3 | import { ILocationMessageProps } from '../type'
4 |
5 | const STATIC_URL =
6 | 'https://maps.googleapis.com/maps/api/staticmap?markers=color:MARKER_COLOR|LATITUDE,LONGITUDE&zoom=ZOOM&size=270x200&scale=2&key=KEY'
7 | const MAP_URL = 'https://www.google.com/maps/search/?api=1&query=LATITUDE,LONGITUDE&zoom=ZOOM'
8 |
9 | const LocationMessage: React.FC = ({ markerColor = 'red', target = '_blank', zoom = '14', ...props }) => {
10 | const buildURL = (url: string) => {
11 | return url
12 | .replace(/LATITUDE/g, props?.data.latitude)
13 | .replace(/LONGITUDE/g, props?.data.longitude)
14 | .replace('MARKER_COLOR', markerColor)
15 | .replace('ZOOM', zoom)
16 | .replace('KEY', props.apiKey)
17 | }
18 | const className = () => {
19 | var _className = classNames('rce-mbox-location', props.className)
20 |
21 | if (props.text) {
22 | _className = classNames(_className, 'rce-mbox-location-has-text')
23 | }
24 |
25 | return _className
26 | }
27 |
28 | return (
29 |
30 |
36 |
41 |
42 | {props.text &&
{props.text}
}
43 |
44 | )
45 | }
46 |
47 | export default LocationMessage
48 |
--------------------------------------------------------------------------------
/src/LocationMessage/__tests__/LocationMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import LocationMessage from '../LocationMessage'
5 |
6 | describe('LocationMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/LocationMessage/__tests__/__snapshots__/LocationMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`LocationMessage component should render without issues 1`] = `
4 |
19 | `;
20 |
--------------------------------------------------------------------------------
/src/MeetingItem/MeetingItem.css:
--------------------------------------------------------------------------------
1 | .rce-container-mtitem {
2 | flex-direction: column;
3 | display: block;
4 | overflow: hidden;
5 | min-width: 240px;
6 | }
7 |
8 | .rce-mtitem {
9 | position: relative;
10 | background: white;
11 | display: flex;
12 | flex-direction: column;
13 | user-select: none;
14 | max-width: 100%;
15 | overflow: hidden;
16 | min-width: 240px;
17 | border-bottom: 1px solid rgba(0, 0, 0, 0.05);
18 | }
19 |
20 | .rce-mtitem:hover {
21 | background: #fbfbfb;
22 | }
23 |
24 | .rce-mtitem-top {
25 | display: flex;
26 | flex-direction: row;
27 | position: relative;
28 | justify-content: space-between;
29 | }
30 |
31 | .rce-mtitem-subject {
32 | padding: 0 10px;
33 | margin-top: 5px;
34 | font-size: 15px;
35 | overflow: hidden;
36 | color: #333;
37 | max-height: 35px;
38 | text-overflow: ellipsis;
39 | }
40 |
41 | .rce-mtitem-body {
42 | display: flex;
43 | flex: 1;
44 | flex-direction: row;
45 | display: flex;
46 | justify-content: center;
47 | padding: 0 10px;
48 | overflow: hidden;
49 | align-items: center;
50 | }
51 |
52 | .rce-mtitem-body--avatars {
53 | display: flex;
54 | flex: 1;
55 | overflow: hidden;
56 | opacity: 0.7;
57 | }
58 |
59 | .rce-mtitem-body--functions {
60 | width: 70px;
61 | display: flex;
62 | align-items: center;
63 | justify-content: flex-end;
64 | }
65 |
66 | .rce-mtitem-footer {
67 | padding: 0 10px;
68 | display: flex;
69 | align-items: center;
70 | justify-content: space-between;
71 | margin-bottom: 5px;
72 | }
73 |
74 | .rce-mtitem-body--avatars .rce-avatar-container {
75 | margin-left: -10px;
76 | border: 2px solid #fff;
77 | }
78 |
79 | .rce-mtitem-body--avatars .rce-avatar-container:first-child {
80 | margin: 0;
81 | }
82 |
83 | .rce-mtitem-letter {
84 | color: #fff;
85 | background: #e48989;
86 | display: flex;
87 | align-items: center;
88 | justify-content: center;
89 | }
90 |
91 | .rce-mtitem-button {
92 | font-size: 25px;
93 | display: flex;
94 | align-items: center;
95 | justify-content: center;
96 | color: #fff;
97 | background: #5ba7c5;
98 | border-radius: 100%;
99 | padding: 3px;
100 | transition: 300ms;
101 | }
102 |
103 | .rce-mtitem-closable {
104 | font-size: 25px;
105 | color: #fff;
106 | background: #ff4a4f;
107 | display: none;
108 | justify-content: center;
109 | align-items: center;
110 | border-radius: 100%;
111 | padding: 3px;
112 | margin-right: 7px;
113 | }
114 |
115 | .rce-mtitem:hover .rce-mtitem-closable {
116 | display: flex;
117 | cursor: pointer;
118 | }
119 |
120 | .rce-mtitem-share {
121 | font-size: 30px;
122 | display: flex;
123 | align-items: center;
124 | justify-content: center;
125 | color: #5ba7c5;
126 | margin: -10px 0;
127 | transition: 300ms;
128 | position: relative;
129 | left: -10px;
130 | }
131 |
132 | .rce-mtitem-button:hover,
133 | .rce-mtitem-share:hover {
134 | opacity: 0.8;
135 | cursor: pointer;
136 | }
137 |
138 | .rce-mtitem-date {
139 | color: #9f9f9f;
140 | font-size: 13px;
141 | }
142 |
--------------------------------------------------------------------------------
/src/MeetingItem/MeetingItem.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import './MeetingItem.css'
3 |
4 | import Avatar from '../Avatar/Avatar'
5 |
6 | import { format } from 'timeago.js'
7 |
8 | import classNames from 'classnames'
9 | import { IMeetingItemProps } from '../type'
10 |
11 | import { HugeiconsIcon } from '@hugeicons/react';
12 | // @ts-ignore
13 | import { Video01Icon, Link05Icon, Call02Icon } from '@hugeicons/core-free-icons';
14 |
15 | const MeetingItem: FC = ({
16 | subjectLimit = 60,
17 | onClick = () => void 0,
18 | avatarFlexible = false,
19 | date = new Date(),
20 | lazyLoadingImage = undefined,
21 | avatarLimit = 5,
22 | avatars = [],
23 | audioMuted = true,
24 | onAvatarError = () => void 0,
25 | onMeetingClick = () => void 0,
26 | onShareClick = () => void 0,
27 | ...props
28 | }) => {
29 | const statusColorType = props.statusColorType
30 | const AVATAR_LIMIT = avatarLimit
31 |
32 | const dateText = date && (props.dateString || format(date))
33 |
34 | const subject =
35 | props.subject && subjectLimit && props.subject.substring(0, subjectLimit) + (props.subject.length > subjectLimit ? '...' : '')
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
{subject}
44 |
45 |
46 |
47 |
48 |
49 |
50 | {
51 | // props.avatars?.slice(0, AVATAR_LIMIT).map((x, i) => x instanceof Avatar ? x : (
52 | avatars?.slice(0, AVATAR_LIMIT).map((x, i) => (
53 |
74 | {x.statusText}
75 |
76 | ) : (
77 | <>>
78 | )
79 | }
80 | onError={onAvatarError}
81 | lazyLoadingImage={lazyLoadingImage}
82 | type={classNames('circle', { 'flexible': avatarFlexible })}
83 | />
84 | ))
85 | }
86 |
87 | {avatars && AVATAR_LIMIT && avatars.length > AVATAR_LIMIT && (
88 |
89 | {'+' + (avatars.length - AVATAR_LIMIT)}
90 |
91 | )}
92 |
93 |
94 | {props.closable && (
95 |
96 |
97 |
98 | )}
99 |
100 |
101 |
102 |
103 |
104 |
105 | {dateText}
106 |
107 |
108 |
109 | )
110 | }
111 |
112 | export default MeetingItem
113 |
--------------------------------------------------------------------------------
/src/MeetingItem/__tests__/MeetingItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import MeetingItem from '../MeetingItem'
5 |
6 | describe('MeetingItem component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/MeetingItem/__tests__/__snapshots__/MeetingItem.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MeetingItem component should render without issues 1`] = `
4 |
8 |
14 |
17 |
30 |
47 |
50 |
53 | just now
54 |
55 |
56 |
57 |
58 | `;
59 |
--------------------------------------------------------------------------------
/src/MeetingLink/MeetingLink.css:
--------------------------------------------------------------------------------
1 | .rce-mtlink {
2 | padding-bottom: 15px;
3 | min-width: 400px;
4 | }
5 |
6 | .rce-mtlink-content {
7 | background-color: #e2dfec;
8 | border-radius: 4px;
9 | padding: 10px;
10 | display: flex;
11 | align-items: center;
12 | justify-content: space-between;
13 | overflow: hidden;
14 | }
15 |
16 | .rce-mtlink-item {
17 | color: #6c687c;
18 | display: flex;
19 | align-items: center;
20 | user-select: none;
21 | max-width: 100%;
22 | overflow: hidden;
23 | min-width: 240px;
24 | justify-content: flex-start;
25 | }
26 |
27 | .rce-mtlink-title {
28 | color: #07030a;
29 | padding-left: 7px;
30 | font-size: 15px;
31 | }
32 |
33 | .rce-mtlink-btn {
34 | background: #ededed;
35 | border-radius: 4px;
36 | display: flex;
37 | margin-left: 5px;
38 | }
39 |
40 | .rce-mtlink-btn-content {
41 | padding: 4px;
42 | cursor: pointer;
43 | border-right: 1px solid;
44 | }
45 | .rce-mtlink-btn-content:last-child {
46 | border-right: none;
47 | }
48 |
--------------------------------------------------------------------------------
/src/MeetingLink/MeetingLink.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import './MeetingLink.css'
3 |
4 | import { IMeetingLinkMessageProps, MeetingLinkActionButtons } from '../type'
5 |
6 | const MeetingLink: FC = props => {
7 | return (
8 |
9 |
10 |
13 |
14 | {props?.actionButtons?.map((Item: MeetingLinkActionButtons) => {
15 | return (
16 |
Item.onClickButton(props?.meetingID ?? '')}>
17 |
18 |
19 | )
20 | })}
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | export default MeetingLink
28 |
--------------------------------------------------------------------------------
/src/MeetingLink/__tests__/MeetingLink.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import MeetingLink from '../MeetingLink'
5 |
6 | describe('MeetingLink component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/MeetingLink/__tests__/__snapshots__/MeetingLink.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MeetingLink component should render without issues 1`] = `
4 |
27 | `;
28 |
--------------------------------------------------------------------------------
/src/MeetingList/MeetingList.css:
--------------------------------------------------------------------------------
1 | .rce-container-mtlist {
2 | display: block;
3 | overflow: auto;
4 | }
5 |
--------------------------------------------------------------------------------
/src/MeetingList/MeetingList.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, Key } from 'react'
2 | import './MeetingList.css'
3 |
4 | import MeetingItem from '../MeetingItem/MeetingItem'
5 | import classNames from 'classnames'
6 | import { IMeetingListProps, MeetingListEvent } from '../type'
7 |
8 | const MeetingList: FC = props => {
9 | const onClick: MeetingListEvent = (item, index, event) => {
10 | if (props.onClick instanceof Function) props.onClick(item, index, event)
11 | }
12 |
13 | const onContextMenu: MeetingListEvent = (item, index, event) => {
14 | event.preventDefault()
15 | if (props.onContextMenu instanceof Function) props.onContextMenu(item, index, event)
16 | }
17 |
18 | const onAvatarError: MeetingListEvent = (item, index, event) => {
19 | if (props.onAvatarError instanceof Function) props.onAvatarError(item, index, event)
20 | }
21 |
22 | const onMeetingClick: MeetingListEvent = (item, index, event) => {
23 | if (props.onMeetingClick instanceof Function) props.onMeetingClick(item, index, event)
24 | }
25 |
26 | const onShareClick: MeetingListEvent = (item, index, event) => {
27 | if (props.onShareClick instanceof Function) props.onShareClick(item, index, event)
28 | }
29 |
30 | const onCloseClick: MeetingListEvent = (item, index, event) => {
31 | if (props.onCloseClick instanceof Function) props.onCloseClick(item, index, event)
32 | }
33 |
34 | return (
35 |
36 | {props.dataSource?.map((x, i: number) => (
37 | ) => onAvatarError(x, i, e)}
42 | onContextMenu={(e: React.MouseEvent) => onContextMenu(x, i, e)}
43 | onClick={(e: React.MouseEvent) => onClick(x, i, e)}
44 | onMeetingClick={(e: React.MouseEvent) => onMeetingClick(x, i, e)}
45 | onShareClick={(e: React.MouseEvent) => onShareClick(x, i, e)}
46 | onCloseClick={(e: React.MouseEvent) => onCloseClick(x, i, e)}
47 | />
48 | ))}
49 |
50 | )
51 | }
52 |
53 | export default MeetingList
54 |
--------------------------------------------------------------------------------
/src/MeetingList/__tests__/MeetList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import MeetingList from '../MeetingList'
5 |
6 | describe('MeetingList component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/MeetingList/__tests__/__snapshots__/MeetList.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MeetingList component should render without issues 1`] = `
4 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/MeetingMessage/MeetingMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-mtmg {
2 | display: flex;
3 | justify-content: center;
4 | align-content: center;
5 | padding-bottom: 13px;
6 | min-width: 425px;
7 | max-width: 425px;
8 | }
9 |
10 | .rce-mtmg {
11 | width: 100%;
12 | position: relative;
13 | background: #f5f5f5;
14 | display: flex;
15 | flex-direction: column;
16 | margin: 5px 0px;
17 | float: left;
18 | border-radius: 2px;
19 | }
20 |
21 | .rce-mtmg-title {
22 | text-overflow: ellipsis;
23 | white-space: nowrap;
24 | overflow: hidden;
25 | width: 300px;
26 | }
27 |
28 | .rce-mtmg-subject {
29 | text-align: start;
30 | display: inline-block;
31 | font-size: 15px;
32 | padding: 5px 9px;
33 | }
34 |
35 | .rce-mtmg-toogleItem {
36 | width: 100%;
37 | height: 100%;
38 | }
39 |
40 | .rce-mtmg-body {
41 | height: 50px;
42 | background: #6264a7;
43 | color: white;
44 | font-size: 15px;
45 | display: flex;
46 | justify-content: space-between;
47 | }
48 |
49 | .rce-mtmg-body:hover {
50 | opacity: 0.9;
51 | }
52 |
53 | .rce-mtmg-item {
54 | display: flex;
55 | align-items: center;
56 | justify-content: flex-start;
57 | padding: 0px 10px;
58 | }
59 |
60 | .rce-mtmg-item > svg {
61 | width: 23px;
62 | height: 23px;
63 | }
64 |
65 | .rce-mtmg-content {
66 | display: flex;
67 | flex-direction: column;
68 | padding: 0 10px;
69 | }
70 |
71 | .rce-mtmg-date {
72 | color: #cecece;
73 | font-size: 13px;
74 | }
75 |
76 | .rce-mtmg-body-bottom {
77 | display: flex;
78 | flex-direction: row;
79 | padding: 9px;
80 | color: #6264a7;
81 | cursor: pointer;
82 | font-size: 13px;
83 | }
84 |
85 | .rce-mtmg-bottom--tptitle {
86 | display: flex;
87 | justify-content: center;
88 | align-items: center;
89 | }
90 |
91 | .rce-mtmg-bottom--tptitle > svg,
92 | .rce-mtmg-body-bottom--bttitle > svg {
93 | color: #6264a7a1;
94 | }
95 |
96 | .rce-mtmg-toogleContent {
97 | display: none;
98 | height: auto;
99 | min-height: 60px;
100 | flex-direction: column;
101 | }
102 |
103 | .rce-mtmg-toogleContent--click {
104 | display: flex;
105 | }
106 |
107 | .rce-mtmg-right-icon {
108 | right: 10px;
109 | cursor: pointer;
110 | height: 100%;
111 | background: transparent !important;
112 | }
113 |
114 | .rce-mtmg-body .rce-dropdown-container {
115 | height: 100%;
116 | }
117 |
118 | .rce-mtmg-right-icon > svg {
119 | width: 23px;
120 | height: 23px;
121 | }
122 |
123 | .rce-mitem {
124 | display: flex;
125 | padding: 10px 8px;
126 | }
127 |
128 | .rce-mitem:hover,
129 | .rce-mitem-event:hover {
130 | background: #ececec;
131 | }
132 |
133 | .rce-mitem-event {
134 | user-select: none;
135 | }
136 |
137 | .rce-mitem-body {
138 | display: flex;
139 | justify-content: center;
140 | align-items: flex-start;
141 | flex-direction: column;
142 | }
143 |
144 | .rce-mitem-body-content {
145 | display: flex;
146 | align-items: flex-start;
147 | justify-content: flex-start;
148 | }
149 |
150 | .rce-mitem-body--top {
151 | display: flex;
152 | align-items: center;
153 | margin-bottom: 2px;
154 | user-select: none;
155 | }
156 |
157 | .rce-mitem-body--top-title {
158 | font-size: 15px;
159 | color: #6264a7;
160 | padding: 0px 15px 0 0;
161 | text-transform: capitalize;
162 | font-weight: 600;
163 | }
164 |
165 | .rce-mitem-body--top-title:hover {
166 | cursor: pointer;
167 | text-decoration: underline;
168 | }
169 |
170 | .rce-mitem-body--bottom-title {
171 | color: #252525;
172 | font-size: 13px;
173 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue',
174 | sans-serif;
175 | }
176 |
177 | .rce-mitem-body--top-time {
178 | font-size: 12px;
179 | color: rgba(0, 0, 0, 0.4);
180 | }
181 |
182 | .rce-mitem-bottom-body {
183 | padding: 10px 8px;
184 | display: flex;
185 | align-items: flex-start;
186 | justify-content: flex-start;
187 | }
188 |
189 | .rce-mitem-body.avatar {
190 | padding: 8px 10px 0 0;
191 | }
192 |
193 | .rce-mitem.avatar {
194 | padding: 8px 5px 0 0;
195 | }
196 |
197 | .rce-mitem.no-avatar {
198 | padding: 8px 10px 0 0;
199 | }
200 |
201 | .rce-mitem.no-avatar > svg {
202 | width: 19px;
203 | height: 19px;
204 | }
205 |
206 | .rce-mitem.avatar img {
207 | width: 22px;
208 | height: 22px;
209 | border: none !important;
210 | background: #ccc;
211 | border-radius: 100%;
212 | }
213 |
214 | .rce-mitem-body.avatar > svg {
215 | width: 19px;
216 | height: 19px;
217 | }
218 |
219 | .rce-mitem-bottom-body-top {
220 | display: flex;
221 | flex-direction: column;
222 | }
223 |
224 | .rce-mitem-bottom-body-top-title > svg {
225 | padding: 0 7px 0 0;
226 | }
227 |
228 | .rce-mitem-avatar-content {
229 | position: absolute;
230 | right: 10px;
231 | display: flex;
232 | }
233 |
234 | .rce-mitem-avatar {
235 | padding: 0 3px 0 0;
236 | display: flex;
237 | }
238 |
239 | .rce-mitem-tooltip {
240 | display: inline;
241 | position: relative;
242 | }
243 |
244 | .rce-mitem-tooltip-text {
245 | margin: 5px;
246 | }
247 |
248 | .rce-mitem-tooltip-text:after {
249 | content: '';
250 | left: 15%;
251 | top: 29px;
252 | position: absolute;
253 | border-left: 8px solid transparent;
254 | border-right: 8px solid transparent;
255 | border-bottom: 11px solid #444;
256 | opacity: 0;
257 | transition: opacity 0.8s linear 0.2s;
258 | }
259 |
260 | .rce-mitem-tooltip[tooltip]:after {
261 | display: flex;
262 | justify-content: center;
263 | background: #444;
264 | border-radius: 8px;
265 | color: #fff;
266 | content: attr(tooltip);
267 | font-size: 14px;
268 | padding: 5px;
269 | position: absolute;
270 | opacity: 0;
271 | transition: opacity 0.8s linear 0.2s;
272 | min-width: 415px;
273 | max-width: 415px;
274 | top: 40px;
275 | right: -13px;
276 | z-index: 1;
277 | }
278 |
279 | .rce-mitem-tooltip-text:hover:after {
280 | opacity: 1;
281 | transition: opacity 0.8s linear;
282 | }
283 |
284 | .rce-mitem-tooltip[tooltip]:hover:after {
285 | opacity: 1;
286 | transition: opacity 0.8s linear 0.1s;
287 | }
288 |
289 | .rce-mitem-tooltip[tooltip]:hover .rce-mitem-tooltip-text:after {
290 | opacity: 1;
291 | }
292 |
293 | .rce-mitem-length {
294 | color: #fff;
295 | font-size: 14px;
296 | background: #e48989;
297 | display: flex;
298 | align-items: center;
299 | text-align: center;
300 | width: 25px;
301 | height: 25px;
302 | display: flex;
303 | border-radius: 50%;
304 | }
305 |
306 | .rce-mitem-avatar img {
307 | width: 50px;
308 | height: 50px;
309 | border: none !important;
310 | background: #ccc;
311 | border-radius: 100%;
312 | }
313 |
314 | .rce-mtmg-call-record {
315 | width: 350px;
316 | height: 85%;
317 | background: #eaeaea;
318 | margin-top: 11px;
319 | }
320 |
321 | .rce-mtmg-call-body {
322 | display: flex;
323 | align-items: flex-start;
324 | flex-direction: row;
325 | position: relative;
326 | }
327 |
328 | .rce-mtmg-call-avatars {
329 | width: 140px !important;
330 | height: 100px !important;
331 | position: relative;
332 | }
333 |
334 | .rce-mtmg-call-avatars img {
335 | width: 100% !important;
336 | height: 100% !important;
337 | background: #ccc;
338 | cursor: pointer;
339 | }
340 |
341 | .rce-mtmg-call-body-title {
342 | display: flex;
343 | position: relative;
344 | flex-direction: column;
345 | top: 30px;
346 | left: 15px;
347 | }
348 |
349 | .rce-mtmg-call-body-title > span {
350 | text-overflow: ellipsis;
351 | white-space: nowrap;
352 | overflow: hidden;
353 | width: 185px;
354 | }
355 |
356 | .rce-mtmg-call-body-bottom {
357 | color: #505050;
358 | text-overflow: ellipsis;
359 | white-space: nowrap;
360 | overflow: hidden;
361 | width: 185px;
362 | font-size: 12px;
363 | }
364 |
365 | .rce-mtmg-record-time {
366 | position: absolute;
367 | bottom: 5px;
368 | right: 5px;
369 | font-size: 13px;
370 | background: #000000cf;
371 | color: white;
372 | padding: 4px;
373 | border-radius: 5px;
374 | }
375 |
--------------------------------------------------------------------------------
/src/MeetingMessage/MeetingMessage.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useState } from 'react'
2 | import './MeetingMessage.css'
3 |
4 | import { format } from 'timeago.js'
5 |
6 | import Avatar from '../Avatar/Avatar'
7 | import Dropdown from '../Dropdown/Dropdown'
8 |
9 | import classNames from 'classnames'
10 | import { IMeetingMessageProps, MeetingMessageEvent } from '../type'
11 |
12 | import { HugeiconsIcon } from '@hugeicons/react';
13 | // @ts-ignore
14 | import { Calendar04Icon, ArrowDown01Icon, ArrowRight01Icon, Video01Icon, MessageMultiple01Icon, MoreHorizontalIcon } from '@hugeicons/core-free-icons';
15 |
16 | const MeetingMessage: FC = ({
17 | date,
18 | dateString,
19 | title,
20 | subject,
21 | collapseTitle,
22 | moreItems,
23 | participants,
24 | dataSource,
25 |
26 | onClick,
27 | onMeetingTitleClick,
28 | onMeetingVideoLinkClick,
29 | onMeetingMoreSelect,
30 | ...props
31 | }) => {
32 | const [toogle, setToogle] = useState(false)
33 |
34 | const PARTICIPANT_LIMIT = props.participantsLimit
35 | const dateText = dateString ? dateString : date && format(date)
36 |
37 | const _onMeetingLinkClick: MeetingMessageEvent = (item, index, event) => {
38 | if (onMeetingTitleClick instanceof Function) onMeetingTitleClick(item, index, event)
39 | }
40 |
41 | const _onMeetingVideoLinkClick: MeetingMessageEvent = (item, index, event) => {
42 | if (onMeetingVideoLinkClick instanceof Function) onMeetingVideoLinkClick(item, index, event)
43 | }
44 |
45 | const toggleClick = () => {
46 | setToogle(!toogle)
47 | }
48 |
49 | return (
50 |
51 |
52 |
{subject || 'Unknown Meeting'}
53 |
54 |
55 |
56 |
57 | {title}
58 | {dateText}
59 |
60 |
61 |
62 | {onMeetingMoreSelect && moreItems && moreItems.length > 0 && (
63 |
64 | ,
71 | size: 24,
72 | },
73 | }}
74 | items={moreItems}
75 | onSelect={onMeetingMoreSelect}
76 | />
77 |
78 | )}
79 |
80 |
81 | {toogle === true ? (
82 |
83 |
84 | {collapseTitle}
85 |
86 | ) : (
87 |
88 |
89 |
90 | {participants
91 | ?.slice(0, PARTICIPANT_LIMIT)
92 | .map(x => x.title || 'Unknow')
93 | .join(', ')}
94 | {participants &&
95 | PARTICIPANT_LIMIT &&
96 | participants.length > PARTICIPANT_LIMIT &&
97 | `, +${participants.length - PARTICIPANT_LIMIT}`}
98 |
99 |
100 | )}
101 |
102 |
103 | {dataSource &&
104 | dataSource.map((x, i) => {
105 | return (
106 |
107 | {!x.event && (
108 |
109 |
110 | {x.avatar ?
:
}
111 |
112 |
113 |
114 |
) => _onMeetingLinkClick(x, i, e)}
117 | >
118 | {x.title}
119 |
120 |
121 | {x.dateString ? x.dateString : x.date && x.date && format(x.date)}
122 |
123 |
124 |
127 |
128 |
129 | )}
130 | {x.event && (
131 |
132 |
133 |
134 |
135 |
136 |
137 | {x.event.title}
138 |
{x.dateString ? x.dateString : x.date && format(x.date)}
139 |
140 | {
141 |
142 | {x.event.avatars &&
143 | // x.event.avatars.slice(0, x.event.avatarsLimit).map((x, i) => x instanceof Avatar ? x : (
144 | x.event.avatars.slice(0, x.event.avatarsLimit).map((x, i) =>
)}
145 | {x.event.avatars && x.event.avatarsLimit && x.event.avatars.length > x.event.avatarsLimit && (
146 |
avatar.title)
151 | .join(',')
152 | .toString()}
153 | >
154 |
155 | {'+' + (x.event.avatars.length - x.event.avatarsLimit)}
156 |
157 |
158 | )}
159 |
160 | }
161 |
162 | {x.record && (
163 |
164 |
165 |
) => _onMeetingVideoLinkClick(x, i, e)}
167 | className='rce-mtmg-call-avatars'
168 | >
169 |
170 |
{x.record.time}
171 |
172 |
173 |
{x.record.title}
174 |
{x.record.savedBy}
175 |
176 |
177 |
178 | )}
179 |
180 |
181 |
182 | )}
183 |
184 | )
185 | })}
186 |
187 |
188 |
189 | )
190 | }
191 |
192 | export default MeetingMessage
193 |
--------------------------------------------------------------------------------
/src/MeetingMessage/__tests__/MeetingMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import MeetingMessage from '../MeetingMessage'
5 |
6 | describe('MeetingMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/MeetingMessage/__tests__/__snapshots__/MeetingMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MeetingMessage component should render without issues 1`] = `
4 |
7 |
10 |
13 | Unknown Meeting
14 |
15 |
19 |
22 |
23 |
26 |
29 |
32 | just now
33 |
34 |
35 |
36 |
37 |
48 |
51 |
52 |
53 | `;
54 |
--------------------------------------------------------------------------------
/src/MessageBox/MessageBox.css:
--------------------------------------------------------------------------------
1 | .rce-container-mbox {
2 | flex-direction: column;
3 | display: block;
4 | overflow-x: hidden;
5 | min-width: 300px;
6 | }
7 |
8 | .rce-mbox-forward {
9 | width: 30px;
10 | height: 30px;
11 | border-radius: 20px;
12 | background: #fff;
13 | position: absolute;
14 | /*display: none;*/
15 | flex-direction: row;
16 | align-self: center;
17 | align-items: center;
18 | justify-content: center;
19 | box-shadow: 0 0 5px 0 rgba(164, 164, 164, 1);
20 | cursor: pointer;
21 | transition: all 0.3s ease;
22 | top: 0;
23 | bottom: 0;
24 | margin: auto;
25 | }
26 |
27 | .rce-mbox-forward-left {
28 | display: flex;
29 | opacity: 0;
30 | visibility: hidden;
31 | left: -50px;
32 | }
33 |
34 | .rce-mbox-forward-right {
35 | display: flex;
36 | opacity: 0;
37 | visibility: hidden;
38 | right: -50px;
39 | }
40 |
41 | .rce-mbox-reply-btn-left {
42 | display: flex;
43 | opacity: 0;
44 | visibility: hidden;
45 | left: -85px;
46 | }
47 |
48 | .rce-mbox-reply-btn-right {
49 | display: flex;
50 | opacity: 0;
51 | visibility: hidden;
52 | right: -85px;
53 | }
54 |
55 | .rce-container-mbox:hover .rce-mbox-forward-left {
56 | opacity: 1;
57 | visibility: visible;
58 | }
59 |
60 | .rce-container-mbox:hover .rce-mbox-forward-right {
61 | opacity: 1;
62 | visibility: visible;
63 | }
64 |
65 | .rce-mbox-remove {
66 | width: 30px;
67 | height: 30px;
68 | border-radius: 20px;
69 | background: #fff;
70 | position: absolute;
71 | /*display: none;*/
72 | flex-direction: row;
73 | align-self: center;
74 | align-items: center;
75 | justify-content: center;
76 | box-shadow: 0 0 5px 0 rgba(164, 164, 164, 1);
77 | cursor: pointer;
78 | transition: all 0.3s ease;
79 | top: 0;
80 | bottom: 0;
81 | margin: auto;
82 | }
83 |
84 | .rce-mbox-remove-left {
85 | display: flex;
86 | opacity: 0;
87 | visibility: hidden;
88 | left: -120px;
89 | }
90 |
91 | .rce-mbox-remove-right {
92 | display: flex;
93 | opacity: 0;
94 | visibility: hidden;
95 | right: -120px;
96 | }
97 |
98 | .rce-container-mbox:hover .rce-mbox-remove-left {
99 | opacity: 1;
100 | visibility: visible;
101 | }
102 |
103 | .rce-container-mbox:hover .rce-mbox-remove-right {
104 | opacity: 1;
105 | visibility: visible;
106 | }
107 |
108 | .rce-container-mbox:hover .rce-mbox-reply-btn-left {
109 | opacity: 1;
110 | visibility: visible;
111 | }
112 |
113 | .rce-container-mbox:hover .rce-mbox-reply-btn-right {
114 | opacity: 1;
115 | visibility: visible;
116 | }
117 |
118 | .rce-mbox {
119 | position: relative;
120 | background: white;
121 | border-radius: 5px;
122 | box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.2);
123 | border-top-left-radius: 0px;
124 | margin-left: 20px;
125 | margin-right: 5px;
126 | margin-top: 3px;
127 | flex-direction: column;
128 | margin-bottom: 3px;
129 | padding: 6px 9px 8px 9px;
130 | float: left;
131 | min-width: 140px;
132 | }
133 |
134 | .rce-mbox.message-focus {
135 | animation-iteration-count: 2;
136 | -webkit-animation-iteration-count: 2;
137 | -webkit-animation-duration: 1s;
138 | animation-name: message-box-default-focus;
139 | animation-duration: 1s;
140 | }
141 |
142 | @-webkit-keyframes message-box-default-focus {
143 | from {
144 | background-color: #fff;
145 | }
146 | to {
147 | background-color: #dfdfdf;
148 | }
149 | }
150 |
151 | .rce-mbox-body {
152 | margin: 0;
153 | padding: 0;
154 | position: relative;
155 | }
156 |
157 | .rce-mbox.rce-mbox-right {
158 | float: right;
159 | margin-left: 5px;
160 | margin-right: 20px;
161 | border-top-right-radius: 0px;
162 | border-top-left-radius: 5px;
163 | }
164 |
165 | .rce-mbox.rce-mbox-right.message-focus {
166 | animation-iteration-count: 2;
167 | -webkit-animation-iteration-count: 2;
168 | -webkit-animation-duration: 1s;
169 | animation-name: message-box-right-focus;
170 | animation-duration: 1s;
171 | }
172 |
173 | @-webkit-keyframes message-box-right-focus {
174 | from {
175 | background-color: #d4f1fb;
176 | }
177 | to {
178 | background-color: #b8dae6;
179 | }
180 | }
181 |
182 | .rce-mbox-text {
183 | font-size: 13.6px;
184 | word-break: break-word;
185 | }
186 |
187 | .rce-mbox-text-retracted {
188 | font-style: italic;
189 | user-select: none;
190 | display: flex;
191 | align-items: center;
192 | }
193 |
194 | .rce-mbox-text.rce-mbox-text-retracted svg {
195 | margin-right: 3px;
196 | }
197 |
198 | .rce-mbox-text-retracted.left {
199 | color: #555555b3 !important;
200 | }
201 |
202 | .rce-mbox-text-retracted.right {
203 | color: #efefefb3 !important;
204 | }
205 |
206 | .rce-mbox-text:after {
207 | content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
208 | }
209 |
210 | .rce-mbox-time {
211 | display: flex;
212 | justify-content: flex-end;
213 | align-items: center;
214 | text-align: right;
215 | color: rgba(0, 0, 0, 0.45);
216 | font-size: 12px;
217 | right: -4px;
218 | bottom: -5px;
219 | user-select: none;
220 | }
221 |
222 | .rce-mbox-time.non-copiable:before {
223 | content: attr(data-text);
224 | }
225 |
226 | .rce-mbox-time-block {
227 | display: flex;
228 | align-items: center;
229 | justify-content: flex-end;
230 | right: 0;
231 | bottom: 0;
232 | left: 0;
233 | margin-right: -6px;
234 | margin-left: -6px;
235 | padding-top: 5px;
236 | padding-right: 3px;
237 | padding-bottom: 2px;
238 | background: linear-gradient(to top, rgba(0, 0, 0, 0.33), transparent);
239 | border-bottom-left-radius: 5px;
240 | border-bottom-right-radius: 5px;
241 | color: #fff;
242 | }
243 |
244 | .rce-mbox--clear-padding {
245 | padding-bottom: 3px;
246 | }
247 |
248 | .rce-mbox.rce-mbox--clear-notch {
249 | border-radius: 5px 5px 5px 5px !important;
250 | }
251 |
252 | .rce-mbox-right-notch {
253 | position: absolute;
254 | right: -14px;
255 | top: 0px;
256 | width: 15px;
257 | height: 15px;
258 | fill: white;
259 | filter: drop-shadow(2px 0px 1px rgba(0, 0, 0, 0.2));
260 | }
261 |
262 | .rce-mbox-right-notch.message-focus {
263 | animation-iteration-count: 2;
264 | -webkit-animation-iteration-count: 2;
265 | -webkit-animation-duration: 1s;
266 | animation-name: message-right-notch-focus;
267 | animation-duration: 1s;
268 | }
269 |
270 | @-webkit-keyframes message-right-notch-focus {
271 | from {
272 | fill: #d4f1fb;
273 | }
274 | to {
275 | fill: #b8dae6;
276 | }
277 | }
278 |
279 | .rce-mbox-left-notch {
280 | position: absolute;
281 | left: -14px;
282 | top: 0px;
283 | width: 15px;
284 | height: 15px;
285 | fill: white;
286 | }
287 |
288 | .rce-mbox-left-notch.message-focus {
289 | animation-iteration-count: 2;
290 | -webkit-animation-iteration-count: 2;
291 | -webkit-animation-duration: 1s;
292 | animation-name: message-left-notch-focus;
293 | animation-duration: 1s;
294 | }
295 |
296 | @-webkit-keyframes message-left-notch-focus {
297 | from {
298 | fill: #fff;
299 | }
300 | to {
301 | fill: #dfdfdf;
302 | }
303 | }
304 |
305 | .rce-mbox-title {
306 | margin: 0;
307 | margin-bottom: 8px;
308 | font-weight: 500;
309 | font-size: 13px;
310 | color: #4f81a1;
311 | user-select: none;
312 | cursor: pointer;
313 | display: flex;
314 | align-items: center;
315 | }
316 |
317 | .rce-mbox-title:hover {
318 | text-decoration: underline;
319 | }
320 |
321 | .rce-mbox-title--clear {
322 | margin-bottom: 5px;
323 | }
324 |
325 | .rce-mbox-status {
326 | margin-left: 3px;
327 | font-size: 15px;
328 | height: 15px;
329 | }
330 |
331 | .rce-mbox-title > .rce-avatar-container {
332 | margin-right: 5px;
333 | }
334 |
335 | .rce-mbox-forwardedMessage {
336 | position: relative;
337 | overflow: hidden;
338 | display: flex;
339 | margin-top: -1px;
340 | margin-bottom: 6px;
341 | margin-right: -6px;
342 | margin-left: -6px;
343 | border-radius: 5px;
344 | padding: 0 5px;
345 | padding-right: 0;
346 | transition: 200ms;
347 | user-select: none;
348 | }
349 |
350 | .rce-mbox-forwarded-message {
351 | display: flex;
352 | align-items: center;
353 | padding: 1px;
354 | font-size: 13px;
355 | font-style: italic;
356 | }
357 |
--------------------------------------------------------------------------------
/src/MessageBox/MessageBox.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react'
2 | import './MessageBox.css'
3 |
4 | import PhotoMessage from '../PhotoMessage/PhotoMessage'
5 | import FileMessage from '../FileMessage/FileMessage'
6 | import SystemMessage from '../SystemMessage/SystemMessage'
7 | import LocationMessage from '../LocationMessage/LocationMessage'
8 | import SpotifyMessage from '../SpotifyMessage/SpotifyMessage'
9 | import ReplyMessage from '../ReplyMessage/ReplyMessage'
10 | import MeetingMessage from '../MeetingMessage/MeetingMessage'
11 | import VideoMessage from '../VideoMessage/VideoMessage'
12 | import AudioMessage from '../AudioMessage/AudioMessage'
13 | import MeetingLink from '../MeetingLink/MeetingLink'
14 |
15 | import Avatar from '../Avatar/Avatar'
16 |
17 | import { format } from 'timeago.js'
18 |
19 | import classNames from 'classnames'
20 | import { MessageBoxType } from '../type'
21 |
22 | import { HugeiconsIcon } from '@hugeicons/react';
23 | import {
24 | LinkForwardIcon,
25 | TickDouble02Icon,
26 | ClockIcon,
27 | Tick02Icon,
28 | MessageOutgoing01Icon,
29 | Delete02Icon,
30 | UnavailableIcon,
31 | // @ts-ignore
32 | } from '@hugeicons/core-free-icons';
33 |
34 | const MessageBox: React.FC = ({ focus = false, notch = true, styles, ...props }) => {
35 | const prevProps = useRef(focus)
36 | const messageRef = useRef(null)
37 |
38 | var positionCls = classNames('rce-mbox', { 'rce-mbox-right': props.position === 'right' })
39 | var thatAbsoluteTime =
40 | !/(text|video|file|meeting|audio)/g.test(props.type || 'text') && !(props.type === 'location' && props.text)
41 | const dateText = props.date && (props.dateString || format(props.date))
42 |
43 | useEffect(() => {
44 | if (prevProps.current !== focus && focus === true) {
45 | if (messageRef) {
46 | messageRef.current?.scrollIntoView({
47 | block: 'center',
48 | behavior: 'smooth',
49 | })
50 |
51 | props.onMessageFocused(prevProps)
52 | }
53 | }
54 | prevProps.current = focus
55 | }, [focus, prevProps])
56 |
57 | return (
58 |
59 | {props.renderAddCmp instanceof Function ? props.renderAddCmp() : props.renderAddCmp}
60 | {props.type === 'system' ? (
61 |
62 | ) : (
63 |
72 |
73 | {!props.retracted && props.forwarded === true && (
74 |
82 |
83 |
84 | )}
85 |
86 | {!props.retracted && props.replyButton === true && (
87 |
103 |
104 |
105 | )}
106 |
107 | {!props.retracted && props.removeButton === true && (
108 |
124 |
125 |
126 | )}
127 |
128 | {(props.title || props.avatar) && (
129 |
136 | {props.avatar &&
}
137 | {props.title &&
{props.title}}
138 |
139 | )}
140 |
141 | {props.forwardedMessageText ? (
142 |
143 |
144 |
145 | {props.forwardedMessageText}
146 |
147 |
148 | ) : null}
149 |
150 | {!props.forwardedMessageText && props.reply ? (
151 |
152 | ) : null}
153 |
154 | {props.type === 'text' && (
155 |
162 | {props.retracted && }
163 | {props.text}
164 |
165 | )}
166 |
167 | {props.type === 'location' &&
}
168 |
169 | {props.type === 'photo' &&
}
170 |
171 | {props.type === 'video' &&
}
172 |
173 | {props.type === 'file' &&
}
174 |
175 | {props.type === 'spotify' &&
}
176 |
177 | {props.type === 'meeting' &&
}
178 | {props.type === 'audio' &&
}
179 |
180 | {props.type === 'meetingLink' && (
181 |
182 | )}
183 |
184 |
193 | {props.copiableDate && props.date && (props.dateString || format(props.date))}
194 | {props.status && (
195 |
196 | {props.status === 'waiting' && }
197 |
198 | {props.status === 'sent' && }
199 |
200 | {props.status === 'received' && }
201 |
202 | {props.status === 'read' && }
203 |
204 | )}
205 |
206 |
207 |
208 | {notch &&
209 | (props.position === 'right' ? (
210 |
218 | ) : (
219 |
220 |
235 |
236 | ))}
237 |
238 | )}
239 |
240 | )
241 | }
242 |
243 | export default MessageBox
244 |
--------------------------------------------------------------------------------
/src/MessageBox/__tests__/MessageBox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import MessageBox from '../MessageBox'
5 |
6 | describe('MessageBox component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/MessageBox/__tests__/__snapshots__/MessageBox.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MessageBox component should render without issues 1`] = `
4 |
8 |
11 |
15 |
18 |
22 |
25 |
28 |
29 |
30 |
31 |
32 |
66 |
67 |
68 |
69 | `;
70 |
--------------------------------------------------------------------------------
/src/MessageList/MessageList.css:
--------------------------------------------------------------------------------
1 | .rce-container-mlist {
2 | position: relative;
3 | display: flex;
4 | }
5 |
6 | .rce-mlist {
7 | display: block;
8 | overflow: auto;
9 | position: relative;
10 | flex: 1;
11 | }
12 |
13 | .rce-mlist-down-button {
14 | position: absolute;
15 | right: 10px;
16 | bottom: 15px;
17 | width: 40px;
18 | height: 40px;
19 | background: #fff;
20 | box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05), 0 2px 5px 0 rgba(0, 0, 0, 0.1);
21 | border-radius: 100%;
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | color: #333;
26 | cursor: pointer;
27 | transition: 200ms;
28 | }
29 |
30 | .rce-mlist-down-button:hover {
31 | opacity: 0.7;
32 | }
33 |
34 | .rce-mlist-down-button--badge {
35 | position: absolute;
36 | right: -5px;
37 | top: -5px;
38 | width: 20px;
39 | height: 20px;
40 | border-radius: 100%;
41 | font-size: 12px;
42 | display: flex;
43 | text-align: center;
44 | align-items: center;
45 | justify-content: center;
46 | color: #fff;
47 | font-weight: 700;
48 | }
49 |
--------------------------------------------------------------------------------
/src/MessageList/MessageList.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, Key, useEffect, useRef, useState } from 'react'
2 | import './MessageList.css'
3 |
4 | import MessageBox from '../MessageBox/MessageBox'
5 |
6 | import classNames from 'classnames'
7 | import { IMessageListProps, MessageListEvent } from '../type'
8 |
9 | import { HugeiconsIcon } from '@hugeicons/react';
10 | // @ts-ignore
11 | import { ArrowDown01Icon } from '@hugeicons/core-free-icons';
12 |
13 | const MessageList: FC = ({
14 | referance = null,
15 | lockable = false,
16 | toBottomHeight = 300,
17 | downButton,
18 | ...props
19 | }) => {
20 | const [scrollBottom, setScrollBottom] = useState(0)
21 | const [_downButton, setDownButton] = useState(false)
22 | const prevProps = useRef(props)
23 |
24 | const checkScroll = () => {
25 | var e = referance
26 | if (!e || !e.current) return
27 |
28 | if (toBottomHeight === '100%' || (toBottomHeight && scrollBottom < toBottomHeight)) {
29 | e.current.scrollTop = e.current.scrollHeight // scroll to bottom
30 | } else {
31 | if (lockable === true) {
32 | e.current.scrollTop = e.current.scrollHeight - e.current.offsetHeight - scrollBottom
33 | }
34 | }
35 | }
36 |
37 | useEffect(() => {
38 | if (!referance) return
39 |
40 | if (prevProps.current.dataSource.length !== props.dataSource.length) {
41 | setScrollBottom(getBottom(referance))
42 | checkScroll()
43 | }
44 |
45 | prevProps.current = props
46 | }, [prevProps, props])
47 |
48 | const getBottom = (e: any) => {
49 | if (e.current) return e.current.scrollHeight - e.current.scrollTop - e.current.offsetHeight
50 | return e.scrollHeight - e.scrollTop - e.offsetHeight
51 | }
52 |
53 | const onOpen: MessageListEvent = (item, index, event) => {
54 | if (props.onOpen instanceof Function) props.onOpen(item, index, event)
55 | }
56 |
57 | const onDownload: MessageListEvent = (item, index, event) => {
58 | if (props.onDownload instanceof Function) props.onDownload(item, index, event)
59 | }
60 |
61 | const onPhotoError: MessageListEvent = (item, index, event) => {
62 | if (props.onPhotoError instanceof Function) props.onPhotoError(item, index, event)
63 | }
64 |
65 | const onClick: MessageListEvent = (item, index, event) => {
66 | if (props.onClick instanceof Function) props.onClick(item, index, event)
67 | }
68 |
69 | const onTitleClick: MessageListEvent = (item, index, event) => {
70 | if (props.onTitleClick instanceof Function) props.onTitleClick(item, index, event)
71 | }
72 |
73 | const onForwardClick: MessageListEvent = (item, index, event) => {
74 | if (props.onForwardClick instanceof Function) props.onForwardClick(item, index, event)
75 | }
76 |
77 | const onReplyClick: MessageListEvent = (item, index, event) => {
78 | if (props.onReplyClick instanceof Function) props.onReplyClick(item, index, event)
79 | }
80 |
81 | const onReplyMessageClick: MessageListEvent = (item, index, event) => {
82 | if (props.onReplyMessageClick instanceof Function) props.onReplyMessageClick(item, index, event)
83 | }
84 |
85 | const onRemoveMessageClick: MessageListEvent = (item, index, event) => {
86 | if (props.onRemoveMessageClick instanceof Function) props.onRemoveMessageClick(item, index, event)
87 | }
88 |
89 | const onContextMenu: MessageListEvent = (item, index, event) => {
90 | if (props.onContextMenu instanceof Function) props.onContextMenu(item, index, event)
91 | }
92 |
93 | const onMessageFocused: MessageListEvent = (item, index, event) => {
94 | if (props.onMessageFocused instanceof Function) props.onMessageFocused(item, index, event)
95 | }
96 |
97 | const onMeetingMessageClick: MessageListEvent = (item, index, event) => {
98 | if (props.onMeetingMessageClick instanceof Function) props.onMeetingMessageClick(item, index, event)
99 | }
100 |
101 | const onScroll = (e: React.UIEvent): void => {
102 | var bottom = getBottom(e.currentTarget)
103 | setScrollBottom(bottom)
104 | if (toBottomHeight === '100%' || (toBottomHeight && bottom > toBottomHeight)) {
105 | if (_downButton !== true) {
106 | setDownButton(true)
107 | setScrollBottom(bottom)
108 | }
109 | } else {
110 | if (_downButton !== false) {
111 | setDownButton(false)
112 | setScrollBottom(bottom)
113 | }
114 | }
115 |
116 | if (props.onScroll instanceof Function) {
117 | props.onScroll(e)
118 | }
119 | }
120 |
121 | const toBottom = (e: any) => {
122 | if (!referance) return
123 | referance.current.scrollTop = referance.current.scrollHeight
124 | if (props.onDownButtonClick instanceof Function) {
125 | props.onDownButtonClick(e)
126 | }
127 | }
128 |
129 | const onMeetingMoreSelect: MessageListEvent = (item, i, e) => {
130 | if (props.onMeetingMoreSelect instanceof Function) props.onMeetingMoreSelect(item, i, e)
131 | }
132 |
133 | const onMeetingLinkClick: MessageListEvent = (item, i, e) => {
134 | if (props.onMeetingLinkClick instanceof Function) props.onMeetingLinkClick(item, i, e)
135 | }
136 |
137 | return (
138 |
139 | {!!props.children && props.isShowChild && props.children}
140 |
141 | {props.dataSource.map((x, i: number) => (
142 | ) => onOpen(x, i, e))}
147 | onPhotoError={props.onPhotoError && ((e: React.MouseEvent) => onPhotoError(x, i, e))}
148 | onDownload={props.onDownload && ((e: React.MouseEvent) => onDownload(x, i, e))}
149 | onTitleClick={props.onTitleClick && ((e: React.MouseEvent) => onTitleClick(x, i, e))}
150 | onForwardClick={props.onForwardClick && ((e: React.MouseEvent) => onForwardClick(x, i, e))}
151 | onReplyClick={props.onReplyClick && ((e: React.MouseEvent) => onReplyClick(x, i, e))}
152 | onReplyMessageClick={
153 | props.onReplyMessageClick && ((e: React.MouseEvent) => onReplyMessageClick(x, i, e))
154 | }
155 | onRemoveMessageClick={
156 | props.onRemoveMessageClick && ((e: React.MouseEvent) => onRemoveMessageClick(x, i, e))
157 | }
158 | onClick={props.onClick && ((e: React.MouseEvent) => onClick(x, i, e))}
159 | onContextMenu={props.onContextMenu && ((e: React.MouseEvent) => onContextMenu(x, i, e))}
160 | onMeetingMoreSelect={
161 | props.onMeetingMoreSelect && ((e: React.MouseEvent) => onMeetingMoreSelect(x, i, e))
162 | }
163 | onMessageFocused={props.onMessageFocused && ((e: React.MouseEvent) => onMessageFocused(x, i, e))}
164 | onMeetingMessageClick={
165 | props.onMeetingMessageClick && ((e: React.MouseEvent) => onMeetingMessageClick(x, i, e))
166 | }
167 | onMeetingTitleClick={props.onMeetingTitleClick}
168 | onMeetingVideoLinkClick={props.onMeetingVideoLinkClick}
169 | onMeetingLinkClick={props.onMeetingLinkClick && ((e: React.MouseEvent) => onMeetingLinkClick(x, i, e))}
170 | actionButtons={props.actionButtons}
171 | styles={props.messageBoxStyles}
172 | notchStyle={props.notchStyle}
173 | />
174 | ))}
175 |
176 | {downButton === true && _downButton && toBottomHeight !== '100%' && (
177 |
178 |
179 | {props.downButtonBadge !== undefined ? (
180 | {props.downButtonBadge.toString()}
181 | ) : null}
182 |
183 | )}
184 |
185 | )
186 | }
187 |
188 | export default MessageList
189 |
--------------------------------------------------------------------------------
/src/MessageList/__tests__/MessageList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import MessageList from '../MessageList'
5 |
6 | describe('MessageList component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/MessageList/__tests__/__snapshots__/MessageList.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MessageList component should render without issues 1`] = `
4 |
12 | `;
13 |
--------------------------------------------------------------------------------
/src/Navbar/Navbar.css:
--------------------------------------------------------------------------------
1 | .rce-navbar {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 | justify-content: space-between;
6 | padding: 10px 10px;
7 | }
8 |
9 | .rce-navbar.light {
10 | background: #f4f4f4;
11 | }
12 |
13 | .rce-navbar.dark {
14 | background: #2f414c;
15 | }
16 |
17 | .rce-navbar-item {
18 | display: flex;
19 | flex-direction: row;
20 | align-items: center;
21 | justify-content: flex-start;
22 | }
23 |
24 | .rce-navbar-item > * {
25 | display: flex;
26 | flex-direction: row;
27 | }
28 |
29 | .rce-navbar-item > * > * {
30 | margin-left: 5px;
31 | }
32 |
--------------------------------------------------------------------------------
/src/Navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Navbar.css'
3 | import classNames from 'classnames'
4 | import { INavbarProps } from '../type'
5 |
6 | const Navbar: React.FC = ({ type = 'light', ...props }) => {
7 | return (
8 |
9 |
{props.left}
10 |
{props.center}
11 |
{props.right}
12 |
13 | )
14 | }
15 |
16 | export default Navbar
17 |
--------------------------------------------------------------------------------
/src/Navbar/__tests__/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import Navbar from '../Navbar'
5 |
6 | describe('Navbar component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/Navbar/__tests__/__snapshots__/Navbar.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Navbar component should render without issues 1`] = `
4 |
17 | `;
18 |
--------------------------------------------------------------------------------
/src/PhotoMessage/PhotoMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-photo {
2 | margin-top: -3px;
3 | margin-right: -6px;
4 | margin-left: -6px;
5 | }
6 |
7 | .rce-mbox-photo .rce-mbox-text {
8 | padding: 5px 0px;
9 | max-width: 300px;
10 | margin: auto;
11 | }
12 |
13 | .rce-mbox-photo--img {
14 | position: relative;
15 | display: flex;
16 | overflow: hidden;
17 | justify-content: center;
18 | border-radius: 5px;
19 | max-height: 300px;
20 | }
21 |
22 | .rce-mbox-photo--img__block {
23 | position: absolute;
24 | top: 0;
25 | right: 0;
26 | left: 0;
27 | bottom: 0;
28 | background-color: rgba(0, 0, 0, 0.5);
29 | border-radius: 5px;
30 | display: flex;
31 | }
32 |
33 | .rce-mbox-photo--img img {
34 | height: 100%;
35 | min-height: 100px;
36 | user-select: none;
37 | }
38 |
39 | .rce-mbox-photo--img__block-item {
40 | margin: auto;
41 | cursor: pointer;
42 | width: 100px;
43 | height: 100px;
44 | }
45 |
46 | .rce-mbox-photo--download {
47 | color: #efe;
48 | display: flex;
49 | justify-content: center;
50 | background: none;
51 | border: none;
52 | font-size: 3.2em;
53 | outline: none;
54 | border: 1px solid #eee;
55 | border-radius: 100%;
56 | height: 100px;
57 | width: 100px;
58 | }
59 |
60 | .rce-mbox-photo--download:hover {
61 | opacity: 0.7;
62 | }
63 |
64 | .rce-mbox-photo--download:active {
65 | opacity: 0.3;
66 | }
67 |
68 | .rce-mbox-photo--error {
69 | display: flex;
70 | justify-content: center;
71 | align-items: center;
72 | background: none;
73 | font-size: 70px;
74 | color: #eaeaea;
75 | }
76 |
--------------------------------------------------------------------------------
/src/PhotoMessage/PhotoMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './PhotoMessage.css'
4 |
5 | import ProgressCircle from '../Circle/Circle'
6 | import { IPhotoMessageProps, IProgressOptions } from '../type'
7 |
8 | import { HugeiconsIcon } from '@hugeicons/react';
9 | // @ts-ignore
10 | import { CloudDownloadIcon, Alert02Icon } from '@hugeicons/core-free-icons';
11 |
12 | const PhotoMessage: React.FC = props => {
13 | var progressOptions = {
14 | strokeWidth: 2.3,
15 | color: '#efe',
16 | trailColor: '#aaa',
17 | trailWidth: 1,
18 | step: (
19 | state: IProgressOptions,
20 | circle: {
21 | path: { setAttribute: (arg0: string, arg1: any) => void }
22 | value: () => number
23 | setText: (arg0: string | number) => void
24 | }
25 | ) => {
26 | circle.path.setAttribute('trail', state?.state?.color)
27 | circle.path.setAttribute('trailwidth-width', state?.state?.width)
28 |
29 | var value = Math.round(circle.value() * 100)
30 | if (value === 0) circle.setText('')
31 | else circle.setText(value)
32 | },
33 | }
34 |
35 | const error = props?.data?.status && props?.data?.status.error === true
36 |
37 | return (
38 |
39 |
49 |

56 | {error && (
57 |
58 |
59 |
60 |
61 |
62 | )}
63 | {!error && props?.data?.status && !props?.data?.status?.download && (
64 |
65 | {!props?.data?.status.click && (
66 |
69 | )}
70 | {typeof props?.data?.status.loading === 'number' && props?.data?.status.loading !== 0 && (
71 |
76 | )}
77 |
78 | )}
79 |
80 | {props?.text &&
{props.text}
}
81 |
82 | )
83 | }
84 |
85 | export default PhotoMessage
86 |
--------------------------------------------------------------------------------
/src/PhotoMessage/__tests__/PhotoMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import PhotoMessage from '../PhotoMessage'
5 |
6 | describe('PhotoMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/PhotoMessage/__tests__/__snapshots__/PhotoMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PhotoMessage component should render without issues 1`] = `
4 |
7 |
10 |
![]()
15 |
16 |
17 | `;
18 |
--------------------------------------------------------------------------------
/src/Popup/Popup.css:
--------------------------------------------------------------------------------
1 | .rce-popup-wrapper {
2 | position: fixed;
3 | left: 0;
4 | right: 0;
5 | top: 0;
6 | bottom: 0;
7 | margin: auto;
8 | background: rgba(255, 255, 255, 0.7);
9 | z-index: 9999999999;
10 | display: flex;
11 | flex-direction: row;
12 | align-items: center;
13 | justify-content: center;
14 | }
15 |
16 | .rce-popup {
17 | background: #fff;
18 | border-radius: 5px;
19 | padding: 0 10px 0 10px;
20 | width: 400px;
21 | min-height: 100px;
22 | box-shadow: 0px 0px 25px -2px rgba(79, 79, 79, 1);
23 | display: flex;
24 | flex-direction: column;
25 | align-items: stretch;
26 | justify-content: flex-start;
27 | animation: popup-scaling 0.4s ease forwards;
28 | box-sizing: border-box;
29 | }
30 |
31 | @keyframes popup-scaling {
32 | 0% {
33 | transform: scale(0);
34 | opacity: 0;
35 | }
36 | 50% {
37 | transform: scale(1.2);
38 | opacity: 0.5;
39 | }
40 | 100% {
41 | transform: scale(1);
42 | opacity: 1;
43 | }
44 | }
45 |
46 | .rce-popup-header {
47 | padding: 18px 8px;
48 | display: flex;
49 | flex-direction: row;
50 | align-items: center;
51 | justify-content: space-between;
52 | box-sizing: border-box;
53 | }
54 |
55 | .rce-popup-content {
56 | padding: 8px;
57 | font-size: 14px;
58 | box-sizing: border-box;
59 | }
60 |
61 | .rce-popup-content * {
62 | margin: 0;
63 | }
64 |
65 | .rce-popup-footer {
66 | padding: 18px 8px;
67 | display: flex;
68 | flex-direction: row;
69 | align-items: center;
70 | justify-content: flex-end;
71 | box-sizing: border-box;
72 | }
73 |
74 | .rce-popup-footer > * {
75 | margin-left: 5px;
76 | }
77 |
--------------------------------------------------------------------------------
/src/Popup/Popup.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Popup.css'
3 |
4 | import Button from '../Button/Button'
5 |
6 | import classNames from 'classnames'
7 | import { IPopupProps } from '../type'
8 |
9 | const Popup: React.FC = ({ ...props }) => {
10 | if (props.popup?.show === true)
11 | return (
12 |
13 |
14 | {props.popup?.renderHeader ? (
15 |
{props.popup?.renderHeader()}
16 | ) : (
17 |
18 | {props.popup?.header}
19 | {props.popup?.header && props.popup?.headerButtons?.map((x, i) => )}
20 |
21 | )}
22 |
23 | {props.popup?.renderContent ? props.popup?.renderContent() : props.popup?.text}
24 |
25 |
26 | {props.popup?.renderFooter
27 | ? props.popup?.renderFooter()
28 | : props.popup?.footerButtons?.map((x, i) => )}
29 |
30 |
31 |
32 | )
33 | return null
34 | }
35 |
36 | export default Popup
37 |
--------------------------------------------------------------------------------
/src/Popup/__tests__/Popup.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import Popup from '../Popup'
5 |
6 | describe('Popup component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/Popup/__tests__/__snapshots__/Popup.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Popup component should render without issues 1`] = `
4 |
7 |
10 |
13 |
14 |
15 |
23 |
26 |
27 |
28 | `;
29 |
--------------------------------------------------------------------------------
/src/ReplyMessage/ReplyMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-reply {
2 | position: relative;
3 | overflow: hidden;
4 | display: flex;
5 | margin-top: -3px;
6 | margin-bottom: 6px;
7 | margin-right: -6px;
8 | margin-left: -6px;
9 | border-radius: 5px;
10 | background: #ececec;
11 | padding: 0 5px;
12 | padding-right: 0;
13 | font-size: 12px;
14 | cursor: pointer;
15 | transition: 200ms;
16 | user-select: none;
17 | }
18 |
19 | .rce-mbox-reply.rce-mbox-reply-border {
20 | border-left: 5px solid;
21 | }
22 |
23 | .rce-mbox-reply:hover {
24 | opacity: 0.85;
25 | }
26 |
27 | .rce-mbox-reply-left {
28 | display: flex;
29 | flex-direction: column;
30 | justify-content: space-between;
31 | padding: 3px 0;
32 | flex: 1;
33 | }
34 |
35 | .rce-mbox-reply-owner {
36 | font-size: 13px;
37 | }
38 |
39 | .rce-mbox-reply-message {
40 | color: #5a5a5a;
41 | overflow: hidden;
42 | white-space: nowrap;
43 | text-overflow: ellipsis;
44 | max-width: 150px;
45 | }
46 |
47 | .rce-mbox-reply-right {
48 | width: 40px;
49 | height: 40px;
50 | }
51 |
52 | .rce-mbox-reply-right img {
53 | width: 100%;
54 | }
55 |
56 | .rce-mbox-reply-text {
57 | padding: 5px 0;
58 | width: 250px;
59 | margin-left: -6px;
60 | margin-right: -6px;
61 | }
62 |
--------------------------------------------------------------------------------
/src/ReplyMessage/ReplyMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './ReplyMessage.css'
3 |
4 | import classNames from 'classnames'
5 | import { IReplyMessageProps } from '../type'
6 |
7 | const ReplyMessage: React.FC = ({onClick, ...props}) => {
8 | return (
9 |
16 |
17 |
18 | {props.title || 'Unknown'}
19 |
20 |
{props.message || '...'}
21 |
22 | {props.photoURL && (
23 |
24 |

25 |
26 | )}
27 |
28 | )
29 | }
30 |
31 | export default ReplyMessage
32 |
--------------------------------------------------------------------------------
/src/ReplyMessage/__tests__/ReplyMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import ReplyMessage from '../ReplyMessage'
5 |
6 | describe('ReplyMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/ReplyMessage/__tests__/__snapshots__/ReplyMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ReplyMessage component should render without issues 1`] = `
4 |
9 |
12 |
16 | Unknown
17 |
18 |
21 | ...
22 |
23 |
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/src/SideBar/SideBar.css:
--------------------------------------------------------------------------------
1 | .rce-sbar {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: space-between;
6 | padding: 10px;
7 | box-sizing: border-box;
8 | min-height: 100%;
9 | }
10 |
11 | .rce-sbar.light {
12 | background: #f4f4f4;
13 | }
14 |
15 | .rce-sbar.dark {
16 | background: #2f414c;
17 | }
18 |
19 | .rce-sbar-item {
20 | display: flex;
21 | flex-direction: row;
22 | align-items: center;
23 | justify-content: flex-start;
24 | max-width: 100%;
25 | }
26 |
27 | .rce-sbar-item > * {
28 | display: flex;
29 | flex-direction: column;
30 | }
31 |
32 | .rce-sbar-item__center {
33 | margin: 15px 0;
34 | }
35 |
--------------------------------------------------------------------------------
/src/SideBar/SideBar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './SideBar.css'
3 |
4 | import classNames from 'classnames'
5 | import { ISideBarProps } from '../type'
6 |
7 | const SideBar: React.FC = ({ type = 'dark', ...props }) => {
8 | return (
9 |
10 |
{props.data?.top}
11 |
{props.data?.center}
12 |
{props.data?.bottom}
13 |
14 | )
15 | }
16 |
17 | export default SideBar
18 |
--------------------------------------------------------------------------------
/src/SideBar/__tests__/SideBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import SideBar from '../SideBar'
5 |
6 | describe('SideBar component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/SideBar/__tests__/__snapshots__/SideBar.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`SideBar component should render without issues 1`] = `
4 |
17 | `;
18 |
--------------------------------------------------------------------------------
/src/SpotifyMessage/SpotifyMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-spotify {
2 | margin-top: -2px;
3 | overflow: hidden;
4 | margin-right: -6px;
5 | margin-left: -6px;
6 | display: flex;
7 | border-radius: 5px;
8 | }
9 |
--------------------------------------------------------------------------------
/src/SpotifyMessage/SpotifyMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ISpotifyMessageProps } from '../type'
3 | import './SpotifyMessage.css'
4 |
5 | const SpotifyMessage: React.FC = ({
6 | width = 300,
7 | height = 380,
8 | ...props
9 | }) => {
10 | const toUrl = (): string => {
11 | var formBody: string[] | string = []
12 | for (var property in props) {
13 | var encodedKey = encodeURIComponent(property)
14 | // @ts-ignore
15 | var encodedValue = encodeURIComponent(props[property])
16 | formBody.push(encodedKey + '=' + encodedValue)
17 | }
18 |
19 | return formBody.join('&')
20 | }
21 |
22 | if (!props.uri) return null
23 | return (
24 |
25 |
32 |
33 | )
34 | }
35 |
36 | export default SpotifyMessage
37 |
--------------------------------------------------------------------------------
/src/SpotifyMessage/__tests__/SpotifyMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import SpotifyMessage from '../SpotifyMessage'
5 |
6 | describe('SpotifyMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 | expect(component.length).toBe(1)
10 | expect(toJson(component)).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/SpotifyMessage/__tests__/__snapshots__/SpotifyMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`SpotifyMessage component should render without issues 1`] = `null`;
4 |
--------------------------------------------------------------------------------
/src/SystemMessage/SystemMessage.css:
--------------------------------------------------------------------------------
1 | .rce-container-smsg {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 |
7 | .rce-smsg {
8 | position: relative;
9 | background: white;
10 | border-radius: 10px;
11 | box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.2);
12 | display: flex;
13 | flex-direction: column;
14 | margin: 5px 0px;
15 | padding: 6px 9px 8px 9px;
16 | float: left;
17 | max-width: 70%;
18 | align-items: center;
19 | justify-content: center;
20 | }
21 |
22 | .rce-smsg-text {
23 | text-align: center;
24 | display: inline-block;
25 | font-size: 15px;
26 | }
27 |
--------------------------------------------------------------------------------
/src/SystemMessage/SystemMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './SystemMessage.css'
3 |
4 | import classNames from 'classnames'
5 | import { ISystemMessageProps } from '../type'
6 |
7 | const SystemMessage: React.FC = props => {
8 | return (
9 |
14 | )
15 | }
16 |
17 | export default SystemMessage
18 |
--------------------------------------------------------------------------------
/src/SystemMessage/__tests__/SystemMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import SystemMessage from '../SystemMessage'
5 |
6 | describe('SystemMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/SystemMessage/__tests__/__snapshots__/SystemMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`SystemMessage component should render without issues 1`] = `
4 |
15 | `;
16 |
--------------------------------------------------------------------------------
/src/VideoMessage/VideoMessage.css:
--------------------------------------------------------------------------------
1 | .rce-mbox-video {
2 | margin-top: -3px;
3 | margin-right: -6px;
4 | margin-left: -6px;
5 | }
6 |
7 | .rce-mbox-video.padding-time {
8 | padding-bottom: 12px;
9 | }
10 |
11 | .rce-mbox-video .rce-mbox-text {
12 | padding: 5px 0px;
13 | max-width: 300px;
14 | margin: auto;
15 | }
16 |
17 | .rce-mbox-video--video {
18 | position: relative;
19 | display: flex;
20 | overflow: hidden;
21 | justify-content: center;
22 | border-radius: 5px;
23 | max-height: 500px;
24 | }
25 |
26 | .rce-mbox-video--video__block {
27 | position: absolute;
28 | top: 0;
29 | right: 0;
30 | left: 0;
31 | bottom: 0;
32 | background-color: rgba(0, 0, 0, 0.5);
33 | border-radius: 5px;
34 | display: flex;
35 | }
36 |
37 | .rce-mbox-video--video img {
38 | height: 100%;
39 | min-height: 100px;
40 | user-select: none;
41 | }
42 |
43 | .rce-mbox-video--video video {
44 | width: 100%;
45 | /*min-height: 100px;*/
46 | user-select: none;
47 | }
48 |
49 | .rce-mbox-video--video__block-item {
50 | margin: auto;
51 | cursor: pointer;
52 | width: 100px;
53 | height: 100px;
54 | }
55 |
56 | .rce-mbox-video--download {
57 | color: #efe;
58 | display: flex;
59 | justify-content: center;
60 | background: none;
61 | border: none;
62 | font-size: 3.2em;
63 | outline: none;
64 | border: 1px solid #eee;
65 | border-radius: 100%;
66 | height: 100px;
67 | width: 100px;
68 | }
69 |
70 | .rce-mbox-video--download:hover {
71 | opacity: 0.7;
72 | }
73 |
74 | .rce-mbox-video--download:active {
75 | opacity: 0.3;
76 | }
77 |
78 | .rce-mbox-video--error {
79 | display: flex;
80 | justify-content: center;
81 | align-items: center;
82 | background: none;
83 | font-size: 70px;
84 | color: #eaeaea;
85 | }
86 |
--------------------------------------------------------------------------------
/src/VideoMessage/VideoMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './VideoMessage.css'
4 |
5 | import classNames from 'classnames'
6 | import ProgressCircle from '../Circle/Circle'
7 | import { IProgressOptions, IVideoMessageProps } from '../type'
8 |
9 | import { HugeiconsIcon } from '@hugeicons/react';
10 | // @ts-ignore
11 | import { CloudDownloadIcon, Alert02Icon } from '@hugeicons/core-free-icons';
12 |
13 | const VideoMessage: React.FC = props => {
14 | var progressOptions = {
15 | strokeWidth: 2.3,
16 | color: '#efe',
17 | trailColor: '#aaa',
18 | trailWidth: 1,
19 | step: (
20 | data: IProgressOptions,
21 | circle: {
22 | path: { setAttribute: (arg0: string, arg1: string) => void }
23 | value: () => number
24 | setText: (arg0: string | number) => void
25 | }
26 | ) => {
27 | circle.path.setAttribute('trail', (data.state !== undefined && data?.state?.color) || '')
28 | circle.path.setAttribute('trailwidth-width', (data.state !== undefined && data?.state?.width) || '')
29 |
30 | var value = Math.round(circle?.value() * 100)
31 | if (value === 0) circle?.setText('')
32 | else circle?.setText(value)
33 | },
34 | }
35 |
36 | const error = props?.data?.status && props?.data?.status.error === true
37 | const downloaded = props?.data?.status && props?.data?.status.download
38 |
39 | return (
40 |
45 |
55 | {!downloaded && (
56 |

63 | )}
64 |
65 | {downloaded && (
66 |
70 | )}
71 |
72 | {error && (
73 |
74 |
75 |
76 |
77 |
78 | )}
79 | {!error && props?.data?.status && !downloaded && (
80 |
81 | {!props.data.status.click && (
82 |
85 | )}
86 | {typeof props.data.status.loading === 'number' && props.data.status.loading !== 0 && (
87 |
92 | )}
93 |
94 | )}
95 |
96 | {props?.text &&
{props.text}
}
97 |
98 | )
99 | }
100 |
101 | export default VideoMessage
102 |
--------------------------------------------------------------------------------
/src/VideoMessage/__tests__/VideoMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { shallow } from 'enzyme'
3 | import toJson from 'enzyme-to-json'
4 | import VideoMessage from '../VideoMessage'
5 |
6 | describe('VideoMessage component', () => {
7 | it('should render without issues', () => {
8 | const component = shallow()
9 |
10 | expect(component.length).toBe(1)
11 | expect(toJson(component)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/VideoMessage/__tests__/__snapshots__/VideoMessage.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`VideoMessage component should render without issues 1`] = `
4 |
7 |
10 |
![]()
15 |
16 |
17 | `;
18 |
--------------------------------------------------------------------------------
/src/assets/img/leftArrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/src/assets/img/rightArrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/src/declaration.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | declare module 'react' {
4 | interface HTMLAttributes extends React.DOMAttributes {
5 | tooltip?: any
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import MessageBox from './MessageBox/MessageBox'
2 | import ChatItem from './ChatItem/ChatItem'
3 | import ChatList from './ChatList/ChatList'
4 | import MessageList from './MessageList/MessageList'
5 | import MeetingItem from './MeetingItem/MeetingItem'
6 | import MeetingList from './MeetingList/MeetingList'
7 | import SystemMessage from './SystemMessage/SystemMessage'
8 | import ReplyMessage from './ReplyMessage/ReplyMessage'
9 | import MeetingMessage from './MeetingMessage/MeetingMessage'
10 | import AudioMessage from './AudioMessage/AudioMessage'
11 | import FileMessage from './FileMessage/FileMessage'
12 | import LocationMessage from './LocationMessage/LocationMessage'
13 | import SpotifyMessage from './SpotifyMessage/SpotifyMessage'
14 | import VideoMessage from './VideoMessage/VideoMessage'
15 | import PhotoMessage from './PhotoMessage/PhotoMessage'
16 | import MeetingLink from './MeetingLink/MeetingLink'
17 | import Input from './Input/Input'
18 | import Button from './Button/Button'
19 | import Avatar from './Avatar/Avatar'
20 | import Navbar from './Navbar/Navbar'
21 | import Dropdown from './Dropdown/Dropdown'
22 | import SideBar from './SideBar/SideBar'
23 | import Popup from './Popup/Popup'
24 | import Circle from './Circle/Circle'
25 |
26 | export {
27 | MessageBox,
28 | ChatItem,
29 | ChatList,
30 | MessageList,
31 | MeetingItem,
32 | MeetingList,
33 | SystemMessage,
34 | ReplyMessage,
35 | MeetingMessage,
36 | AudioMessage,
37 | FileMessage,
38 | LocationMessage,
39 | SpotifyMessage,
40 | VideoMessage,
41 | PhotoMessage,
42 | MeetingLink,
43 | Input,
44 | Button,
45 | Avatar,
46 | Navbar,
47 | Dropdown,
48 | SideBar,
49 | Popup,
50 | Circle,
51 | }
52 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import './example/index'
2 |
--------------------------------------------------------------------------------
/src/loremIpsum.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'lorem-ipsum'
2 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom'
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist",
4 | "sourceMap": true,
5 | "noImplicitAny": true,
6 | "module": "es6",
7 | "target": "es5",
8 | "jsx": "react-jsxdev",
9 | "allowJs": true,
10 | "moduleResolution": "node",
11 | "esModuleInterop": true,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "removeComments": true,
10 | "suppressImplicitAnyIndexErrors": true,
11 | "noUnusedLocals": true,
12 | "declaration": false,
13 | "experimentalDecorators": true,
14 | "outDir": "build",
15 | "allowJs": true,
16 | "skipLibCheck": true,
17 | "esModuleInterop": true,
18 | "allowSyntheticDefaultImports": true,
19 | "strict": true,
20 | "forceConsistentCasingInFileNames": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "module": "esnext",
23 | "moduleResolution": "node",
24 | "resolveJsonModule": true,
25 | "isolatedModules": true,
26 | "noEmit": false,
27 | "jsx": "react-jsx"
28 | },
29 | "include": [
30 | "src/**/*"
31 | ],
32 | "exclude": [
33 | "node_modules",
34 | "build",
35 | "build/lib",
36 | "scripts",
37 | "es",
38 | "lib",
39 | "tests/**",
40 | ".storybook/**",
41 | ".idea",
42 | ".html",
43 | "src/**/*.test.js",
44 | "src/**/*.test.ts",
45 | "src/**/*.stories.js",
46 | "src/**/*.stories.ts",
47 | "src/setupTests.ts"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const path = require("path");
4 |
5 | module.exports = {
6 | mode: 'development',
7 | devtool: "inline-source-map",
8 | entry: {
9 | main: path.resolve(__dirname, './example/index.tsx'),
10 | },
11 | mode: "development",
12 | devServer: {
13 | hot: true,
14 | port: 8090,
15 | headers: { "Access-Control-Allow-Origin": "*" },
16 | open: true,
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.tsx?$/,
22 | use: 'ts-loader',
23 | exclude: /node_modules/,
24 | },
25 | {
26 | test: /\.html$/,
27 | use:{
28 | loader: 'html-loader'
29 | }
30 | },
31 | {
32 | test: /\.css$/i,
33 | use: [MiniCssExtractPlugin.loader, "css-loader"],
34 | },
35 | ]
36 | },
37 | optimization: {
38 | splitChunks: {
39 | chunks: "all"
40 | }
41 | },
42 | resolve: {
43 | extensions: [".ts", ".tsx", ".js", "jsx"],
44 | },
45 | plugins: [
46 | new HtmlWebpackPlugin({
47 | filename: "index.html",
48 | inject: true
49 | }),
50 | new MiniCssExtractPlugin({
51 | filename: "css/[name].bundle.[fullhash].css",
52 | chunkFilename: "chunks/[id].chunk.[fullhash].css"
53 | })
54 | ],
55 | stats: {
56 | children: true,
57 | errorDetails: true
58 | }
59 | };
--------------------------------------------------------------------------------