index.tsx raw
1 import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
2 import { Skeleton } from '@/components/ui/skeleton'
3 import { useFetchProfile } from '@/hooks'
4 import { toProfile } from '@/lib/link'
5 import { generateImageByPubkey } from '@/lib/pubkey'
6 import { cn, isTouchDevice } from '@/lib/utils'
7 import { SecondaryPageLink } from '@/PageManager'
8 import { useMemo } from 'react'
9 import Image from '../Image'
10 import ProfileCard from '../ProfileCard'
11
12 const UserAvatarSizeCnMap = {
13 large: 'w-24 h-24',
14 big: 'w-16 h-16',
15 semiBig: 'w-12 h-12',
16 normal: 'w-10 h-10',
17 medium: 'w-9 h-9',
18 small: 'w-7 h-7',
19 xSmall: 'w-5 h-5',
20 tiny: 'w-4 h-4'
21 }
22
23 export default function UserAvatar({
24 userId,
25 className,
26 size = 'normal'
27 }: {
28 userId: string
29 className?: string
30 size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
31 }) {
32 const supportTouch = useMemo(() => isTouchDevice(), [])
33
34 const trigger = (
35 <SecondaryPageLink to={toProfile(userId)} onClick={(e) => e.stopPropagation()}>
36 <SimpleUserAvatar userId={userId} size={size} className={className} />
37 </SecondaryPageLink>
38 )
39
40 if (supportTouch) {
41 return trigger
42 }
43
44 return (
45 <HoverCard>
46 <HoverCardTrigger>{trigger}</HoverCardTrigger>
47 <HoverCardContent className="w-72">
48 <ProfileCard userId={userId} />
49 </HoverCardContent>
50 </HoverCard>
51 )
52 }
53
54 export function SimpleUserAvatar({
55 userId,
56 size = 'normal',
57 className,
58 onClick
59 }: {
60 userId: string
61 size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
62 className?: string
63 onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
64 }) {
65 const { profile } = useFetchProfile(userId)
66 const defaultAvatar = useMemo(
67 () => (profile?.pubkey ? generateImageByPubkey(profile.pubkey) : ''),
68 [profile]
69 )
70
71 if (!profile) {
72 return (
73 <Skeleton className={cn('shrink-0', UserAvatarSizeCnMap[size], 'rounded-full', className)} />
74 )
75 }
76 const { avatar, pubkey } = profile || {}
77
78 return (
79 <Image
80 image={{ url: avatar ?? defaultAvatar, pubkey }}
81 errorPlaceholder={defaultAvatar}
82 className="object-cover object-center"
83 classNames={{
84 wrapper: cn('shrink-0 rounded-full bg-background', UserAvatarSizeCnMap[size], className)
85 }}
86 onClick={onClick}
87 />
88 )
89 }
90