First templates
This commit is contained in:
parent
d3701a728a
commit
68f25fd20f
12 changed files with 786 additions and 8 deletions
78
src/lib/components/ChatSidebar.svelte
Normal file
78
src/lib/components/ChatSidebar.svelte
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts">
|
||||
import { chatMessages } from "$lib/mock";
|
||||
|
||||
type Props = {
|
||||
expanded?: boolean;
|
||||
onToggle: () => void;
|
||||
};
|
||||
|
||||
let { expanded = false, onToggle }: Props = $props();
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="flex shrink-0 flex-col rounded-tl-xl bg-white transition-all duration-200
|
||||
{expanded ? 'w-[420px]' : 'w-80'}"
|
||||
>
|
||||
<div class="flex h-12 shrink-0 items-center justify-between px-4">
|
||||
<span class="font-semibold text-gray-900">Chat</span>
|
||||
<button
|
||||
onclick={onToggle}
|
||||
class="text-gray-400 hover:text-gray-600"
|
||||
aria-label="Toggle chat size"
|
||||
>
|
||||
{#if expanded}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-3 space-y-3">
|
||||
{#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
|
||||
>
|
||||
<p class=" text-gray-700">{msg.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-200 p-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Message..."
|
||||
class="w-full rounded border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-brand"
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
74
src/lib/components/LeftSidebar.svelte
Normal file
74
src/lib/components/LeftSidebar.svelte
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts">
|
||||
import { rooms, community } from "$lib/mock";
|
||||
|
||||
type Props = {
|
||||
mode: "simple" | "full";
|
||||
activeRoom?: string;
|
||||
};
|
||||
|
||||
let { mode, activeRoom }: Props = $props();
|
||||
|
||||
const groups = [...new Set(rooms.map((r) => r.group))];
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="flex max-w-52 min-w-48 shrink-0 flex-col justify-between py-2 pl-8 pr-3 pb-8"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
href="/"
|
||||
class="flex items-center gap-2 py-1 hover:bg-gray-100 text-gray-700}"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
|
||||
{#if mode === "simple"}
|
||||
<div class="flex mt-6">
|
||||
<p class="text-sm text-gray-500">{community.about}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<nav class="flex-auto mt-6">
|
||||
{#each groups as group}
|
||||
<div class="mb-8">
|
||||
<p
|
||||
class="pb-1 text-xs font-semibold uppercase tracking-wider text-gray-400"
|
||||
>
|
||||
{group}
|
||||
</p>
|
||||
{#each rooms.filter((r) => r.group === group) as room}
|
||||
<a
|
||||
href="/room/{room.slug}"
|
||||
class="flex items-center gap-2 py-1 hover:bg-gray-100
|
||||
{activeRoom === room.slug ? ' text-brand' : 'text-gray-700'}"
|
||||
>
|
||||
{room.name}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<nav class="flex-auto mt-6">
|
||||
<div class="">
|
||||
<p
|
||||
class="pb-1 text-xs font-semibold uppercase tracking-wider text-gray-400"
|
||||
>
|
||||
More
|
||||
</p>
|
||||
<a href="/settings" class="block py-1 text-gray-500 hover:text-gray-900"
|
||||
>Settings</a
|
||||
>
|
||||
<a href="/contacts" class="block py-1 text-gray-500 hover:text-gray-900"
|
||||
>Contacts</a
|
||||
>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="mt-3 w-full rounded bg-brand px-3 py-1.5 font-medium text-sm text-white hover:bg-brand-hover"
|
||||
>
|
||||
Join with Nostr
|
||||
</button>
|
||||
</aside>
|
||||
18
src/lib/components/Navbar.svelte
Normal file
18
src/lib/components/Navbar.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { community } from "$lib/mock";
|
||||
</script>
|
||||
|
||||
<header class="flex h-16 shrink-0 items-center justify-between px-8">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full bg-red-500 font-bold text-white"
|
||||
>
|
||||
{community.name[0]}
|
||||
</div>
|
||||
<span class="text-[1.7rem] font-medium text-gray-900">{community.name}</span
|
||||
>
|
||||
</div>
|
||||
<button class="text-sm text-gray-500 hover:text-gray-900"
|
||||
>Create your own community</button
|
||||
>
|
||||
</header>
|
||||
31
src/lib/components/Reactions.svelte
Normal file
31
src/lib/components/Reactions.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts">
|
||||
import type { Reaction } from "$lib/mock";
|
||||
|
||||
type Props = {
|
||||
reactions: Reaction[];
|
||||
zaps: number;
|
||||
};
|
||||
|
||||
let { reactions, zaps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
{#each reactions as r}
|
||||
<button
|
||||
class="flex items-center gap-1 rounded-full border border-gray-200 px-2 py-0.5 text-xs hover:bg-gray-50"
|
||||
>
|
||||
<span>{r.emoji}</span>
|
||||
<span class="text-gray-600">{r.count}</span>
|
||||
</button>
|
||||
{/each}
|
||||
{#if zaps > 0}
|
||||
<button
|
||||
class="flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-xs hover:bg-amber-100"
|
||||
>
|
||||
<span>⚡</span>
|
||||
<span class="text-amber-700"
|
||||
>{zaps >= 1000 ? (zaps / 1000).toFixed(1) + "k" : zaps}</span
|
||||
>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
8
src/lib/components/Tag.svelte
Normal file
8
src/lib/components/Tag.svelte
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts">
|
||||
type Props = { label: string };
|
||||
let { label }: Props = $props();
|
||||
</script>
|
||||
|
||||
<span class="rounded-lg bg-accent px-2.5 py-0 text-sm text-white">
|
||||
{label}
|
||||
</span>
|
||||
76
src/lib/components/ThreadItem.svelte
Normal file
76
src/lib/components/ThreadItem.svelte
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts">
|
||||
import type { Thread } from "$lib/mock";
|
||||
import Tag from "./Tag.svelte";
|
||||
|
||||
type Props = { thread: Thread };
|
||||
let { thread }: Props = $props();
|
||||
|
||||
// Placeholder until scoring formula is defined
|
||||
const score = $derived(
|
||||
thread.op.reactions.reduce((s, r) => s + r.count, 0) + thread.op.zaps,
|
||||
);
|
||||
</script>
|
||||
|
||||
<a
|
||||
href="/thread/{thread.id}"
|
||||
class="flex items-center gap-6 border-b border-gray-100 py-4 hover:bg-gray-50"
|
||||
>
|
||||
<!-- Col 1: title + byline -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-lg mb-1.5 text-gray-900 leading-6">{thread.title}</div>
|
||||
<div class="flex items-center gap-1.5 text-sm text-gray-500">
|
||||
<span>by</span>
|
||||
<img
|
||||
src={thread.op.author.picture}
|
||||
alt={thread.op.author.name}
|
||||
class="h-5 w-5 rounded-full"
|
||||
/>
|
||||
{#if thread.participants.length > 1}
|
||||
<span>and</span>
|
||||
<div class="flex -space-x-1.5">
|
||||
{#each thread.participants
|
||||
.filter((p) => p.pubkey !== thread.op.author.pubkey)
|
||||
.slice(0, 4) as p}
|
||||
<img
|
||||
src={p.picture}
|
||||
alt={p.name}
|
||||
class="h-5 w-5 rounded-full ring-1 ring-white"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 2: tags -->
|
||||
<div class="flex shrink-0 flex-wrap justify-end gap-1.5">
|
||||
{#each thread.tags as tag}
|
||||
<Tag label={tag.label} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Col 3: replies | score | activity -->
|
||||
<div class="flex shrink-0 items-center gap-6">
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<span class=" text-gray-800">{thread.replyCount}</span>
|
||||
<span class="text-sm text-gray-400">replies</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<span class=" text-accent">{score}</span>
|
||||
<span class="text-sm text-gray-400">score</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<div class="flex items-center gap-1">
|
||||
<img
|
||||
src={thread.participants[thread.participants.length - 1].picture}
|
||||
alt={thread.participants[thread.participants.length - 1].name}
|
||||
class="h-5 w-5 rounded-full"
|
||||
/>
|
||||
<span class=" text-gray-800">{thread.lastActivity}</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-400">activity</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
Loading…
Add table
Add a link
Reference in a new issue