refactor(ark): add note provider

This commit is contained in:
2023-12-27 10:52:13 +07:00
parent 3956ed622d
commit b4dac2d477
38 changed files with 793 additions and 1140 deletions

View File

@@ -1,30 +1,25 @@
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
export function WelcomeScreen() { export function WelcomeScreen() {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full flex-col items-center justify-center gap-10">
<div className="mx-auto flex w-full max-w-xs flex-col gap-10"> <div className="mx-auto max-w-md flex w-full text-center flex-col items-center">
<div className="text-center"> <h1 className="mb-2 text-4xl font-semibold">Welcome to Nostr!</h1>
<img src="/icon.png" alt="Lume's logo" className="mx-auto mb-1 h-auto w-16" /> </div>
<h1 className="text-2xl"> <div className="flex flex-col gap-2 px-8 mx-auto max-w-sm">
Welcome to <span className="font-bold">Lume</span> <Link
</h1> to="/auth/create"
</div> className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-blue-500 font-medium text-white hover:bg-blue-600"
<div className="flex flex-col gap-2 px-8"> >
<Link Create new account
to="/auth/create" </Link>
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-blue-500 font-medium text-white hover:bg-blue-600" <Link
> to="/auth/import"
Create new account className="inline-flex h-11 w-full items-center justify-center rounded-lg font-medium text-neutral-900 hover:bg-neutral-100 dark:text-neutral-100 dark:hover:bg-neutral-900"
</Link> >
<Link Log in
to="/auth/import" </Link>
className="inline-flex h-11 w-full items-center justify-center rounded-lg font-medium text-neutral-900 hover:bg-neutral-100 dark:text-neutral-100 dark:hover:bg-neutral-900" </div>
> </div>
Log in );
</Link>
</div>
</div>
</div>
);
} }

View File

@@ -0,0 +1,119 @@
import { RepostNote, TextNote, useArk, useStorage } from "@lume/ark";
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
import { FETCH_LIMIT } from "@lume/utils";
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
import { useInfiniteQuery } from "@tanstack/react-query";
import { useEffect, useMemo, useRef } from "react";
import { CacheSnapshot, VList, VListHandle } from "virtua";
export function HomeRoute({ colKey }: { colKey: string }) {
const ark = useArk();
const storage = useStorage();
const ref = useRef<VListHandle>();
const cacheKey = "notification-vlist";
const [offset, cache] = useMemo(() => {
const serialized = sessionStorage.getItem(cacheKey);
if (!serialized) return [];
return JSON.parse(serialized) as [number, CacheSnapshot];
}, []);
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({
queryKey: [colKey],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
const events = await ark.getInfiniteEvents({
filter: {
kinds: [NDKKind.Text, NDKKind.Repost],
authors: !storage.account.contacts.length
? [storage.account.pubkey]
: storage.account.contacts,
},
limit: FETCH_LIMIT,
pageParam,
signal,
});
return events;
},
getNextPageParam: (lastPage) => {
const lastEvent = lastPage.at(-1);
if (!lastEvent) return;
return lastEvent.created_at - 1;
},
refetchOnWindowFocus: false,
});
const allEvents = useMemo(
() => (data ? data.pages.flatMap((page) => page) : []),
[data],
);
const renderItem = (event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
return <TextNote key={event.id} event={event} className="mt-3" />;
case NDKKind.Repost:
return <RepostNote key={event.id} event={event} className="mt-3" />;
default:
return <TextNote key={event.id} event={event} className="mt-3" />;
}
};
useEffect(() => {
if (!ref.current) return;
const handle = ref.current;
if (offset) {
handle.scrollTo(offset);
}
return () => {
sessionStorage.setItem(
cacheKey,
JSON.stringify([handle.scrollOffset, handle.cache]),
);
};
}, []);
return (
<div className="w-full h-full">
<VList ref={ref} cache={cache} overscan={2} className="flex-1 px-3">
{isLoading ? (
<div className="inline-flex h-16 items-center justify-center gap-2 px-3 py-1.5">
<LoaderIcon className="size-5" />
Loading
</div>
) : (
allEvents.map((item) => renderItem(item))
)}
<div className="flex h-16 items-center justify-center">
{hasNextPage ? (
<button
type="button"
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none"
>
{isFetchingNextPage ? (
<LoaderIcon className="h-5 w-5 animate-spin" />
) : (
<>
<ArrowRightCircleIcon className="h-5 w-5" />
Load more
</>
)}
</button>
) : null}
</div>
</VList>
</div>
);
}

View File

@@ -1 +0,0 @@
export * from "./notification";

View File

@@ -0,0 +1,21 @@
import { Column } from "@lume/ark";
import { BellIcon } from "@lume/icons";
import { HomeRoute } from "./home";
export function Notification() {
const colKey = "notification";
return (
<Column.Root>
<Column.Header
id="9999"
queryKey={[colKey]}
title="Notifications"
icon={<BellIcon className="size-4" />}
/>
<Column.Content>
<Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
</Column.Content>
</Column.Root>
);
}

View File

@@ -1,173 +0,0 @@
import { NoteSkeleton, TextNote, Widget, useArk, useStorage } from "@lume/ark";
import {
AnnouncementIcon,
ArrowRightCircleIcon,
LoaderIcon,
} from "@lume/icons";
import { FETCH_LIMIT, sendNativeNotification } from "@lume/utils";
import { NDKEvent, NDKKind, NDKSubscription } from "@nostr-dev-kit/ndk";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useMemo } from "react";
import { VList } from "virtua";
export function NotificationColumn() {
const ark = useArk();
const storage = useStorage();
const queryClient = useQueryClient();
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({
queryKey: ["notification"],
initialPageParam: 0,
queryFn: async ({
signal,
pageParam,
}: {
signal: AbortSignal;
pageParam: number;
}) => {
const events = await ark.getInfiniteEvents({
filter: {
kinds: [
NDKKind.Text,
NDKKind.Repost,
NDKKind.Reaction,
NDKKind.Zap,
],
"#p": [storage.account.pubkey],
},
limit: FETCH_LIMIT,
pageParam,
signal,
});
return events;
},
getNextPageParam: (lastPage) => {
const lastEvent = lastPage.at(-1);
if (!lastEvent) return;
return lastEvent.created_at - 1;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
staleTime: Infinity,
});
const allEvents = useMemo(
() => (data ? data.pages.flatMap((page) => page) : []),
[data],
);
const renderEvent = (event: NDKEvent) => {
if (event.pubkey === storage.account.pubkey) return null;
return <TextNote key={event.id} event={event} />;
};
useEffect(() => {
let sub: NDKSubscription = undefined;
if (status === "success" && storage.account) {
const filter = {
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
"#p": [storage.account.pubkey],
since: Math.floor(Date.now() / 1000),
};
sub = ark.subscribe({
filter,
closeOnEose: false,
cb: async (event) => {
queryClient.setQueryData(
["notification"],
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
...prev,
pages: [[event], ...prev.pages],
}),
);
const profile = await ark.getUserProfile({ pubkey: event.pubkey });
switch (event.kind) {
case NDKKind.Text:
return await sendNativeNotification(
`${
profile.displayName || profile.name
} has replied to your note`,
);
case NDKKind.Repost:
return await sendNativeNotification(
`${
profile.displayName || profile.name
} has reposted to your note`,
);
case NDKKind.Reaction:
return await sendNativeNotification(
`${profile.displayName || profile.name} has reacted ${
event.content
} to your note`,
);
case NDKKind.Zap:
return await sendNativeNotification(
`${
profile.displayName || profile.name
} has zapped to your note`,
);
default:
break;
}
},
});
}
return () => {
if (sub) sub.stop();
};
}, [status]);
return (
<Widget.Root>
<Widget.Header
id="9998"
queryKey={["notification"]}
title="Notification"
icon={<AnnouncementIcon className="h-5 w-5" />}
/>
<Widget.Content>
<VList className="flex-1" overscan={2}>
{status === "pending" ? (
<NoteSkeleton />
) : allEvents.length < 1 ? (
<div className="my-3 flex w-full items-center justify-center gap-2">
<div>🎉</div>
<p className="text-center font-medium text-neutral-900 dark:text-neutral-100">
Hmm! Nothing new yet.
</p>
</div>
) : (
allEvents.map((event) => renderEvent(event))
)}
<div className="flex h-16 items-center justify-center px-3 pb-3">
{hasNextPage ? (
<button
type="button"
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none"
>
{isFetchingNextPage ? (
<LoaderIcon className="h-4 w-4 animate-spin" />
) : (
<>
<ArrowRightCircleIcon className="h-5 w-5" />
Load more
</>
)}
</button>
) : null}
</div>
</VList>
</Widget.Content>
</Widget.Root>
);
}

