Unread indicator for chat

This commit is contained in:
Kalle
2022-03-27 14:22:01 +03:00
parent 1a25aece60
commit 9b775dcf41
4 changed files with 65 additions and 10 deletions

View File

@@ -23,10 +23,11 @@ export function Chat({
inputRef,
actionFetcher,
isOpen,
setIsOpen,
toggleOpen,
unreadCount,
} = useChat(id);
if (!user) return null;
if (!user || !messages) return null;
return (
<>
@@ -76,13 +77,16 @@ export function Chat({
</actionFetcher.Form>
</div>
)}
<button className="chat__fab" onClick={() => setIsOpen(() => !isOpen)}>
<button className="chat__fab" onClick={toggleOpen}>
{isOpen ? (
<CrossIcon className="chat__fab__icon" />
) : (
<SpeechBubbleIcon className="chat__fab__icon" />
)}
</button>
{unreadCount > 0 && (
<div className="chat__fab__unread">{unreadCount}</div>
)}
</>
);
}

View File

@@ -12,6 +12,7 @@ export default function useChat(id: string) {
const [messagesAfterLoad, setMessagesAfterLoad] = React.useState<
ChatLoaderData["messages"]
>([]);
const [unreadCount, setUnreadCount] = React.useState(0);
const loaderFetcher = useFetcher<ChatLoaderData>();
const actionFetcher = useFetcher<ChatActionData>();
const containerRef = React.useRef<HTMLUListElement>(null);
@@ -21,16 +22,33 @@ export default function useChat(id: string) {
const user = useUser();
invariant(user, "!user");
useSocketEvent(`chat-${id}`, (data: Unpacked<ChatLoaderData["messages"]>) => {
if (data.sender.id === user.id) return;
setMessagesAfterLoad((messages) => [...messages, data]);
setIsOpen(true);
});
const eventHandler = React.useCallback(
(data: Unpacked<ChatLoaderData["messages"]>) => {
if (data.sender.id === user.id) return;
setMessagesAfterLoad((messages) => [...messages, data]);
if (!isOpen) {
setUnreadCount((count) => count + 1);
}
},
[isOpen]
);
useSocketEvent(`chat-${id}`, eventHandler);
React.useEffect(() => {
loaderFetcher.load(chatRoute([id]));
}, []);
// open chat on data load if there are messages
React.useEffect(() => {
if (!loaderFetcher.data) return;
if (loaderFetcher.data.messages.length > 0) {
setIsOpen(true);
}
}, [loaderFetcher.data]);
// after sending message reset and refocus input so user can keep typing
React.useEffect(() => {
if (actionFetcher.submission) {
formRef.current?.reset();
@@ -69,6 +87,17 @@ export default function useChat(id: string) {
return newMessageContent;
}, [actionFetcher]);
const toggleOpen = React.useCallback(() => {
setIsOpen((open) => {
if (!open) {
setUnreadCount(0);
return true;
}
return false;
});
}, []);
React.useEffect(() => {
if (!containerRef.current) return;
containerRef.current.scrollTop = containerRef.current.scrollHeight;
@@ -82,6 +111,7 @@ export default function useChat(id: string) {
inputRef,
actionFetcher,
isOpen,
setIsOpen,
toggleOpen,
unreadCount,
};
}

View File

@@ -11,5 +11,5 @@ export function useSocketEvent(event: string, handler: (data: any) => void) {
return () => {
socket.close();
};
}, []);
}, [handler]);
}

View File

@@ -588,6 +588,23 @@ hr {
width: 28px;
}
.chat__fab__unread {
position: fixed;
z-index: 70;
bottom: 60px;
left: 50.9%;
display: flex;
width: 1rem;
height: 1rem;
align-items: center;
justify-content: center;
background-color: var(--theme-warning);
border-radius: 50%;
color: var(--button-text);
font-size: var(--fonts-xs);
font-weight: var(--bold);
}
.chat__window {
position: fixed;
z-index: 50;
@@ -620,6 +637,10 @@ hr {
.chat__window {
left: 85%;
}
.chat__fab__unread {
left: 85.9%;
}
}
.chat__messages {