diff --git a/package-lock.json b/package-lock.json index 70ee4074..69b76ec8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": [ diff --git a/src/components/Feed.tsx b/src/components/Feed.tsx index 37ad9b78..c756ed8a 100644 --- a/src/components/Feed.tsx +++ b/src/components/Feed.tsx @@ -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 ? (
- handleSetActiveTab('follows')} /> + handleSetActiveTab('follows')} /> {!isKindSpecificPage && showDittoFeed && ( - handleSetActiveTab('ditto')} /> + handleSetActiveTab('ditto')} /> )} {!isKindSpecificPage && showCommunityFeed && ( - handleSetActiveTab('communities')} /> + handleSetActiveTab('communities')} /> )} {(isKindSpecificPage || showGlobalFeed) && ( - handleSetActiveTab('global')} /> + handleSetActiveTab('global')} /> )} {showSavedFeedTabs && savedFeeds.map((feed) => ( - ))} {showSavedFeedTabs && hashtags.map((tag) => ( - 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 ( - - ); -} diff --git a/src/components/TabButton.tsx b/src/components/TabButton.tsx new file mode 100644 index 00000000..b9705f43 --- /dev/null +++ b/src/components/TabButton.tsx @@ -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 ` + ); +} diff --git a/src/pages/BooksPage.tsx b/src/pages/BooksPage.tsx index 60887c1d..7f87e965 100644 --- a/src/pages/BooksPage.tsx +++ b/src/pages/BooksPage.tsx @@ -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 && (
- setActiveTab('follows')} /> - setActiveTab('global')} /> + setActiveTab('follows')} /> + setActiveTab('global')} />
)} @@ -325,9 +325,3 @@ function BookSearchResultItem({ book, onSelect }: { book: BookSearchResult; onSe ); } - -// --------------------------------------------------------------------------- -// Tab Button -// --------------------------------------------------------------------------- - - diff --git a/src/pages/EventsFeedPage.tsx b/src/pages/EventsFeedPage.tsx index 63763704..27d17945 100644 --- a/src/pages/EventsFeedPage.tsx +++ b/src/pages/EventsFeedPage.tsx @@ -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 && (
- setActiveTab('follows')} /> - setActiveTab('global')} /> + setActiveTab('follows')} /> + setActiveTab('global')} />
)} @@ -183,5 +183,3 @@ function EventCardSkeleton() {
); } - - diff --git a/src/pages/ListDetailPage.tsx b/src/pages/ListDetailPage.tsx index 449a6c3f..f0e80402 100644 --- a/src/pages/ListDetailPage.tsx +++ b/src/pages/ListDetailPage.tsx @@ -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() { )} - - {/* Tab bar */}
- - +
diff --git a/src/pages/NotificationsPage.tsx b/src/pages/NotificationsPage.tsx index 6ca2cd71..8b556122 100644 --- a/src/pages/NotificationsPage.tsx +++ b/src/pages/NotificationsPage.tsx @@ -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 */}
{tabs.map(({ key, label }) => ( - + className="sidebar:py-5 sidebar:font-semibold" + indicatorClassName="sidebar:h-[3px]" + /> ))}
diff --git a/src/pages/PhotosFeedPage.tsx b/src/pages/PhotosFeedPage.tsx index fd25b740..4ba6d3e2 100644 --- a/src/pages/PhotosFeedPage.tsx +++ b/src/pages/PhotosFeedPage.tsx @@ -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 */}
- setActiveTab('follows')} disabled={!user} /> - setActiveTab('global')} /> + setActiveTab('follows')} disabled={!user} /> + setActiveTab('global')} />
{/* Grid */} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index b9b3d0c2..0e3969a3 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -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 ( -
- setActiveTab('posts')} className="sidebar:py-5" /> - setActiveTab('accounts')} className="sidebar:py-5" /> + setActiveTab('posts')} className="sidebar:py-5" /> + setActiveTab('accounts')} className="sidebar:py-5" />
@@ -816,8 +814,6 @@ export function SearchPage() { /* ── Shared sub-components ── */ - - function AccountItem({ profile, isFollowed }: { profile: { pubkey: string; metadata: Record; 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 }; diff --git a/src/pages/ThemesPage.tsx b/src/pages/ThemesPage.tsx index 3db4127a..4eb05546 100644 --- a/src/pages/ThemesPage.tsx +++ b/src/pages/ThemesPage.tsx @@ -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 */}
- setActiveTab('my-themes')} /> - setActiveTab('follows')} disabled={!user} /> - setActiveTab('global')} /> + setActiveTab('my-themes')} /> + setActiveTab('follows')} disabled={!user} /> + setActiveTab('global')} />
{/* Tab content */} @@ -186,12 +186,6 @@ export function ThemesPage() { ); } -// --------------------------------------------------------------------------- -// Tab Button -// --------------------------------------------------------------------------- - - - // --------------------------------------------------------------------------- // Skeleton // --------------------------------------------------------------------------- diff --git a/src/pages/VideosFeedPage.tsx b/src/pages/VideosFeedPage.tsx index 375156a3..57c5e211 100644 --- a/src/pages/VideosFeedPage.tsx +++ b/src/pages/VideosFeedPage.tsx @@ -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 */}
- setFeedTab('follows')} disabled={!user} /> - setFeedTab('global')} /> + setFeedTab('follows')} disabled={!user} /> + setFeedTab('global')} />
{/* Live streams strip — follows tab filters by followed authors */} diff --git a/src/pages/VinesFeedPage.tsx b/src/pages/VinesFeedPage.tsx index effc0676..f5ba32a9 100644 --- a/src/pages/VinesFeedPage.tsx +++ b/src/pages/VinesFeedPage.tsx @@ -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 (
{hasUser && ( - onTabChange('follows')} /> + onTabChange('follows')} className="sidebar:py-5 sidebar:font-semibold" indicatorClassName="sidebar:h-[3px]" /> )} - onTabChange('global')} /> + onTabChange('global')} className="sidebar:py-5 sidebar:font-semibold" indicatorClassName="sidebar:h-[3px]" />
); } - -function VinesTabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) { - return ( - - ); -}