├── .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 |
7 | 8 |
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 |
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 | {props.alt} 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( 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 |
14 | 23 |
24 |
27 |
30 |
33 |
36 | just now 37 |
38 |
39 |
42 |
45 |
50 |
53 |
56 |
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 |
8 |
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 |
7 | 12 | 17 | 18 |
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 |
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 |