Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-slot": "^1.2.4",
Expand Down
33 changes: 33 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions src/components/common/CreatorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import CreatorListRowDivider from '@/components/common/CreatorListRowDivider';
import BuyActionHelperText from '@/components/common/BuyActionHelperText';
import NetworkFeeHint from '@/components/common/NetworkFeeHint';
import CreatorBio from '@/components/common/CreatorBio';
import CreatorHandleHoverCard from '@/components/common/CreatorHandleHoverCard';
import { CREATOR_CARD_MEDIA_RADIUS_CLASS } from '@/utils/creatorCardTokens';

interface CreatorCardProps {
Expand Down Expand Up @@ -257,7 +258,15 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
{isRecentlyActive && <RecentActivityBadge />}
</div>
<p className="marketplace-label-muted font-jakarta text-sm">
{displayInstructorHandle}
<CreatorHandleHoverCard
handle={displayInstructorHandle}
volume24h={creator.volume24h}
change24h={creator.change24h}
creatorShareSupply={creator.creatorShareSupply}
isVerified={creator.isVerified}
>
{displayInstructorHandle}
</CreatorHandleHoverCard>
</p>

<CreatorBio bio={creator.description} variant="card" className="mt-2" />
Expand All @@ -269,7 +278,15 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
{creator.socialHandle ? (
<div className="marketplace-label-muted mt-2 flex items-center gap-1.5 text-xs">
<LinkIcon className="creator-action-icon text-amber-500/70" />
<span className="truncate">{displaySocialHandle}</span>
<CreatorHandleHoverCard
handle={displaySocialHandle}
volume24h={creator.volume24h}
change24h={creator.change24h}
creatorShareSupply={creator.creatorShareSupply}
isVerified={creator.isVerified}
>
<span className="truncate">{displaySocialHandle}</span>
</CreatorHandleHoverCard>
</div>
) : (
<div
Expand Down
107 changes: 107 additions & 0 deletions src/components/common/CreatorHandleHoverCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { ReactNode } from 'react';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/components/ui/hover-card';
import VerifiedBadge from '@/components/common/VerifiedBadge';
import { formatCompactNumber, formatPercent } from '@/utils/numberFormat.utils';
import { cn } from '@/lib/utils';

interface CreatorHandleHoverCardProps {
handle: string;
volume24h?: number;
change24h?: number;
creatorShareSupply?: number;
isVerified?: boolean;
children: ReactNode;
className?: string;
}

const CreatorHandleHoverCard: React.FC<CreatorHandleHoverCardProps> = ({
handle,
volume24h,
change24h,
creatorShareSupply,
isVerified,
children,
className,
}) => {
const hasStats =
volume24h !== undefined ||
change24h !== undefined ||
creatorShareSupply !== undefined;

return (
<HoverCard openDelay={300} closeDelay={150}>
<HoverCardTrigger asChild>
<span
className={cn(
'cursor-pointer border-b border-dotted border-white/20 transition-colors hover:border-amber-400/50',
className
)}
>
{children}
</span>
</HoverCardTrigger>
<HoverCardContent
side="top"
align="center"
className="w-56 border-white/10 bg-slate-900/95 backdrop-blur-xl"
>
<div className="space-y-3">
<div className="flex items-center gap-2">
<span className="font-jakarta text-sm font-semibold text-white">
{handle}
</span>
{isVerified && <VerifiedBadge verified className="shrink-0" />}
</div>

{hasStats && (
<div className="grid grid-cols-2 gap-2">
{volume24h !== undefined && (
<div className="rounded-lg bg-white/[0.06] px-2.5 py-1.5">
<p className="text-[10px] uppercase tracking-[0.12em] text-white/42">
Volume 24h
</p>
<p className="font-jakarta text-sm font-semibold text-white">
{formatCompactNumber(volume24h)} ETH
</p>
</div>
)}
{change24h !== undefined && (
<div className="rounded-lg bg-white/[0.06] px-2.5 py-1.5">
<p className="text-[10px] uppercase tracking-[0.12em] text-white/42">
24h Change
</p>
<p
className={cn(
'font-jakarta text-sm font-semibold',
change24h > 0 && 'text-emerald-400',
change24h < 0 && 'text-red-400',
change24h === 0 && 'text-white'
)}
>
{formatPercent(change24h, { signed: true })}
</p>
</div>
)}
{creatorShareSupply !== undefined && (
<div className="rounded-lg bg-white/[0.06] px-2.5 py-1.5">
<p className="text-[10px] uppercase tracking-[0.12em] text-white/42">
Supply
</p>
<p className="font-jakarta text-sm font-semibold text-white">
{formatCompactNumber(creatorShareSupply)}
</p>
</div>
)}
</div>
)}
</div>
</HoverCardContent>
</HoverCard>
);
};

export default CreatorHandleHoverCard;
5 changes: 4 additions & 1 deletion src/components/common/CreatorProfileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import VerifiedBadge from '@/components/common/VerifiedBadge';
import CreatorInitialsAvatar from '@/components/common/CreatorInitialsAvatar';
import CreatorBio from '@/components/common/CreatorBio';
import { formatCreatorHandle } from '@/utils/handleDisplay.utils';
import CreatorHandleHoverCard from '@/components/common/CreatorHandleHoverCard';
import { CREATOR_CARD_MEDIA_RADIUS_CLASS } from '@/utils/creatorCardTokens';

interface CreatorProfileHeaderProps {
Expand Down Expand Up @@ -110,7 +111,9 @@ const CreatorProfileHeader: React.FC<CreatorProfileHeaderProps> = ({
CREATOR_PROFILE_SUBTITLE_WRAP_CLASS_NAME
)}
>
{displayHandle || `@${handle}`}
<CreatorHandleHoverCard handle={displayHandle || `@${handle}`}>
{displayHandle || `@${handle}`}
</CreatorHandleHoverCard>
</p>
{/* #315: profile bio auto-collapses with a Show more / less
toggle once long enough. Short bios render unchanged. */}
Expand Down
42 changes: 42 additions & 0 deletions src/components/ui/hover-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';

import { cn } from '@/lib/utils';

function HoverCard({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
}

function HoverCardTrigger({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
);
}

function HoverCardContent({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
return (
<HoverCardPrimitive.Portal>
<HoverCardPrimitive.Content
data-slot="hover-card-content"
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
className
)}
{...props}
/>
</HoverCardPrimitive.Portal>
);
}

export { HoverCard, HoverCardTrigger, HoverCardContent };
Loading