Browse Source

got rid of profile drawer and hover

imwald
Silberengel 5 months ago
parent
commit
93612879f2
  1. 81
      src/PageManager.tsx
  2. 69
      src/components/UserAvatar/index.tsx
  3. 65
      src/components/Username/index.tsx

81
src/PageManager.tsx

@ -172,15 +172,14 @@ export function useSmartProfileNavigation() {
const { setPrimaryNoteView } = usePrimaryNoteView() const { setPrimaryNoteView } = usePrimaryNoteView()
const navigateToProfile = (url: string) => { const navigateToProfile = (url: string) => {
if (!showRecommendedRelaysPanel) { if (showRecommendedRelaysPanel) {
// When right panel is hidden, show profile in primary area // Secondary panel is available - show profile in secondary panel
// Extract profile ID from URL (e.g., "/users/npub1..." -> "npub1...") pushSecondary(url)
} else {
// Secondary panel is not available - show profile in primary panel
const profileId = url.replace('/users/', '') const profileId = url.replace('/users/', '')
window.history.replaceState(null, '', url) window.history.replaceState(null, '', url)
setPrimaryNoteView(<SecondaryProfilePage id={profileId} index={0} hideTitlebar={true} />, 'profile') setPrimaryNoteView(<SecondaryProfilePage id={profileId} index={0} hideTitlebar={true} />, 'profile')
} else {
// Normal behavior - use secondary navigation
pushSecondary(url)
} }
} }
@ -678,28 +677,58 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
<CurrentRelaysProvider> <CurrentRelaysProvider>
<NotificationProvider> <NotificationProvider>
<PrimaryNoteViewContext.Provider value={{ setPrimaryNoteView, primaryViewType }}> <PrimaryNoteViewContext.Provider value={{ setPrimaryNoteView, primaryViewType }}>
{!!secondaryStack.length && {primaryNoteView ? (
secondaryStack.map((item, index) => ( // Show primary note view with back button on mobile
<div <div className="flex flex-col h-full w-full">
key={item.index} <div className="flex gap-1 p-1 items-center justify-between font-semibold border-b">
style={{ <div className="flex items-center flex-1 w-0">
display: index === secondaryStack.length - 1 ? 'block' : 'none' <Button
}} className="flex gap-1 items-center w-fit max-w-full justify-start pl-2 pr-3"
> variant="ghost"
{item.component} size="titlebar-icon"
title="Back to feed"
onClick={() => setPrimaryNoteView(null)}
>
<ChevronLeft />
<div className="truncate text-lg font-semibold">
{primaryViewType === 'settings' ? 'Settings' :
primaryViewType === 'settings-sub' ? 'Settings' :
primaryViewType === 'profile' ? 'Back' :
primaryViewType === 'hashtag' ? 'Hashtag' : 'Note'}
</div>
</Button>
</div>
</div>
<div className="flex-1 overflow-auto">
{primaryNoteView}
</div> </div>
))}
{primaryPages.map(({ name, element, props }) => (
<div
key={name}
style={{
display:
secondaryStack.length === 0 && currentPrimaryPage === name ? 'block' : 'none'
}}
>
{props ? cloneElement(element as React.ReactElement, props) : element}
</div> </div>
))} ) : (
<>
{!!secondaryStack.length &&
secondaryStack.map((item, index) => (
<div
key={item.index}
style={{
display: index === secondaryStack.length - 1 ? 'block' : 'none'
}}
>
{item.component}
</div>
))}
{primaryPages.map(({ name, element, props }) => (
<div
key={name}
style={{
display:
secondaryStack.length === 0 && currentPrimaryPage === name ? 'block' : 'none'
}}
>
{props ? cloneElement(element as React.ReactElement, props) : element}
</div>
))}
</>
)}
<BottomNavigationBar /> <BottomNavigationBar />
<TooManyRelaysAlertDialog /> <TooManyRelaysAlertDialog />
<CreateWalletGuideToast /> <CreateWalletGuideToast />

69
src/components/UserAvatar/index.tsx

@ -1,13 +1,11 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks' import { useFetchProfile } from '@/hooks'
import { generateImageByPubkey } from '@/lib/pubkey' import { generateImageByPubkey } from '@/lib/pubkey'
import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useSmartProfileNavigation } from '@/PageManager'
import { useMemo, useState } from 'react' import { useMemo } from 'react'
import ProfileCard from '../ProfileCard'
const UserAvatarSizeCnMap = { const UserAvatarSizeCnMap = {
large: 'w-24 h-24', large: 'w-24 h-24',
@ -30,8 +28,7 @@ export default function UserAvatar({
size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny' size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
}) { }) {
const { profile } = useFetchProfile(userId) const { profile } = useFetchProfile(userId)
const { isSmallScreen } = useScreenSize() const { navigateToProfile } = useSmartProfileNavigation()
const [drawerOpen, setDrawerOpen] = useState(false)
const defaultAvatar = useMemo( const defaultAvatar = useMemo(
() => (profile?.pubkey ? generateImageByPubkey(profile.pubkey) : ''), () => (profile?.pubkey ? generateImageByPubkey(profile.pubkey) : ''),
[profile] [profile]
@ -42,59 +39,30 @@ export default function UserAvatar({
<Skeleton className={cn('shrink-0', UserAvatarSizeCnMap[size], 'rounded-full', className)} /> <Skeleton className={cn('shrink-0', UserAvatarSizeCnMap[size], 'rounded-full', className)} />
) )
} }
const { avatar, pubkey } = profile
if (isSmallScreen) { const { avatar, pubkey } = profile
return (
<>
<Avatar
className={cn('shrink-0 cursor-pointer', UserAvatarSizeCnMap[size], className)}
onClick={() => setDrawerOpen(true)}
>
<AvatarImage src={avatar} className="object-cover object-center" />
<AvatarFallback>
<img src={defaultAvatar} alt={pubkey} />
</AvatarFallback>
</Avatar>
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerOverlay onClick={() => setDrawerOpen(false)} />
<DrawerContent hideOverlay className="max-h-[90vh]">
<div className="overflow-y-auto overscroll-contain p-4" style={{ touchAction: 'pan-y' }}>
<ProfileCard pubkey={pubkey} />
</div>
</DrawerContent>
</Drawer>
</>
)
}
return ( return (
<HoverCard> <Avatar
<HoverCardTrigger asChild> className={cn('shrink-0 cursor-pointer', UserAvatarSizeCnMap[size], className)}
<Avatar className={cn('shrink-0 cursor-pointer', UserAvatarSizeCnMap[size], className)}> onClick={() => navigateToProfile(toProfile(pubkey))}
<AvatarImage src={avatar} className="object-cover object-center" /> >
<AvatarFallback> <AvatarImage src={avatar} className="object-cover object-center" />
<img src={defaultAvatar} alt={pubkey} /> <AvatarFallback>
</AvatarFallback> <img src={defaultAvatar} alt={pubkey} />
</Avatar> </AvatarFallback>
</HoverCardTrigger> </Avatar>
<HoverCardContent className="w-72">
<ProfileCard pubkey={pubkey} />
</HoverCardContent>
</HoverCard>
) )
} }
export function SimpleUserAvatar({ export function SimpleUserAvatar({
userId, userId,
size = 'normal', size = 'normal',
className, className
onClick
}: { }: {
userId: string userId: string
size?: 'large' | 'big' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny' size?: 'large' | 'big' | 'semiBig' | 'normal' | 'medium' | 'small' | 'xSmall' | 'tiny'
className?: string className?: string
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
}) { }) {
const { profile } = useFetchProfile(userId) const { profile } = useFetchProfile(userId)
const defaultAvatar = useMemo( const defaultAvatar = useMemo(
@ -107,14 +75,15 @@ export function SimpleUserAvatar({
<Skeleton className={cn('shrink-0', UserAvatarSizeCnMap[size], 'rounded-full', className)} /> <Skeleton className={cn('shrink-0', UserAvatarSizeCnMap[size], 'rounded-full', className)} />
) )
} }
const { avatar, pubkey } = profile const { avatar, pubkey } = profile
return ( return (
<Avatar className={cn('shrink-0', UserAvatarSizeCnMap[size], className)} onClick={onClick}> <Avatar className={cn('shrink-0', UserAvatarSizeCnMap[size], className)}>
<AvatarImage src={avatar} className="object-cover object-center" /> <AvatarImage src={avatar} className="object-cover object-center" />
<AvatarFallback> <AvatarFallback>
<img src={defaultAvatar} alt={pubkey} /> <img src={defaultAvatar} alt={pubkey} />
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
) )
} }

