1 Commits

Author SHA1 Message Date
codytseng
7deb3fe2d3 refactor: feed switcher 2025-12-16 18:08:02 +08:00
22 changed files with 221 additions and 129 deletions

View File

@@ -6,7 +6,8 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useFeed } from '@/providers/FeedProvider' import { useFeed } from '@/providers/FeedProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { usePinnedUsers } from '@/providers/PinnedUsersProvider' import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
import { Star, UsersRound } from 'lucide-react' import { Settings2, Star, UsersRound } from 'lucide-react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import RelayIcon from '../RelayIcon' import RelayIcon from '../RelayIcon'
import RelaySetCard from '../RelaySetCard' import RelaySetCard from '../RelaySetCard'
@@ -17,81 +18,112 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
const { relaySets, favoriteRelays } = useFavoriteRelays() const { relaySets, favoriteRelays } = useFavoriteRelays()
const { feedInfo, switchFeed } = useFeed() const { feedInfo, switchFeed } = useFeed()
const { pinnedPubkeySet } = usePinnedUsers() const { pinnedPubkeySet } = usePinnedUsers()
const filteredRelaySets = useMemo(
() => relaySets.filter((set) => set.relayUrls.length > 0),
[relaySets]
)
const hasRelays = filteredRelaySets.length > 0 || favoriteRelays.length > 0
return ( return (
<div className="space-y-2"> <div className="space-y-4">
<FeedSwitcherItem {/* Personal Feeds Section */}
isActive={feedInfo?.feedType === 'following'} <div className="space-y-2">
disabled={!pubkey} <SectionHeader title={t('Personal Feeds')} />
onClick={() => { <div className="space-y-1.5">
if (!pubkey) return <FeedSwitcherItem
switchFeed('following', { pubkey }) isActive={feedInfo?.feedType === 'following'}
close?.() disabled={!pubkey}
}} onClick={() => {
> if (!pubkey) return
<div className="flex gap-2 items-center"> switchFeed('following', { pubkey })
<div className="flex justify-center items-center w-6 h-6 shrink-0">
<UsersRound className="size-4" />
</div>
<div>{t('Following')}</div>
</div>
</FeedSwitcherItem>
<FeedSwitcherItem
isActive={feedInfo?.feedType === 'pinned'}
disabled={!pubkey || pinnedPubkeySet.size === 0}
onClick={() => {
if (!pubkey) return
switchFeed('pinned', { pubkey })
close?.()
}}
>
<div className="flex gap-2 items-center">
<div className="flex justify-center items-center w-6 h-6 shrink-0">
<Star className="size-4" />
</div>
<div>{t('Special Follow')}</div>
</div>
</FeedSwitcherItem>
<div className="flex justify-end items-center text-sm">
<SecondaryPageLink
to={toRelaySettings()}
className="text-primary font-semibold"
onClick={() => close?.()}
>
{t('edit')}
</SecondaryPageLink>
</div>
{relaySets
.filter((set) => set.relayUrls.length > 0)
.map((set) => (
<RelaySetCard
key={set.id}
relaySet={set}
select={feedInfo?.feedType === 'relays' && set.id === feedInfo.id}
onSelectChange={(select) => {
if (!select) return
switchFeed('relays', { activeRelaySetId: set.id })
close?.() close?.()
}} }}
>
<div className="flex gap-3 items-center">
<div className="flex justify-center items-center size-6 shrink-0">
<UsersRound className="size-5" />
</div>
<div className="flex-1">{t('Following')}</div>
</div>
</FeedSwitcherItem>
<FeedSwitcherItem
isActive={feedInfo?.feedType === 'pinned'}
disabled={!pubkey || pinnedPubkeySet.size === 0}
onClick={() => {
if (!pubkey) return
switchFeed('pinned', { pubkey })
close?.()
}}
>
<div className="flex gap-3 items-center">
<div className="flex justify-center items-center size-6 shrink-0">
<Star className="size-5" />
</div>
<div className="flex-1">{t('Special Follow')}</div>
</div>
</FeedSwitcherItem>
</div>
</div>
{/* Relay Feeds Section */}
{hasRelays && (
<div className="space-y-2">
<SectionHeader
title={t('Relay Feeds')}
action={
<SecondaryPageLink
to={toRelaySettings()}
className="flex items-center gap-1 text-xs text-primary hover:text-primary-hover transition-colors font-medium"
onClick={() => close?.()}
>
<Settings2 className="size-3" />
{t('edit')}
</SecondaryPageLink>
}
/> />
))} <div className="space-y-1.5">
{favoriteRelays.map((relay) => ( {filteredRelaySets.map((set) => (
<FeedSwitcherItem <RelaySetCard
key={relay} key={set.id}
isActive={feedInfo?.feedType === 'relay' && feedInfo.id === relay} relaySet={set}
onClick={() => { select={feedInfo?.feedType === 'relays' && set.id === feedInfo.id}
switchFeed('relay', { relay }) onSelectChange={(select) => {
close?.() if (!select) return
}} switchFeed('relays', { activeRelaySetId: set.id })
> close?.()
<div className="flex gap-2 items-center w-full"> }}
<RelayIcon url={relay} /> />
<div className="flex-1 w-0 truncate">{simplifyUrl(relay)}</div> ))}
{favoriteRelays.map((relay) => (
<FeedSwitcherItem
key={relay}
isActive={feedInfo?.feedType === 'relay' && feedInfo.id === relay}
onClick={() => {
switchFeed('relay', { relay })
close?.()
}}
>
<div className="flex gap-3 items-center w-full">
<RelayIcon url={relay} className="shrink-0" />
<div className="flex-1 w-0 truncate">{simplifyUrl(relay)}</div>
</div>
</FeedSwitcherItem>
))}
</div> </div>
</FeedSwitcherItem> </div>
))} )}
</div>
)
}
function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) {
return (
<div className="flex justify-between items-center px-1 py-1">
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{title}
</h3>
{action}
</div> </div>
) )
} }
@@ -100,30 +132,29 @@ function FeedSwitcherItem({
children, children,
isActive, isActive,
disabled, disabled,
onClick, onClick
controls
}: { }: {
children: React.ReactNode children: React.ReactNode
isActive: boolean isActive: boolean
disabled?: boolean disabled?: boolean
onClick: () => void onClick: () => void
controls?: React.ReactNode
}) { }) {
return ( return (
<div <div
className={cn( className={cn(
'w-full border rounded-lg p-4', 'group relative w-full border rounded-lg px-3 py-2.5 transition-all duration-200',
disabled && 'opacity-50 pointer-events-none', disabled && 'opacity-50 pointer-events-none',
isActive ? 'border-primary bg-primary/5' : 'clickable' isActive
? 'border-primary bg-primary/5 shadow-sm'
: 'border-border hover:border-primary/50 hover:bg-accent/50 clickable'
)} )}
onClick={() => { onClick={() => {
if (disabled) return if (disabled) return
onClick() onClick()
}} }}
> >
<div className="flex justify-between items-center"> <div className="flex justify-between items-center gap-2">
<div className="font-semibold flex-1">{children}</div> <div className="font-medium flex-1 min-w-0">{children}</div>
{controls}
</div> </div>
</div> </div>
) )

View File

@@ -52,7 +52,7 @@ export default function MailboxRelay({
onClick={() => push(toRelay(mailboxRelay.url))} onClick={() => push(toRelay(mailboxRelay.url))}
> >
<RelayIcon url={mailboxRelay.url} /> <RelayIcon url={mailboxRelay.url} />
<div className="truncate">{mailboxRelay.url}</div> <div className="truncate flex-1 w-0">{mailboxRelay.url}</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">

View File

@@ -1,17 +1,19 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { useFetchRelayInfo } from '@/hooks' import { useFetchRelayInfo } from '@/hooks'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Server } from 'lucide-react' import { Server } from 'lucide-react'
import { useMemo } from 'react' import { useMemo } from 'react'
import Image from '../Image'
export default function RelayIcon({ export default function RelayIcon({
url, url,
className, className,
iconSize = 14 classNames
}: { }: {
url?: string url?: string
className?: string className?: string
iconSize?: number classNames?: {
fallback?: string
}
}) { }) {
const { relayInfo } = useFetchRelayInfo(url) const { relayInfo } = useFetchRelayInfo(url)
const iconUrl = useMemo(() => { const iconUrl = useMemo(() => {
@@ -23,12 +25,21 @@ export default function RelayIcon({
return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico` return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico`
}, [url, relayInfo]) }, [url, relayInfo])
const fallback = <Server className={cn('size-5 bg-transparent', classNames?.fallback)} />
if (!iconUrl) {
return fallback
}
return ( return (
<Avatar className={cn('w-6 h-6', className)}> <Image
<AvatarImage src={iconUrl} className="object-cover object-center" /> image={{ url: iconUrl, dim: { width: 20, height: 20 } }}
<AvatarFallback> className={cn('size-6 rounded-full', className)}
<Server size={iconSize} /> classNames={{
</AvatarFallback> skeleton: cn('size-6 rounded-full', className),
</Avatar> errorPlaceholder: 'bg-transparent rounded-none shrink-0'
}}
errorPlaceholder={fallback}
/>
) )
} }

View File

@@ -1,3 +1,4 @@
import { cn } from '@/lib/utils'
import { TRelaySet } from '@/types' import { TRelaySet } from '@/types'
import { ChevronDown, FolderClosed } from 'lucide-react' import { ChevronDown, FolderClosed } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
@@ -18,17 +19,22 @@ export default function RelaySetCard({
return ( return (
<div <div
className={`w-full border rounded-lg p-4 clickable ${select ? 'border-primary bg-primary/5' : ''}`} className={cn(
'group relative w-full border rounded-lg px-3 py-2.5 transition-all duration-200',
select
? 'border-primary bg-primary/5 shadow-sm'
: 'border-border hover:border-primary/50 hover:bg-accent/50 clickable'
)}
onClick={() => onSelectChange(!select)} onClick={() => onSelectChange(!select)}
> >
<div className="flex justify-between items-center"> <div className="flex justify-between items-center gap-2">
<div className="flex space-x-2 items-center cursor-pointer"> <div className="flex gap-3 items-center flex-1 min-w-0">
<div className="flex justify-center items-center w-6 h-6 shrink-0"> <div className="flex justify-center items-center size-6 shrink-0">
<FolderClosed className="size-4" /> <FolderClosed className="size-5" />
</div> </div>
<div className="h-8 font-semibold flex items-center select-none">{relaySet.name}</div> <div className="font-medium select-none truncate">{relaySet.name}</div>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1 items-center shrink-0">
<RelayUrlsExpandToggle expand={expand} onExpandChange={setExpand}> <RelayUrlsExpandToggle expand={expand} onExpandChange={setExpand}>
{t('n relays', { n: relaySet.relayUrls.length })} {t('n relays', { n: relaySet.relayUrls.length })}
</RelayUrlsExpandToggle> </RelayUrlsExpandToggle>
@@ -50,16 +56,16 @@ function RelayUrlsExpandToggle({
}) { }) {
return ( return (
<div <div
className="text-sm text-muted-foreground flex items-center gap-1 cursor-pointer hover:text-foreground" className="text-xs text-muted-foreground flex items-center gap-0.5 cursor-pointer hover:text-foreground transition-colors"
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onExpandChange(!expand) onExpandChange(!expand)
}} }}
> >
<div className="select-none">{children}</div> <div className="select-none font-medium">{children}</div>
<ChevronDown <ChevronDown
size={16} size={14}
className={`transition-transform duration-200 ${expand ? 'rotate-180' : ''}`} className={cn('transition-transform duration-200', expand && 'rotate-180')}
/> />
</div> </div>
) )
@@ -69,11 +75,11 @@ function RelayUrls({ urls }: { urls: string[] }) {
if (!urls) return null if (!urls) return null
return ( return (
<div className="pl-1 space-y-1"> <div className="mt-2.5 pt-2.5 border-t space-y-1.5">
{urls.map((url) => ( {urls.map((url) => (
<div key={url} className="flex items-center gap-3"> <div key={url} className="flex items-center gap-2.5 pl-1">
<RelayIcon url={url} className="w-4 h-4" iconSize={10} /> <RelayIcon url={url} className="size-4 shrink-0" classNames={{ fallback: 'size-3' }} />
<div className="text-muted-foreground text-sm truncate">{url}</div> <div className="text-muted-foreground text-xs truncate">{url}</div>
</div> </div>
))} ))}
</div> </div>

View File

@@ -581,6 +581,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'متابعة خاصة', 'Special Follow': 'متابعة خاصة',
'Unfollow Special': 'إلغاء المتابعة الخاصة' 'Unfollow Special': 'إلغاء المتابعة الخاصة',
'Personal Feeds': 'التدفقات الشخصية',
'Relay Feeds': 'تدفقات الترحيل'
} }
} }

View File

@@ -597,6 +597,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Besonders Folgen', 'Special Follow': 'Besonders Folgen',
'Unfollow Special': 'Besonders Entfolgen' 'Unfollow Special': 'Besonders Entfolgen',
'Personal Feeds': 'Persönliche Feeds',
'Relay Feeds': 'Relay-Feeds'
} }
} }

