Review chat UI and make it expandable

This commit is contained in:
dtonon 2026-02-20 21:00:02 +01:00
parent 4476d45228
commit 403cf32b5c
4 changed files with 128 additions and 45 deletions

View file

@ -7,68 +7,142 @@
};
let { expanded = false, onToggle }: Props = $props();
let asideEl: HTMLElement;
let openMenuId = $state<string | null>(null);
function formatTime(iso: string) {
return new Date(iso).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
});
}
$effect(() => {
if (!expanded) return;
function handleClick(e: MouseEvent) {
if (!asideEl.contains(e.target as Node)) onToggle();
}
document.addEventListener("click", handleClick);
return () => document.removeEventListener("click", handleClick);
});
$effect(() => {
if (!openMenuId) return;
function closeMenu() {
openMenuId = null;
}
document.addEventListener("click", closeMenu);
return () => document.removeEventListener("click", closeMenu);
});
</script>
<aside
class="flex shrink-0 flex-col rounded-tl-xl min-[1540px]:rounded-tr-xl bg-white transition-all duration-200
{expanded ? 'w-[420px]' : 'w-80'}"
bind:this={asideEl}
class="absolute right-0 top-2 h-[calc(100%-0.5rem)] z-10 flex flex-col rounded-tl-xl min-[1540px]:rounded-tr-xl bg-white transition-all duration-200
{expanded ? 'w-150 shadow-2xl' : 'w-80'} px-6 py-6"
>
<div class="flex h-12 shrink-0 items-center justify-between px-4">
<span class="font-semibold text-gray-900">Chat</span>
<div class="flex shrink-0 items-center justify-between mb-6">
<span class="text-[1.5rem] text-brand leading-7">Chat</span>
<button
onclick={onToggle}
class="text-gray-400 hover:text-gray-600"
aria-label="Toggle chat size"
class="rounded bg-gray-100 hover:bg-gray-200 transition-colors"
aria-label={expanded ? "Collapse chat" : "Expand chat"}
>
{#if expanded}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-7 w-7"
viewBox="0 0 37 33"
fill="none"
>
{#if expanded}
<path
fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd"
d="M10.8656 23.2958C11.0971 23.0642 11.4726 23.0642 11.7042 23.2958C11.9358 23.5274 11.9358 23.9029 11.7042 24.1344L11.0124 24.8263C10.7808 25.0579 10.4053 25.0579 10.1737 24.8263C9.9421 24.5947 9.9421 24.2192 10.1737 23.9876L10.8656 23.2958ZM25.9876 8.17369C26.2192 7.9421 26.5947 7.9421 26.8263 8.17369C27.0579 8.40528 27.0579 8.78077 26.8263 9.01235L22.3038 13.5349H25.5033L25.5186 13.5351C25.839 13.5432 26.0963 13.8055 26.0963 14.1279C26.0963 14.4503 25.839 14.7126 25.5186 14.7207L25.5033 14.7209H20.8721C20.5446 14.7209 20.2791 14.4554 20.2791 14.1279V9.49669C20.2791 9.16917 20.5446 8.90367 20.8721 8.90366C21.1996 8.90366 21.4651 9.16917 21.4651 9.49669L21.4651 12.6962L25.9876 8.17369ZM16.7209 23.5033C16.7209 23.8308 16.4554 24.0963 16.1279 24.0963C15.8004 24.0963 15.5349 23.8308 15.5349 23.5033V20.3038L13.7798 22.0589C13.5482 22.2905 13.1727 22.2904 12.9411 22.0589C12.7095 21.8273 12.7095 21.4518 12.9411 21.2202L14.6962 19.4651H11.4967C11.1692 19.4651 10.9037 19.1996 10.9037 18.8721C10.9037 18.5446 11.1692 18.2791 11.4967 18.2791H16.1279C16.4554 18.2791 16.7209 18.5446 16.7209 18.8721V23.5033Z"
fill="#3C3C3C"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
{:else}
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
d="M15.7086 18.4527C15.9402 18.2212 16.3156 18.2212 16.5472 18.4527C16.7788 18.6843 16.7788 19.0598 16.5472 19.2914L12.0247 23.814H15.2243L15.2395 23.8141C15.56 23.8223 15.8173 24.0846 15.8173 24.407C15.8173 24.7294 15.56 24.9917 15.2395 24.9998L15.2243 25H10.593C10.2655 25 10 24.7345 10 24.407V19.7758C10 19.4483 10.2655 19.1827 10.593 19.1827C10.9154 19.1827 11.1777 19.44 11.1859 19.7605L11.186 19.7758V22.9753L15.7086 18.4527ZM21.1446 13.0167C21.3762 12.7851 21.7517 12.7851 21.9833 13.0167C22.2149 13.2483 22.2149 13.6238 21.9833 13.8554L21.2914 14.5472C21.0598 14.7788 20.6843 14.7788 20.4527 14.5472C20.2212 14.3156 20.2212 13.9402 20.4527 13.7086L21.1446 13.0167ZM27 13.2243C27 13.5518 26.7345 13.8173 26.407 13.8173C26.0795 13.8173 25.814 13.5518 25.814 13.2243V10.0247L24.0589 11.7798C23.8273 12.0114 23.4518 12.0114 23.2202 11.7798C22.9886 11.5482 22.9886 11.1727 23.2202 10.9411L24.9753 9.18605H21.7758C21.4483 9.18604 21.1827 8.92054 21.1827 8.59302C21.1827 8.26551 21.4483 8 21.7758 8H26.407L26.4223 8.00019C26.7427 8.0083 27 8.27062 27 8.59302V13.2243Z"
fill="#3C3C3C"
/>
</svg>
{/if}
{/if}
</svg>
</button>
</div>
<div class="flex-1 overflow-y-auto p-3 space-y-3">
<div class="flex-1 overflow-y-auto space-y-5 pb-4">
{#each chatMessages as msg}
<div class="flex gap-2">
<img
src={msg.author.picture}
alt={msg.author.name}
class="h-7 w-7 shrink-0 rounded-full"
/>
<div>
<span class="text-xs font-semibold text-gray-900"
>{msg.author.name}</span
<div>
<!-- Header: pfp, name, time -->
<div class="flex items-center gap-2 mb-1">
<img
src={msg.author.picture}
alt={msg.author.name}
class="h-6 w-6 shrink-0 rounded-full"
/>
<span class="font-medium text-gray-600">{msg.author.name}</span>
<span class="ml-auto text-xs text-gray-400"
>{formatTime(msg.createdAt)}</span
>
<p class=" text-gray-700">{msg.content}</p>
</div>
<!-- Content + reactions -->
<div class="mt-0.5">
<p class=" text-gray-700 leading-5">{msg.content}</p>
<div class="mt-1.5 flex items-center gap-2">
{#each msg.reactions ?? [] as r}
<span class="flex items-center gap-0.5 text-xs text-gray-500">
{r.emoji}
{r.count}
</span>
{/each}
{#if msg.zaps}
<span class="flex items-center gap-0.5 text-xs text-gray-500">
{msg.zaps}
</span>
{/if}
<div class="relative ml-auto">
<button
onclick={(e) => {
e.stopPropagation();
openMenuId = openMenuId === msg.id ? null : msg.id;
}}
class="flex items-center justify-center rounded p-0.5 text-gray-300 hover:text-gray-500 hover:bg-gray-100 transition-colors"
aria-label="Message actions"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z"
/>
</svg>
</button>
{#if openMenuId === msg.id}
<div
class="absolute right-0 bottom-6 z-20 w-36 rounded-lg border border-gray-100 bg-white py-1 shadow-lg text-sm"
>
<button class="w-full px-3 py-1.5 text-left hover:bg-gray-50"
>React</button
>
<button class="w-full px-3 py-1.5 text-left hover:bg-gray-50"
>Zap</button
>
<button class="w-full px-3 py-1.5 text-left hover:bg-gray-50"
>Reply</button
>
</div>
{/if}
</div>
</div>
</div>
</div>
{/each}
</div>
<div class="border-t border-gray-200 p-3">
<div class="border-t border-gray-200 pt-4">
<input
type="text"
placeholder="Message..."

View file

@ -329,6 +329,8 @@ export type ChatMessage = {
author: Author;
content: string;
createdAt: string;
reactions?: Reaction[];
zaps?: number;
};
export const chatMessages: ChatMessage[] = [
@ -338,6 +340,8 @@ export const chatMessages: ChatMessage[] = [
content:
"Quisque sagittis quam dui, nec pulvinar velit aliquam in. Sed volutpat in.",
createdAt: "2025-01-15T13:00:00Z",
reactions: [{ emoji: "👍", count: 3 }],
zaps: 210,
},
{
id: "c2",
@ -352,6 +356,7 @@ export const chatMessages: ChatMessage[] = [
content:
"Proin vitae ex iaculis, luctus elit in, fermentum turpis. Pellentesque sagittis congue quam.",
createdAt: "2025-01-15T13:05:00Z",
reactions: [{ emoji: "❤️", count: 2 }, { emoji: "🎉", count: 1 }],
},
{
id: "c4",
@ -359,6 +364,7 @@ export const chatMessages: ChatMessage[] = [
content:
"Mauro dui dolor, sagittis id sem nec, rhoncus consequat duis. Suspendisse dapibus mauris maximus mauris imperdiet.",
createdAt: "2025-01-15T13:08:00Z",
zaps: 500,
},
{
id: "c5",
@ -366,5 +372,7 @@ export const chatMessages: ChatMessage[] = [
content:
"In hac habitasse platea dictumst. Nulla a quam tempor, posuere neque non, fringilla velit.",
createdAt: "2025-01-15T13:10:00Z",
reactions: [{ emoji: "👍", count: 7 }],
zaps: 140,
},
];

View file

@ -23,14 +23,15 @@
<div class="flex h-screen flex-col overflow-hidden max-w-[1540px] mx-auto">
<Navbar />
<div class="flex flex-1 gap-5 overflow-hidden pt-2">
<div class="relative flex flex-1 gap-5 overflow-hidden pt-2">
<LeftSidebar {mode} {activeRoom} />
<main class="flex-1 overflow-y-auto rounded-t-xl bg-white px-10 pt-6 pb-20">
{@render children()}
</main>
{#if chatEnabled}
<div class="shrink-0 w-80" aria-hidden="true"></div>
<ChatSidebar
{chatExpanded}
expanded={chatExpanded}
onToggle={() => (chatExpanded = !chatExpanded)}
/>
{/if}

View file

@ -97,7 +97,7 @@
</div>
<div class="flex-1 min-w-0">
<div class="flex items-baseline justify-between mb-2">
<span class="font-semibold text-gray-900"
<span class="font-medium text-gray-600"
>{thread.op.author.name}</span
>
<span class="text-sm text-gray-400 ml-4 flex-shrink-0"
@ -145,7 +145,7 @@
</div>
<div class="flex-1 min-w-0">
<div class="flex items-baseline justify-between mb-2">
<span class="font-semibold text-gray-900"
<span class="font-medium text-gray-600"
>{reply.author.name}</span
>
<span class="text-sm text-gray-400 ml-4 flex-shrink-0"