65
src/components/Username/index.tsx

@ -1,11 +1,8 @@
import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks' import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useSmartProfileNavigation } from '@/PageManager'
import { useState } from 'react'
import ProfileCard from '../ProfileCard'
export default function Username({ export default function Username({
userId, userId,
@ -21,8 +18,7 @@ export default function Username({
withoutSkeleton?: boolean withoutSkeleton?: boolean
}) { }) {
const { profile } = useFetchProfile(userId) const { profile } = useFetchProfile(userId)
const { isSmallScreen } = useScreenSize() const { navigateToProfile } = useSmartProfileNavigation()
const [drawerOpen, setDrawerOpen] = useState(false)
if (!profile && !withoutSkeleton) { if (!profile && !withoutSkeleton) {
return ( return (
@ -31,44 +27,21 @@ export default function Username({
</div> </div>
) )
} }
if (!profile) return null
const { username, pubkey } = profile if (!profile) {
return null
if (isSmallScreen) {
return (
<>
<div
className={cn('truncate hover:underline cursor-pointer', className)}
onClick={() => setDrawerOpen(true)}
>
{showAt && '@'}
{username}
</div>
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerOverlay onClick={() => setDrawerOpen(false)} />
<DrawerContent hideOverlay className="max-h-[90vh]">
<div className="overflow-y-auto overscroll-contain p-4" style={{ touchAction: 'pan-y' }}>
<ProfileCard pubkey={pubkey} />
</div>
</DrawerContent>
</Drawer>
</>
)
} }
const { username, pubkey } = profile
return ( return (
<HoverCard> <div
<HoverCardTrigger asChild> className={cn('truncate hover:underline cursor-pointer', className)}
<div className={cn('truncate hover:underline cursor-pointer', className)}> onClick={() => navigateToProfile(toProfile(pubkey))}
{showAt && '@'} >
{username} {showAt && '@'}
</div> {username}
</HoverCardTrigger> </div>
<HoverCardContent className="w-80">
<ProfileCard pubkey={pubkey} />
</HoverCardContent>
</HoverCard>
) )
} }
@ -86,6 +59,7 @@ export function SimpleUsername({
withoutSkeleton?: boolean withoutSkeleton?: boolean
}) { }) {
const { profile } = useFetchProfile(userId) const { profile } = useFetchProfile(userId)
if (!profile && !withoutSkeleton) { if (!profile && !withoutSkeleton) {
return ( return (
<div className="py-1"> <div className="py-1">
@ -93,14 +67,17 @@ export function SimpleUsername({
</div> </div>
) )
} }
if (!profile) return null
if (!profile) {
return null
}
const { username } = profile const { username } = profile
return ( return (
<div className={className}> <div className={cn('truncate', className)}>
{showAt && '@'} {showAt && '@'}
{username} {username}
</div> </div>
) )
} }
Loading…
Cancel
Save