View File

@@ -584,6 +584,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Special Follow', 'Special Follow': 'Special Follow',
'Unfollow Special': 'Unfollow Special' 'Unfollow Special': 'Unfollow Special',
'Personal Feeds': 'Personal Feeds',
'Relay Feeds': 'Relay Feeds'
} }
} }

View File

@@ -593,6 +593,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Seguir Especial', 'Special Follow': 'Seguir Especial',
'Unfollow Special': 'Dejar de Seguir Especial' 'Unfollow Special': 'Dejar de Seguir Especial',
'Personal Feeds': 'Feeds Personales',
'Relay Feeds': 'Feeds de Relays'
} }
} }

View File

@@ -587,6 +587,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'دنبال کردن ویژه', 'Special Follow': 'دنبال کردن ویژه',
'Unfollow Special': 'لغو دنبال کردن ویژه' 'Unfollow Special': 'لغو دنبال کردن ویژه',
'Personal Feeds': 'فیدهای شخصی',
'Relay Feeds': 'فیدهای رله'
} }
} }

View File

@@ -596,6 +596,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Suivre Spécial', 'Special Follow': 'Suivre Spécial',
'Unfollow Special': 'Ne Plus Suivre Spécial' 'Unfollow Special': 'Ne Plus Suivre Spécial',
'Personal Feeds': 'Flux Personnels',
'Relay Feeds': 'Flux de Relais'
} }
} }

