import React, { useEffect, useRef, useState, useMemo } from 'react'; import {useHMSTheme} from '../../hooks/HMSThemeProvider' import { CloseIcon, DownCaratIcon, PeopleIcon, SendIcon } from '../Icons'; import './index.css'; import { hmsUiClassParserGenerator } from '../../utils/classes'; import Autolinker from 'autolinker'; import ReactHtmlParser from 'react-html-parser'; import { Button } from '../TwButton'; import { useInView } from 'react-intersection-observer'; import { HMSMessage } from '../../store/schema'; import { isTotallyScrolled, scrollToBottom } from './chatBoxUtils'; import { useHMSActions, useHMSStore } from '../../hooks/HMSRoomProvider'; import { selectHMSMessages } from '../../store/selectors'; interface ChatBoxClasses { root?: string; header?: string; headerLine?: string; headerRoot?: string; headerText?: string; headerCloseButton?: string; messageBox?: string; messageRoot?: string; messageInfo?: string; messageSender?: string; messageTime?: string; messageText?: string; notificationRoot?: string; notificationInfo?: string; notificationText?: string; notificationTime?: string; time?: string; noMessageRoot?: string; footer?: string; chatInput?: string; unreadMessagesContainer?: string; unreadMessagesInner?: string; unreadIcon?: string; } const defaultClasses: ChatBoxClasses = { root: 'w-full h-full rounded-2xl flex flex-col shadow-2', header: `bg-white dark:bg-gray-200 rounded-t-2xl p-3 text-gray-300 dark:text-gray-500 flex flex-col justify-center items-center shadow border-b-1 border-gray-500`, headerLine: 'w-8 h-1 rounded bg-white dark:bg-gray-400 m-2', headerRoot: 'flex w-full justify-between', headerText: 'text-gray-300 dark:text-gray-500 flex items-center', headerCloseButton: 'focus:outline-none', messageBox: 'bg-white dark:bg-gray-100 w-full h-full p-3 text-gray-300 dark:text-gray-500 overflow-y-auto no-scrollbar flex-grow', messageRoot: 'py-3', messageInfo: 'flex justify-between', messageTime: 'text-xs', messageText: 'text-gray-100 dark:text-white leading-5 max-w-full break-words', noMessageRoot: 'flex justify-center items-center text-gray-300 dark:text-gray-400 h-full', footer: 'bg-white dark:bg-gray-200 min-h-11 rounded-b-2xl flex w-full justify-between p-3 border-t-1 border-gray-500 relative', chatInput: 'bg-white dark:bg-gray-200 placeholder-gray-500 text-gray-100 dark:text-white focus:outline-none leading-5 overflow-y-auto no-scrollbar resize-none w-5/6', notificationRoot: 'py-3', notificationInfo: 'flex justify-between text-gray-300 dark:text-gray-400', notificationTime: 'text-xs', unreadMessagesContainer: 'absolute left-0 p-1 w-full bottom-full flex justify-center', unreadMessagesInner: 'rounded-md px-2 py-1 bg-brand-main text-white flex cursor-pointer items-center ', unreadIcon: 'ml-2 w-3 h-3', }; const customClasses: ChatBoxClasses = { messageBox: 'hmsui-chatBox-no-scrollbar', chatInput: 'hmsui-chatBox-no-scrollbar', }; export interface Message extends HMSMessage { notification?: boolean; direction?: 'left' | 'right' | 'center'; } export interface ChatProps { messages?: Message[]; onSend?: (message: string) => void; onClose?: () => void; // when the chat box is closed autoScrollToBottom?: boolean; scrollAnimation?: ScrollBehavior; messageFormatter?: (message: string) => React.ReactNode; /** * extra classes added by user */ classes?: ChatBoxClasses; timeFormatter?: (date: Date) => string; } export const ChatBox = ({ messages, onSend, onClose, autoScrollToBottom = true, //TODO shouldn't be exposed as a prop scrollAnimation = 'auto', //TODO shouldn't be exposed as a prop messageFormatter = (message: string) => { let text = Autolinker.link(message, { sanitizeHtml: true, mention: 'twitter', className: 'text-brand-tint', }); return (
{ReactHtmlParser(text.trim())}
); }, classes, timeFormatter = (date: Date) => { const min = date.getMinutes(); const minString = min < 10 ? `0${min}` : min; return `${date.getHours()}:${minString}`; }, }: ChatProps) => { const {tw} = useHMSTheme(); const styler = useMemo(()=> hmsUiClassParserGenerator({ tw, classes, customClasses, defaultClasses, tag: 'hmsui-chatBox', }),[]); const storeMessages = useHMSStore(selectHMSMessages); const hmsActions = useHMSActions(); messages = messages || storeMessages; const sendMessage = (msg: string) => onSend ? onSend(msg) : hmsActions.sendMessage(msg); const [messageDraft, setMessageDraft] = useState(''); const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); // a dummy element with messagesEndRef is created and put in the end const { ref: messagesEndRef, inView: messagesEndInView } = useInView(); const messageListRef = useRef(null); useEffect(() => { if (messages && messages.length > 0) { const myOwnMessage = messages[messages.length - 1].senderName === 'You'; if ( autoScrollToBottom && (myOwnMessage || isTotallyScrolled(messageListRef)) ) { scrollToBottom(messageListRef, scrollAnimation); setUnreadMessagesCount(0); } else { // unread should be read/updated from/to central store setUnreadMessagesCount(unreadMessagesCount => unreadMessagesCount + 1); } } }, [messages]) if (messagesEndInView && unreadMessagesCount != 0) { setUnreadMessagesCount(0); } return ( {/* root */}
{/* header */}
{/* header-line */}
{/* header-root */}
{/* header-text */}
Everyone
{/* headerCloseButton */} {/* */} {/* */}
{/* messageBox */} {/* TODO: move no scroll bar css logic to tailwind */}
{messages.map(message => { return message.notification ? ( /* notificationRoot */
{/* notificationInfo*/}
{/*notificationText*/} {messageFormatter ? messageFormatter(message.message) : message.message} {timeFormatter(message.time)}
) : ( /* messageRoot */
{/* messageInfo */}
{/* messageSender */} {message.senderName} {/* messageTime */} {timeFormatter(message.time)}
{/* messageText */}
{/* {ReactHtmlParser( Autolinker.link(message.message, { sanitizeHtml: true }), )} */} {/* {message.message} */} {messageFormatter ? messageFormatter(message.message) : message.message}
); })} {messages.length === 0 && ( /* NoMessageRoot */
There are no messages here.
)}
{/* footer */}
{unreadMessagesCount !== 0 && (
{ scrollToBottom(messageListRef, scrollAnimation); }} > {`New message${unreadMessagesCount > 1 ? 's' : ''}`}
)} {/* chatInput */} {/* TODO: move no scrollbar logic to tailwind */}