183 lines
5.4 KiB
TypeScript
183 lines
5.4 KiB
TypeScript
import { useRef, useState, useEffect } from "react";
|
||
import SendIcon from "../icons/SendIcon";
|
||
import Button from "../ui/Button";
|
||
import { useMe } from "../../hooks/useAuth";
|
||
import clsx from "clsx";
|
||
import PopupWrapper from "../PopupWrapper";
|
||
|
||
export default function ChatPopup() {
|
||
const [messages, setMessages] = useState<MessageItemProps[]>([
|
||
{
|
||
senderId: "1",
|
||
timestamp: "12:22",
|
||
content:
|
||
"У меня все сломалось, ничего не работает, картинки нет, все пошло по пизде, помогите мне кто-нибудь, пожалуйста",
|
||
},
|
||
{
|
||
senderId: "2",
|
||
timestamp: "12:22",
|
||
content: "🤡🤡🤡",
|
||
},
|
||
]);
|
||
|
||
function onMessageSend(message: string) {
|
||
setMessages([
|
||
...messages,
|
||
{
|
||
senderId: "2",
|
||
timestamp: "12:22",
|
||
content: message,
|
||
},
|
||
]);
|
||
}
|
||
|
||
return (
|
||
<PopupWrapper title="Чат" draggable className="sm:overflow-hidden">
|
||
<div className="flex flex-col 2xl:h-[27.778vw] relative 2xl:-m-[1.389vw] -m-5">
|
||
<MessageFeed messages={messages} />
|
||
<MessageInput onMessageSend={onMessageSend} />
|
||
</div>
|
||
</PopupWrapper>
|
||
);
|
||
}
|
||
|
||
function MessageFeed({ messages }: { messages: MessageItemProps[] }) {
|
||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||
|
||
// Скролл к концу при получении нового сообщения
|
||
useEffect(() => {
|
||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||
}, [messages]);
|
||
|
||
return (
|
||
<div
|
||
className="flex flex-col w-full 2xl:h-[calc(100%-4.444vw)] bg-[#F0F0F0] 2xl:p-[1.111vw] p-4 pb-0 overflow-y-auto [-webkit-scrollbar]:hidden"
|
||
style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
|
||
>
|
||
{messages.length === 0 ? (
|
||
<div className="w-full flex flex-col 2xl:gap-[1.667vw] gap-6 items-center justify-center 2xl:px-[2.778vw] px-10 m-auto">
|
||
<img
|
||
src="/img/popups/EmptyMessageFeed.svg"
|
||
className="2xl:size-[8.333vw] size-[120px]"
|
||
/>
|
||
<span className="text-center text-s">
|
||
Здесь пока нет сообщений. <br /> Можно начать беседу с приветствия
|
||
</span>
|
||
</div>
|
||
) : (
|
||
<div className="flex flex-col 2xl:gap-[1.111vw] gap-4 items-end mt-auto">
|
||
{messages.map((message, index) => (
|
||
<MessageItem key={index} {...message} />
|
||
))}
|
||
<div ref={messagesEndRef} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
interface MessageItemProps {
|
||
senderId: string;
|
||
timestamp: string;
|
||
content: string;
|
||
}
|
||
|
||
function MessageItem({ senderId, timestamp, content }: MessageItemProps) {
|
||
const { data: user } = useMe();
|
||
|
||
const isFromMe = senderId === "1";
|
||
|
||
return (
|
||
<div
|
||
className={clsx(
|
||
"flex w-full items-end 2xl:gap-[0.556vw] gap-2 justify-end",
|
||
isFromMe ? "" : "flex-row-reverse"
|
||
)}
|
||
>
|
||
<div
|
||
className={clsx(
|
||
"2xl:p-[1.111vw] p-4 2xl:w-[16.667vw] w-[240px] flex items-end justify-between",
|
||
isFromMe
|
||
? "bg-[#7B60F3] 2xl:rounded-[1.111vw_1.111vw_0_1.111vw] rounded-[16px_16px_0_16px] text-white"
|
||
: "bg-[#FFFFFF] 2xl:rounded-[1.111vw_1.111vw_1.111vw_0] rounded-[16px_16px_16px_0] text-[#141414]"
|
||
)}
|
||
>
|
||
<div className="break-words text-s 2xl:space-y-[0.278vw] space-y-1">
|
||
{!isFromMe && (
|
||
<div className="caption-s text-[#7B60F3]">{user?.fullName}</div>
|
||
)}
|
||
<div>{content}</div>
|
||
</div>
|
||
<span className="caption-xs opacity-30">{timestamp}</span>
|
||
</div>
|
||
|
||
<div className="2xl:size-[2.222vw] size-8 rounded-full">
|
||
<img
|
||
src="/img/popups/MessageUserPfp.png"
|
||
className="size-full object-cover"
|
||
alt="user"
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function MessageInput({
|
||
onMessageSend,
|
||
}: {
|
||
onMessageSend: (message: string) => void;
|
||
}) {
|
||
const [message, setMessage] = useState("");
|
||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||
|
||
useEffect(() => {
|
||
const textarea = textareaRef.current;
|
||
if (!textarea) return;
|
||
textarea.style.height = "auto";
|
||
|
||
const computedStyle = window.getComputedStyle(textarea);
|
||
const lineHeight = parseInt(computedStyle.lineHeight);
|
||
const maxHeight = lineHeight * 4;
|
||
const newHeight = Math.min(textarea.scrollHeight, maxHeight);
|
||
|
||
textarea.style.height = `${newHeight}px`;
|
||
textarea.style.overflowY =
|
||
textarea.scrollHeight > maxHeight ? "auto" : "hidden";
|
||
}, [message]);
|
||
|
||
function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
|
||
setMessage(e.target.value);
|
||
}
|
||
|
||
function sendMessage() {
|
||
setMessage("");
|
||
onMessageSend(message);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
onClick={() => textareaRef.current?.focus()}
|
||
className="flex w-full 2xl:min-h-[4.444vw] min-h-16 2xl:p-[1.111vw] p-4 items-end justify-between absolute bottom-0 left-0 bg-white"
|
||
>
|
||
<textarea
|
||
ref={textareaRef}
|
||
value={message}
|
||
onChange={handleChange}
|
||
className="w-[80%] resize-none focus:outline-none my-auto text-s"
|
||
rows={1}
|
||
placeholder="Сообщение..."
|
||
/>
|
||
<Button
|
||
size="small"
|
||
variant="cta"
|
||
disabled={message.length === 0}
|
||
onClick={sendMessage}
|
||
>
|
||
<div className="2xl:size-[1.111vw] size-4">
|
||
<SendIcon />
|
||
</div>
|
||
</Button>
|
||
</div>
|
||
);
|
||
}
|