Files
eranos/src/components/ReplyContext.tsx
T

85 lines
2.9 KiB
TypeScript

import { Link } from 'react-router-dom';
import { EmbeddedNote } from '@/components/EmbeddedNote';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
import { Skeleton } from '@/components/ui/skeleton';
import { ProfileHoverCard } from '@/components/ProfileHoverCard';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
import { useProfileUrl } from '@/hooks/useProfileUrl';
interface ReplyContextProps {
pubkeys: string[];
/** Hex event ID of the parent post being replied to. */
parentEventId?: string;
className?: string;
}
/**
* Displays "Replying to @username" or "Replying to @user1 and @user2" context for reply posts.
* When parentEventId is provided, hovering over the line shows an embedded preview of the parent post.
* Used consistently across NoteCard and notification views.
*/
export function ReplyContext({ pubkeys, parentEventId, className }: ReplyContextProps) {
// Show max 2 authors for cleaner UI
const displayPubkeys = pubkeys.slice(0, 2);
const replyingToLabel = parentEventId ? (
<HoverCard openDelay={300} closeDelay={150}>
<HoverCardTrigger asChild>
<span className="shrink-0 cursor-pointer hover:underline">Replying to</span>
</HoverCardTrigger>
<HoverCardContent
side="bottom"
align="start"
sideOffset={4}
className="w-80 p-0 rounded-2xl shadow-lg"
onClick={(e) => e.stopPropagation()}
>
<EmbeddedNote eventId={parentEventId} className="border-0 rounded-none" disableHoverCards />
</HoverCardContent>
</HoverCard>
) : (
<span className="shrink-0">Replying to</span>
);
return (
<div className={className || 'flex items-center flex-wrap gap-x-1 text-sm text-muted-foreground mt-2 mb-1 min-w-0 overflow-hidden'}>
{replyingToLabel}
{displayPubkeys.map((pubkey, index) => (
<span key={pubkey} className="inline-flex items-center gap-1 min-w-0">
<ReplyAuthor pubkey={pubkey} />
{index < displayPubkeys.length - 1 && <span className="shrink-0">and</span>}
</span>
))}
{pubkeys.length > 2 && (
<span className="text-muted-foreground shrink-0">
and {pubkeys.length - 2} other{pubkeys.length - 2 !== 1 ? 's' : ''}
</span>
)}
</div>
);
}
function ReplyAuthor({ pubkey }: { pubkey: string }) {
const author = useAuthor(pubkey);
const name = author.data?.metadata?.name || genUserName(pubkey);
const profileUrl = useProfileUrl(pubkey, author.data?.metadata);
if (author.isLoading) {
return <Skeleton className="h-3.5 w-20 inline-block" />;
}
return (
<ProfileHoverCard pubkey={pubkey} asChild>
<Link
to={profileUrl}
className="text-primary hover:underline truncate max-w-[200px] inline-block align-bottom"
onClick={(e) => e.stopPropagation()}
>
@{name}
</Link>
</ProfileHoverCard>
);
}