feat: improve ui

This commit is contained in:
reya 2024-02-29 13:02:16 +07:00
parent 09df8672d0
commit cfcb9bc6ed
27 changed files with 408 additions and 344 deletions

View File

@ -71,7 +71,7 @@ export function RepostNote({
return ( return (
<Note.Root <Note.Root
className={cn( className={cn(
"mb-3 flex flex-col gap-2 border-b border-neutral-100 pb-3 dark:border-neutral-900", "mb-5 flex flex-col gap-2 border-b border-neutral-100 pb-5 dark:border-neutral-900",
className, className,
)} )}
> >
@ -96,14 +96,13 @@ export function RepostNote({
<div className="size-11 shrink-0" /> <div className="size-11 shrink-0" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<Note.Content /> <Note.Content />
<div className="mt-5 flex items-center justify-between"> <div className="mt-4 flex items-center justify-between">
<Note.Reaction /> <div className="-ml-1 inline-flex items-center gap-4">
<div className="inline-flex items-center gap-4">
<Note.Reply /> <Note.Reply />
<Note.Repost /> <Note.Repost />
<Note.Zap /> <Note.Zap />
<Note.Menu />
</div> </div>
<Note.Menu />
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@ export function TextNote({
<Note.Provider event={event}> <Note.Provider event={event}>
<Note.Root <Note.Root
className={cn( className={cn(
"mb-3 flex flex-col gap-2 border-b border-neutral-100 pb-3 dark:border-neutral-900", "mb-5 flex flex-col gap-2 border-b border-neutral-100 pb-5 dark:border-neutral-900",
className, className,
)} )}
> >
@ -21,16 +21,15 @@ export function TextNote({
<div className="flex gap-3"> <div className="flex gap-3">
<div className="size-11 shrink-0" /> <div className="size-11 shrink-0" />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<Note.Thread className="mb-2" /> <Note.Content className="mb-2" />
<Note.Content /> <Note.Thread />
<div className="mt-5 flex items-center justify-between"> <div className="mt-4 flex items-center justify-between">
<Note.Reaction /> <div className="-ml-1 inline-flex items-center gap-4">
<div className="inline-flex items-center gap-4">
<Note.Reply /> <Note.Reply />
<Note.Repost /> <Note.Repost />
<Note.Zap /> <Note.Zap />
<Note.Menu />
</div> </div>
<Note.Menu />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,9 +1,10 @@
import { useEvent } from "@lume/ark"; import { useEvent } from "@lume/ark";
import { LoaderIcon } from "@lume/icons"; import { LoaderIcon } from "@lume/icons";
import { Note, User } from "@lume/ui"; import { Box, Container, Note, User } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { WindowVirtualizer } from "virtua"; import { WindowVirtualizer } from "virtua";
import { ReplyList } from "./-components/replyList"; import { ReplyList } from "./-components/replyList";
import { Event } from "@lume/types";
export const Route = createLazyFileRoute("/events/$eventId")({ export const Route = createLazyFileRoute("/events/$eventId")({
component: Event, component: Event,
@ -29,45 +30,43 @@ function Event() {
return ( return (
<WindowVirtualizer> <WindowVirtualizer>
<div className="flex h-screen w-screen flex-col bg-gradient-to-tr from-neutral-200 to-neutral-100 dark:from-neutral-950 dark:to-neutral-900"> <Container withDrag>
<div data-tauri-drag-region className="h-11 w-full shrink-0" /> <Box>
<div className="flex h-full min-h-0 w-full"> <MainNote data={data} />
<div className="h-full w-full flex-1 px-2 pb-2"> {data ? <ReplyList eventId={eventId} /> : null}
<div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white px-3 shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5"> </Box>
<Note.Provider event={data}> </Container>
<Note.Root className="flex flex-col">
<div className="mb-2 flex items-center justify-between pt-4">
<User.Provider pubkey={data.pubkey}>
<User.Root className="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.Menu />
</div>
<Note.Thread className="mb-2" />
<Note.Content className="min-w-0" />
<div className="flex h-14 items-center justify-between">
<Note.Reaction />
<div className="inline-flex items-center gap-4">
<Note.Repost />
<Note.Zap />
</div>
</div>
</Note.Root>
</Note.Provider>
{data ? <ReplyList eventId={eventId} /> : null}
</div>
</div>
</div>
</div>
</WindowVirtualizer> </WindowVirtualizer>
); );
} }
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>
);
}

View File

@ -1,65 +1,47 @@
import { NavArrowDownIcon } from "@lume/icons";
import { EventWithReplies } from "@lume/types"; import { EventWithReplies } from "@lume/types";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import * as Collapsible from "@radix-ui/react-collapsible"; import { Note, User } from "@lume/ui";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Note } from "@lume/ui";
import { SubReply } from "./subReply"; import { SubReply } from "./subReply";
export function Reply({ event }: { event: EventWithReplies }) { export function Reply({ event }: { event: EventWithReplies }) {
const [t] = useTranslation();
const [open, setOpen] = useState(false);
return ( return (
<Collapsible.Root open={open} onOpenChange={setOpen}> <Note.Provider event={event}>
<Note.Provider event={event}> <Note.Root className="border-t border-neutral-100 pt-3 dark:border-neutral-900">
<Note.Root className="pt-2"> <User.Provider pubkey={event.pubkey}>
<div className="flex h-14 items-center justify-between"> <User.Root className="mb-2 flex items-center justify-between">
<Note.User className="flex-1 pr-2" /> <div className="inline-flex gap-2">
<Note.Menu /> <User.Avatar className="size-6 rounded-full" />
</div> <div className="inline-flex items-center gap-2">
<Note.Content /> <User.Name className="font-semibold" />
<div className="flex h-14 items-center justify-between"> <User.NIP05 className="text-base lowercase text-neutral-600 dark:text-neutral-400" />
{event.replies?.length > 0 ? ( </div>
<Collapsible.Trigger asChild>
<div className="inline-flex h-14 items-center gap-1 text-sm font-semibold text-neutral-600 dark:text-neutral-400">
<NavArrowDownIcon
className={cn("size-5", open ? "rotate-180 transform" : "")}
/>
{`${event.replies?.length} ${
event.replies?.length === 1
? t("note.reply.single")
: t("note.reply.plural")
}`}
</div>
</Collapsible.Trigger>
) : (
<div />
)}
<div className="inline-flex items-center gap-4">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</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> </div>
<div <Note.Menu />
className={cn( </div>
open <div
? "border-t border-neutral-100 pb-3 dark:border-neutral-900" 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 ? ( )}
<Collapsible.Content className="divide-y divide-neutral-100 pl-6 dark:divide-neutral-900"> >
{event.replies?.map((childEvent) => ( {event.replies?.length > 0
<SubReply key={childEvent.id} event={childEvent} /> ? event.replies?.map((childEvent) => (
))} <SubReply key={childEvent.id} event={childEvent} />
</Collapsible.Content> ))
) : null} : null}
</div> </div>
</Note.Root> </Note.Root>
</Note.Provider> </Note.Provider>
</Collapsible.Root>
); );
} }

View File

@ -27,12 +27,7 @@ export function ReplyList({
}, [eventId]); }, [eventId]);
return ( return (
<div <div className={cn("flex flex-col gap-3", className)}>
className={cn(
"flex flex-col divide-y divide-neutral-100 dark:divide-neutral-900",
className,
)}
>
{!data ? ( {!data ? (
<div className="mt-4 flex h-16 items-center justify-center p-3"> <div className="mt-4 flex h-16 items-center justify-center p-3">
<LoaderIcon className="h-5 w-5 animate-spin" /> <LoaderIcon className="h-5 w-5 animate-spin" />

View File

@ -1,18 +1,30 @@
import { Event } from "@lume/types"; import { Event } from "@lume/types";
import { Note } from "@lume/ui"; import { Note, User } from "@lume/ui";
export function SubReply({ event }: { event: Event; rootEventId?: string }) { export function SubReply({ event }: { event: Event; rootEventId?: string }) {
return ( return (
<Note.Provider event={event}> <Note.Provider event={event}>
<Note.Root className="py-2"> <Note.Root className="pt-3">
<div className="flex h-14 items-center justify-between"> <User.Provider pubkey={event.pubkey}>
<Note.User className="flex-1 pr-2" /> <User.Root className="mb-2 flex items-center justify-between">
<Note.Menu /> <div className="inline-flex gap-2">
</div> <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 /> <Note.Content />
<div className="mt-2 flex items-center justify-end gap-4"> <div className="mt-4 flex items-center justify-between">
<Note.Repost /> <div className="-ml-1 inline-flex items-center gap-4">
<Note.Zap /> <Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
<Note.Menu />
</div> </div>
</Note.Root> </Note.Root>
</Note.Provider> </Note.Provider>

View File

@ -394,15 +394,6 @@ export class Ark {
} }
} }
public async get_nwc_status() {
try {
const cmd: boolean = await invoke("get_nwc_status");
return cmd;
} catch {
return false;
}
}
public async set_nwc(uri: string) { public async set_nwc(uri: string) {
try { try {
const cmd: boolean = await invoke("set_nwc", { uri }); const cmd: boolean = await invoke("set_nwc", { uri });
@ -490,7 +481,7 @@ export class Ark {
title: "Thread", title: "Thread",
url: `/events/${id}`, url: `/events/${id}`,
minWidth: 500, minWidth: 500,
width: 500, width: 600,
height: 800, height: 800,
hiddenTitle: true, hiddenTitle: true,
titleBarStyle: "overlay", titleBarStyle: "overlay",

View File

@ -115,3 +115,4 @@ export * from "./src/searchFilled";
export * from "./src/arrowUp"; export * from "./src/arrowUp";
export * from "./src/arrowUpSquare"; export * from "./src/arrowUpSquare";
export * from "./src/arrowDown"; export * from "./src/arrowDown";
export * from "./src/link";

View File

@ -0,0 +1,24 @@
import { SVGProps } from "react";
export function LinkIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M20.5 13.5c0 1.395 0 2.092-.138 2.667a5 5 0 01-3.695 3.695C16.092 20 15.394 20 14 20h-1.5c-2.8 0-4.2 0-5.27-.545a5 5 0 01-2.185-2.185C4.5 16.2 4.5 14.8 4.5 12v-.5c0-2.33 0-3.495.38-4.413A5 5 0 017.588 4.38C8.363 4.059 9.317 4.009 11 4m9.26 5.454c.262-1.633.31-3.285.142-4.914a.495.495 0 00-.142-.3m0 0a.496.496 0 00-.301-.143 18.815 18.815 0 00-4.913.142m5.214 0L10.5 14"
></path>
</svg>
);
}

20
packages/ui/src/box.tsx Normal file
View File

@ -0,0 +1,20 @@
import { cn } from "@lume/utils";
import { ReactNode } from "react";
export function Box({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return (
<div className={cn("flex h-full min-h-0 w-full", className)}>
<div className="h-full w-full flex-1 px-2 pb-2">
<div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white px-3 pt-3 shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
{children}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,26 @@
import { cn } from "@lume/utils";
import { ReactNode } from "react";
export function Container({
children,
withDrag = false,
className,
}: {
children: ReactNode;
withDrag?: boolean;
className?: string;
}) {
return (
<div
className={cn(
"flex h-screen w-screen flex-col bg-gradient-to-tr from-neutral-200 to-neutral-100 dark:from-neutral-950 dark:to-neutral-900",
className,
)}
>
{withDrag ? (
<div data-tauri-drag-region className="h-11 w-full shrink-0" />
) : null}
{children}
</div>
);
}

View File

@ -1,5 +1,8 @@
// New
export * from "./user"; export * from "./user";
export * from "./note"; export * from "./note";
export * from "./column"; export * from "./column";
export * from "./emptyFeed"; export * from "./emptyFeed";
// UI
export * from "./container";
export * from "./box";

View File

@ -19,7 +19,7 @@ export function NoteReply() {
<Tooltip.Trigger asChild> <Tooltip.Trigger asChild>
<button <button
type="button" type="button"
className="size07 group inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200" className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
> >
<ReplyIcon className="size-5 group-hover:text-blue-500" /> <ReplyIcon className="size-5 group-hover:text-blue-500" />
</button> </button>

View File

@ -43,7 +43,7 @@ export function NoteRepost() {
<Tooltip.Trigger asChild> <Tooltip.Trigger asChild>
<button <button
type="button" type="button"
className="size07 group inline-flex items-center justify-center text-neutral-800 dark:text-neutral-200" className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
> >
{loading ? ( {loading ? (
<LoaderIcon className="size-4 animate-spin" /> <LoaderIcon className="size-4 animate-spin" />

View File

@ -4,7 +4,7 @@ export function NoteZap() {
return ( return (
<button <button
type="button" type="button"
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-800 dark:text-neutral-200" className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
> >
<ZapIcon className="size-5 group-hover:text-blue-500" /> <ZapIcon className="size-5 group-hover:text-blue-500" />
</button> </button>

View File

@ -109,11 +109,11 @@ export function NoteChild({
<User.Provider pubkey={data.pubkey}> <User.Provider pubkey={data.pubkey}>
<User.Root> <User.Root>
<User.Avatar className="size-10 shrink-0 rounded-full object-cover" /> <User.Avatar className="size-10 shrink-0 rounded-full object-cover" />
<div className="absolute left-3 top-3 inline-flex items-center gap-1.5 font-semibold leading-tight"> <div className="absolute left-3 top-3">
<User.Name className="max-w-[10rem] truncate" /> <User.Name className="inline font-semibold" />{" "}
<div className="font-normal text-neutral-700 dark:text-neutral-300"> <span className="inline font-normal text-neutral-700 dark:text-neutral-300">
{isRoot ? t("note.posted") : t("note.replied")}: {isRoot ? t("note.posted") : t("note.replied")}:
</div> </span>
</div> </div>
</User.Root> </User.Root>
</User.Provider> </User.Provider>

View File

@ -8,104 +8,38 @@ import {
canPreview, canPreview,
cn, cn,
} from "@lume/utils"; } from "@lume/utils";
import getUrls from "get-urls"; import { NIP89 } from "./nip89";
import { nanoid } from "nanoid"; import { useNoteContext } from "./provider";
import { ReactNode, useMemo } from "react"; import { ReactNode, useMemo } from "react";
import reactStringReplace from "react-string-replace"; import reactStringReplace from "react-string-replace";
import { stripHtml } from "string-strip-html"; import { nanoid } from "nanoid";
import { Hashtag } from "./mentions/hashtag";
import { MentionNote } from "./mentions/note";
import { MentionUser } from "./mentions/user"; import { MentionUser } from "./mentions/user";
import { NIP89 } from "./nip89"; import { MentionNote } from "./mentions/note";
import { ImagePreview } from "./preview/image"; import { Hashtag } from "./mentions/hashtag";
import { LinkPreview } from "./preview/link";
import { VideoPreview } from "./preview/video"; import { VideoPreview } from "./preview/video";
import { useNoteContext } from "./provider"; import { stripHtml } from "string-strip-html";
import getUrl from "get-urls";
import { ImagePreview } from "./preview/image";
export function NoteContent({ className }: { className?: string }) { export function NoteContent({ className }: { className?: string }) {
const event = useNoteContext(); const event = useNoteContext();
const content = useMemo(() => {
const richContent = useMemo(() => { const text = stripHtml(event.content.trim()).result;
if (event.kind !== Kind.Text) return event.content;
let parsedContent: string | ReactNode[] = stripHtml(event.content).result;
let linkPreview: string = undefined;
let images: string[] = [];
let videos: string[] = [];
let audios: string[] = [];
let events: string[] = [];
const text = parsedContent;
const words = text.split(/( |\n)/); const words = text.split(/( |\n)/);
const urls = [...getUrls(text)]; const urls = [...getUrl(text)];
images = urls.filter((word) => // @ts-ignore, kaboom !!!
IMAGES.some((el) => { let parsedContent: ReactNode[] = text;
const url = new URL(word);
const extension = url.pathname.split(".")[1];
if (extension === el) return true;
return false;
}),
);
videos = urls.filter((word) =>
VIDEOS.some((el) => {
const url = new URL(word);
const extension = url.pathname.split(".")[1];
if (extension === el) return true;
return false;
}),
);
audios = urls.filter((word) =>
AUDIOS.some((el) => {
const url = new URL(word);
const extension = url.pathname.split(".")[1];
if (extension === el) return true;
return false;
}),
);
events = words.filter((word) =>
NOSTR_EVENTS.some((el) => word.startsWith(el)),
);
const hashtags = words.filter((word) => word.startsWith("#")); const hashtags = words.filter((word) => word.startsWith("#"));
const events = words.filter((word) =>
NOSTR_EVENTS.some((el) => word.startsWith(el)),
);
const mentions = words.filter((word) => const mentions = words.filter((word) =>
NOSTR_MENTIONS.some((el) => word.startsWith(el)), NOSTR_MENTIONS.some((el) => word.startsWith(el)),
); );
try { try {
if (images.length) {
for (const image of images) {
parsedContent = reactStringReplace(
parsedContent,
image,
(match, i) => <ImagePreview key={match + i} url={match} />,
);
}
}
if (videos.length) {
for (const video of videos) {
parsedContent = reactStringReplace(
parsedContent,
video,
(match, i) => <VideoPreview key={match + i} url={match} />,
);
}
}
if (audios.length) {
for (const audio of audios) {
parsedContent = reactStringReplace(
parsedContent,
audio,
(match, i) => <VideoPreview key={match + i} url={match} />,
);
}
}
if (hashtags.length) { if (hashtags.length) {
for (const hashtag of hashtags) { for (const hashtag of hashtags) {
const regex = new RegExp(`(|^)${hashtag}\\b`, "g"); const regex = new RegExp(`(|^)${hashtag}\\b`, "g");
@ -137,41 +71,54 @@ export function NoteContent({ className }: { className?: string }) {
parsedContent = reactStringReplace( parsedContent = reactStringReplace(
parsedContent, parsedContent,
/(https?:\/\/\S+)/g, /(https?:\/\/\S+)/gi,
(match, i) => { (match, i) => {
const url = new URL(match); try {
const url = new URL(match);
const ext = url.pathname.split(".")[1];
if (!linkPreview && canPreview(match)) { if (IMAGES.includes(ext)) {
linkPreview = match; return <ImagePreview key={match + i} url={url.toString()} />;
return <LinkPreview key={match + i} url={url.toString()} />; }
if (VIDEOS.includes(ext)) {
return <VideoPreview key={match + i} url={url.toString()} />;
}
if (AUDIOS.includes(ext)) {
return <VideoPreview key={match + i} url={url.toString()} />;
}
return (
<a
key={match + i}
href={match}
target="_blank"
rel="noreferrer"
className="content-break w-full font-normal text-blue-500 hover:text-blue-600"
>
{match}
</a>
);
} catch {
return (
<a
key={match + i}
href={match}
target="_blank"
rel="noreferrer"
className="content-break w-full font-normal text-blue-500 hover:text-blue-600"
>
{match}
</a>
);
} }
return (
<a
key={match + i}
href={url.toString()}
target="_blank"
rel="noreferrer"
className="content-break inline-block w-full truncate font-normal text-blue-500 hover:text-blue-600"
>
{url.toString()}
</a>
);
}, },
); );
parsedContent = reactStringReplace(parsedContent, "\n", () => {
return <br key={nanoid()} />;
});
if (typeof parsedContent[0] === "string") {
parsedContent[0] = parsedContent[0].trimStart();
}
return parsedContent; return parsedContent;
} catch (e) { } catch (e) {
console.warn(event.id, `[parser] parse failed: ${e}`); return text;
return parsedContent;
} }
}, []); }, []);
@ -180,9 +127,9 @@ export function NoteContent({ className }: { className?: string }) {
} }
return ( return (
<div className={cn(className)}> <div className={cn("select-text", className)}>
<div className="content-break select-text whitespace-pre-line text-balance leading-normal"> <div className="content-break whitespace-pre-line text-balance leading-normal">
{richContent} {content}
</div> </div>
</div> </div>
); );

View File

@ -6,6 +6,8 @@ import { User } from "../../user";
import { Hashtag } from "./hashtag"; import { Hashtag } from "./hashtag";
import { MentionUser } from "./user"; import { MentionUser } from "./user";
import { useArk, useEvent } from "@lume/ark"; import { useArk, useEvent } from "@lume/ark";
import { LinkIcon } from "@lume/icons";
import { stripHtml } from "string-strip-html";
export function MentionNote({ export function MentionNote({
eventId, eventId,
@ -18,14 +20,15 @@ export function MentionNote({
const { isLoading, isError, data } = useEvent(eventId); const { isLoading, isError, data } = useEvent(eventId);
const ark = useArk(); const ark = useArk();
const richContent = useMemo(() => { const content = useMemo(() => {
if (!data) return ""; if (!data) return "";
let parsedContent: string | ReactNode[] = data.content; const text = stripHtml(data.content.trim()).result;
const text = parsedContent as string;
const words = text.split(/( |\n)/); const words = text.split(/( |\n)/);
// @ts-ignore, kaboom !!!
let parsedContent: ReactNode[] = text;
const hashtags = words.filter((word) => word.startsWith("#")); const hashtags = words.filter((word) => word.startsWith("#"));
const mentions = words.filter((word) => const mentions = words.filter((word) =>
NOSTR_MENTIONS.some((el) => word.startsWith(el)), NOSTR_MENTIONS.some((el) => word.startsWith(el)),
@ -75,8 +78,7 @@ export function MentionNote({
return parsedContent; return parsedContent;
} catch (e) { } catch (e) {
console.log(e); return text;
return parsedContent;
} }
}, [data]); }, [data]);
@ -118,16 +120,17 @@ export function MentionNote({
</User.Root> </User.Root>
</User.Provider> </User.Provider>
<div className="line-clamp-4 select-text whitespace-normal text-balance leading-normal"> <div className="line-clamp-4 select-text whitespace-normal text-balance leading-normal">
{richContent} {content}
</div> </div>
{openable ? ( {openable ? (
<div className="flex h-10 items-center justify-between"> <div className="flex h-10 items-center justify-between">
<button <button
type="button" type="button"
onClick={() => ark.open_thread(data.id)} onClick={() => ark.open_thread(data.id)}
className="text-blue-500 hover:text-blue-600" className="inline-flex items-center gap-1 text-sm text-neutral-600 hover:text-blue-500 dark:text-neutral-400"
> >
{t("note.showMore")} {t("note.showMore")}
<LinkIcon className="size-4" />
</button> </button>
</div> </div>
) : ( ) : (

View File

@ -1,38 +1,21 @@
import { useProfile } from "@lume/ark"; import { useArk, useProfile } from "@lume/ark";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { displayNpub } from "@lume/utils";
import { useTranslation } from "react-i18next";
export function MentionUser({ pubkey }: { pubkey: string }) { export function MentionUser({ pubkey }: { pubkey: string }) {
const { isLoading, isError, user } = useProfile(pubkey); const ark = useArk();
const { t } = useTranslation(); const { isLoading, isError, profile } = useProfile(pubkey);
return ( return (
<DropdownMenu.Root> <button
<DropdownMenu.Trigger className="break-words text-start text-blue-500 hover:text-blue-600"> type="button"
{isLoading onClick={() => ark.open_profile(pubkey)}
? "@anon" className="break-words text-start text-blue-500 hover:text-blue-600"
: isError >
? pubkey {isLoading
: `@${user?.name || user?.display_name || user?.name || "anon"}`} ? "@anon"
</DropdownMenu.Trigger> : isError
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-2xl bg-white/50 p-2 ring-1 ring-black/10 backdrop-blur-2xl focus:outline-none dark:bg-black/50 dark:ring-white/10"> ? displayNpub(pubkey, 16)
<DropdownMenu.Item asChild> : `@${profile?.name || profile?.display_name || profile?.name || "anon"}`}
<a </button>
href={`/users/${pubkey}`}
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
>
{t("note.buttons.viewProfile")}
</a>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
type="button"
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
>
{t("note.buttons.pin")}
</button>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
); );
} }

View File

@ -34,7 +34,7 @@ export function NoteMenu() {
<DropdownMenu.Trigger asChild> <DropdownMenu.Trigger asChild>
<button <button
type="button" type="button"
className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200" className="group inline-flex size-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
> >
<HorizontalDotsIcon className="size-5" /> <HorizontalDotsIcon className="size-5" />
</button> </button>

View File

@ -35,10 +35,7 @@ export function ImagePreview({ url }: { url: string }) {
return ( return (
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> // biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
<div <div onClick={open} className="group relative my-1 rounded-2xl">
onClick={open}
className="group relative my-1 rounded-2xl border border-black/10 dark:border-white/10"
>
<img <img
src={url} src={url}
alt={url} alt={url}
@ -51,12 +48,12 @@ export function ImagePreview({ url }: { url: string }) {
<button <button
type="button" type="button"
onClick={(e) => downloadImage(e)} onClick={(e) => downloadImage(e)}
className="absolute right-2 top-2 z-10 hidden size-10 items-center justify-center rounded-lg bg-white/10 text-black/70 backdrop-blur-2xl hover:bg-blue-500 hover:text-white group-hover:inline-flex" className="absolute right-2 top-2 z-20 hidden size-8 items-center justify-center rounded-md bg-white/10 text-white/70 backdrop-blur-2xl hover:bg-blue-500 hover:text-white group-hover:inline-flex"
> >
{downloaded ? ( {downloaded ? (
<CheckCircleIcon className="size-5" /> <CheckCircleIcon className="size-4" />
) : ( ) : (
<DownloadIcon className="size-5" /> <DownloadIcon className="size-4" />
)} )}
</button> </button>
</div> </div>

View File

@ -9,7 +9,7 @@ import {
export function VideoPreview({ url }: { url: string }) { export function VideoPreview({ url }: { url: string }) {
return ( return (
<div className="my-1 w-full overflow-hidden rounded-2xl border border-black/10 dark:border-white/10"> <div className="my-1">
<MediaController> <MediaController>
<video <video
slot="media" slot="media"

View File

@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { Note } from "."; import { Note } from ".";
import { useNoteContext } from "./provider"; import { useNoteContext } from "./provider";
import { useArk } from "@lume/ark"; import { useArk } from "@lume/ark";
import { LinkIcon } from "@lume/icons";
export function NoteThread({ className }: { className?: string }) { export function NoteThread({ className }: { className?: string }) {
const ark = useArk(); const ark = useArk();
@ -25,15 +26,16 @@ export function NoteThread({ className }: { className?: string }) {
{thread.replyEventId ? ( {thread.replyEventId ? (
<Note.Child eventId={thread.replyEventId} /> <Note.Child eventId={thread.replyEventId} />
) : null} ) : null}
<div className="inline-flex items-center justify-between"> <div className="inline-flex justify-end">
<button <button
type="button" type="button"
onClick={() => onClick={() =>
ark.open_thread(thread.rootEventId || thread.rootEventId) ark.open_thread(thread.rootEventId || thread.replyEventId)
} }
className="self-start text-blue-500 hover:text-blue-600" className="inline-flex items-center gap-1 text-sm text-neutral-600 hover:text-blue-500 dark:text-neutral-400"
> >
{t("note.showThread")} {t("note.showThread")}
<LinkIcon className="size-4" />
</button> </button>
</div> </div>
</div> </div>

View File

@ -14,42 +14,44 @@ export function NoteUser({ className }: { className?: string }) {
<User.Root <User.Root
className={cn("flex items-start justify-between", className)} className={cn("flex items-start justify-between", className)}
> >
<div className="flex gap-3"> <div className="flex w-full gap-3">
<HoverCard.Trigger> <HoverCard.Trigger>
<User.Avatar className="size-11 shrink-0 rounded-full object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" /> <User.Avatar className="size-11 shrink-0 rounded-full object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
</HoverCard.Trigger> </HoverCard.Trigger>
<div> <div className="flex-1">
<User.Name className="font-semibold leading-tight text-neutral-950 dark:text-neutral-50" /> <div className="flex w-full items-center justify-between">
<User.Name className="font-semibold leading-tight text-neutral-950 dark:text-neutral-50" />
<User.Time
time={event.created_at}
className="leading-tight text-neutral-600 dark:text-neutral-400"
/>
</div>
<User.NIP05 className="leading-tight text-neutral-600 dark:text-neutral-400" /> <User.NIP05 className="leading-tight text-neutral-600 dark:text-neutral-400" />
</div> </div>
</div> </div>
<User.Time
time={event.created_at}
className="text-neutral-600 dark:text-neutral-400"
/>
</User.Root> </User.Root>
<HoverCard.Portal> <HoverCard.Portal>
<HoverCard.Content <HoverCard.Content
className="data-[side=bottom]:animate-slideUpAndFade w-[300px] rounded-xl bg-white p-3 shadow-lg shadow-neutral-500/20 data-[state=open]:transition-all dark:border dark:border-neutral-800 dark:bg-neutral-900 dark:shadow-none" className="data-[side=bottom]:animate-slideUpAndFade w-[300px] rounded-xl bg-black p-3 data-[state=open]:transition-all dark:bg-white dark:shadow-none"
sideOffset={5} sideOffset={5}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<User.Avatar className="size-11 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" /> <User.Avatar className="size-11 rounded-lg object-cover" />
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div> <div>
<User.Name className="font-semibold leading-tight" /> <User.Name className="font-semibold leading-tight text-white dark:text-neutral-900" />
<User.NIP05 className="text-neutral-600 dark:text-neutral-400" /> <User.NIP05 className="leading-tight text-neutral-400 dark:text-neutral-500" />
</div> </div>
<User.About className="line-clamp-3" /> <User.About className="line-clamp-3 text-sm text-white dark:text-neutral-900" />
<button <button
onClick={() => ark.open_profile(event.pubkey)} onClick={() => ark.open_profile(event.pubkey)}
className="mt-2 inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800" className="mt-2 inline-flex h-9 w-full items-center justify-center rounded-lg bg-white text-sm font-medium hover:bg-neutral-100 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-neutral-200"
> >
View profile View profile
</button> </button>
</div> </div>
</div> </div>
<HoverCard.Arrow className="fill-white dark:fill-neutral-800" /> <HoverCard.Arrow className="fill-black dark:fill-white" />
</HoverCard.Content> </HoverCard.Content>
</HoverCard.Portal> </HoverCard.Portal>
</HoverCard.Root> </HoverCard.Root>

View File

@ -1,7 +1,13 @@
import { cn, displayNpub } from "@lume/utils"; import { cn, displayNpub } from "@lume/utils";
import { useUserContext } from "./provider"; import { useUserContext } from "./provider";
export function UserName({ className }: { className?: string }) { export function UserName({
className,
suffix,
}: {
className?: string;
suffix?: string;
}) {
const user = useUserContext(); const user = useUserContext();
return ( return (
@ -9,6 +15,7 @@ export function UserName({ className }: { className?: string }) {
{user.profile?.display_name || {user.profile?.display_name ||
user.profile?.name || user.profile?.name ||
displayNpub(user.pubkey, 16)} displayNpub(user.pubkey, 16)}
{suffix}
</div> </div>
); );
} }

View File

@ -10,5 +10,9 @@ export function UserTime({
}) { }) {
const createdAt = useMemo(() => formatCreatedAt(time), [time]); const createdAt = useMemo(() => formatCreatedAt(time), [time]);
return <div className={cn("leading-tight", className)}>{createdAt}</div>; return (
<div className={cn("text-neutral-600 dark:text-neutral-400", className)}>
{createdAt}
</div>
);
} }

View File

@ -868,7 +868,7 @@ importers:
version: 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) version: 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collapsible': '@radix-ui/react-collapsible':
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) version: 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-dialog': '@radix-ui/react-dialog':
specifier: ^1.0.5 specifier: ^1.0.5
version: 1.0.5(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) version: 1.0.5(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
@ -1885,13 +1885,13 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/primitive': 1.0.1 '@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-collapsible': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collection': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-collection': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
@ -1916,7 +1916,7 @@ packages:
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-dialog': 1.0.5(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dialog': 1.0.5(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
@ -1937,7 +1937,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
@ -1958,7 +1958,7 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
@ -2022,6 +2022,33 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-collapsible@1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.9
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collection@1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-collection@1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies: peerDependencies:
@ -2038,7 +2065,7 @@ packages:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
@ -2095,8 +2122,8 @@ packages:
'@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-presence': 1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
@ -2136,7 +2163,7 @@ packages:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/primitive': 1.0.1 '@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
@ -2163,7 +2190,7 @@ packages:
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-menu': 2.0.6(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-menu': 2.0.6(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
@ -2199,7 +2226,7 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
@ -2226,8 +2253,8 @@ packages:
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-presence': 1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
@ -2274,8 +2301,8 @@ packages:
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-presence': 1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-roving-focus': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-roving-focus': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0)
@ -2309,8 +2336,8 @@ packages:
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-presence': 1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
@ -2338,7 +2365,7 @@ packages:
'@radix-ui/react-arrow': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-arrow': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.58)(react@18.2.0)
@ -2363,7 +2390,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
@ -2391,6 +2418,27 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-presence@1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.9
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
peerDependencies: peerDependencies:
@ -2412,6 +2460,26 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-primitive@1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.9
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-roving-focus@1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-roving-focus@1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
peerDependencies: peerDependencies:
@ -2432,7 +2500,7 @@ packages:
'@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
@ -2476,8 +2544,8 @@ packages:
'@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-id': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-popper': 1.1.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-portal': 1.0.4(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-presence': 1.0.1(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.58)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-visually-hidden': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
@ -2602,7 +2670,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.58)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.58 '@types/react': 18.2.58
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)