Fix sticky tabs not scrolling to top on click or switch
This commit is contained in:
Generated
+25
@@ -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": [
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user