This commit is contained in:
Ren Amamiya
2023-10-10 11:51:01 +07:00
parent 043c1b1220
commit bc4c3b9803
39 changed files with 411 additions and 216 deletions
+10 -27
View File
@@ -28,33 +28,16 @@ export function NoteActions({
return (
<Tooltip.Provider>
<div className="-ml-1 mt-3 inline-flex w-full items-center">
<div className="inline-flex items-center gap-2">
<div className="-ml-1 mt-4 inline-flex w-full items-center">
<div className="inline-flex items-center gap-8">
<NoteReply id={id} pubkey={pubkey} root={root} />
<NoteReaction id={id} pubkey={pubkey} />
<NoteRepost id={id} pubkey={pubkey} />
<NoteZap id={id} pubkey={pubkey} />
</div>
{extraButtons && (
<>
<div className="mx-2 block h-4 w-px bg-white/10 backdrop-blur-xl" />
<div className="inline-flex items-center gap-2">
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<Link
to={`/notes/text/${id}`}
className="group inline-flex h-7 w-7 items-center justify-center"
>
<FocusIcon className="h-5 w-5 text-white/80 group-hover:text-blue-400" />
</Link>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none 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">
Focus
<Tooltip.Arrow className="fill-black" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<div className="ml-auto">
<div className="inline-flex items-center gap-3">
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
@@ -66,22 +49,22 @@ export function NoteActions({
content: id,
})
}
className="group inline-flex h-7 w-7 items-center justify-center"
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
>
<ThreadIcon className="h-5 w-5 text-white/80 group-hover:text-blue-400" />
<ThreadIcon className="h-5 w-5 group-hover:text-blue-500" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none 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">
<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">
Open thread
<Tooltip.Arrow className="fill-black" />
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<MoreActions id={id} pubkey={pubkey} />
</div>
</>
</div>
)}
<MoreActions id={id} pubkey={pubkey} />
</div>
</Tooltip.Provider>
);
+16 -7
View File
@@ -30,26 +30,35 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
<DropdownMenu.Trigger asChild>
<button
type="button"
className="group ml-auto inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
>
<HorizontalDotsIcon className="h-5 w-5 group-hover:text-blue-500" />
</button>
</DropdownMenu.Trigger>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none 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">
<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">
More
<Tooltip.Arrow className="fill-black" />
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<DropdownMenu.Portal>
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-white/10 bg-white/20 p-2 backdrop-blur-3xl focus:outline-none">
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-300 bg-neutral-200 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800">
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => copyLink()}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10 focus:outline-none"
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
>
Focus
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
type="button"
onClick={() => copyLink()}
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
>
Copy shareable link
</button>
@@ -58,7 +67,7 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
<button
type="button"
onClick={() => copyID()}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10 focus:outline-none"
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
>
Copy ID
</button>
@@ -66,7 +75,7 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
<DropdownMenu.Item asChild>
<Link
to={`/users/${pubkey}`}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10 focus:outline-none"
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
>
View profile
</Link>
+4 -4
View File
@@ -65,15 +65,15 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
>
{reaction ? (
<img src={getReactionImage(reaction)} alt={reaction} className="h-6 w-6" />
<img src={getReactionImage(reaction)} alt={reaction} className="h-5 w-5" />
) : (
<ReactionIcon className="h-5 w-5 group-hover:text-red-400" />
<ReactionIcon className="h-5 w-5 group-hover:text-blue-500" />
)}
</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="select-none rounded-md bg-black 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"
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"
sideOffset={0}
side="top"
>
@@ -122,7 +122,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
<img src="/clown_face.png" alt="Clown Face" className="h-6 w-6" />
</button>
</div>
<Popover.Arrow className="fill-black" />
<Popover.Arrow className="fill-neutral-200 dark:fill-neutral-800" />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
+3 -3
View File
@@ -23,13 +23,13 @@ export function NoteReply({
onClick={() => setReply(id, pubkey, root)}
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
>
<ReplyIcon className="h-5 w-5 group-hover:text-green-500" />
<ReplyIcon className="h-5 w-5 group-hover:text-blue-500" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none 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">
<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">
Quick reply
<Tooltip.Arrow className="fill-black" />
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
+3 -3
View File
@@ -48,7 +48,7 @@ export function NoteRepost({ id, pubkey }: { id: string; pubkey: string }) {
>
<RepostIcon
className={twMerge(
'h-5 w-5 group-hover:text-blue-500',
'h-5 w-5 group-hover:text-blue-600',
isRepost ? 'text-blue-500' : ''
)}
/>
@@ -56,9 +56,9 @@ export function NoteRepost({ id, pubkey }: { id: string; pubkey: string }) {
</AlertDialog.Trigger>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none 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">
<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">
Repost
<Tooltip.Arrow className="fill-black" />
<Tooltip.Arrow className="fill-neutral-200 dark:fill-neutral-800" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
+1 -1
View File
@@ -95,7 +95,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
type="button"
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
>
<ZapIcon className="h-5 w-5 group-hover:text-orange-400" />
<ZapIcon className="h-5 w-5 group-hover:text-blue-500" />
</button>
</Dialog.Trigger>
<Dialog.Portal>
+2 -2
View File
@@ -56,7 +56,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) {
Lume <span className="text-green-500">(System)</span>
</h5>
</div>
<div className="-mt-5 flex items-start gap-3">
<div className="-mt-3 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div>
<div className="relative z-20 mt-1 flex-1 select-text">
@@ -81,7 +81,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) {
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.6rem)] w-0.5 bg-gradient-to-t from-black/20 to-black/10 dark:from-white/20 dark:to-white/10" />
<div className="mb-6 flex flex-col">
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-4 flex items-start gap-3">
<div className="-mt-3 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div className="relative z-20 flex-1">
{renderKind(data)}
+3 -1
View File
@@ -1,5 +1,5 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useMemo } from 'react';
import { memo, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { Image } from '@shared/image';
@@ -54,3 +54,5 @@ export function ArticleNote(props: { event?: NDKEvent }) {
</Link>
);
}
export const MemoizedArticleNote = memo(ArticleNote);
+12 -3
View File
@@ -5,6 +5,7 @@ import {
DefaultVideoLayout,
defaultLayoutIcons,
} from '@vidstack/react/player/layouts/default';
import { memo } from 'react';
import { Link } from 'react-router-dom';
import { Image } from '@shared/image';
@@ -34,13 +35,19 @@ export function FileNote(props: { event?: NDKEvent }) {
key={url}
src={url}
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
load="visible"
load="idle"
aspectRatio="16/9"
muted={true}
crossorigin=""
className="player"
>
<MediaProvider />
<MediaProvider>
<Poster
className="vds-poster"
src={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
alt="poster"
/>
</MediaProvider>
<DefaultAudioLayout
icons={defaultLayoutIcons}
smallLayoutWhen="(width < 500) or (height < 380)"
@@ -61,10 +68,12 @@ export function FileNote(props: { event?: NDKEvent }) {
<Link
to={url}
target="_blank"
className="break-all font-normal text-blue-500 hover:text-blue-500"
className="break-all font-normal text-blue-500 hover:text-blue-600"
>
{url}
</Link>
</div>
);
}
export const MemoizedFileNote = memo(FileNote);
+6 -4
View File
@@ -1,7 +1,7 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { nip19 } from 'nostr-tools';
import { useCallback } from 'react';
import { memo, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
@@ -71,7 +71,7 @@ export function Repost({
<User pubkey={event.pubkey} time={event.created_at} variant="repost" />
<div className="relative flex flex-col">
<User pubkey={embedEvent.pubkey} time={embedEvent.created_at} />
<div className="-mt-4 flex items-start gap-3">
<div className="-mt-3 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div className="relative z-20 flex-1">
{renderKind(embedEvent)}
@@ -116,7 +116,7 @@ export function Repost({
Lume <span className="text-green-500">(System)</span>
</h5>
</div>
<div className="-mt-4 flex items-start gap-3">
<div className="-mt-3 flex items-start gap-3">
<div className="w-11 shrink-0" />
<div>
<div className="relative z-20 mt-1 flex-1 select-text">
@@ -148,7 +148,7 @@ export function Repost({
<User pubkey={event.pubkey} time={event.created_at} variant="repost" />
<div className="relative flex flex-col">
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-4 flex items-start gap-3">
<div className="-mt-3 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div className="relative z-20 flex-1">
{renderKind(data)}
@@ -160,3 +160,5 @@ export function Repost({
</div>
);
}
export const MemoizedRepost = memo(Repost);
+3
View File
@@ -1,3 +1,4 @@
import { memo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Link } from 'react-router-dom';
import remarkGfm from 'remark-gfm';
@@ -82,3 +83,5 @@ export function TextNote(props: { content?: string }) {
</div>
);
}
export const MemoizedTextNote = memo(TextNote);
+2 -2
View File
@@ -2,7 +2,7 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
export function UnknownNote(props: { event?: NDKEvent }) {
return (
<div className="mt-2 flex w-full flex-col gap-2">
<div className="flex w-full flex-col gap-2">
<div className="inline-flex flex-col rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800">
<span className="text-sm font-medium text-neutral-500 dark:text-neutral-400">
Kind: {props.event.kind}
@@ -11,7 +11,7 @@ export function UnknownNote(props: { event?: NDKEvent }) {
Unsupport kind on newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-all text-neutral-800 dark:text-neutral-200">
<div className="select-text whitespace-pre-line break-words text-neutral-800 dark:text-neutral-200">
<p>{props.event.content.toString()}</p>
</div>
</div>
+1 -1
View File
@@ -1,3 +1,3 @@
export function Boost({ boost }: { boost: string }) {
return <span className="break-words text-blue-400 hover:text-blue-500">{boost}</span>;
return <span className="break-words text-blue-400 hover:text-blue-600">{boost}</span>;
}
+1 -1
View File
@@ -24,7 +24,7 @@ export function Hashtag({ tag }: { tag: string }) {
content: tag.replace('#', ''),
})
}
className="break-all text-blue-500 hover:text-blue-500"
className="cursor-default break-all text-blue-500 hover:text-blue-600"
>
{tag}
</span>
+5 -4
View File
@@ -1,9 +1,10 @@
import { QRCodeSVG } from 'qrcode.react';
import { memo } from 'react';
export function Invoice({ invoice }: { invoice: string }) {
export const Invoice = memo(function Invoice({ invoice }: { invoice: string }) {
return (
<div className="mt-2 flex items-center rounded-lg bg-neutral-200 p-2 dark:bg-neutral-800">
<span className="mt-2 flex items-center rounded-lg bg-neutral-200 p-2 dark:bg-neutral-800">
<QRCodeSVG value={invoice} includeMargin={true} className="rounded-lg" />
</div>
</span>
);
}
});
+1 -1
View File
@@ -30,7 +30,7 @@ export const MentionUser = memo(function MentionUser({ pubkey }: { pubkey: strin
content: pubkey,
})
}
className="break-words text-blue-500 hover:text-blue-500"
className="break-words text-blue-500 hover:text-blue-600"
>
{'@' +
(user?.name ||
+19 -24
View File
@@ -11,30 +11,25 @@ export function ImagePreview({ urls, truncate }: { urls: string[]; truncate?: bo
};
return (
<div className="mt-3 overflow-hidden">
<div className="flex flex-col gap-2">
{urls.map((url) => (
<div key={url} className="group relative min-w-0 shrink-0 grow-0 basis-full">
<img
src={url}
alt="image"
className={`${
truncate ? 'h-auto max-h-[300px]' : 'h-auto'
} w-full rounded-lg border border-white/10 object-cover`}
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
/>
<button
type="button"
onClick={() => downloadImage(url)}
className="absolute right-2 top-2 hidden h-8 w-8 items-center justify-center rounded-md bg-black/50 backdrop-blur-md hover:bg-black/40 group-hover:inline-flex"
>
<DownloadIcon className="h-5 w-5 text-white" />
</button>
</div>
))}
</div>
<div className="flex flex-col gap-2">
{urls.map((url) => (
<div key={url} className="group relative min-w-0 shrink-0 grow-0 basis-full">
<img
src={url}
alt="image"
className={`${
truncate ? 'h-auto max-h-[300px]' : 'h-auto'
} w-full rounded-lg border border-white/10 object-cover`}
/>
<button
type="button"
onClick={() => downloadImage(url)}
className="absolute right-2 top-2 hidden h-8 w-8 items-center justify-center rounded-md bg-black/50 backdrop-blur-md group-hover:inline-flex hover:bg-black/40"
>
<DownloadIcon className="h-5 w-5 text-white" />
</button>
</div>
))}
</div>
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
const domain = new URL(urls[0]);
return (
<div className="mt-3 overflow-hidden rounded-lg bg-white/10 backdrop-blur-xl">
<div className="rounded-lg bg-neutral-200 dark:bg-neutral-800">
{status === 'loading' ? (
<div className="flex flex-col">
<div className="h-44 w-full animate-pulse bg-white/10 backdrop-blur-xl" />
+13 -5
View File
@@ -4,21 +4,29 @@ import {
DefaultVideoLayout,
defaultLayoutIcons,
} from '@vidstack/react/player/layouts/default';
import { memo } from 'react';
export function VideoPreview({ urls }: { urls: string[] }) {
export const VideoPreview = memo(function VideoPreview({ urls }: { urls: string[] }) {
return (
<div className="relative mt-3 flex w-full flex-col gap-2">
<div className="flex flex-col gap-2">
{urls.map((url) => (
<MediaPlayer
key={url}
src={url}
load="visible"
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
load="idle"
aspectRatio="16/9"
crossorigin=""
muted={true}
className="player"
>
<MediaProvider />
<MediaProvider>
<Poster
className="vds-poster"
src={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
alt="poster"
/>
</MediaProvider>
<DefaultAudioLayout
icons={defaultLayoutIcons}
smallLayoutWhen="(width < 500) or (height < 380)"
@@ -33,4 +41,4 @@ export function VideoPreview({ urls }: { urls: string[] }) {
))}
</div>
);
}
});
+1 -1
View File
@@ -31,7 +31,7 @@ export function NoteWrapper({
<div className="relative">{reply && <ChildNote id={reply} root={root} />}</div>
<div className="relative flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-4 flex items-start gap-3">
<div className="-mt-3 flex items-start gap-3">
<div className="w-10 shrink-0" />
<div className="relative z-20 flex-1">
{cloneElement(