This commit is contained in:
2023-10-04 17:28:00 +05:00
parent 1deb30f6ca
commit 5342b3b303
5 changed files with 283 additions and 6 deletions
+19
View File
@@ -0,0 +1,19 @@
.messages::-webkit-scrollbar {
width: 4px;
transition: 1s color;
}
.messages::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.02); /* color of the tracking area */
border-radius: 4px;
}
.messages::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.05); /* color of the scroll thumb */
border-radius: 4px; /* roundness of the scroll thumb */
}
.messages:hover::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1); /* color of the scroll thumb */
border-radius: 4px; /* roundness of the scroll thumb */
}
+162
View File
@@ -0,0 +1,162 @@
/* 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 ">Chat</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.id}:</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="Message"
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"
>
Send
</button>
</form>
</div>
);
}
export default Chat;
+19
View File
@@ -0,0 +1,19 @@
interface IconProps {
className?: string;
}
function ChatIcon({ className }: IconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className}
>
<path d="M4.913 2.658c2.075-.27 4.19-.408 6.337-.408 2.147 0 4.262.139 6.337.408 1.922.25 3.291 1.861 3.405 3.727a4.403 4.403 0 00-1.032-.211 50.89 50.89 0 00-8.42 0c-2.358.196-4.04 2.19-4.04 4.434v4.286a4.47 4.47 0 002.433 3.984L7.28 21.53A.75.75 0 016 21v-4.03a48.527 48.527 0 01-1.087-.128C2.905 16.58 1.5 14.833 1.5 12.862V6.638c0-1.97 1.405-3.718 3.413-3.979z" />
<path d="M15.75 7.5c-1.376 0-2.739.057-4.086.169C10.124 7.797 9 9.103 9 10.609v4.285c0 1.507 1.128 2.814 2.67 2.94 1.243.102 2.5.157 3.768.165l2.782 2.781a.75.75 0 001.28-.53v-2.39l.33-.026c1.542-.125 2.67-1.433 2.67-2.94v-4.286c0-1.505-1.125-2.811-2.664-2.94A49.392 49.392 0 0015.75 7.5z" />
</svg>
);
}
export default ChatIcon;