+ {pubkey === "anon" ? (
Anon
) : (
-
+
)}
{formatSats(total)}
@@ -132,7 +117,7 @@ function ChatMessage({ ev, link }: { ev: TaggedRawEvent; link: NostrLink }) {
return (
);
}
diff --git a/src/element/mention.tsx b/src/element/mention.tsx
index b54ff0c..5f5dfaa 100644
--- a/src/element/mention.tsx
+++ b/src/element/mention.tsx
@@ -1,5 +1,7 @@
+import { Link } from "react-router-dom";
import { useUserProfile } from "@snort/system-react";
import { System } from "index";
+import { hexToBech32 } from "utils";
interface MentionProps {
pubkey: string;
@@ -8,13 +10,6 @@ interface MentionProps {
export function Mention({ pubkey, relays }: MentionProps) {
const user = useUserProfile(System, pubkey);
- return (
-
- {user?.name || pubkey}
-
- );
+ const npub = hexToBech32("npub", pubkey);
+ return
{user?.name || pubkey};
}
diff --git a/src/element/profile.tsx b/src/element/profile.tsx
index 8fc9204..f723858 100644
--- a/src/element/profile.tsx
+++ b/src/element/profile.tsx
@@ -1,7 +1,9 @@
import "./profile.css";
+import { Link } from "react-router-dom";
import { useUserProfile } from "@snort/system-react";
import { UserMetadata } from "@snort/system";
import { hexToBech32 } from "@snort/shared";
+import { Icon } from "element/icon";
import { System } from "index";
export interface ProfileOptions {
@@ -12,13 +14,14 @@ export interface ProfileOptions {
}
export function getName(pk: string, user?: UserMetadata) {
- const shortPubkey = hexToBech32("npub", pk).slice(0, 12);
- if ((user?.display_name?.length ?? 0) > 0) {
- return user?.display_name;
- }
+ const npub = hexToBech32("npub", pk);
+ const shortPubkey = npub.slice(0, 12);
if ((user?.name?.length ?? 0) > 0) {
return user?.name;
}
+ if ((user?.display_name?.length ?? 0) > 0) {
+ return user?.display_name;
+ }
return shortPubkey;
}
@@ -33,17 +36,32 @@ export function Profile({
}) {
const profile = useUserProfile(System, pubkey);
- return (
-
- {(options?.showAvatar ?? true) && (
+ const content = (
+ <>
+ {(options?.showAvatar ?? true) && pubkey === "anon" ? (
+
+ ) : (
)}
- {(options?.showName ?? true) &&
- (options?.overrideName ?? getName(pubkey, profile))}
-
+ {(options?.showName ?? true) && (
+
+ {options?.overrideName ?? pubkey === "anon"
+ ? "Anon"
+ : getName(pubkey, profile)}
+
+ )}
+ >
+ );
+
+ return pubkey === "anon" ? (
+
{content}
+ ) : (
+
+ {content}
+
);
}
diff --git a/src/element/send-zap.tsx b/src/element/send-zap.tsx
index 84257f0..0931917 100644
--- a/src/element/send-zap.tsx
+++ b/src/element/send-zap.tsx
@@ -1,6 +1,6 @@
import "./send-zap.css";
import * as Dialog from "@radix-ui/react-dialog";
-import { useEffect, useState } from "react";
+import { useEffect, useState, ReactNode } from "react";
import { LNURL } from "@snort/shared";
import { NostrEvent, EventPublisher } from "@snort/system";
import { formatSats } from "../number";
@@ -15,6 +15,7 @@ interface SendZapsProps {
ev?: NostrEvent;
targetName?: string;
onFinish: () => void;
+ button?: ReactNode;
}
function SendZaps({ lnurl, ev, targetName, onFinish }: SendZapsProps) {
@@ -154,15 +155,20 @@ export function SendZapsDialog({
lnurl,
ev,
targetName,
+ button,
}: Omit
) {
const [isOpen, setIsOpen] = useState(false);
return (
-
+ {button ? (
+ button
+ ) : (
+
+ )}
diff --git a/src/element/tags.tsx b/src/element/tags.tsx
new file mode 100644
index 0000000..4cd5c05
--- /dev/null
+++ b/src/element/tags.tsx
@@ -0,0 +1,26 @@
+import moment from "moment";
+import { TaggedRawEvent } from "@snort/system";
+import { StreamState } from "index";
+import { findTag } from "utils";
+
+export function Tags({ ev }: { ev: TaggedRawEvent }) {
+ const status = findTag(ev, "status");
+ const start = findTag(ev, "starts");
+ return (
+
+ {status === StreamState.Planned && (
+
+ Starts {moment(Number(start) * 1000).fromNow()}
+
+ )}
+ {ev.tags
+ .filter((a) => a[0] === "t")
+ .map((a) => a[1])
+ .map((a) => (
+
+ {a}
+
+ ))}
+
+ );
+}
diff --git a/src/element/text.tsx b/src/element/text.tsx
index e722d55..56070f3 100644
--- a/src/element/text.tsx
+++ b/src/element/text.tsx
@@ -1,5 +1,5 @@
import { useMemo, type ReactNode } from "react";
-import { TaggedRawEvent, validateNostrLink } from "@snort/system";
+import { validateNostrLink } from "@snort/system";
import { splitByUrl } from "utils";
import { Emoji } from "./emoji";
import { HyperText } from "./hypertext";
@@ -74,11 +74,11 @@ function extractLinks(fragments: Fragment[]) {
.flat();
}
-export function Text({ ev }: { ev: TaggedRawEvent }) {
+export function Text({ content, tags }: { content: string; tags: string[][] }) {
// todo: RTL langugage support
const element = useMemo(() => {
- return {transformText([ev.content], ev.tags)};
- }, [ev]);
+ return {transformText([content], tags)};
+ }, [content, tags]);
return <>{element}>;
}
diff --git a/src/hooks/live-chat.tsx b/src/hooks/live-chat.tsx
index fec2c02..01a28fd 100644
--- a/src/hooks/live-chat.tsx
+++ b/src/hooks/live-chat.tsx
@@ -1,7 +1,13 @@
-import { NostrLink, RequestBuilder, EventKind, FlatNoteStore } from "@snort/system";
+import {
+ NostrLink,
+ RequestBuilder,
+ EventKind,
+ FlatNoteStore,
+} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { System } from "index";
import { useMemo } from "react";
+import { LIVE_STREAM_CHAT } from "const";
export function useLiveChatFeed(link: NostrLink) {
const sub = useMemo(() => {
@@ -10,11 +16,11 @@ export function useLiveChatFeed(link: NostrLink) {
leaveOpen: true,
});
rb.withFilter()
- .kinds([EventKind.ZapReceipt, 1311 as EventKind])
+ .kinds([EventKind.ZapReceipt, LIVE_STREAM_CHAT])
.tag("a", [`${link.kind}:${link.author}:${link.id}`])
.limit(100);
return rb;
}, [link]);
return useRequestBuilder(System, FlatNoteStore, sub);
-}
\ No newline at end of file
+}
diff --git a/src/hooks/login.ts b/src/hooks/login.ts
index 1829200..77c8c07 100644
--- a/src/hooks/login.ts
+++ b/src/hooks/login.ts
@@ -2,5 +2,8 @@ import { Login } from "index";
import { useSyncExternalStore } from "react";
export function useLogin() {
- return useSyncExternalStore(c => Login.hook(c), () => Login.snapshot());
-}
\ No newline at end of file
+ return useSyncExternalStore(
+ (c) => Login.hook(c),
+ () => Login.snapshot()
+ );
+}
diff --git a/src/hooks/profile.ts b/src/hooks/profile.ts
new file mode 100644
index 0000000..fabd01a
--- /dev/null
+++ b/src/hooks/profile.ts
@@ -0,0 +1,67 @@
+import { useMemo } from "react";
+import {
+ RequestBuilder,
+ ReplaceableNoteStore,
+ FlatNoteStore,
+ NostrLink,
+ EventKind,
+ parseZap,
+} from "@snort/system";
+import { useRequestBuilder } from "@snort/system-react";
+import { LIVE_STREAM } from "const";
+import { findTag } from "utils";
+import { System } from "index";
+
+export function useProfile(link: NostrLink, leaveOpen = false) {
+ const sub = useMemo(() => {
+ const b = new RequestBuilder(`profile:${link.id.slice(0, 12)}`);
+ b.withOptions({
+ leaveOpen,
+ })
+ .withFilter()
+ .kinds([LIVE_STREAM])
+ .authors([link.id]);
+ return b;
+ }, [link, leaveOpen]);
+
+ const { data: streamsData } = useRequestBuilder(
+ System,
+ ReplaceableNoteStore,
+ sub
+ );
+
+ const streams = Array.isArray(streamsData)
+ ? streamsData
+ : streamsData
+ ? [streamsData]
+ : [];
+
+ const addresses = useMemo(() => {
+ return streams.map((e) => `${e.kind}:${e.pubkey}:${findTag(e, "d")}`);
+ }, [streamsData]);
+
+ const zapsSub = useMemo(() => {
+ const b = new RequestBuilder(`profile-zaps:${link.id.slice(0, 12)}`);
+ b.withOptions({
+ leaveOpen,
+ })
+ .withFilter()
+ .kinds([EventKind.ZapReceipt])
+ .tag("a", addresses);
+ return b;
+ }, [link, addresses, leaveOpen]);
+
+ const { data: zapsData } = useRequestBuilder(
+ System,
+ FlatNoteStore,
+ zapsSub
+ );
+ const zaps = (zapsData ?? [])
+ .map((ev) => parseZap(ev, System.ProfileLoader.Cache))
+ .filter((z) => z && z.valid);
+
+ return {
+ streams,
+ zaps,
+ };
+}
diff --git a/src/hooks/top-zappers.ts b/src/hooks/top-zappers.ts
new file mode 100644
index 0000000..1ce59fc
--- /dev/null
+++ b/src/hooks/top-zappers.ts
@@ -0,0 +1,25 @@
+import { useMemo } from "react";
+import { ParsedZap } from "@snort/system";
+
+function totalZapped(pubkey: string, zaps: ParsedZap[]) {
+ return zaps
+ .filter((z) => (z.anonZap ? pubkey === "anon" : z.sender === pubkey))
+ .reduce((acc, z) => acc + z.amount, 0);
+}
+
+export default function useTopZappers(zaps: ParsedZap[]) {
+ const zappers = zaps
+ .map((z) => (z.anonZap ? "anon" : z.sender))
+ .map((p) => p as string);
+
+ const sorted = useMemo(() => {
+ const pubkeys = [...new Set([...zappers])];
+ const result = pubkeys.map((pubkey) => {
+ return { pubkey, total: totalZapped(pubkey, zaps) };
+ });
+ result.sort((a, b) => b.total - a.total);
+ return result;
+ }, [zaps, zappers]);
+
+ return sorted;
+}
diff --git a/src/index.tsx b/src/index.tsx
index 1483aaa..ae3af0d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -7,6 +7,7 @@ import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { RootPage } from "./pages/root";
import { LayoutPage } from "pages/layout";
+import { ProfilePage } from "pages/profile-page";
import { StreamPage } from "pages/stream-page";
import { ChatPopout } from "pages/chat-popout";
import { LoginStore } from "login";
@@ -14,7 +15,7 @@ import { LoginStore } from "login";
export enum StreamState {
Live = "live",
Ended = "ended",
- Planned = "planned"
+ Planned = "planned",
}
export const System = new NostrSystem({});
@@ -42,7 +43,11 @@ const router = createBrowserRouter([
element: ,
},
{
- path: "/live/:id",
+ path: "/p/:npub",
+ element: ,
+ },
+ {
+ path: "/:id",
element: ,
},
{
diff --git a/src/login.ts b/src/login.ts
index c856704..0603211 100644
--- a/src/login.ts
+++ b/src/login.ts
@@ -1,29 +1,31 @@
import { ExternalStore } from "@snort/shared";
export interface LoginSession {
- pubkey: string
+ pubkey: string;
+ follows: string[];
}
export class LoginStore extends ExternalStore {
- #session?: LoginSession;
+ #session?: LoginSession;
- constructor() {
- super();
- const json = window.localStorage.getItem("session");
- if (json) {
- this.#session = JSON.parse(json);
- }
+ constructor() {
+ super();
+ const json = window.localStorage.getItem("session");
+ if (json) {
+ this.#session = JSON.parse(json);
}
+ }
- loginWithPubkey(pk: string) {
- this.#session = {
- pubkey: pk
- };
- window.localStorage.setItem("session", JSON.stringify(this.#session));
- this.notifyChange();
- }
+ loginWithPubkey(pk: string) {
+ this.#session = {
+ pubkey: pk,
+ follows: [],
+ };
+ window.localStorage.setItem("session", JSON.stringify(this.#session));
+ this.notifyChange();
+ }
- takeSnapshot() {
- return this.#session ? { ...this.#session } : undefined;
- }
-}
\ No newline at end of file
+ takeSnapshot() {
+ return this.#session ? { ...this.#session } : undefined;
+ }
+}
diff --git a/src/pages/layout.css b/src/pages/layout.css
index 321ad6a..81becb4 100644
--- a/src/pages/layout.css
+++ b/src/pages/layout.css
@@ -11,8 +11,7 @@
height: 100vh;
}
-
-.page.home {
+.page.only-content {
display: grid;
height: 100vh;
grid-template-areas:
@@ -84,6 +83,10 @@ header {
gap: 24px;
padding: 24px 0 32px 0;
}
+
+ .page.only-content {
+ grid-template-rows: 88px 1fr;
+ }
}
header .logo {
@@ -172,3 +175,12 @@ button span.hide-on-mobile {
max-height: 85vh;
padding: 25px;
}
+
+.zap-icon {
+ color: #FF8D2B;
+}
+
+.tags {
+ display: flex;
+ gap: 8px;
+}
diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx
index 3a2f8d9..1f85581 100644
--- a/src/pages/layout.tsx
+++ b/src/pages/layout.tsx
@@ -70,8 +70,8 @@ export function LayoutPage() {
return (
+
+
+
+
{formatSats(total)}
+
+
+ );
+}
+
+function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
+ const zappers = useTopZappers(zaps);
+ return (
+
+ {zappers.map((z) => (
+
+ ))}
+
+ );
+}
+
+const defaultBanner = "https://void.cat/d/Hn1AdN5UKmceuDkgDW847q.webp";
+
+export function ProfilePage() {
+ const navigate = useNavigate();
+ const params = useParams();
+ const link = parseNostrLink(params.npub!);
+ const profile = useUserProfile(System, link.id);
+ const zapTarget = profile?.lud16 ?? profile?.lud06;
+ const { streams, zaps } = useProfile(link, true);
+ const liveEvent = useMemo(() => {
+ return streams.find((ev) => findTag(ev, "status") === StreamState.Live);
+ }, [streams]);
+ const pastStreams = useMemo(() => {
+ return streams.filter((ev) => findTag(ev, "status") === StreamState.Ended);
+ }, [streams]);
+ const futureStreams = useMemo(() => {
+ return streams.filter(
+ (ev) => findTag(ev, "status") === StreamState.Planned
+ );
+ }, [streams]);
+ const isLive = Boolean(liveEvent);
+
+ function goToLive() {
+ if (liveEvent) {
+ const d =
+ liveEvent.tags?.find((t: string[]) => t?.at(0) === "d")?.at(1) || "";
+ const naddr = encodeTLV(
+ NostrPrefix.Address,
+ d,
+ undefined,
+ liveEvent.kind,
+ liveEvent.pubkey
+ );
+ navigate(`/${naddr}`);
+ }
+ }
+
+ // todo: follow
+
+ return (
+
+
+
+
+ {profile?.picture && (
+
+ )}
+
+ {isLive ? (
+
+
+ live
+
+ ) : (
+
offline
+ )}
+
+
+ {zapTarget && (
+
+
+ Zap
+
+
+
+ }
+ targetName={profile?.name || link.id}
+ />
+ )}
+
+
+ {profile?.name &&
{profile.name}
}
+ {profile?.about && (
+
+
+
+ )}
+
+
+
+
+ Top Zappers
+
+
+ Past Streams
+
+
+ Schedule
+
+
+
+
+
+
+
+ {pastStreams.map((ev) => (
+
+
+
+
+ ))}
+
+
+
+
+ {futureStreams.map((ev) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/root.tsx b/src/pages/root.tsx
index 456edc7..4536b22 100644
--- a/src/pages/root.tsx
+++ b/src/pages/root.tsx
@@ -2,53 +2,84 @@ import "./root.css";
import { useMemo } from "react";
import { unixNow } from "@snort/shared";
-import { EventKind, ParameterizedReplaceableNoteStore, RequestBuilder } from "@snort/system";
+import {
+ ParameterizedReplaceableNoteStore,
+ RequestBuilder,
+} from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { StreamState, System } from "..";
import { VideoTile } from "../element/video-tile";
import { findTag } from "utils";
+import { LIVE_STREAM } from "const";
export function RootPage() {
- const rb = useMemo(() => {
- const rb = new RequestBuilder("root");
- rb.withOptions({
- leaveOpen: true
- }).withFilter()
- .kinds([30_311 as EventKind])
- .since(unixNow() - 86400);
- return rb;
- }, []);
+ const rb = useMemo(() => {
+ const rb = new RequestBuilder("root");
+ rb.withOptions({
+ leaveOpen: true,
+ })
+ .withFilter()
+ .kinds([LIVE_STREAM])
+ .since(unixNow() - 86400);
+ return rb;
+ }, []);
- const feed = useRequestBuilder(System, ParameterizedReplaceableNoteStore, rb);
- const feedSorted = useMemo(() => {
- if (feed.data) {
- return [...feed.data].sort((a, b) => {
- const aStatus = findTag(a, "status")!;
- const bStatus = findTag(b, "status")!;
- if (aStatus === bStatus) {
- return b.created_at > a.created_at ? 1 : -1;
- } else {
- return aStatus === "live" ? -1 : 1;
- }
- });
+ const feed = useRequestBuilder(
+ System,
+ ParameterizedReplaceableNoteStore,
+ rb
+ );
+ const feedSorted = useMemo(() => {
+ if (feed.data) {
+ return [...feed.data].sort((a, b) => {
+ const aStatus = findTag(a, "status")!;
+ const bStatus = findTag(b, "status")!;
+ if (aStatus === bStatus) {
+ return b.created_at > a.created_at ? 1 : -1;
+ } else {
+ return aStatus === "live" ? -1 : 1;
}
- return [];
- }, [feed.data])
+ });
+ }
+ return [];
+ }, [feed.data]);
- const live = feedSorted.filter(a => findTag(a, "status") === StreamState.Live);
- const planned = feedSorted.filter(a => findTag(a, "status") === StreamState.Planned);
- const ended = feedSorted.filter(a => findTag(a, "status") === StreamState.Ended);
- return
-
- {live.map(e => )}
-
- {planned.length > 0 && <>
Planned
-
- {planned.map(e => )}
-
>}
- {ended.length > 0 && <>
Ended
-
- {ended.map(e => )}
-
>}
+ const live = feedSorted.filter(
+ (a) => findTag(a, "status") === StreamState.Live
+ );
+ const planned = feedSorted.filter(
+ (a) => findTag(a, "status") === StreamState.Planned
+ );
+ const ended = feedSorted.filter(
+ (a) => findTag(a, "status") === StreamState.Ended
+ );
+ return (
+
+
+ {live.map((e) => (
+
+ ))}
+
+ {planned.length > 0 && (
+ <>
+
Planned
+
+ {planned.map((e) => (
+
+ ))}
+
+ >
+ )}
+ {ended.length > 0 && (
+ <>
+
Ended
+
+ {ended.map((e) => (
+
+ ))}
+
+ >
+ )}
-}
\ No newline at end of file
+ );
+}
diff --git a/src/pages/stream-page.css b/src/pages/stream-page.css
index 0d6e83e..139f721 100644
--- a/src/pages/stream-page.css
+++ b/src/pages/stream-page.css
@@ -43,6 +43,7 @@
.pill.live {
color: inherit;
+ text-transform: uppercase;
}
@media (min-width: 1020px) {
@@ -103,11 +104,6 @@
margin: 0 0 12px 0;
}
-.tags {
- display: flex;
- gap: 8px;
-}
-
.actions {
margin: 8px 0 0 0;
display: flex;
diff --git a/src/pages/stream-page.tsx b/src/pages/stream-page.tsx
index 4db320d..e9eae89 100644
--- a/src/pages/stream-page.tsx
+++ b/src/pages/stream-page.tsx
@@ -2,7 +2,6 @@ import "./stream-page.css";
import { useRef } from "react";
import { parseNostrLink, EventPublisher } from "@snort/system";
import { useNavigate, useParams } from "react-router-dom";
-import moment from "moment";
import useEventFeed from "hooks/event-feed";
import { LiveVideoPlayer } from "element/live-video-player";
@@ -11,11 +10,12 @@ import { Profile, getName } from "element/profile";
import { LiveChat } from "element/live-chat";
import AsyncButton from "element/async-button";
import { useLogin } from "hooks/login";
-import { StreamState, System } from "index";
+import { System } from "index";
import { SendZapsDialog } from "element/send-zap";
import type { NostrLink } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { NewStreamDialog } from "element/new-stream";
+import { Tags } from "element/tags";
function ProfileInfo({ link }: { link: NostrLink }) {
const thisEvent = useEventFeed(link, true);
@@ -24,9 +24,6 @@ function ProfileInfo({ link }: { link: NostrLink }) {
const profile = useUserProfile(System, thisEvent.data?.pubkey);
const zapTarget = profile?.lud16 ?? profile?.lud06;
- const status = findTag(thisEvent.data, "status");
- const start = findTag(thisEvent.data, "starts");
- const isLive = status === "live";
const isMine = link.author === login?.pubkey;
async function deleteStream() {
@@ -45,22 +42,7 @@ function ProfileInfo({ link }: { link: NostrLink }) {
{findTag(thisEvent.data, "title")}
{findTag(thisEvent.data, "summary")}
-
- {status}
- {status === StreamState.Planned && (
-
- Starts {moment(Number(start) * 1000).fromNow()}
-
- )}
- {thisEvent.data?.tags
- .filter((a) => a[0] === "t")
- .map((a) => a[1])
- .map((a) => (
-
- {a}
-
- ))}
-
+ {thisEvent?.data &&
}
{isMine && (
{thisEvent.data && (
diff --git a/yarn.lock b/yarn.lock
index b2fc368..6d10e5c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1706,6 +1706,17 @@
dependencies:
"@babel/runtime" "^7.13.10"
+"@radix-ui/react-collection@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159"
+ integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-slot" "1.0.2"
+
"@radix-ui/react-compose-refs@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989"
@@ -1741,6 +1752,13 @@
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
+"@radix-ui/react-direction@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
+ integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@radix-ui/react-dismissable-layer@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz#883a48f5f938fa679427aa17fcba70c5494c6978"
@@ -1803,6 +1821,22 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "1.0.2"
+"@radix-ui/react-roving-focus@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
+ integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-collection" "1.0.3"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-direction" "1.0.1"
+ "@radix-ui/react-id" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+ "@radix-ui/react-use-controllable-state" "1.0.1"
+
"@radix-ui/react-slot@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
@@ -1811,6 +1845,21 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.1"
+"@radix-ui/react-tabs@^1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2"
+ integrity sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-direction" "1.0.1"
+ "@radix-ui/react-id" "1.0.1"
+ "@radix-ui/react-presence" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-roving-focus" "1.0.4"
+ "@radix-ui/react-use-controllable-state" "1.0.1"
+
"@radix-ui/react-use-callback-ref@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"