View File

@@ -588,6 +588,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'विशेष फ़ॉलो', 'Special Follow': 'विशेष फ़ॉलो',
'Unfollow Special': 'विशेष अनफ़ॉलो' 'Unfollow Special': 'विशेष अनफ़ॉलो',
'Personal Feeds': 'व्यक्तिगत फ़ीड',
'Relay Feeds': 'रिले फ़ीड'
} }
} }

View File

@@ -582,6 +582,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Különleges Követés', 'Special Follow': 'Különleges Követés',
'Unfollow Special': 'Különleges Követés Megszüntetése' 'Unfollow Special': 'Különleges Követés Megszüntetése',
'Personal Feeds': 'Személyes Feedek',
'Relay Feeds': 'Relay Feedek'
} }
} }

View File

@@ -592,6 +592,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Segui Speciale', 'Special Follow': 'Segui Speciale',
'Unfollow Special': 'Smetti di Seguire Speciale' 'Unfollow Special': 'Smetti di Seguire Speciale',
'Personal Feeds': 'Feed Personali',
'Relay Feeds': 'Feed di Relay'
} }
} }

View File

@@ -587,6 +587,8 @@ export default {
'Show directly': '直接表示', 'Show directly': '直接表示',
'Click to view': 'クリックして表示', 'Click to view': 'クリックして表示',
'Special Follow': '特別フォロー', 'Special Follow': '特別フォロー',
'Unfollow Special': '特別フォロー解除' 'Unfollow Special': '特別フォロー解除',
'Personal Feeds': '個人フィード',
'Relay Feeds': 'リレーフィード'
} }
} }