View File

@@ -6,7 +6,7 @@ import { useInfiniteQuery } from "@tanstack/react-query";
import { useEffect, useMemo, useRef } from "react"; import { useEffect, useMemo, useRef } from "react";
import { CacheSnapshot, VList, VListHandle } from "virtua"; import { CacheSnapshot, VList, VListHandle } from "virtua";
export function HomeRoute() { export function HomeRoute({ colKey }: { colKey: string }) {
const ark = useArk(); const ark = useArk();
const storage = useStorage(); const storage = useStorage();
const ref = useRef<VListHandle>(); const ref = useRef<VListHandle>();
@@ -20,7 +20,7 @@ export function HomeRoute() {
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({ useInfiniteQuery({
queryKey: ["newsfeed"], queryKey: [colKey],
initialPageParam: 0, initialPageParam: 0,
queryFn: async ({ queryFn: async ({
signal, signal,
@@ -61,9 +61,9 @@ export function HomeRoute() {
case NDKKind.Text: case NDKKind.Text:
return <TextNote key={event.id} event={event} className="mt-3" />; return <TextNote key={event.id} event={event} className="mt-3" />;
case NDKKind.Repost: case NDKKind.Repost:
return <RepostNote key={event.id} event={event} />; return <RepostNote key={event.id} event={event} className="mt-3" />;
default: default:
return <TextNote key={event.id} event={event} />; return <TextNote key={event.id} event={event} className="mt-3" />;
} }
}; };

View File

@@ -1,20 +1,48 @@
import { Column } from "@lume/ark"; import { Column, useStorage } from "@lume/ark";
import { TimelineIcon } from "@lume/icons"; import { TimelineIcon } from "@lume/icons";
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
import { useQueryClient } from "@tanstack/react-query";
import { useRef } from "react";
import { EventRoute } from "./event"; import { EventRoute } from "./event";
import { HomeRoute } from "./home"; import { HomeRoute } from "./home";
import { UserRoute } from "./user"; import { UserRoute } from "./user";
export function Timeline() { export function Timeline() {
const colKey = "newsfeed";
const storage = useStorage();
const queryClient = useQueryClient();
const since = useRef(Math.floor(Date.now() / 1000));
const refreshTimeline = async (events: NDKEvent[]) => {
await queryClient.setQueryData(
[colKey],
(prev: { pageParams: number; pages: Array<NDKEvent[]> }) => ({
...prev,
pages: [[...events], ...prev.pages],
}),
);
};
return ( return (
<Column.Root> <Column.Root>
<Column.Header <Column.Header
id="9999" id="9999"
queryKey={["newsfeed"]} queryKey={[colKey]}
title="Timeline" title="Timeline"
icon={<TimelineIcon className="size-4" />} icon={<TimelineIcon className="size-4" />}
/> />
<Column.Live
filter={{
kinds: [NDKKind.Text, NDKKind.Repost],
authors: !storage.account.contacts.length
? [storage.account.pubkey]
: storage.account.contacts,
since: since.current,
}}
onClick={refreshTimeline}
/>
<Column.Content> <Column.Content>
<Column.Route path="/" element={<HomeRoute />} /> <Column.Route path="/" element={<HomeRoute colKey={colKey} />} />
<Column.Route path="/events/:id" element={<EventRoute />} /> <Column.Route path="/events/:id" element={<EventRoute />} />
<Column.Route path="/users/:id" element={<UserRoute />} /> <Column.Route path="/users/:id" element={<UserRoute />} />
</Column.Content> </Column.Content>

View File

@@ -15,6 +15,7 @@
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.14.2", "@tanstack/react-query": "^5.14.2",

View File

@@ -1,9 +1,5 @@
import { LumeStorage } from "@lume/storage"; import { LumeStorage } from "@lume/storage";
import { import { type NDKEventWithReplies, type NIP05 } from "@lume/types";
type Account,
type NDKEventWithReplies,
type NIP05,
} from "@lume/types";
import NDK, { import NDK, {
NDKEvent, NDKEvent,
NDKFilter, NDKFilter,
@@ -26,7 +22,6 @@ export class Ark {
#storage: LumeStorage; #storage: LumeStorage;
#fetcher: NostrFetcher; #fetcher: NostrFetcher;
public ndk: NDK; public ndk: NDK;
public account: Account;
constructor({ constructor({
ndk, ndk,
@@ -36,7 +31,6 @@ export class Ark {
}: { }: {
ndk: NDK; ndk: NDK;
storage: LumeStorage; storage: LumeStorage;
fetcher: NostrFetcher; fetcher: NostrFetcher;
}) { }) {
this.ndk = ndk; this.ndk = ndk;

View File

@@ -8,11 +8,17 @@ export function ColumnLiveWidget({
onClick, onClick,
}: { }: {
filter: NDKFilter; filter: NDKFilter;
onClick: () => void; onClick: (event: NDKEvent[]) => void;
}) { }) {
const ark = useArk(); const ark = useArk();
const [events, setEvents] = useState<NDKEvent[]>([]); const [events, setEvents] = useState<NDKEvent[]>([]);
const update = async () => {
onClick(events);
// reset
setEvents([]);
};
useEffect(() => { useEffect(() => {
const sub = ark.subscribe({ const sub = ark.subscribe({
filter, filter,
@@ -31,7 +37,7 @@ export function ColumnLiveWidget({
<div className="absolute left-0 top-11 z-50 flex h-11 w-full items-center justify-center"> <div className="absolute left-0 top-11 z-50 flex h-11 w-full items-center justify-center">
<button <button
type="button" type="button"
onClick={onClick} onClick={update}
className="inline-flex h-9 w-max items-center justify-center gap-1 rounded-full bg-blue-500 px-2.5 text-sm font-semibold text-white hover:bg-blue-600" className="inline-flex h-9 w-max items-center justify-center gap-1 rounded-full bg-blue-500 px-2.5 text-sm font-semibold text-white hover:bg-blue-600"
> >
<ChevronUpIcon className="h-4 w-4" /> <ChevronUpIcon className="h-4 w-4" />

View File

@@ -0,0 +1,22 @@
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { Note } from "..";
export function ChildReply({
event,
rootEventId,
}: { event: NDKEvent; rootEventId?: string }) {
return (
<Note.Provider event={event}>
<Note.Root>
<Note.User />
<Note.TextContent content={event.content} className="min-w-0" />
<div className="-ml-1 flex h-14 items-center gap-10">
<Note.Reply rootEventId={rootEventId} />
<Note.Reaction />
<Note.Repost />
<Note.Zap />
</div>
</Note.Root>
</Note.Provider>
);
}

View File

@@ -4,6 +4,7 @@ import * as Collapsible from "@radix-ui/react-collapsible";
import { useState } from "react"; import { useState } from "react";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { Note } from ".."; import { Note } from "..";
import { ChildReply } from "./childReply";
export function Reply({ export function Reply({
event, event,
@@ -16,58 +17,44 @@ export function Reply({
return ( return (
<Collapsible.Root open={open} onOpenChange={setOpen}> <Collapsible.Root open={open} onOpenChange={setOpen}>
<Note.Root> <Note.Provider event={event}>
<Note.User <Note.Root>
pubkey={event.pubkey} <Note.User className="h-14 px-3" />
time={event.created_at} <Note.TextContent content={event.content} className="min-w-0 px-3" />
className="h-14 px-3" <div className="-ml-1 flex items-center justify-between h-14 px-3">
/> {event.replies?.length > 0 ? (
<Note.TextContent content={event.content} className="min-w-0 px-3" /> <Collapsible.Trigger asChild>
<div className="-ml-1 flex items-center justify-between h-14 px-3"> <div className="ml-1 inline-flex h-14 items-center gap-1 font-semibold text-blue-500">
{event.replies?.length > 0 ? ( <NavArrowDownIcon
<Collapsible.Trigger asChild> className={twMerge(
<div className="ml-1 inline-flex h-14 items-center gap-1 font-semibold text-blue-500"> "h-3 w-3",
<NavArrowDownIcon open ? "rotate-180 transform" : "",
className={twMerge( )}
"h-3 w-3",
open ? "rotate-180 transform" : "",
)}
/>
{`${event.replies?.length} ${
event.replies?.length === 1 ? "reply" : "replies"
}`}
</div>
</Collapsible.Trigger>
) : null}
<div className="inline-flex items-center gap-10">
<Note.Reply eventId={event.id} rootEventId={rootEvent} />
<Note.Reaction event={event} />
<Note.Repost event={event} />
<Note.Zap event={event} />
</div>
</div>
<div className={twMerge("px-3", open ? "pb-3" : "")}>
{event.replies?.length > 0 ? (
<Collapsible.Content>
{event.replies?.map((childEvent) => (
<Note.Root key={childEvent.id}>
<Note.User pubkey={event.pubkey} time={event.created_at} />
<Note.TextContent
content={event.content}
className="min-w-0"
/> />
<div className="-ml-1 flex h-14 items-center gap-10"> {`${event.replies?.length} ${
<Note.Reply eventId={event.id} rootEventId={rootEvent} /> event.replies?.length === 1 ? "reply" : "replies"
<Note.Reaction event={event} /> }`}
<Note.Repost event={event} /> </div>
<Note.Zap event={event} /> </Collapsible.Trigger>
</div> ) : null}
</Note.Root> <div className="inline-flex items-center gap-10">
))} <Note.Reply rootEventId={rootEvent} />
</Collapsible.Content> <Note.Reaction />
) : null} <Note.Repost />
</div> <Note.Zap />
</Note.Root> </div>
</div>
<div className={twMerge("px-3", open ? "pb-3" : "")}>
{event.replies?.length > 0 ? (
<Collapsible.Content>
{event.replies?.map((childEvent) => (
<ChildReply key={childEvent.id} event={childEvent} />
))}
</Collapsible.Content>
) : null}
</div>
</Note.Root>
</Note.Provider>
</Collapsible.Root> </Collapsible.Root>
); );
} }

View File

@@ -3,8 +3,12 @@ import { useQuery } from "@tanstack/react-query";
import { Note } from ".."; import { Note } from "..";
import { useArk } from "../../../provider"; import { useArk } from "../../../provider";
export function RepostNote({ event }: { event: NDKEvent }) { export function RepostNote({
event,
className,
}: { event: NDKEvent; className?: string }) {
const ark = useArk(); const ark = useArk();
const { const {
isLoading, isLoading,
isError, isError,
@@ -57,26 +61,23 @@ export function RepostNote({ event }: { event: NDKEvent }) {
} }
return ( return (
<Note.Root> <Note.Provider event={repostEvent}>
<Note.User <Note.Root className={className}>
pubkey={event.pubkey} <Note.User variant="repost" className="h-14" />
time={event.created_at} <div className="relative flex flex-col gap-2 px-3">
variant="repost" <Note.User />
className="h-14" {renderContentByKind()}
/> <div className="flex h-14 items-center justify-between">
<div className="relative flex flex-col gap-2 px-3"> <Note.Pin />
<Note.User pubkey={repostEvent.pubkey} time={repostEvent.created_at} /> <div className="inline-flex items-center gap-10">
{renderContentByKind()} <Note.Reply />
<div className="flex h-14 items-center justify-between"> <Note.Reaction />
<Note.Pin eventId={event.id} /> <Note.Repost />
<div className="inline-flex items-center gap-10"> <Note.Zap />
<Note.Reply eventId={repostEvent.id} /> </div>
<Note.Reaction event={repostEvent} />
<Note.Repost event={repostEvent} />
<Note.Zap event={repostEvent} />
</div> </div>
</div> </div>
</div> </Note.Root>
</Note.Root> </Note.Provider>
); );
} }

View File

@@ -10,23 +10,24 @@ export function TextNote({
const thread = ark.getEventThread({ tags: event.tags }); const thread = ark.getEventThread({ tags: event.tags });
return ( return (
<Note.Root className={className}> <Note.Provider event={event}>
<Note.User <Note.Root className={className}>
pubkey={event.pubkey} <div className="h-14 px-3 flex items-center justify-between">
time={event.created_at} <Note.User className="flex-1 pr-1" />
className="h-14 px-3" <Note.Menu />
/>
<Note.Thread thread={thread} className="mb-2" />
<Note.TextContent content={event.content} className="min-w-0 px-3" />
<div className="flex h-14 items-center justify-between px-3">
<Note.Pin eventId={event.id} />
<div className="inline-flex items-center gap-10">
<Note.Reply eventId={event.id} rootEventId={thread?.rootEventId} />
<Note.Reaction event={event} />
<Note.Repost event={event} />
<Note.Zap event={event} />
</div> </div>
</div> <Note.Thread thread={thread} className="mb-2" />
</Note.Root> <Note.TextContent content={event.content} className="min-w-0 px-3" />
<div className="flex h-14 items-center justify-between px-3">
<Note.Pin />
<div className="inline-flex items-center gap-10">
<Note.Reply rootEventId={thread?.rootEventId} />
<Note.Reaction />
<Note.Repost />
<Note.Zap />
</div>
</div>
</Note.Root>
</Note.Provider>
); );
} }

View File

@@ -33,23 +33,20 @@ export function ThreadNote({ eventId }: { eventId: string }) {
} }
return ( return (
<Note.Root> <Note.Provider event={data}>
<Note.User <Note.Root>
pubkey={data.pubkey} <Note.User variant="thread" className="h-16 px-3" />
time={data.created_at} {renderEventKind(data)}
variant="thread" <div className="flex h-14 items-center justify-between px-3">
className="h-16 px-3" <Note.Pin />
/> <div className="inline-flex items-center gap-10">
{renderEventKind(data)} <Note.Reply />
<div className="flex h-14 items-center justify-between px-3"> <Note.Reaction />
<Note.Pin eventId={data.id} /> <Note.Repost />
<div className="inline-flex items-center gap-10"> <Note.Zap />
<Note.Reply eventId={data.id} /> </div>
<Note.Reaction event={data} />
<Note.Repost event={data} />
<Note.Zap event={data} />
</div> </div>
</div> </Note.Root>
</Note.Root> </Note.Provider>
); );
} }

View File

@@ -1,9 +1,11 @@
import { PinIcon } from "@lume/icons"; import { PinIcon } from "@lume/icons";
import { WIDGET_KIND } from "@lume/utils"; import { WIDGET_KIND } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
import { useNoteContext } from "..";
import { useWidget } from "../../../hooks/useWidget"; import { useWidget } from "../../../hooks/useWidget";
export function NotePin({ eventId }: { eventId: string }) { export function NotePin() {
const event = useNoteContext();
const { addWidget } = useWidget(); const { addWidget } = useWidget();
return ( return (
@@ -16,19 +18,19 @@ export function NotePin({ eventId }: { eventId: string }) {
addWidget.mutate({ addWidget.mutate({
kind: WIDGET_KIND.thread, kind: WIDGET_KIND.thread,
title: "Thread", title: "Thread",
content: eventId, content: event.id,
}) })
} }
className="inline-flex h-7 w-max items-center justify-center gap-2 rounded-full bg-neutral-100 px-2 text-sm font-medium dark:bg-neutral-900" className="inline-flex h-7 w-max items-center justify-center gap-2 rounded-full bg-neutral-100 hover:bg-neutral-200 dark:hover:bg-neutral-800 px-2 text-sm font-medium dark:bg-neutral-900"
> >
<PinIcon className="size-4" /> <PinIcon className="size-4" />
Pin Pin
</button> </button>
</Tooltip.Trigger> </Tooltip.Trigger>
<Tooltip.Portal> <Tooltip.Portal>
<Tooltip.Content className="-left-10 inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-200 px-3.5 text-sm text-neutral-900 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-800 dark:text-neutral-100"> <Tooltip.Content className="bg-neutral-950 text-white -left-10 inline-flex h-7 select-none items-center justify-center rounded-md px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
Pin note Pin note
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" /> <Tooltip.Arrow className="fill-neutral-950" />
</Tooltip.Content> </Tooltip.Content>
</Tooltip.Portal> </Tooltip.Portal>
</Tooltip.Root> </Tooltip.Root>

View File

@@ -1,8 +1,8 @@
import { ReactionIcon } from "@lume/icons"; import { ReactionIcon } from "@lume/icons";
import { NDKEvent } from "@nostr-dev-kit/ndk"; import * as HoverCard from "@radix-ui/react-hover-card";
import * as Popover from "@radix-ui/react-popover";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useNoteContext } from "../provider";
const REACTIONS = [ const REACTIONS = [
{ {
@@ -27,7 +27,9 @@ const REACTIONS = [
}, },
]; ];
export function NoteReaction({ event }: { event: NDKEvent }) { export function NoteReaction() {
const event = useNoteContext();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [reaction, setReaction] = useState<string | null>(null); const [reaction, setReaction] = useState<string | null>(null);
@@ -52,8 +54,8 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
}; };
return ( return (
<Popover.Root open={open} onOpenChange={setOpen}> <HoverCard.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger asChild> <HoverCard.Trigger asChild>
<button <button
type="button" type="button"
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400" className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
@@ -62,16 +64,16 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
<img <img
src={getReactionImage(reaction)} src={getReactionImage(reaction)}
alt={reaction} alt={reaction}
className="h-5 w-5" className="size-5"
/> />
) : ( ) : (
<ReactionIcon className="h-5 w-5 group-hover:text-blue-500" /> <ReactionIcon className="size-5 group-hover:text-blue-500" />
)} )}
</button> </button>
</Popover.Trigger> </HoverCard.Trigger>
<Popover.Portal> <HoverCard.Portal>
<Popover.Content <HoverCard.Content
className="select-none rounded-md bg-neutral-200 px-1 py-1 text-sm will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-800" className="select-none rounded-lg bg-neutral-950 px-1 py-1 text-sm will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={0} sideOffset={0}
side="top" side="top"
> >
@@ -79,7 +81,7 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
<button <button
type="button" type="button"
onClick={() => react("👏")} onClick={() => react("👏")}
className="inline-flex h-8 w-8 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10" className="inline-flex h-8 w-8 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10"
> >
<img <img
src="/clapping_hands.png" src="/clapping_hands.png"
@@ -90,7 +92,7 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
<button <button
type="button" type="button"
onClick={() => react("🤪")} onClick={() => react("🤪")}
className="inline-flex h-7 w-7 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10" className="inline-flex h-8 w-8 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10"
> >
<img <img
src="/face_with_tongue.png" src="/face_with_tongue.png"
@@ -101,7 +103,7 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
<button <button
type="button" type="button"
onClick={() => react("😮")} onClick={() => react("😮")}
className="inline-flex h-7 w-7 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10" className="inline-flex h-8 w-8 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10"
> >
<img <img
src="/face_with_open_mouth.png" src="/face_with_open_mouth.png"
@@ -112,7 +114,7 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
<button <button
type="button" type="button"
onClick={() => react("😢")} onClick={() => react("😢")}
className="inline-flex h-7 w-7 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10" className="inline-flex h-8 w-8 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10"
> >
<img <img
src="/crying_face.png" src="/crying_face.png"
@@ -123,14 +125,14 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
<button <button
type="button" type="button"
onClick={() => react("🤡")} onClick={() => react("🤡")}
className="inline-flex h-7 w-7 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10" className="inline-flex h-8 w-8 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10"
> >
<img src="/clown_face.png" alt="Clown Face" className="h-6 w-6" /> <img src="/clown_face.png" alt="Clown Face" className="h-6 w-6" />
</button> </button>
</div> </div>
<Popover.Arrow className="fill-neutral-200 dark:fill-neutral-800" /> <HoverCard.Arrow className="fill-neutral-950" />
</Popover.Content> </HoverCard.Content>
</Popover.Portal> </HoverCard.Portal>
</Popover.Root> </HoverCard.Root>
); );
} }

View File

@@ -1,14 +1,14 @@
import { ReplyIcon } from "@lume/icons"; import { ReplyIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
import { createSearchParams, useNavigate } from "react-router-dom"; import { createSearchParams, useNavigate } from "react-router-dom";
import { useNoteContext } from "../provider";
export function NoteReply({ export function NoteReply({
eventId,
rootEventId, rootEventId,
}: { }: {
eventId: string;
rootEventId?: string; rootEventId?: string;
}) { }) {
const event = useNoteContext();
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
@@ -21,7 +21,7 @@ export function NoteReply({
navigate({ navigate({
pathname: "/new/", pathname: "/new/",
search: createSearchParams({ search: createSearchParams({
replyTo: eventId, replyTo: event.id,
rootReplyTo: rootEventId, rootReplyTo: rootEventId,
}).toString(), }).toString(),
}) })
@@ -32,9 +32,9 @@ export function NoteReply({
</button> </button>
</Tooltip.Trigger> </Tooltip.Trigger>
<Tooltip.Portal> <Tooltip.Portal>
<Tooltip.Content className="-left-10 inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-200 px-3.5 text-sm text-neutral-900 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-800 dark:text-neutral-100"> <Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-white will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
Quick reply Quick reply
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" /> <Tooltip.Arrow className="fill-neutral-950" />
</Tooltip.Content> </Tooltip.Content>
</Tooltip.Portal> </Tooltip.Portal>
</Tooltip.Root> </Tooltip.Root>

View File

@@ -1,11 +1,12 @@
import { RepostIcon } from "@lume/icons"; import { RepostIcon } from "@lume/icons";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { useNoteContext } from "../provider";
export function NoteRepost({ event }: { event: NDKEvent }) { export function NoteRepost() {
const event = useNoteContext();
const [isRepost, setIsRepost] = useState(false); const [isRepost, setIsRepost] = useState(false);
const submit = async () => { const submit = async () => {
@@ -39,9 +40,9 @@ export function NoteRepost({ event }: { event: NDKEvent }) {
</button> </button>
</Tooltip.Trigger> </Tooltip.Trigger>
<Tooltip.Portal> <Tooltip.Portal>
<Tooltip.Content className="-left-10 inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-200 px-3.5 text-sm text-neutral-900 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-800 dark:text-neutral-100"> <Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-white will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
Repost Repost
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" /> <Tooltip.Arrow className="fill-neutral-950" />
</Tooltip.Content> </Tooltip.Content>
</Tooltip.Portal> </Tooltip.Portal>
</Tooltip.Root> </Tooltip.Root>

View File

@@ -16,8 +16,9 @@ import CurrencyInput from "react-currency-input-field";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useProfile } from "../../../hooks/useProfile"; import { useProfile } from "../../../hooks/useProfile";
import { useArk, useStorage } from "../../../provider"; import { useArk, useStorage } from "../../../provider";
import { useNoteContext } from "../provider";
export function NoteZap({ event }: { event: NDKEvent }) { export function NoteZap() {
const [walletConnectURL, setWalletConnectURL] = useState<string>(null); const [walletConnectURL, setWalletConnectURL] = useState<string>(null);
const [amount, setAmount] = useState<string>("21"); const [amount, setAmount] = useState<string>("21");
const [zapMessage, setZapMessage] = useState<string>(""); const [zapMessage, setZapMessage] = useState<string>("");
@@ -26,13 +27,14 @@ export function NoteZap({ event }: { event: NDKEvent }) {
const [isCompleted, setIsCompleted] = useState(false); const [isCompleted, setIsCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { user } = useProfile(event.pubkey);
const ark = useArk(); const ark = useArk();
const event = useNoteContext();
const storage = useStorage(); const storage = useStorage();
const nwc = useRef(null); const nwc = useRef(null);
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useProfile(event.pubkey);
const createZapRequest = async () => { const createZapRequest = async () => {
try { try {
if (!ark.ndk.signer) return navigate("/new/privkey"); if (!ark.ndk.signer) return navigate("/new/privkey");

View File

@@ -8,12 +8,14 @@ import { NoteArticleContent } from "./kinds/article";
import { NoteMediaContent } from "./kinds/media"; import { NoteMediaContent } from "./kinds/media";
import { NoteTextContent } from "./kinds/text"; import { NoteTextContent } from "./kinds/text";
import { NoteMenu } from "./menu"; import { NoteMenu } from "./menu";
import { NoteProvider } from "./provider";
import { NoteReplyList } from "./reply"; import { NoteReplyList } from "./reply";
import { NoteRoot } from "./root"; import { NoteRoot } from "./root";
import { NoteThread } from "./thread"; import { NoteThread } from "./thread";
import { NoteUser } from "./user"; import { NoteUser } from "./user";
export const Note = { export const Note = {
Provider: NoteProvider,
Root: NoteRoot, Root: NoteRoot,
User: NoteUser, User: NoteUser,
Menu: NoteMenu, Menu: NoteMenu,
@@ -30,6 +32,7 @@ export const Note = {
ReplyList: NoteReplyList, ReplyList: NoteReplyList,
}; };
export * from "./provider";
export * from "./builds/text"; export * from "./builds/text";
export * from "./builds/repost"; export * from "./builds/repost";
export * from "./builds/skeleton"; export * from "./builds/skeleton";

View File

@@ -1,63 +1,76 @@
import { HorizontalDotsIcon } from '@lume/icons'; import { HorizontalDotsIcon } from "@lume/icons";
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { nip19 } from 'nostr-tools'; import { nip19 } from "nostr-tools";
import { EventPointer } from 'nostr-tools/lib/types/nip19'; import { EventPointer } from "nostr-tools/lib/types/nip19";
import { useState } from 'react'; import { useState } from "react";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
import { useNoteContext } from "./provider";
export function NoteMenu({ eventId, pubkey }: { eventId: string; pubkey: string }) { export function NoteMenu() {
const [open, setOpen] = useState(false); const event = useNoteContext();
const [open, setOpen] = useState(false);
const copyID = async () => { const copyID = async () => {
await writeText(nip19.neventEncode({ id: eventId, author: pubkey } as EventPointer)); await writeText(
setOpen(false); nip19.neventEncode({
}; id: event.id,
author: event.pubkey,
} as EventPointer),
);
setOpen(false);
};
const copyLink = async () => { const copyLink = async () => {
await writeText( await writeText(
`https://njump.me/${nip19.neventEncode({ id: eventId, author: pubkey } as EventPointer)}` `https://njump.me/${nip19.neventEncode({
); id: event.id,
setOpen(false); author: event.pubkey,
}; } as EventPointer)}`,
);
setOpen(false);
};
return ( return (
<DropdownMenu.Root open={open} onOpenChange={setOpen}> <DropdownMenu.Root open={open} onOpenChange={setOpen}>
<DropdownMenu.Trigger asChild> <DropdownMenu.Trigger asChild>
<button type="button" className="inline-flex h-6 w-6 items-center justify-center"> <button
<HorizontalDotsIcon className="h-4 w-4 text-neutral-800 hover:text-blue-500 dark:text-neutral-200" /> type="button"
</button> className="inline-flex h-6 w-6 items-center justify-center"
</DropdownMenu.Trigger> >
<DropdownMenu.Portal> <HorizontalDotsIcon className="h-4 w-4 text-neutral-800 hover:text-blue-500 dark:text-neutral-200" />
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-200 bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:bg-neutral-900"> </button>
<DropdownMenu.Item asChild> </DropdownMenu.Trigger>
<button <DropdownMenu.Portal>
type="button" <DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-200 bg-neutral-950 focus:outline-none dark:border-neutral-900">
onClick={() => copyLink()} <DropdownMenu.Item asChild>
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800" <button
> type="button"
Copy shareable link onClick={() => copyLink()}
</button> className="inline-flex h-10 items-center px-4 text-sm text-white hover:bg-neutral-900 focus:outline-none"
</DropdownMenu.Item> >
<DropdownMenu.Item asChild> Copy shareable link
<button </button>
type="button" </DropdownMenu.Item>
onClick={() => copyID()} <DropdownMenu.Item asChild>
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800" <button
> type="button"
Copy ID onClick={() => copyID()}
</button> className="inline-flex h-10 items-center px-4 text-sm text-white hover:bg-neutral-900 focus:outline-none"
</DropdownMenu.Item> >
<DropdownMenu.Item asChild> Copy note ID
<Link </button>
to={`/users/${pubkey}`} </DropdownMenu.Item>
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800" <DropdownMenu.Item asChild>
> <Link
View profile to={`/users/${event.pubkey}`}
</Link> className="inline-flex h-10 items-center px-4 text-sm text-white hover:bg-neutral-900 focus:outline-none"
</DropdownMenu.Item> >
</DropdownMenu.Content> View profile
</DropdownMenu.Portal> </Link>
</DropdownMenu.Root> </DropdownMenu.Item>
); </DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
} }

View File

@@ -0,0 +1,21 @@
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { ReactNode, createContext, useContext } from "react";
const EventContext = createContext<NDKEvent>(null);
export function NoteProvider({
event,
children,
}: { event: NDKEvent; children: ReactNode }) {
return (
<EventContext.Provider value={event}>{children}</EventContext.Provider>
);
}
export function useNoteContext() {
const context = useContext(EventContext);
if (context === undefined) {
throw new Error("Please import Note Provider to use useNoteContext() hook");
}
return context;
}

View File

@@ -1,5 +1,5 @@
import { cn } from "@lume/utils";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { twMerge } from "tailwind-merge";
export function NoteRoot({ export function NoteRoot({
children, children,
@@ -10,7 +10,7 @@ export function NoteRoot({
}) { }) {
return ( return (
<div <div
className={twMerge( className={cn(
"flex h-min w-full flex-col overflow-hidden rounded-xl bg-neutral-50 dark:bg-neutral-950", "flex h-min w-full flex-col overflow-hidden rounded-xl bg-neutral-50 dark:bg-neutral-950",
className, className,
)} )}

View File

@@ -24,7 +24,7 @@ export function NoteThread({
to={`/events/${thread?.rootEventId || thread?.replyEventId}`} to={`/events/${thread?.rootEventId || thread?.replyEventId}`}
className="self-start text-blue-500 hover:text-blue-600" className="self-start text-blue-500 hover:text-blue-600"
> >
Show full thread Show thread
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -5,29 +5,33 @@ import { minidenticon } from "minidenticons";
import { useMemo } from "react"; import { useMemo } from "react";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { useNoteContext } from "./provider";
export function NoteUser({ export function NoteUser({
pubkey,
time,
variant = "text", variant = "text",
className, className,
}: { }: {
pubkey: string;
time: number;
variant?: "text" | "repost" | "mention" | "thread"; variant?: "text" | "repost" | "mention" | "thread";
className?: string; className?: string;
}) { }) {
const createdAt = useMemo(() => formatCreatedAt(time), [time]); const event = useNoteContext();
const fallbackName = useMemo(() => displayNpub(pubkey, 16), [pubkey]); const createdAt = useMemo(
() => formatCreatedAt(event.created_at),
[event.created_at],
);
const fallbackName = useMemo(
() => displayNpub(event.pubkey, 16),
[event.pubkey],
);
const fallbackAvatar = useMemo( const fallbackAvatar = useMemo(
() => () =>
`data:image/svg+xml;utf8,${encodeURIComponent( `data:image/svg+xml;utf8,${encodeURIComponent(
minidenticon(pubkey, 90, 50), minidenticon(event.pubkey, 90, 50),
)}`, )}`,
[pubkey], [event.pubkey],
); );
const { isLoading, user } = useProfile(pubkey); const { isLoading, user } = useProfile(event.pubkey);
if (variant === "mention") { if (variant === "mention") {
if (isLoading) { if (isLoading) {
@@ -36,7 +40,7 @@ export function NoteUser({
<Avatar.Root className="shrink-0"> <Avatar.Root className="shrink-0">
<Avatar.Image <Avatar.Image
src={fallbackAvatar} src={fallbackAvatar}
alt={pubkey} alt={event.pubkey}
className="h-6 w-6 rounded-md bg-black dark:bg-white" className="h-6 w-6 rounded-md bg-black dark:bg-white"
/> />
</Avatar.Root> </Avatar.Root>
@@ -58,7 +62,7 @@ export function NoteUser({
<Avatar.Root className="shrink-0"> <Avatar.Root className="shrink-0">
<Avatar.Image <Avatar.Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={event.pubkey}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
className="h-6 w-6 rounded-md" className="h-6 w-6 rounded-md"
@@ -66,7 +70,7 @@ export function NoteUser({
<Avatar.Fallback delayMs={300}> <Avatar.Fallback delayMs={300}>
<img <img
src={fallbackAvatar} src={fallbackAvatar}
alt={pubkey} alt={event.pubkey}
className="h-6 w-6 rounded-md bg-black dark:bg-white" className="h-6 w-6 rounded-md bg-black dark:bg-white"
/> />
</Avatar.Fallback> </Avatar.Fallback>
@@ -111,7 +115,7 @@ export function NoteUser({
<Avatar.Root className="shrink-0"> <Avatar.Root className="shrink-0">
<Avatar.Image <Avatar.Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={event.pubkey}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
className="h-6 w-6 rounded object-cover" className="h-6 w-6 rounded object-cover"
@@ -119,7 +123,7 @@ export function NoteUser({
<Avatar.Fallback delayMs={300}> <Avatar.Fallback delayMs={300}>
<img <img
src={fallbackAvatar} src={fallbackAvatar}
alt={pubkey} alt={event.pubkey}
className="h-6 w-6 rounded bg-black dark:bg-white" className="h-6 w-6 rounded bg-black dark:bg-white"
/> />
</Avatar.Fallback> </Avatar.Fallback>
@@ -156,7 +160,7 @@ export function NoteUser({
<Avatar.Root className="h-10 w-10 shrink-0"> <Avatar.Root className="h-10 w-10 shrink-0">
<Avatar.Image <Avatar.Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={event.pubkey}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
className="h-10 w-10 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" className="h-10 w-10 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
@@ -164,7 +168,7 @@ export function NoteUser({
<Avatar.Fallback delayMs={300}> <Avatar.Fallback delayMs={300}>
<img <img
src={fallbackAvatar} src={fallbackAvatar}
alt={pubkey} alt={event.pubkey}
className="h-10 w-10 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50" className="h-10 w-10 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
/> />
</Avatar.Fallback> </Avatar.Fallback>
@@ -189,7 +193,7 @@ export function NoteUser({
<Avatar.Root className="h-9 w-9 shrink-0"> <Avatar.Root className="h-9 w-9 shrink-0">
<Avatar.Image <Avatar.Image
src={fallbackAvatar} src={fallbackAvatar}
alt={pubkey} alt={event.pubkey}
className="h-9 w-9 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50" className="h-9 w-9 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
/> />
</Avatar.Root> </Avatar.Root>
@@ -207,7 +211,7 @@ export function NoteUser({
<Avatar.Root className="h-9 w-9 shrink-0"> <Avatar.Root className="h-9 w-9 shrink-0">
<Avatar.Image <Avatar.Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={event.pubkey}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
className="h-9 w-9 rounded-lg bg-white object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" className="h-9 w-9 rounded-lg bg-white object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
@@ -215,7 +219,7 @@ export function NoteUser({
<Avatar.Fallback delayMs={300}> <Avatar.Fallback delayMs={300}>
<img <img
src={fallbackAvatar} src={fallbackAvatar}
alt={pubkey} alt={event.pubkey}
className="h-9 w-9 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50" className="h-9 w-9 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
/> />
</Avatar.Fallback> </Avatar.Fallback>

View File

@@ -1,183 +1,209 @@
import { nanoid } from 'nanoid'; import { nip19 } from "nostr-tools";
import { nip19 } from 'nostr-tools'; import { ReactNode } from "react";
import { ReactNode } from 'react'; import { Link } from "react-router-dom";
import { Link } from 'react-router-dom'; import reactStringReplace from "react-string-replace";
import reactStringReplace from 'react-string-replace';
import { import {
Hashtag, Hashtag,
ImagePreview, ImagePreview,
LinkPreview, LinkPreview,
MentionNote, MentionNote,
MentionUser, MentionUser,
VideoPreview, VideoPreview,
} from '../components/note'; } from "../components/note";
import { useStorage } from '../provider'; import { useStorage } from "../provider";
const NOSTR_MENTIONS = [ const NOSTR_MENTIONS = [
'@npub1', "@npub1",
'nostr:npub1', "nostr:npub1",
'nostr:nprofile1', "nostr:nprofile1",
'nostr:naddr1', "nostr:naddr1",
'npub1', "npub1",
'nprofile1', "nprofile1",
'naddr1', "naddr1",
'Nostr:npub1', "Nostr:npub1",
'Nostr:nprofile1', "Nostr:nprofile1",
'Nostr:naddre1', "Nostr:naddre1",
]; ];
const NOSTR_EVENTS = [ const NOSTR_EVENTS = [
'nostr:note1', "nostr:note1",
'note1', "note1",
'nostr:nevent1', "nostr:nevent1",
'nevent1', "nevent1",
'Nostr:note1', "Nostr:note1",
'Nostr:nevent1', "Nostr:nevent1",
]; ];
// const BITCOINS = ['lnbc', 'bc1p', 'bc1q']; // const BITCOINS = ['lnbc', 'bc1p', 'bc1q'];
const IMAGES = ['.jpg', '.jpeg', '.gif', '.png', '.webp', '.avif', '.tiff']; const IMAGES = [".jpg", ".jpeg", ".gif", ".png", ".webp", ".avif", ".tiff"];
const VIDEOS = [ const VIDEOS = [
'.mp4', ".mp4",
'.mov', ".mov",
'.webm', ".webm",
'.wmv', ".wmv",
'.flv', ".flv",
'.mts', ".mts",
'.avi', ".avi",
'.ogv', ".ogv",
'.mkv', ".mkv",
'.mp3', ".mp3",
'.m3u8', ".m3u8",
]; ];
export function useRichContent(content: string, textmode = false) { export function useRichContent(content: string, textmode = false) {
const storage = useStorage(); const storage = useStorage();
let parsedContent: string | ReactNode[] = content.replace(/\n+/g, '\n'); let parsedContent: string | ReactNode[] = content.replace(/\n+/g, "\n");
let linkPreview: string; let linkPreview: string;
let images: string[] = []; let images: string[] = [];
let videos: string[] = []; let videos: string[] = [];
let events: string[] = []; let events: string[] = [];
const text = parsedContent; const text = parsedContent;
const words = text.split(/( |\n)/); const words = text.split(/( |\n)/);
if (!textmode) { if (!textmode) {
if (storage.settings.media) { if (storage.settings.media) {
images = words.filter((word) => IMAGES.some((el) => word.endsWith(el))); images = words.filter((word) => IMAGES.some((el) => word.endsWith(el)));
videos = words.filter((word) => VIDEOS.some((el) => word.endsWith(el))); videos = words.filter((word) => VIDEOS.some((el) => word.endsWith(el)));
} }
events = words.filter((word) => NOSTR_EVENTS.some((el) => word.startsWith(el))); 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 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) { if (images.length) {
for(const image of images) { for (const image of images) {
parsedContent = reactStringReplace(parsedContent, image, (match, i) => ( parsedContent = reactStringReplace(parsedContent, image, (match, i) => (
<ImagePreview key={match + i} url={match} /> <ImagePreview key={match + i} url={match} />
)); ));
} }
} }
if (videos.length) { if (videos.length) {
for(const video of videos) { for (const video of videos) {
parsedContent = reactStringReplace(parsedContent, video, (match, i) => ( parsedContent = reactStringReplace(parsedContent, video, (match, i) => (
<VideoPreview key={match + i} url={match} /> <VideoPreview key={match + i} url={match} />
)); ));
} }
} }
if (hashtags.length) { if (hashtags.length) {
for(const hashtag of hashtags) { for (const hashtag of hashtags) {
parsedContent = reactStringReplace(parsedContent, hashtag, (match, i) => { parsedContent = reactStringReplace(
if (storage.settings.hashtag) return <Hashtag key={match + i} tag={hashtag} />; parsedContent,
return null; hashtag,
}); (match, i) => {
} if (storage.settings.hashtag)
} return <Hashtag key={match + i} tag={hashtag} />;
return null;
},
);
}
}
if (events.length) { if (events.length) {
for(const event of events) { for (const event of events) {
const address = event.replace('nostr:', '').replace(/[^a-zA-Z0-9]/g, ''); const address = event
const decoded = nip19.decode(address); .replace("nostr:", "")
.replace(/[^a-zA-Z0-9]/g, "");
const decoded = nip19.decode(address);
if (decoded.type === 'note') { if (decoded.type === "note") {
parsedContent = reactStringReplace(parsedContent, event, (match, i) => ( parsedContent = reactStringReplace(
<MentionNote key={match + i} eventId={decoded.data} /> parsedContent,
)); event,
} (match, i) => (
<MentionNote key={match + i} eventId={decoded.data} />
),
);
}
if (decoded.type === 'nevent') { if (decoded.type === "nevent") {
parsedContent = reactStringReplace(parsedContent, event, (match, i) => ( parsedContent = reactStringReplace(
<MentionNote key={match + i} eventId={decoded.data.id} /> parsedContent,
)); event,
} (match, i) => (
} <MentionNote key={match + i} eventId={decoded.data.id} />
} ),
);
}
}
}
if (mentions.length) { if (mentions.length) {
for(const mention of mentions) { for (const mention of mentions) {
const address = mention const address = mention
.replace('nostr:', '') .replace("nostr:", "")
.replace('@', '') .replace("@", "")
.replace(/[^a-zA-Z0-9]/g, ''); .replace(/[^a-zA-Z0-9]/g, "");
const decoded = nip19.decode(address); const decoded = nip19.decode(address);
if (decoded.type === 'npub') { if (decoded.type === "npub") {
parsedContent = reactStringReplace(parsedContent, mention, (match, i) => ( parsedContent = reactStringReplace(
<MentionUser key={match + i} pubkey={decoded.data} /> parsedContent,
)); mention,
} (match, i) => <MentionUser key={match + i} pubkey={decoded.data} />,
);
}
if (decoded.type === 'nprofile' || decoded.type === 'naddr') { if (decoded.type === "nprofile" || decoded.type === "naddr") {
parsedContent = reactStringReplace(parsedContent, mention, (match, i) => ( parsedContent = reactStringReplace(
<MentionUser key={match + i} pubkey={decoded.data.pubkey} /> parsedContent,
)); mention,
} (match, i) => (
} <MentionUser key={match + i} pubkey={decoded.data.pubkey} />
} ),
);
}
}
}
parsedContent = reactStringReplace(parsedContent, /(https?:\/\/\S+)/g, (match, i) => { parsedContent = reactStringReplace(
const url = new URL(match); parsedContent,
url.search = ''; /(https?:\/\/\S+)/g,
(match, i) => {
const url = new URL(match);
url.search = "";
if (!linkPreview && !textmode) { if (!linkPreview && !textmode) {
linkPreview = match; linkPreview = match;
return <LinkPreview key={match + i} url={url.toString()} />; return <LinkPreview key={match + i} url={url.toString()} />;
} }
return ( return (
<Link <Link
key={match + i} key={match + i}
to={url.toString()} to={url.toString()}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="break-all font-normal text-blue-500 hover:text-blue-600" className="break-all font-normal text-blue-500 hover:text-blue-600"
> >
{url.toString()} {url.toString()}
</Link> </Link>
); );
}); },
);
parsedContent = reactStringReplace(parsedContent, '\n', () => { parsedContent = reactStringReplace(parsedContent, "\n", () => {
return <div key={nanoid()} className="h-3" />; return null;
}); });
if (typeof parsedContent[0] === 'string') { if (typeof parsedContent[0] === "string") {
parsedContent[0] = parsedContent[0].trimStart(); parsedContent[0] = parsedContent[0].trimStart();
} }
return { parsedContent }; return { parsedContent };
} catch (e) { } catch (e) {
console.warn('[parser] parse failed: ', e); console.warn("[parser] parse failed: ", e);
return { parsedContent }; return { parsedContent };
} }
} }

View File

@@ -1,20 +1,18 @@
export function HorizontalDotsIcon(props: JSX.IntrinsicElements['svg']) { export function HorizontalDotsIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg
{...props} {...props}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24" fill="none"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth="2" strokeWidth="2"
> >
<path d="M6 12a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" /> <path d="M6 12a1 1 0 11-2 0 1 1 0 012 0zM13 12a1 1 0 11-2 0 1 1 0 012 0zM20 12a1 1 0 11-2 0 1 1 0 012 0z" />
<path d="M13 12a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" /> </svg>
<path d="M20 12a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" /> );
</svg>
);
} }

View File

@@ -1,18 +1,18 @@
export function PinIcon(props: JSX.IntrinsicElements['svg']) { export function PinIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg
{...props} {...props}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24" fill="none"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth="2" strokeWidth="2"
> >
<path d="M12 21v-5.307m0 0c-2.201 0-4.39-.184-6.567-.495a.504.504 0 0 1-.433-.5 4.54 4.54 0 0 1 2.194-3.886c.63-.381 1.034-1.029.85-1.788l-.986-4.092a1.6 1.6 0 0 1 1.406-1.967c2.366-.22 4.706-.22 7.072 0a1.6 1.6 0 0 1 1.406 1.967l-.986 4.092c-.183.76.22 1.407.85 1.788A4.54 4.54 0 0 1 19 14.698a.504.504 0 0 1-.433.5c-2.178.31-4.366.495-6.567.495Z" /> <path d="M12 21v-5.307m0 0c-2.201 0-4.39-.184-6.567-.495a.504.504 0 01-.433-.5 4.54 4.54 0 012.194-3.886c.63-.381 1.034-1.029.85-1.788l-.986-4.092a1.6 1.6 0 011.406-1.967c2.366-.22 4.706-.22 7.072 0a1.6 1.6 0 011.406 1.967l-.986 4.092c-.183.76.22 1.407.85 1.788A4.54 4.54 0 0119 14.698a.504.504 0 01-.433.5c-2.178.31-4.366.495-6.567.495z" />
</svg> </svg>
); );
} }

View File

@@ -1,18 +1,18 @@
export function ReactionIcon(props: JSX.IntrinsicElements['svg']) { export function ReactionIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg
{...props} {...props}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24" fill="none"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth="2" strokeWidth="2"
> >
<path d="M8.43 14.5A4.985 4.985 0 0 0 12 16a4.985 4.985 0 0 0 3.57-1.5m-3.42 6.8a9.15 9.15 0 1 1 0-18.3 9.15 9.15 0 0 1 0 18.3Z" /> <path d="M9 10v1m6.5-.5h-1m-6.07 4A4.985 4.985 0 0012 16a4.985 4.985 0 003.57-1.5M12 21.15a9.15 9.15 0 110-18.3 9.15 9.15 0 010 18.3z" />
</svg> </svg>
); );
} }

View File

@@ -1,18 +1,18 @@
export function ReplyIcon(props: JSX.IntrinsicElements['svg']) { export function ReplyIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg
{...props} {...props}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="25"
width="24" height="24"
height="24" fill="none"
fill="none" viewBox="0 0 25 24"
stroke="currentColor" stroke="currentColor"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth="2" strokeWidth="2"
> >
<path d="M8 10h8m-8 4h4m9-2a9 9 0 0 1-10.272 8.91c-1.203-.17-1.805-.255-1.964-.267-.257-.02-.165-.016-.423-.014-.159 0-.34.014-.702.04l-2.153.153c-.857.062-1.286.092-1.607-.06a1.35 1.35 0 0 1-.641-.641c-.152-.32-.122-.75-.06-1.607l.153-2.153c.026-.362.04-.543.04-.702.002-.258.006-.166-.014-.423-.012-.159-.098-.76-.268-1.964A9 9 0 1 1 21 12Z" /> <path d="M9.5 12h3m0 0h3m-3 0V9m0 3v3m9-3a9 9 0 01-10.272 8.91c-1.203-.17-1.805-.255-1.964-.267-.257-.02-.165-.016-.423-.014-.159 0-.34.014-.702.04l-2.153.153c-.857.062-1.286.092-1.607-.06a1.35 1.35 0 01-.641-.641c-.152-.32-.122-.75-.06-1.607l.153-2.153c.026-.362.04-.543.04-.702.002-.258.006-.166-.014-.423-.012-.159-.098-.76-.268-1.964A9 9 0 1121.5 12z" />
</svg> </svg>
); );
} }

View File

@@ -1,18 +1,18 @@
export function RepostIcon(props: JSX.IntrinsicElements['svg']) { export function RepostIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg
{...props} {...props}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24" fill="none"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth="2" strokeWidth="2"
> >
<path d="M12 2a15.267 15.267 0 0 1 2.92 2.777c.054.066.08.145.08.225M12 8a15.266 15.266 0 0 0 2.92-2.777.356.356 0 0 0 .08-.221M12 16a15.264 15.264 0 0 0-2.92 2.777.356.356 0 0 0-.08.221M12 22a15.264 15.264 0 0 1-2.92-2.777.355.355 0 0 1-.08-.225m6-13.996C14.7 5 14.368 5 14 5h-4c-1.861 0-2.792 0-3.545.245a5 5 0 0 0-3.21 3.21C3 9.208 3 10.139 3 12s0 2.792.245 3.545A5 5 0 0 0 5 18m4 .998c.3.002.632.002 1 .002h4c1.861 0 2.792 0 3.545-.245a5 5 0 0 0 3.21-3.21C21 14.792 21 13.861 21 12s0-2.792-.245-3.545A5 5 0 0 0 19 6" /> <path d="M12 2a15.267 15.267 0 012.92 2.777c.054.066.08.145.08.225M12 8a15.266 15.266 0 002.92-2.777.356.356 0 00.08-.221M12 16a15.264 15.264 0 00-2.92 2.777.356.356 0 00-.08.221M12 22a15.264 15.264 0 01-2.92-2.777.355.355 0 01-.08-.225m6-13.996C14.7 5 14.368 5 14 5h-4c-1.861 0-2.792 0-3.545.245a5 5 0 00-3.21 3.21C3 9.208 3 10.139 3 12s0 2.792.245 3.545A5 5 0 005 18m4 .998c.3.002.632.002 1 .002h4c1.861 0 2.792 0 3.545-.245a5 5 0 003.21-3.21C21 14.792 21 13.861 21 12s0-2.792-.245-3.545A5 5 0 0019 6" />
</svg> </svg>
); );
} }

View File

@@ -1,18 +1,18 @@
export function ZapIcon(props: JSX.IntrinsicElements['svg']) { export function ZapIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg
{...props} {...props}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24"
width="24" height="24"
height="24" fill="none"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth="2" strokeWidth="2"
> >
<path d="m6.054 8.38 5.756-4.513c1.638-1.284 2.457-1.926 3.018-1.863.485.055.912.366 1.136.828.26.533-.003 1.58-.53 3.673l-.44 1.753c-.211.838-.316 1.257-.244 1.62.064.32.221.611.45.83.259.249.652.361 1.438.585l.51.146c1.512.432 2.268.648 2.57 1.087.264.382.348.872.23 1.328-.137.526-.772 1.014-2.041 1.99l-5.652 4.342c-1.628 1.25-2.442 1.876-3 1.81a1.458 1.458 0 0 1-1.129-.831c-.257-.532.003-1.565.522-3.63l.394-1.568c.211-.838.316-1.257.244-1.621a1.575 1.575 0 0 0-.45-.83c-.259-.248-.652-.36-1.438-.585l-.568-.162c-1.496-.427-2.244-.64-2.546-1.077a1.631 1.631 0 0 1-.234-1.322c.132-.524.756-1.013 2.004-1.99Z" /> <path d="M6.054 8.38l5.756-4.513c1.638-1.284 2.457-1.926 3.018-1.863.485.055.912.366 1.136.828.26.533-.003 1.58-.53 3.673l-.44 1.753c-.211.838-.316 1.257-.244 1.62.064.32.221.611.45.83.259.249.652.361 1.438.585l.51.146c1.512.432 2.268.648 2.57 1.087.264.382.348.872.23 1.328-.137.526-.772 1.014-2.041 1.99l-5.652 4.342c-1.628 1.25-2.442 1.876-3 1.81a1.458 1.458 0 01-1.129-.831c-.257-.532.003-1.565.522-3.63l.394-1.568c.211-.838.316-1.257.244-1.621a1.575 1.575 0 00-.45-.83c-.259-.248-.652-.36-1.438-.585l-.568-.162c-1.496-.427-2.244-.64-2.546-1.077a1.631 1.631 0 01-.234-1.322c.132-.524.756-1.013 2.004-1.99z" />
</svg> </svg>
); );
} }

View File

@@ -1,10 +1,16 @@
import { cn } from "@lume/utils";
import { type Platform } from "@tauri-apps/plugin-os"; import { type Platform } from "@tauri-apps/plugin-os";
import { Outlet, ScrollRestoration } from "react-router-dom"; import { Outlet, ScrollRestoration } from "react-router-dom";
import { WindowTitleBar } from "../titlebar"; import { WindowTitleBar } from "../titlebar";
export function AuthLayout({ platform }: { platform: Platform }) { export function AuthLayout({ platform }: { platform: Platform }) {
return ( return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950"> <div
className={cn(
"flex h-screen w-screen flex-col",
platform !== "macos" ? "bg-neutral-50 dark:bg-neutral-950" : "",
)}
>
{platform !== "macos" ? ( {platform !== "macos" ? (
<WindowTitleBar platform={platform} /> <WindowTitleBar platform={platform} />
) : ( ) : (

View File

@@ -5,5 +5,4 @@ export * from "./src/nip94";
export * from "./src/notification"; export * from "./src/notification";
export * from "./src/hooks/useNetworkStatus"; export * from "./src/hooks/useNetworkStatus";
export * from "./src/hooks/useOpenGraph"; export * from "./src/hooks/useOpenGraph";
export * from "../ark/src/hooks/useRelay"; export * from "./src/cn";
export * from "../ark/src/hooks/useSuggestion";

View File

@@ -11,9 +11,11 @@
"@tanstack/react-query": "^5.14.2", "@tanstack/react-query": "^5.14.2",
"@tauri-apps/api": "2.0.0-alpha.11", "@tauri-apps/api": "2.0.0-alpha.11",
"@tauri-apps/plugin-notification": "2.0.0-alpha.3", "@tauri-apps/plugin-notification": "2.0.0-alpha.3",
"clsx": "^2.0.0",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"nostr-tools": "1.17.0", "nostr-tools": "1.17.0",
"react": "^18.2.0" "react": "^18.2.0",
"tailwind-merge": "^1.14.0"
}, },
"devDependencies": { "devDependencies": {
"@lume/tsconfig": "workspace:^", "@lume/tsconfig": "workspace:^",

6
packages/utils/src/cn.ts Normal file
View File

@@ -0,0 +1,6 @@
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

460
pnpm-lock.yaml generated
View File

@@ -431,22 +431,25 @@ importers:
version: 0.14.1(@nostr-dev-kit/ndk@2.3.1)(nostr-fetch@0.14.1) version: 0.14.1(@nostr-dev-kit/ndk@2.3.1)(nostr-fetch@0.14.1)
'@radix-ui/react-avatar': '@radix-ui/react-avatar':
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) version: 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(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@18.2.45)(react-dom@18.2.0)(react@18.2.0) version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(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.45)(react-dom@18.2.0)(react@18.2.0) version: 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-dropdown-menu': '@radix-ui/react-dropdown-menu':
specifier: ^2.0.6 specifier: ^2.0.6
version: 2.0.6(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) version: 2.0.6(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-hover-card':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-popover': '@radix-ui/react-popover':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^5.14.2 specifier: ^5.14.2
version: 5.14.2(react@18.2.0) version: 5.14.2(react@18.2.0)
@@ -744,6 +747,9 @@ importers:
'@tauri-apps/plugin-notification': '@tauri-apps/plugin-notification':
specifier: 2.0.0-alpha.3 specifier: 2.0.0-alpha.3
version: 2.0.0-alpha.3 version: 2.0.0-alpha.3
clsx:
specifier: ^2.0.0
version: 2.0.0
dayjs: dayjs:
specifier: ^1.11.10 specifier: ^1.11.10
version: 1.11.10 version: 1.11.10
@@ -753,6 +759,9 @@ importers:
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
tailwind-merge:
specifier: ^1.14.0
version: 1.14.0
devDependencies: devDependencies:
'@lume/tsconfig': '@lume/tsconfig':
specifier: workspace:^ specifier: workspace:^
@@ -1325,26 +1334,6 @@ 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-arrow@1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==}
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.6
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-avatar@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-avatar@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==} resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==}
peerDependencies: peerDependencies:
@@ -1369,29 +1358,6 @@ 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-avatar@1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==}
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.6
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==} resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies: peerDependencies:
@@ -1420,33 +1386,6 @@ 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.45)(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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies: peerDependencies:
@@ -1471,29 +1410,6 @@ 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-collection@1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
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.6
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.45)(react@18.2.0): /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.45)(react@18.2.0):
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
peerDependencies: peerDependencies:
@@ -1556,39 +1472,6 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0) react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0)
dev: false dev: false
/@radix-ui/react-dialog@1.0.5(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0)
dev: false
/@radix-ui/react-direction@1.0.1(@types/react@18.2.45)(react@18.2.0): /@radix-ui/react-direction@1.0.1(@types/react@18.2.45)(react@18.2.0):
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies: peerDependencies:
@@ -1628,30 +1511,6 @@ 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-dismissable-layer@1.0.5(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==} resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==}
peerDependencies: peerDependencies:
@@ -1679,32 +1538,6 @@ 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-dropdown-menu@2.0.6(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-menu': 2.0.6(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.45)(react@18.2.0): /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.45)(react@18.2.0):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies: peerDependencies:
@@ -1742,28 +1575,6 @@ 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-focus-scope@1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
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.6
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-hover-card@1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-hover-card@1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==} resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==}
peerDependencies: peerDependencies:
@@ -1846,43 +1657,6 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0) react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0)
dev: false dev: false
/@radix-ui/react-menu@2.0.6(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-roving-focus': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0)
dev: false
/@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==}
peerDependencies: peerDependencies:
@@ -1918,40 +1692,6 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0) react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0)
dev: false dev: false
/@radix-ui/react-popover@1.0.7(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.45)(react@18.2.0)
dev: false
/@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==}
peerDependencies: peerDependencies:
@@ -1982,35 +1722,6 @@ 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-popper@1.1.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==}
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.6
'@floating-ui/react-dom': 2.0.4(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/rect': 1.0.1
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
peerDependencies: peerDependencies:
@@ -2032,26 +1743,6 @@ 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-portal@1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
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.6
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies: peerDependencies:
@@ -2074,27 +1765,6 @@ 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.45)(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.6
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
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.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(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:
@@ -2116,26 +1786,6 @@ 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.45)(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.6
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
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-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(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:
@@ -2165,34 +1815,6 @@ 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-roving-focus@1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==} resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
peerDependencies: peerDependencies:
@@ -2365,37 +1987,6 @@ 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-tooltip@1.0.7(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
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.6
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.45)(react@18.2.0): /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.45)(react@18.2.0):
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
peerDependencies: peerDependencies:
@@ -2519,26 +2110,6 @@ 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-visually-hidden@1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
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.6
'@radix-ui/react-primitive': 1.0.3(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.45
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/rect@1.0.1: /@radix-ui/rect@1.0.1:
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
dependencies: dependencies:
@@ -5887,7 +5458,6 @@ packages:
/tailwind-merge@1.14.0: /tailwind-merge@1.14.0:
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
dev: true
/tailwind-scrollbar@3.0.5(tailwindcss@3.4.0): /tailwind-scrollbar@3.0.5(tailwindcss@3.4.0):
resolution: {integrity: sha512-0ZwxTivevqq9BY9fRP9zDjHl7Tu+J5giBGbln+0O1R/7nHtBUKnjQcA1aTIhK7Oyjp6Uc/Dj6/dn8Dq58k5Uww==} resolution: {integrity: sha512-0ZwxTivevqq9BY9fRP9zDjHl7Tu+J5giBGbln+0O1R/7nHtBUKnjQcA1aTIhK7Oyjp6Uc/Dj6/dn8Dq58k5Uww==}