Use avatar with emoji badge overlay for reaction cards in threads

Replace the standalone emoji bubble with the user's normal avatar plus a
small reaction emoji badge at the bottom-right corner. This keeps the
avatar column aligned with other threaded posts while still making the
reaction visually distinct.
This commit is contained in:
Alex Gleason
2026-03-01 20:18:10 -06:00
parent e6c971827b
commit f58a0ceed4
2 changed files with 62 additions and 84 deletions
+31 -54
View File
@@ -461,34 +461,25 @@ export function NoteCard({ event, className, repostedBy, compact, threaded, thre
onAuxClick={handleAuxClick}
>
<div className="flex gap-3">
{/* Left column: avatar with emoji badge overlay + optional thread line */}
<div className="flex flex-col items-center">
{/* Reaction emoji bubble instead of avatar */}
<div className="flex items-center justify-center size-10 rounded-full bg-pink-500/10 shrink-0">
<ReactionEmoji content={event.content} tags={event.tags} className="text-lg leading-none" />
<div className="relative shrink-0">
{avatarElement}
{/* Emoji badge overlaid at bottom-right of avatar */}
<div className="absolute -bottom-1 -right-1 flex items-center justify-center size-5 rounded-full bg-pink-500/10 ring-2 ring-background">
<ReactionEmoji content={event.content} tags={event.tags} className="text-xs leading-none" />
</div>
</div>
{threaded && <div className="w-0.5 flex-1 mt-2 bg-foreground/20 rounded-full" />}
</div>
<div className={cn('flex-1 min-w-0', threaded && 'pb-3')}>
<div className="flex items-center gap-2">
{/* Author name + "reacted" label */}
<div className="flex items-center gap-1.5">
{author.isLoading ? (
<Skeleton className="size-5 rounded-full shrink-0" />
<Skeleton className="h-4 w-20" />
) : (
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="shrink-0" onClick={(e) => e.stopPropagation()}>
<Avatar className="size-5">
<AvatarImage src={metadata?.picture} alt={displayName} />
<AvatarFallback className="bg-primary/20 text-primary text-[8px]">
{displayName[0]?.toUpperCase()}
</AvatarFallback>
</Avatar>
</Link>
</ProfileHoverCard>
)}
{author.isLoading ? (
<Skeleton className="h-3.5 w-20" />
) : (
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="font-semibold text-sm hover:underline truncate" onClick={(e) => e.stopPropagation()}>
<Link to={profileUrl} className="font-bold text-[15px] hover:underline truncate" onClick={(e) => e.stopPropagation()}>
{author.data?.event ? (
<EmojifiedText tags={author.data.event.tags}>{displayName}</EmojifiedText>
) : displayName}
@@ -515,43 +506,29 @@ export function NoteCard({ event, className, repostedBy, compact, threaded, thre
onAuxClick={handleAuxClick}
>
<div className="flex items-center gap-3">
{/* Large reaction emoji */}
<div className="flex items-center justify-center size-11 rounded-full bg-pink-500/10 shrink-0">
<ReactionEmoji content={event.content} tags={event.tags} className="text-xl leading-none" />
<div className="relative shrink-0">
{avatarElement}
{/* Emoji badge overlaid at bottom-right of avatar */}
<div className="absolute -bottom-1 -right-1 flex items-center justify-center size-6 rounded-full bg-pink-500/10 ring-2 ring-background">
<ReactionEmoji content={event.content} tags={event.tags} className="text-sm leading-none" />
</div>
</div>
{/* Author + "reacted" label */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
{author.isLoading ? (
<>
<Skeleton className="size-5 rounded-full shrink-0" />
<Skeleton className="h-4 w-24" />
</>
) : (
<>
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="shrink-0" onClick={(e) => e.stopPropagation()}>
<Avatar className="size-5">
<AvatarImage src={metadata?.picture} alt={displayName} />
<AvatarFallback className="bg-primary/20 text-primary text-[8px]">
{displayName[0]?.toUpperCase()}
</AvatarFallback>
</Avatar>
</Link>
</ProfileHoverCard>
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="font-semibold text-sm hover:underline truncate" onClick={(e) => e.stopPropagation()}>
{author.data?.event ? (
<EmojifiedText tags={author.data.event.tags}>{displayName}</EmojifiedText>
) : displayName}
</Link>
</ProfileHoverCard>
<span className="text-sm text-muted-foreground">reacted</span>
<span className="text-xs text-muted-foreground ml-auto shrink-0">{timeAgo(event.created_at)}</span>
</>
)}
</div>
<div className="flex items-center gap-1.5 flex-1 min-w-0">
{author.isLoading ? (
<Skeleton className="h-4 w-24" />
) : (
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="font-bold text-[15px] hover:underline truncate" onClick={(e) => e.stopPropagation()}>
{author.data?.event ? (
<EmojifiedText tags={author.data.event.tags}>{displayName}</EmojifiedText>
) : displayName}
</Link>
</ProfileHoverCard>
)}
<span className="text-sm text-muted-foreground">reacted</span>
<span className="text-xs text-muted-foreground ml-auto shrink-0">{timeAgo(event.created_at)}</span>
</div>
</div>
</article>
+31 -30
View File
@@ -852,41 +852,42 @@ function PostDetailContent({ event }: { event: NostrEvent }) {
{isReaction && (
<article ref={focusedPostRef} className="px-4 py-3">
<div className="flex items-center gap-3">
{/* Reaction emoji bubble — size-10 matches the threaded ancestor avatar column */}
<div className="flex items-center justify-center size-10 rounded-full bg-pink-500/10 shrink-0">
<ReactionEmoji content={event.content} tags={event.tags} className="text-xl leading-none" />
{/* Avatar with emoji badge overlay */}
<div className="relative shrink-0">
{author.isLoading ? (
<Skeleton className="size-11 rounded-full" />
) : (
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl}>
<Avatar className="size-11">
<AvatarImage src={metadata?.picture} alt={displayName} />
<AvatarFallback className="bg-primary/20 text-primary text-sm">
{displayName[0]?.toUpperCase()}
</AvatarFallback>
</Avatar>
</Link>
</ProfileHoverCard>
)}
<div className="absolute -bottom-1 -right-1 flex items-center justify-center size-6 rounded-full bg-pink-500/10 ring-2 ring-background">
<ReactionEmoji content={event.content} tags={event.tags} className="text-sm leading-none" />
</div>
</div>
{/* Author + "reacted" label + timestamp — single line */}
<div className="flex items-center gap-2 flex-1 min-w-0">
{/* Author + "reacted" label + timestamp */}
<div className="flex items-center gap-1.5 flex-1 min-w-0">
{author.isLoading ? (
<>
<Skeleton className="size-6 rounded-full shrink-0" />
<Skeleton className="h-4 w-28" />
</>
<Skeleton className="h-4 w-28" />
) : (
<>
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="shrink-0">
<Avatar className="size-6">
<AvatarImage src={metadata?.picture} alt={displayName} />
<AvatarFallback className="bg-primary/20 text-primary text-[10px]">
{displayName[0]?.toUpperCase()}
</AvatarFallback>
</Avatar>
</Link>
</ProfileHoverCard>
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="font-bold text-sm hover:underline truncate">
{author.data?.event ? (
<EmojifiedText tags={author.data.event.tags}>{displayName}</EmojifiedText>
) : displayName}
</Link>
</ProfileHoverCard>
<span className="text-sm text-muted-foreground">reacted</span>
<span className="text-xs text-muted-foreground ml-auto shrink-0">{formatFullDate(event.created_at)}</span>
</>
<ProfileHoverCard pubkey={event.pubkey} asChild>
<Link to={profileUrl} className="font-bold text-[15px] hover:underline truncate">
{author.data?.event ? (
<EmojifiedText tags={author.data.event.tags}>{displayName}</EmojifiedText>
) : displayName}
</Link>
</ProfileHoverCard>
)}
<span className="text-sm text-muted-foreground">reacted</span>
<span className="text-xs text-muted-foreground ml-auto shrink-0">{formatFullDate(event.created_at)}</span>
</div>
</div>
</article>