View File

@@ -586,6 +586,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': '특별 팔로우', 'Special Follow': '특별 팔로우',
'Unfollow Special': '특별 팔로우 해제' 'Unfollow Special': '특별 팔로우 해제',
'Personal Feeds': '개인 피드',
'Relay Feeds': '릴레이 피드'
} }
} }

View File

@@ -593,6 +593,8 @@ export default {
'Show directly': 'Pokaż bezpośrednio', 'Show directly': 'Pokaż bezpośrednio',
'Click to view': 'Wyświetl', 'Click to view': 'Wyświetl',
'Special Follow': 'Specjalne Śledzenie', 'Special Follow': 'Specjalne Śledzenie',
'Unfollow Special': 'Cofnij Specjalne Śledzenie' 'Unfollow Special': 'Cofnij Specjalne Śledzenie',
'Personal Feeds': 'Osobiste Kanały',
'Relay Feeds': 'Kanały Przekaźników'
} }
} }

View File

@@ -588,6 +588,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Seguir Especial', 'Special Follow': 'Seguir Especial',
'Unfollow Special': 'Deixar de Seguir Especial' 'Unfollow Special': 'Deixar de Seguir Especial',
'Personal Feeds': 'Feeds Pessoais',
'Relay Feeds': 'Feeds de Relays'
} }
} }

View File

