feat(web): redesign chat ui in telegram-like style
All checks were successful
CI / test (push) Successful in 21s

- update overall layout for desktop/mobile chat navigation

- restyle dialogs list, message bubbles and composer

- add atmospheric background and unified panel styling
This commit is contained in:
2026-03-08 00:10:08 +03:00
parent a4d7294628
commit 0a602e4078
6 changed files with 188 additions and 98 deletions

View File

@@ -17,56 +17,60 @@ export function MessageList() {
}, [activeChatId, messagesByChat]);
if (!activeChatId) {
return <div className="flex h-full items-center justify-center text-slate-400">Select a chat</div>;
return <div className="flex h-full items-center justify-center text-slate-300/80">Select a chat</div>;
}
return (
<div className="flex h-full flex-col">
<div className="flex-1 overflow-auto p-4">
{messages.map((message) => (
<div className={`mb-3 flex ${message.sender_id === me?.id ? "justify-end" : "justify-start"}`} key={message.id}>
<div className="max-w-[80%] rounded-lg bg-slate-800 px-3 py-2">
{message.type === "voice" && message.text ? (
renderContent(message.type, message.text)
) : (
renderContent(message.type, message.text)
)}
<p className="mt-1 flex items-center justify-end gap-1 text-right text-[11px] text-slate-400">
<span>{formatTime(message.created_at)}</span>
{message.sender_id === me?.id ? <span className={message.delivery_status === "read" ? "text-sky-400" : ""}>{renderStatus(message.delivery_status)}</span> : null}
</p>
<div className="tg-scrollbar flex-1 overflow-auto px-3 py-4 md:px-6">
{messages.map((message) => {
const own = message.sender_id === me?.id;
return (
<div className={`mb-2 flex ${own ? "justify-end" : "justify-start"}`} key={`${message.id}-${message.client_message_id ?? ""}`}>
<div
className={`max-w-[86%] rounded-2xl px-3 py-2 shadow-sm md:max-w-[72%] ${
own
? "rounded-br-md bg-sky-500/90 text-slate-950"
: "rounded-bl-md border border-slate-700/60 bg-slate-900/80 text-slate-100"
}`}
>
{renderContent(message.type, message.text)}
<p className={`mt-1 flex items-center justify-end gap-1 text-[11px] ${own ? "text-slate-900/85" : "text-slate-400"}`}>
<span>{formatTime(message.created_at)}</span>
{own ? <span className={message.delivery_status === "read" ? "text-cyan-900" : ""}>{renderStatus(message.delivery_status)}</span> : null}
</p>
</div>
</div>
</div>
))}
</div>
<div className="px-4 pb-2 text-xs text-slate-400">
{(typingByChat[activeChatId] ?? []).length > 0 ? "Someone is typing..." : ""}
);
})}
</div>
<div className="px-5 pb-2 text-xs text-slate-300/80">{(typingByChat[activeChatId] ?? []).length > 0 ? "typing..." : ""}</div>
</div>
);
}
function renderContent(messageType: string, text: string | null) {
if (!text) {
return <p className="text-slate-300">[empty]</p>;
}
if (messageType === "image") {
return <img alt="attachment" className="max-h-64 rounded" src={text} />;
}
if (messageType === "video" || messageType === "circle_video") {
return <video className="max-h-64 rounded" controls src={text} />;
}
if (messageType === "audio" || messageType === "voice") {
return <audio controls src={text} />;
}
if (messageType === "file") {
return (
<a className="text-accent underline" href={text} rel="noreferrer" target="_blank">
Open file
</a>
);
function renderContent(messageType: string, text: string | null) {
if (!text) {
return <p className="opacity-80">[empty]</p>;
}
return <p className="whitespace-pre-wrap break-words">{text}</p>;
if (messageType === "image") {
return <img alt="attachment" className="max-h-72 rounded-lg object-cover" src={text} />;
}
if (messageType === "video" || messageType === "circle_video") {
return <video className="max-h-72 rounded-lg" controls src={text} />;
}
if (messageType === "audio" || messageType === "voice") {
return <audio controls src={text} />;
}
if (messageType === "file") {
return (
<a className="underline" href={text} rel="noreferrer" target="_blank">
Open file
</a>
);
}
return <p className="whitespace-pre-wrap break-words">{text}</p>;
}
function renderStatus(status: string | undefined): string {
if (status === "sending") {