final design (#184)
* feat: redesign * feat: update other columns to new design * chore: small fixes * fix: better manage external webview * feat: redesign note * feat: update ui * chore: update * chore: update * chore: polish ui * chore: update auth ui * feat: finalize note design * chore: small fixes * feat: add window management in rust * chore: format * feat: update ui for event screen * feat: update event screen * feat: final
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
import { useEvent } from "@lume/ark";
|
||||
import { Box, Container, Note, Spinner, User } from "@lume/ui";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { ReplyList } from "./-components/replyList";
|
||||
import { WindowVirtualizer } from "virtua";
|
||||
import { type Event } from "@lume/types";
|
||||
|
||||
export const Route = createLazyFileRoute("/events/$eventId")({
|
||||
component: Event,
|
||||
});
|
||||
|
||||
function Event() {
|
||||
const { eventId } = Route.useParams();
|
||||
const { isLoading, isError, data } = useEvent(eventId);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner className="size-5 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<p>Not found.</p>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container withDrag>
|
||||
<Box className="px-3 pt-3 scrollbar-none">
|
||||
<WindowVirtualizer>
|
||||
<MainNote data={data} />
|
||||
{data ? <ReplyList eventId={eventId} /> : null}
|
||||
</WindowVirtualizer>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function MainNote({ data }: { data: Event }) {
|
||||
return (
|
||||
<Note.Provider event={data}>
|
||||
<Note.Root className="flex flex-col pb-3">
|
||||
<User.Provider pubkey={data.pubkey}>
|
||||
<User.Root className="mb-3 flex flex-1 items-center gap-3">
|
||||
<User.Avatar className="size-11 shrink-0 rounded-full object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
||||
<div className="flex flex-1 flex-col">
|
||||
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
|
||||
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
<User.Time time={data.created_at} />
|
||||
<span>·</span>
|
||||
<User.NIP05 />
|
||||
</div>
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<Note.Thread className="mb-2" />
|
||||
<Note.Content className="min-w-0" />
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="-ml-1 inline-flex items-center gap-4">
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
<Note.Menu />
|
||||
</div>
|
||||
</Note.Root>
|
||||
</Note.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useEvent } from "@lume/ark";
|
||||
import type { Event } from "@lume/types";
|
||||
import { Box, Container, Note, Spinner } from "@lume/ui";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { WindowVirtualizer } from "virtua";
|
||||
import { ReplyList } from "./-components/replyList";
|
||||
|
||||
export const Route = createFileRoute("/events/$eventId")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
const ark = context.ark;
|
||||
const settings = await ark.get_settings();
|
||||
|
||||
return { settings };
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { eventId } = Route.useParams();
|
||||
const { isLoading, isError, data } = useEvent(eventId);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<p>Not found.</p>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container withDrag>
|
||||
<Box className="scrollbar-none">
|
||||
<WindowVirtualizer>
|
||||
<MainNote data={data} />
|
||||
{data ? (
|
||||
<ReplyList eventId={eventId} />
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
)}
|
||||
</WindowVirtualizer>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function MainNote({ data }: { data: Event }) {
|
||||
return (
|
||||
<Note.Provider event={data}>
|
||||
<Note.Root>
|
||||
<div className="px-3 h-14 flex items-center justify-between">
|
||||
<Note.User />
|
||||
<Note.Menu />
|
||||
</div>
|
||||
<Note.ContentLarge className="px-3" />
|
||||
<div className="mt-4 h-11 gap-2 flex items-center justify-end px-3">
|
||||
<Note.Reply large />
|
||||
<Note.Repost large />
|
||||
<Note.Zap large />
|
||||
</div>
|
||||
</Note.Root>
|
||||
</Note.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +1,36 @@
|
||||
import { EventWithReplies } from "@lume/types";
|
||||
import { cn } from "@lume/utils";
|
||||
import type { EventWithReplies } from "@lume/types";
|
||||
import { Note, User } from "@lume/ui";
|
||||
import { cn } from "@lume/utils";
|
||||
import { SubReply } from "./subReply";
|
||||
|
||||
export function Reply({ event }: { event: EventWithReplies }) {
|
||||
return (
|
||||
<Note.Provider event={event}>
|
||||
<Note.Root className="border-t border-neutral-100 pt-3 dark:border-neutral-900">
|
||||
<User.Provider pubkey={event.pubkey}>
|
||||
<User.Root className="mb-2 flex items-center justify-between">
|
||||
<div className="inline-flex gap-2">
|
||||
<User.Avatar className="size-6 rounded-full" />
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<User.Name className="font-semibold" />
|
||||
<User.NIP05 className="text-base lowercase text-neutral-600 dark:text-neutral-400" />
|
||||
</div>
|
||||
</div>
|
||||
<User.Time time={event.created_at} />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<Note.Content />
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="-ml-1 inline-flex items-center gap-4">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
<Note.Menu />
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
event.replies?.length > 0
|
||||
? "my-3 mt-6 flex flex-col gap-3 divide-y divide-neutral-100 border-l-2 border-neutral-100 pl-6 dark:divide-neutral-900 dark:border-neutral-900"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{event.replies?.length > 0
|
||||
? event.replies?.map((childEvent) => (
|
||||
<SubReply key={childEvent.id} event={childEvent} />
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</Note.Root>
|
||||
</Note.Provider>
|
||||
);
|
||||
return (
|
||||
<Note.Provider event={event}>
|
||||
<Note.Root className="border-t border-neutral-100 dark:border-neutral-900">
|
||||
<div className="px-3 h-14 flex items-center justify-between">
|
||||
<Note.User />
|
||||
<Note.Menu />
|
||||
</div>
|
||||
<Note.ContentLarge className="px-3" />
|
||||
<div className="mt-3 flex items-center gap-4 px-3 h-14">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
event.replies?.length > 0
|
||||
? "py-2 pl-3 flex flex-col gap-3 divide-y divide-neutral-100 bg-neutral-50 dark:bg-white/5 border-l-2 border-blue-500 dark:divide-neutral-900"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{event.replies?.length > 0
|
||||
? event.replies?.map((childEvent) => (
|
||||
<SubReply key={childEvent.id} event={childEvent} />
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</Note.Root>
|
||||
</Note.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,48 +1,51 @@
|
||||
import type { EventWithReplies } from "@lume/types";
|
||||
import { Spinner } from "@lume/ui";
|
||||
import { cn } from "@lume/utils";
|
||||
import { useRouteContext } from "@tanstack/react-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { EventWithReplies } from "@lume/types";
|
||||
import { Reply } from "./reply";
|
||||
import { useRouteContext } from "@tanstack/react-router";
|
||||
import { Spinner } from "@lume/ui";
|
||||
|
||||
export function ReplyList({
|
||||
eventId,
|
||||
className,
|
||||
eventId,
|
||||
className,
|
||||
}: {
|
||||
eventId: string;
|
||||
className?: string;
|
||||
eventId: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const { ark } = useRouteContext({ strict: false });
|
||||
const [t] = useTranslation();
|
||||
const [data, setData] = useState<null | EventWithReplies[]>(null);
|
||||
const { ark } = useRouteContext({ strict: false });
|
||||
const [t] = useTranslation();
|
||||
const [data, setData] = useState<null | EventWithReplies[]>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function getReplies() {
|
||||
const events = await ark.get_event_thread(eventId);
|
||||
setData(events);
|
||||
}
|
||||
getReplies();
|
||||
}, [eventId]);
|
||||
useEffect(() => {
|
||||
async function getReplies() {
|
||||
const events = await ark.get_event_thread(eventId);
|
||||
setData(events);
|
||||
}
|
||||
getReplies();
|
||||
}, [eventId]);
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-3", className)}>
|
||||
{!data ? (
|
||||
<div className="mt-4 flex h-16 items-center justify-center p-3">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
) : data.length === 0 ? (
|
||||
<div className="mt-4 flex w-full items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||
{t("note.reply.empty")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event) => <Reply key={event.id} event={event} />)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={cn("flex flex-col", className)}>
|
||||
<div className="h-11 flex px-3 items-center text-sm font-semibold text-neutral-700 dark:text-neutral-300 border-t border-neutral-100 dark:border-neutral-900">
|
||||
Replies ({data?.length ?? 0})
|
||||
</div>
|
||||
{!data ? (
|
||||
<div className="flex h-16 items-center justify-center p-3">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
) : data.length === 0 ? (
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||
{t("note.reply.empty")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event) => <Reply key={event.id} event={event} />)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
import { Event } from "@lume/types";
|
||||
import { Note, User } from "@lume/ui";
|
||||
import type { Event } from "@lume/types";
|
||||
import { Note } from "@lume/ui";
|
||||
|
||||
export function SubReply({ event }: { event: Event; rootEventId?: string }) {
|
||||
return (
|
||||
<Note.Provider event={event}>
|
||||
<Note.Root className="pt-3">
|
||||
<User.Provider pubkey={event.pubkey}>
|
||||
<User.Root className="mb-2 flex items-center justify-between">
|
||||
<div className="inline-flex gap-2">
|
||||
<User.Avatar className="size-6 rounded-full" />
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<User.Name className="font-semibold" />
|
||||
<User.NIP05 className="text-base lowercase text-neutral-600 dark:text-neutral-400" />
|
||||
</div>
|
||||
</div>
|
||||
<User.Time time={event.created_at} />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<Note.Content />
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="-ml-1 inline-flex items-center gap-4">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
<Note.Menu />
|
||||
</div>
|
||||
</Note.Root>
|
||||
</Note.Provider>
|
||||
);
|
||||
return (
|
||||
<Note.Provider event={event}>
|
||||
<Note.Root>
|
||||
<div className="px-3 h-14 flex items-center justify-between">
|
||||
<Note.User />
|
||||
<Note.Menu />
|
||||
</div>
|
||||
<Note.ContentLarge className="px-3" />
|
||||
<div className="mt-3 flex items-center gap-4 px-3">
|
||||
<Note.Reply />
|
||||
<Note.Repost />
|
||||
<Note.Zap />
|
||||
</div>
|
||||
</Note.Root>
|
||||
</Note.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user