Fix sticky tabs not scrolling to top on click or switch

This commit is contained in:
Mary Kate
2026-03-17 19:46:44 +00:00
committed by Dirk Rost
parent f17bc899c8
commit c3a4d8be57
14 changed files with 133 additions and 136 deletions
+25
View File
@@ -3159,6 +3159,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3172,6 +3173,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3185,6 +3187,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3198,6 +3201,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3211,6 +3215,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3224,6 +3229,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3237,6 +3243,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3250,6 +3257,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3263,6 +3271,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3276,6 +3285,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3289,6 +3299,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3302,6 +3313,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3315,6 +3327,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3328,6 +3341,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3341,6 +3355,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3354,6 +3369,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3367,6 +3383,7 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3380,6 +3397,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3393,6 +3411,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3406,6 +3425,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3419,6 +3439,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3432,6 +3453,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3445,6 +3467,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3458,6 +3481,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3471,6 +3495,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
+7 -7
View File
@@ -24,7 +24,7 @@ import { useResolveTabFilter } from '@/hooks/useResolveTabFilter';
import { getEnabledFeedKinds } from '@/lib/extraKinds';
import { isRepostKind } from '@/lib/feedUtils';
import { isEventMuted } from '@/lib/muteHelpers';
import { FeedTabButton } from '@/components/FeedTabButton';
import { TabButton } from '@/components/TabButton';
import { DITTO_RELAY } from '@/lib/appRelays';
import type { FeedItem } from '@/lib/feedUtils';
import type { NostrEvent } from '@nostrify/nostrify';
@@ -230,18 +230,18 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee
{/* Tabs (logged in) or CTA (logged out, main feed only) */}
{user ? (
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10 overflow-x-auto scrollbar-none">
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => handleSetActiveTab('follows')} />
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => handleSetActiveTab('follows')} />
{!isKindSpecificPage && showDittoFeed && (
<FeedTabButton label="Ditto" active={activeTab === 'ditto'} onClick={() => handleSetActiveTab('ditto')} />
<TabButton label="Ditto" active={activeTab === 'ditto'} onClick={() => handleSetActiveTab('ditto')} />
)}
{!isKindSpecificPage && showCommunityFeed && (
<FeedTabButton label={communityLabel} active={activeTab === 'communities'} onClick={() => handleSetActiveTab('communities')} />
<TabButton label={communityLabel} active={activeTab === 'communities'} onClick={() => handleSetActiveTab('communities')} />
)}
{(isKindSpecificPage || showGlobalFeed) && (
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => handleSetActiveTab('global')} />
<TabButton label="Global" active={activeTab === 'global'} onClick={() => handleSetActiveTab('global')} />
)}
{showSavedFeedTabs && savedFeeds.map((feed) => (
<FeedTabButton
<TabButton
key={feed.id}
label={feed.label}
active={activeTab === feed.id}
@@ -249,7 +249,7 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee
/>
))}
{showSavedFeedTabs && hashtags.map((tag) => (
<FeedTabButton
<TabButton
key={`hashtag:${tag}`}
label={`#${tag}`}
active={activeTab === `hashtag:${tag}`}
-30
View File
@@ -1,30 +0,0 @@
import { cn } from '@/lib/utils';
interface FeedTabButtonProps {
label: string;
active: boolean;
onClick: () => void;
disabled?: boolean;
className?: string;
}
/** Shared tab button used across feed pages (Follows / Global / etc.). */
export function FeedTabButton({ label, active, onClick, disabled, className }: FeedTabButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={cn(
'flex-1 px-4 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 min-w-0 truncate',
active ? 'text-foreground' : 'text-muted-foreground',
disabled && 'opacity-50 cursor-not-allowed hover:bg-transparent',
className,
)}
>
{label}
{active && (
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-3/4 max-w-16 h-1 bg-primary rounded-full" />
)}
</button>
);
}
+56
View File
@@ -0,0 +1,56 @@
import { cn } from '@/lib/utils';
interface TabButtonProps {
/** Tab display label. */
label: string;
/** Whether this tab is currently selected. */
active: boolean;
/** Called when the tab is clicked. Scroll-to-top is handled internally. */
onClick: () => void;
/** Disable the button (e.g. when logged out). */
disabled?: boolean;
/** Extra classes forwarded to the `<button>`. */
className?: string;
/** Override the default indicator bar classes. */
indicatorClassName?: string;
/** Optional children rendered inside the button instead of the label text. */
children?: React.ReactNode;
}
/**
* Shared sticky-tab button used across all feed / profile pages.
*
* Behaviour (see gitlab#109):
* - Clicking the **active** tab smooth-scrolls to the top.
* - Switching to a **different** tab resets scroll position instantly.
*/
export function TabButton({ label, active, onClick, disabled, className, indicatorClassName, children }: TabButtonProps) {
const handleClick = () => {
if (active) {
// Re-tapping the active tab -> smooth scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
// Switching tabs -> jump to top instantly, then switch
window.scrollTo({ top: 0 });
onClick();
}
};
return (
<button
onClick={handleClick}
disabled={disabled}
className={cn(
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 px-4 whitespace-nowrap',
active ? 'text-foreground' : 'text-muted-foreground',
disabled && 'opacity-50 cursor-not-allowed hover:bg-transparent',
className,
)}
>
{children ?? label}
{active && (
<div className={cn('absolute bottom-0 left-1/2 -translate-x-1/2 h-1 bg-primary rounded-full w-3/4 max-w-16', indicatorClassName)} />
)}
</button>
);
}
+3 -9
View File
@@ -5,7 +5,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { ArrowLeft, BookMarked, Loader2, Search, X } from 'lucide-react';
import { useSeoMeta } from '@unhead/react';
import { FeedTabButton } from '@/components/FeedTabButton';
import { TabButton } from '@/components/TabButton';
import { Input } from '@/components/ui/input';
import { Skeleton } from '@/components/ui/skeleton';
import { PullToRefresh } from '@/components/PullToRefresh';
@@ -111,8 +111,8 @@ export function BooksPage() {
{/* Follows / Global tabs */}
{user && (
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
</div>
)}
@@ -325,9 +325,3 @@ function BookSearchResultItem({ book, onSelect }: { book: BookSearchResult; onSe
</button>
);
}
// ---------------------------------------------------------------------------
// Tab Button
// ---------------------------------------------------------------------------
+3 -5
View File
@@ -11,12 +11,12 @@ import { Skeleton } from '@/components/ui/skeleton';
import { PullToRefresh } from '@/components/PullToRefresh';
import { KindInfoButton } from '@/components/KindInfoButton';
import { NoteCard } from '@/components/NoteCard';
import { TabButton } from '@/components/TabButton';
import { useFeed } from '@/hooks/useFeed';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useFeedTab } from '@/hooks/useFeedTab';
import { useAppContext } from '@/hooks/useAppContext';
import { useLayoutOptions } from '@/contexts/LayoutContext';
import { FeedTabButton } from '@/components/FeedTabButton';
import { useMuteList } from '@/hooks/useMuteList';
import { isEventMuted } from '@/lib/muteHelpers';
import { getExtraKindDef } from '@/lib/extraKinds';
@@ -120,8 +120,8 @@ export function EventsFeedPage() {
{/* Follows / Global tabs */}
{user && (
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
</div>
)}
@@ -183,5 +183,3 @@ function EventCardSkeleton() {
</div>
);
}
+13 -24
View File
@@ -39,6 +39,7 @@ import { shareOrCopy } from '@/lib/share';
import { getRepostKind } from '@/lib/feedUtils';
import { DITTO_RELAY } from '@/lib/appRelays';
import { toast } from '@/hooks/useToast';
import { TabButton } from '@/components/TabButton';
import { cn, STICKY_HEADER_CLASS } from '@/lib/utils';
import type { NostrEvent } from '@nostrify/nostrify';
import type { UserList } from '@/hooks/useUserLists';
@@ -524,45 +525,33 @@ export function ListDetailPage() {
</div>
)}
{/* Tab bar */}
<div className="flex border-b border-border">
<button
className={cn(
'flex-1 py-2.5 text-sm font-medium text-center transition-colors relative',
activeTab === 'feed'
? 'text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
<TabButton
label="Feed"
active={activeTab === 'feed'}
onClick={() => setActiveTab('feed')}
className="py-2.5"
indicatorClassName="left-1/4 right-1/4 w-auto h-0.5"
>
<span className="flex items-center justify-center gap-1.5">
<Rss className="size-4" />
Feed
</span>
{activeTab === 'feed' && (
<div className="absolute bottom-0 left-1/4 right-1/4 h-0.5 bg-primary rounded-full" />
)}
</button>
<button
className={cn(
'flex-1 py-2.5 text-sm font-medium text-center transition-colors relative',
activeTab === 'members'
? 'text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
</TabButton>
<TabButton
label="Members"
active={activeTab === 'members'}
onClick={() => setActiveTab('members')}
className="py-2.5"
indicatorClassName="left-1/4 right-1/4 w-auto h-0.5"
>
<span className="flex items-center justify-center gap-1.5">
<Users className="size-4" />
Members
<span className="text-xs text-muted-foreground">({list.pubkeys.length})</span>
</span>
{activeTab === 'members' && (
<div className="absolute bottom-0 left-1/4 right-1/4 h-0.5 bg-primary rounded-full" />
)}
</button>
</TabButton>
</div>
</div>
+7 -11
View File
@@ -11,6 +11,7 @@ import { useAppContext } from '@/hooks/useAppContext';
import { Skeleton } from '@/components/ui/skeleton';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { NoteCard } from '@/components/NoteCard';
import { TabButton } from '@/components/TabButton';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useAuthor } from '@/hooks/useAuthor';
import { useEvent } from '@/hooks/useEvent';
@@ -108,19 +109,14 @@ export function NotificationsPage() {
{/* Tab bar */}
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
{tabs.map(({ key, label }) => (
<button
<TabButton
key={key}
label={label}
active={activeTab === key}
onClick={() => setActiveTab(key)}
className={cn(
'flex-1 py-3.5 sidebar:py-5 text-sm font-medium sidebar:font-semibold transition-colors relative hover:bg-secondary/40',
activeTab === key ? 'text-foreground' : 'text-muted-foreground',
)}
>
{label}
{activeTab === key && (
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 sidebar:h-[3px] bg-primary rounded-full" />
)}
</button>
className="sidebar:py-5 sidebar:font-semibold"
indicatorClassName="sidebar:h-[3px]"
/>
))}
</div>
+3 -3
View File
@@ -23,7 +23,7 @@ import { useMuteList } from '@/hooks/useMuteList';
import { isEventMuted } from '@/lib/muteHelpers';
import { KindInfoButton } from '@/components/KindInfoButton';
import { sidebarItemIcon } from '@/lib/sidebarItems';
import { FeedTabButton } from '@/components/FeedTabButton';
import { TabButton } from '@/components/TabButton';
import { getExtraKindDef } from '@/lib/extraKinds';
import type { FeedItem } from '@/lib/feedUtils';
import { MediaCollage, MediaCollageSkeleton, eventToMediaItem } from '@/components/MediaCollage';
@@ -104,8 +104,8 @@ export function PhotosFeedPage() {
{/* Tabs */}
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
</div>
{/* Grid */}
+3 -6
View File
@@ -6,7 +6,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Zap, Flame, MoreHorizontal, Share2, ClipboardCopy, ExternalLink, VolumeX, Flag, Bitcoin, Users, Pin, X, QrCode, Check, Copy, Loader2, Download, Palette, Pencil, Trash2, Eye, EyeOff, RefreshCw, MessageSquare, Globe, Mail, Plus, GripVertical, ListPlus, Award } from 'lucide-react';
import { FeedTabButton } from '@/components/FeedTabButton';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { getAvatarShape, isEmoji, emojiAvatarBorderStyle } from '@/lib/avatarShape';
import { Button } from '@/components/ui/button';
@@ -86,6 +86,7 @@ import { FontPicker } from '@/components/FontPicker';
import { BackgroundPicker } from '@/components/BackgroundPicker';
import { PortalContainerProvider } from '@/contexts/PortalContainerContext';
import { formatNumber } from '@/lib/formatNumber';
import { TabButton } from '@/components/TabButton';
import { cn, STICKY_HEADER_CLASS } from '@/lib/utils';
import type { AddrCoords } from '@/hooks/useEvent';
import type { FeedItem } from '@/lib/feedUtils';
@@ -364,10 +365,6 @@ function FollowingListModal({ pubkeys, open, onOpenChange, displayName }: Follow
);
}
// ----- Tab Button -----
type EditableTab = { label: string; isCore: boolean; tab?: ProfileTab };
function SortableTabChip({
@@ -1937,7 +1934,7 @@ export function ProfilePage() {
{!tabEditMode && profileTabsQuery.isFetched && viewTabs.map((tab) => {
const tabId = CORE_TAB_IDS[tab.label] ?? tab.label;
return (
<FeedTabButton
<TabButton
key={tab.label}
label={tab.label}
active={activeTab === tabId}
+3 -7
View File
@@ -15,7 +15,6 @@ import {
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
import { useInView } from 'react-intersection-observer';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { FeedTabButton } from '@/components/FeedTabButton';
import { NoteCard } from '@/components/NoteCard';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { getAvatarShape } from '@/lib/avatarShape';
@@ -46,14 +45,13 @@ import { ListPackPicker } from '@/components/SavedFeedFiltersEditor';
import { genUserName } from '@/lib/genUserName';
import { VerifiedNip05Text } from '@/components/Nip05Badge';
import { TabButton } from '@/components/TabButton';
import { getNostrIdentifierPath } from '@/lib/nostrIdentifier';
import { cn, STICKY_HEADER_CLASS, parseKindFilter } from '@/lib/utils';
import type { TabFilter } from '@/contexts/AppContext';
import { isRepostKind, parseRepostContent } from '@/lib/feedUtils';
import { nip19 } from 'nostr-tools';
type TabType = 'posts' | 'accounts';
const VALID_TABS: TabType[] = ['posts', 'accounts'];
@@ -417,8 +415,8 @@ export function SearchPage() {
{/* Tabs — sticky at top */}
<div className={cn(STICKY_HEADER_CLASS, 'bg-background/80 backdrop-blur-md z-10 border-b border-border')}>
<div className="flex">
<FeedTabButton label="Posts" active={activeTab === 'posts'} onClick={() => setActiveTab('posts')} className="sidebar:py-5" />
<FeedTabButton label="Accounts" active={activeTab === 'accounts'} onClick={() => setActiveTab('accounts')} className="sidebar:py-5" />
<TabButton label="Posts" active={activeTab === 'posts'} onClick={() => setActiveTab('posts')} className="sidebar:py-5" />
<TabButton label="Accounts" active={activeTab === 'accounts'} onClick={() => setActiveTab('accounts')} className="sidebar:py-5" />
</div>
</div>
@@ -816,8 +814,6 @@ export function SearchPage() {
/* ── Shared sub-components ── */
function AccountItem({ profile, isFollowed }: { profile: { pubkey: string; metadata: Record<string, unknown>; event?: { tags: string[][] } }; isFollowed: boolean }) {
const npub = useMemo(() => nip19.npubEncode(profile.pubkey), [profile.pubkey]);
const metadata = profile.metadata as { name?: string; nip05?: string; picture?: string; about?: string; bot?: boolean };
+4 -10
View File
@@ -6,7 +6,6 @@ import { Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import type { NostrEvent } from '@nostrify/nostrify';
import { FeedTabButton } from '@/components/FeedTabButton';
import { NoteCard } from '@/components/NoteCard';
import { PullToRefresh } from '@/components/PullToRefresh';
import { FeedEmptyState } from '@/components/FeedEmptyState';
@@ -14,6 +13,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { ThemeSelector } from '@/components/ThemeSelector';
import { TabButton } from '@/components/TabButton';
import { useThemeFeed } from '@/hooks/useThemeFeed';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useTheme } from '@/hooks/useTheme';
@@ -115,9 +115,9 @@ export function ThemesPage() {
{/* Tabs */}
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
<FeedTabButton label="My Themes" active={activeTab === 'my-themes'} onClick={() => setActiveTab('my-themes')} />
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
<TabButton label="My Themes" active={activeTab === 'my-themes'} onClick={() => setActiveTab('my-themes')} />
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
</div>
{/* Tab content */}
@@ -186,12 +186,6 @@ export function ThemesPage() {
);
}
// ---------------------------------------------------------------------------
// Tab Button
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Skeleton
// ---------------------------------------------------------------------------
+3 -3
View File
@@ -45,7 +45,7 @@ import { useVideoThumbnail } from '@/components/VideoPlayer';
import { sidebarItemIcon } from '@/lib/sidebarItems';
import { getExtraKindDef } from '@/lib/extraKinds';
import { timeAgo } from '@/lib/timeAgo';
import { FeedTabButton } from '@/components/FeedTabButton';
import { TabButton } from '@/components/TabButton';
import { cn } from '@/lib/utils';
import { getEffectiveStreamStatus } from '@/lib/streamStatus';
import type { FeedItem } from '@/lib/feedUtils';
@@ -790,8 +790,8 @@ export function VideosFeedPage() {
{/* Follows / Global tabs */}
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
<FeedTabButton label="Follows" active={feedTab === 'follows'} onClick={() => setFeedTab('follows')} disabled={!user} />
<FeedTabButton label="Global" active={feedTab === 'global'} onClick={() => setFeedTab('global')} />
<TabButton label="Follows" active={feedTab === 'follows'} onClick={() => setFeedTab('follows')} disabled={!user} />
<TabButton label="Global" active={feedTab === 'global'} onClick={() => setFeedTab('global')} />
</div>
{/* Live streams strip — follows tab filters by followed authors */}
+3 -21
View File
@@ -45,13 +45,12 @@ import { ProfileHoverCard } from '@/components/ProfileHoverCard';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { getAvatarShape } from '@/lib/avatarShape';
import { Skeleton } from '@/components/ui/skeleton';
import { CommentsSheet } from '@/components/CommentsSheet';
import { getDisplayName } from '@/lib/getDisplayName';
import { useProfileUrl } from '@/hooks/useProfileUrl';
import { canZap } from '@/lib/canZap';
import { formatNumber } from '@/lib/formatNumber';
import { TabButton } from '@/components/TabButton';
import { cn } from '@/lib/utils';
const VINE_KIND = 34236;
@@ -788,26 +787,9 @@ function VinesTabBar({ tab, onTabChange, hasUser }: VinesTabBarProps) {
return (
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10 shrink-0">
{hasUser && (
<VinesTabButton label="Follows" active={tab === 'follows'} onClick={() => onTabChange('follows')} />
<TabButton label="Follows" active={tab === 'follows'} onClick={() => onTabChange('follows')} className="sidebar:py-5 sidebar:font-semibold" indicatorClassName="sidebar:h-[3px]" />
)}
<VinesTabButton label="Global" active={tab === 'global'} onClick={() => onTabChange('global')} />
<TabButton label="Global" active={tab === 'global'} onClick={() => onTabChange('global')} className="sidebar:py-5 sidebar:font-semibold" indicatorClassName="sidebar:h-[3px]" />
</div>
);
}
function VinesTabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {
return (
<button
onClick={onClick}
className={cn(
'flex-1 py-3.5 sidebar:py-5 text-center text-sm font-medium sidebar:font-semibold transition-colors relative hover:bg-secondary/40',
active ? 'text-foreground' : 'text-muted-foreground',
)}
>
{label}
{active && (
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 sidebar:h-[3px] bg-primary rounded-full" />
)}
</button>
);
}