@@ -591,6 +591,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Seguir Especial', 'Special Follow': 'Seguir Especial',
'Unfollow Special': 'Deixar de Seguir Especial' 'Unfollow Special': 'Deixar de Seguir Especial',
'Personal Feeds': 'Feeds Pessoais',
'Relay Feeds': 'Feeds de Relays'
} }
} }

View File

@@ -593,6 +593,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'Особая Подписка', 'Special Follow': 'Особая Подписка',
'Unfollow Special': 'Отменить Особую Подписку' 'Unfollow Special': 'Отменить Особую Подписку',
'Personal Feeds': 'Личные Ленты',
'Relay Feeds': 'Ленты Релеев'
} }
} }

View File

@@ -580,6 +580,8 @@ export default {
'Show directly': 'Show directly', 'Show directly': 'Show directly',
'Click to view': 'Click to view', 'Click to view': 'Click to view',
'Special Follow': 'ติดตามพิเศษ', 'Special Follow': 'ติดตามพิเศษ',
'Unfollow Special': 'ยกเลิกติดตามพิเศษ' 'Unfollow Special': 'ยกเลิกติดตามพิเศษ',
'Personal Feeds': 'ฟีดส่วนตัว',
'Relay Feeds': 'ฟีดรีเลย์'
} }
} }

View File

@@ -573,6 +573,8 @@ export default {
'Show directly': '直接显示', 'Show directly': '直接显示',
'Click to view': '点击查看', 'Click to view': '点击查看',
'Special Follow': '特别关注', 'Special Follow': '特别关注',
'Unfollow Special': '取消特别关注' 'Unfollow Special': '取消特别关注',
'Personal Feeds': '个人订阅',
'Relay Feeds': '中继订阅'
} }
} }

View File

@@ -1,4 +1,5 @@
import FeedSwitcher from '@/components/FeedSwitcher' import FeedSwitcher from '@/components/FeedSwitcher'
import RelayIcon from '@/components/RelayIcon'
import { Drawer, DrawerContent } from '@/components/ui/drawer' import { Drawer, DrawerContent } from '@/components/ui/drawer'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { simplifyUrl } from '@/lib/url' import { simplifyUrl } from '@/lib/url'
@@ -19,10 +20,13 @@ export default function FeedButton({ className }: { className?: string }) {
<> <>
<FeedSwitcherTrigger className={className} onClick={() => setOpen(true)} /> <FeedSwitcherTrigger className={className} onClick={() => setOpen(true)} />
<Drawer open={open} onOpenChange={setOpen}> <Drawer open={open} onOpenChange={setOpen}>
<DrawerContent className="max-h-[80vh]"> <DrawerContent className="max-h-[85vh]">
<div <div
className="overflow-y-auto overscroll-contain py-2 px-4" className="flex-1 overflow-y-auto overscroll-contain py-3 px-4"
style={{ touchAction: 'pan-y' }} style={{
touchAction: 'pan-y',
WebkitOverflowScrolling: 'touch'
}}
> >
<FeedSwitcher close={() => setOpen(false)} /> <FeedSwitcher close={() => setOpen(false)} />
</div> </div>
@@ -37,12 +41,14 @@ export default function FeedButton({ className }: { className?: string }) {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<FeedSwitcherTrigger className={className} /> <FeedSwitcherTrigger className={className} />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent sideOffset={0} side="bottom" className="w-[400px] p-0 overflow-hidden">
sideOffset={0} <div
side="bottom" className="max-h-[calc(100vh-16rem)] overflow-y-auto overscroll-contain py-3 px-4"
className="w-96 p-4 max-h-[80vh] overflow-auto scrollbar-hide" onWheel={(e) => e.stopPropagation()}
> onTouchMove={(e) => e.stopPropagation()}
<FeedSwitcher close={() => setOpen(false)} /> >
<FeedSwitcher close={() => setOpen(false)} />
</div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
) )
@@ -79,6 +85,10 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
const icon = useMemo(() => { const icon = useMemo(() => {
if (feedInfo?.feedType === 'following') return <UsersRound /> if (feedInfo?.feedType === 'following') return <UsersRound />
if (feedInfo?.feedType === 'pinned') return <Star /> if (feedInfo?.feedType === 'pinned') return <Star />
if (feedInfo?.feedType === 'relay' && feedInfo.id) {
return <RelayIcon url={feedInfo.id} />
}
return <Server /> return <Server />
}, [feedInfo]) }, [feedInfo])