feat(web): redesign chat ui in telegram-like style
All checks were successful
CI / test (push) Successful in 21s
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:
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user