163 lines
4.4 KiB
TypeScript
163 lines
4.4 KiB
TypeScript
/* eslint-disable react-hooks/exhaustive-deps */
|
||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||
import { FormEvent, useEffect, useRef, useState } from "react";
|
||
import useSocketStore from "../stores/useSocketStore";
|
||
import { UIEvent } from "react";
|
||
import "./Chat.css";
|
||
import ChevronDownIcon from "./icons/ChevronDownIcon";
|
||
import CloseIcon from "./icons/CloseIcon";
|
||
import useChatStore from "../stores/useChatStore";
|
||
|
||
interface ChatProps {
|
||
className?: string;
|
||
handleClose: () => void;
|
||
}
|
||
|
||
function Chat({ className, handleClose }: ChatProps) {
|
||
const socket = useSocketStore((state) => state.socket);
|
||
const [message, setMessage] = useState<string>("");
|
||
const [messages, setMessages] = useChatStore((state) => [
|
||
state.messages,
|
||
state.setMessages,
|
||
]);
|
||
const [isBottom, setIsBottom] = useState<boolean>(true);
|
||
const [isNewMessage, setIsNewMessage] = useState<boolean>(false);
|
||
const inputRef = useRef<HTMLInputElement>(null);
|
||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||
|
||
function sendMessage(e: FormEvent) {
|
||
e.preventDefault();
|
||
|
||
if (!socket || !message) return;
|
||
|
||
socket.emit("message", socket.id, message);
|
||
|
||
setMessage("");
|
||
inputRef.current?.focus();
|
||
messagesEndRef.current?.scrollIntoView();
|
||
}
|
||
|
||
const handleScroll = (e: UIEvent<HTMLDivElement>) => {
|
||
if (
|
||
e.currentTarget.scrollHeight - Math.round(e.currentTarget.scrollTop) ===
|
||
e.currentTarget.clientHeight
|
||
) {
|
||
setIsBottom(true);
|
||
setIsNewMessage(false);
|
||
} else {
|
||
setIsBottom(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
function handleKeyUp(e: KeyboardEvent) {
|
||
if (e.key === "Escape") {
|
||
handleClose();
|
||
}
|
||
}
|
||
|
||
document.addEventListener("keyup", handleKeyUp);
|
||
|
||
return () => {
|
||
document.removeEventListener("keyup", handleKeyUp);
|
||
};
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!socket) return;
|
||
|
||
console.log(messages);
|
||
|
||
console.log("CHAT Socket Init: ", socket.id);
|
||
|
||
socket.on("message", (socketId, message) => {
|
||
setMessages([
|
||
...useChatStore.getState().messages,
|
||
{ id: socketId, message },
|
||
]);
|
||
});
|
||
|
||
return () => {
|
||
socket.off("message");
|
||
};
|
||
}, [socket]);
|
||
|
||
useEffect(() => {
|
||
if (isBottom) {
|
||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||
} else {
|
||
setIsNewMessage(true);
|
||
}
|
||
}, [messages]);
|
||
|
||
return (
|
||
<div
|
||
className={[
|
||
"relative p-4 flex flex-col gap-4 w-80 h-screen bg-neutral-900 transition-all",
|
||
className,
|
||
].join(" ")}
|
||
>
|
||
<div className="border-b pb-3 border-y-neutral-800 flex justify-between">
|
||
<p className="text-2xl font-gilroy">Чат</p>
|
||
<button onClick={handleClose}>
|
||
<CloseIcon />
|
||
</button>
|
||
</div>
|
||
<div
|
||
onScroll={handleScroll}
|
||
className="messages flex flex-col gap-4 overflow-y-auto overflow-x-hidden flex-1 pr-2"
|
||
>
|
||
{messages.map((data, index) => (
|
||
<p key={index} className="break-words">
|
||
<b>{data.city && data.city + ":"}</b> <span>{data.message}</span>
|
||
</p>
|
||
))}
|
||
|
||
<div ref={messagesEndRef}></div>
|
||
</div>
|
||
|
||
<div
|
||
onClick={() =>
|
||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
|
||
}
|
||
className={[
|
||
"absolute mr-8 right-0 bottom-[136px] bg-neutral-950 p-2 rounded-full shadow transition-opacity",
|
||
isBottom
|
||
? "opacity-0 pointer-events-none cursor-auto"
|
||
: "opacity-100 pointer-events-auto cursor-pointer",
|
||
].join(" ")}
|
||
>
|
||
<ChevronDownIcon className="w-5 h-5" />
|
||
|
||
<div
|
||
className={[
|
||
"absolute top-0 right-0 w-3 h-3 flex justify-center items-center rounded-full bg-red-500 shadow transition-opacity",
|
||
isNewMessage ? "opacity-100" : "opacity-0",
|
||
].join(" ")}
|
||
></div>
|
||
</div>
|
||
|
||
<form onSubmit={sendMessage} className="flex flex-col gap-4">
|
||
<input
|
||
ref={inputRef}
|
||
type="text"
|
||
placeholder="Написать сообщение..."
|
||
autoFocus
|
||
autoComplete="off"
|
||
value={message}
|
||
onChange={(e) => setMessage(e.target.value)}
|
||
className="px-3 py-2 outline-none rounded"
|
||
/>
|
||
<button
|
||
type="submit"
|
||
className="p-2 bg-blue-700 hover:bg-blue-800 transition-colors outline-none rounded"
|
||
>
|
||
Отправить
|
||
</button>
|
||
</form>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Chat;
|