added chat scroll hook
This commit is contained in:
parent
a105e6180e
commit
ca3057c895
@ -1,14 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Member, Message, Profile } from "@prisma/client";
|
import { Fragment, useRef, ElementRef, use } from "react";
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
import { Member, Message, Profile } from "@prisma/client"
|
||||||
|
|
||||||
import { ChatWelcome } from "./chat-welcome";
|
import { ChatWelcome } from "./chat-welcome";
|
||||||
import { useChatQuery } from "@/hooks/use-chat-query";
|
import { useChatQuery } from "@/hooks/use-chat-query";
|
||||||
import { Loader2, ServerCrash } from "lucide-react";
|
import { Loader2, ServerCrash } from "lucide-react";
|
||||||
import { Fragment } from "react";
|
|
||||||
import { ChatItem } from "./chat-item";
|
import { ChatItem } from "./chat-item";
|
||||||
import { useChatSocket } from "@/hooks/use-chat-socket";
|
import { useChatSocket } from "@/hooks/use-chat-socket";
|
||||||
|
import { useChatScroll } from "@/hooks/use-chat-scroll";
|
||||||
|
|
||||||
const DATE_FORMAT = "d MMM yyyy, HH:mm";
|
const DATE_FORMAT = "d MMM yyyy, HH:mm";
|
||||||
|
|
||||||
@ -45,6 +46,9 @@ export const ChatMessages = ({
|
|||||||
const addKey = `chat:${chatId}:messages`;
|
const addKey = `chat:${chatId}:messages`;
|
||||||
const updateKey = `chat:${chatId}:messages:update`;
|
const updateKey = `chat:${chatId}:messages:update`;
|
||||||
|
|
||||||
|
const chatRef = useRef<ElementRef<"div">>(null);
|
||||||
|
const bottomRef = useRef<ElementRef<"div">>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
@ -58,6 +62,14 @@ export const ChatMessages = ({
|
|||||||
paramValue,
|
paramValue,
|
||||||
});
|
});
|
||||||
useChatSocket({ queryKey, addKey, updateKey});
|
useChatSocket({ queryKey, addKey, updateKey});
|
||||||
|
useChatScroll({
|
||||||
|
chatRef,
|
||||||
|
bottomRef,
|
||||||
|
loadMore: fetchNextPage,
|
||||||
|
shouldLoadMore: !isFetchingNextPage && !!hasNextPage,
|
||||||
|
count: data?.pages?.[0]?.items?.length ?? 0,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
if (status === "pending") {
|
if (status === "pending") {
|
||||||
return (
|
return (
|
||||||
@ -82,12 +94,28 @@ export const ChatMessages = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col py-4 overflow-y-auto">
|
<div ref={chatRef} className="flex-1 flex flex-col py-4 overflow-y-auto">
|
||||||
<div className="flex-1"/>
|
{!hasNextPage && <div className="flex-1"/>}
|
||||||
<ChatWelcome
|
{!hasNextPage &&(
|
||||||
type={type}
|
<ChatWelcome
|
||||||
name={name}
|
type={type}
|
||||||
/>
|
name={name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{hasNextPage && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
{isFetchingNextPage ? (
|
||||||
|
<Loader2 className="h-6 w-6 text-zinc-500 animate-spin my-4"/>
|
||||||
|
): (
|
||||||
|
<button
|
||||||
|
onClick={() => fetchNextPage()}
|
||||||
|
className="text-zinc-500 hover:text-zinc-600 dark:text-zinc-400 text-xs dark:hover:text-zinc-300 transition"
|
||||||
|
>
|
||||||
|
Load previous messages
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-col-reverse mt-auto">
|
<div className="flex flex-col-reverse mt-auto">
|
||||||
{data?.pages.map((group, i) => (
|
{data?.pages.map((group, i) => (
|
||||||
<Fragment key={i}>
|
<Fragment key={i}>
|
||||||
@ -110,6 +138,7 @@ export const ChatMessages = ({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div ref={bottomRef}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
60
hooks/use-chat-scroll.ts
Normal file
60
hooks/use-chat-scroll.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type ChatScrollProps = {
|
||||||
|
chatRef: React.RefObject<HTMLDivElement>;
|
||||||
|
bottomRef: React.RefObject<HTMLDivElement>;
|
||||||
|
shouldLoadMore: boolean;
|
||||||
|
loadMore: () => void;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useChatScroll = ({
|
||||||
|
chatRef,
|
||||||
|
bottomRef,
|
||||||
|
shouldLoadMore,
|
||||||
|
loadMore,
|
||||||
|
count,
|
||||||
|
}: ChatScrollProps) => {
|
||||||
|
const [hasInitialized, setHasInitialized] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const topDiv = chatRef?.current;
|
||||||
|
const hadleScroll = () => {
|
||||||
|
const scrollTop = topDiv?.scrollTop;
|
||||||
|
|
||||||
|
if (scrollTop === 0 && shouldLoadMore) {
|
||||||
|
loadMore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
topDiv?.addEventListener("scroll", hadleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
topDiv?.removeEventListener("scroll", hadleScroll);
|
||||||
|
}
|
||||||
|
}, [shouldLoadMore, loadMore, chatRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const bottomDiv = bottomRef?.current;
|
||||||
|
const topDiv = chatRef?.current;
|
||||||
|
const shouldAutoScroll= () => {
|
||||||
|
if (!hasInitialized && bottomDiv){
|
||||||
|
setHasInitialized(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!topDiv) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distanceFromBottom = topDiv.scrollHeight - topDiv.clientHeight;
|
||||||
|
return distanceFromBottom <= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAutoScroll()) {
|
||||||
|
setTimeout(() => {
|
||||||
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, [bottomRef, chatRef, hasInitialized, count]);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user