fix(subscriptions): add horizontal tab scroll (#93)

* fix(subscriptions): add horizontal tab scroll

* fix(ui): add scroll areas to pages

* fix(downloads): adjust history spacing
This commit is contained in:
Nexmoe
2026-01-14 20:26:00 +08:00
committed by GitHub
parent aed6842319
commit 4a9ee1406b
4 changed files with 108 additions and 114 deletions

View File

@@ -651,13 +651,10 @@ export function DownloadItem({ download, isSelected = false, onToggleSelect }: D
return (
<div
className={`group relative w-full max-w-full overflow-hidden rounded-lg border border-transparent transition-colors ${
className={`px-6 py-2 group relative w-full max-w-full overflow-hidden transition-colors ${
isSelectedHistory ? 'bg-primary/10' : ''
}`}
>
{isSelectedHistory && (
<div className="absolute left-0 top-0 h-full w-1 bg-primary/70" aria-hidden="true" />
)}
<div
className={`flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 ${
selectionEnabled ? 'cursor-pointer' : ''

View File

@@ -25,6 +25,7 @@ import {
removeHistoryRecordsByPlaylistAtom
} from '../../store/downloads'
import { settingsAtom } from '../../store/settings'
import { ScrollArea } from '../ui/scroll-area'
import { DownloadDialog } from './DownloadDialog'
import { DownloadItem } from './DownloadItem'
import { PlaylistDownloadGroup } from './PlaylistDownloadGroup'
@@ -398,8 +399,8 @@ export function UnifiedDownloadHistory({
}, [filteredRecords])
return (
<div className={cn('space-y-4', selectedCount > 0 && 'pb-20')}>
<CardHeader className="gap-4 p-0 pb-4 sticky top-0 z-50 bg-background backdrop-blur supports-[backdrop-filter]:bg-background/95">
<div className={cn('flex flex-col h-full')}>
<CardHeader className="gap-4 p-0 px-6 py-4 z-50 bg-background backdrop-blur">
<div className="flex flex-wrap items-center justify-between gap-2 text-sm">
<div className="flex flex-wrap items-center gap-2">
{filters.map((filter) => {
@@ -437,47 +438,49 @@ export function UnifiedDownloadHistory({
</div>
</div>
</CardHeader>
<CardContent className="space-y-3 p-0 overflow-x-hidden w-full">
{filteredRecords.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border/60 px-6 py-10 text-center text-muted-foreground">
<HistoryIcon className="h-10 w-10 opacity-50" />
<p className="text-sm font-medium">{t('download.noItems')}</p>
</div>
) : (
<div className="space-y-4 w-full">
{groupedView.order.map((item) => {
if (item.type === 'single') {
<ScrollArea className="overflow-y-auto flex-1">
<CardContent className="space-y-3 p-0 overflow-x-hidden w-full">
{filteredRecords.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border/60 px-6 py-10 text-center text-muted-foreground">
<HistoryIcon className="h-10 w-10 opacity-50" />
<p className="text-sm font-medium">{t('download.noItems')}</p>
</div>
) : (
<div className="w-full pb-4">
{groupedView.order.map((item) => {
if (item.type === 'single') {
return (
<DownloadItem
key={`${item.record.entryType}:${item.record.id}`}
download={item.record}
isSelected={selectedIds.has(item.record.id)}
onToggleSelect={handleToggleSelect}
/>
)
}
const group = groupedView.groups.get(item.id)
if (!group) {
return null
}
return (
<DownloadItem
key={`${item.record.entryType}:${item.record.id}`}
download={item.record}
isSelected={selectedIds.has(item.record.id)}
<PlaylistDownloadGroup
key={`group:${group.id}`}
groupId={group.id}
title={group.title}
totalCount={group.totalCount}
records={group.records}
selectedIds={selectedIds}
onToggleSelect={handleToggleSelect}
onDeletePlaylist={handleRequestDeletePlaylist}
/>
)
}
const group = groupedView.groups.get(item.id)
if (!group) {
return null
}
return (
<PlaylistDownloadGroup
key={`group:${group.id}`}
groupId={group.id}
title={group.title}
totalCount={group.totalCount}
records={group.records}
selectedIds={selectedIds}
onToggleSelect={handleToggleSelect}
onDeletePlaylist={handleRequestDeletePlaylist}
/>
)
})}
</div>
)}
</CardContent>
})}
</div>
)}
</CardContent>
</ScrollArea>
{selectedCount > 0 && (
<div className="fixed bottom-4 left-1/2 z-40 w-[calc(100%-2rem)] -translate-x-1/2 sm:left-auto sm:right-6 sm:translate-x-0 sm:w-auto">
<div className="flex flex-wrap items-center justify-between gap-3 rounded-full border border-border/50 bg-background/80 pl-5 pr-2 py-2 shadow-lg backdrop-blur">

View File

@@ -7,17 +7,9 @@ interface HomeProps {
export function Home({ onOpenSupportedSites, onOpenSettings }: HomeProps) {
return (
<div className="w-full h-full flex flex-col min-h-0">
<div
className="container mx-auto max-w-7xl p-6 w-full h-full flex flex-col min-h-0"
style={{ maxWidth: '100%' }}
>
{/* Unified Download History */}
<UnifiedDownloadHistory
onOpenSupportedSites={onOpenSupportedSites}
onOpenSettings={onOpenSettings}
/>
</div>
</div>
<UnifiedDownloadHistory
onOpenSupportedSites={onOpenSupportedSites}
onOpenSettings={onOpenSettings}
/>
)
}

View File

@@ -20,6 +20,7 @@ import {
} from '@renderer/components/ui/context-menu'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@renderer/components/ui/hover-card'
import { RemoteImage } from '@renderer/components/ui/remote-image'
import { ScrollArea, ScrollBar } from '@renderer/components/ui/scroll-area'
import { Tabs, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'
import { ipcServices } from '@renderer/lib/ipc'
@@ -321,26 +322,25 @@ export function Subscriptions() {
return (
<div className="relative w-full h-full flex flex-col">
{/* Channel Tabs Header */}
<div className="flex flex-row pr-6 pb-6">
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
className="w-auto overflow-x-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]"
>
<TabsList className="h-auto w-auto justify-start rounded-none border-none bg-transparent p-0 px-6">
{/* Subscription Channel Tabs */}
{sortedSubscriptions.map((subscription) => (
<SubscriptionTab
key={subscription.id}
subscription={subscription}
isActive={subscription.id === selectedTab}
onRefresh={() => refreshSubscription(subscription.id)}
onRemove={() => removeSubscription(subscription.id)}
onUpdate={(data) => handleUpdateSubscription(subscription.id, data)}
/>
))}
</TabsList>
</Tabs>
<div className="flex flex-row pr-6 pb-6 pl-6">
<ScrollArea className="w-auto overflow-y-auto">
<Tabs value={selectedTab} onValueChange={setSelectedTab} className="w-auto">
<TabsList className="h-auto w-auto justify-start rounded-none border-none bg-transparent p-0">
{/* Subscription Channel Tabs */}
{sortedSubscriptions.map((subscription) => (
<SubscriptionTab
key={subscription.id}
subscription={subscription}
isActive={subscription.id === selectedTab}
onRefresh={() => refreshSubscription(subscription.id)}
onRemove={() => removeSubscription(subscription.id)}
onUpdate={(data) => handleUpdateSubscription(subscription.id, data)}
/>
))}
</TabsList>
</Tabs>
<ScrollBar orientation="horizontal" />
</ScrollArea>
{/* Add RSS Button */}
<Button
@@ -359,46 +359,48 @@ export function Subscriptions() {
</Button>
</div>
{/* Content Area */}
<div className="overflow-y-auto relative space-y-8 p-6 pt-0">
<section className="space-y-4">
{sortedSubscriptions.length === 0 ? (
<div className="py-12 text-center text-sm text-muted-foreground">
{t('subscriptions.empty')}
</div>
) : !selectedTab ? (
<div className="py-12 text-center text-sm text-muted-foreground">
{t('subscriptions.empty')}
</div>
) : (
<div className="space-y-3">
{displayedSubscriptions.map((subscription) => (
<SubscriptionCard key={subscription.id} subscription={subscription} />
))}
</div>
)}
</section>
<ScrollArea className="overflow-y-auto ">
{/* Content Area */}
<div className="relative space-y-8 p-6 pt-0">
<section className="space-y-4">
{sortedSubscriptions.length === 0 ? (
<div className="py-12 text-center text-sm text-muted-foreground">
{t('subscriptions.empty')}
</div>
) : !selectedTab ? (
<div className="py-12 text-center text-sm text-muted-foreground">
{t('subscriptions.empty')}
</div>
) : (
<div className="space-y-3">
{displayedSubscriptions.map((subscription) => (
<SubscriptionCard key={subscription.id} subscription={subscription} />
))}
</div>
)}
</section>
{/* RSSHub Info Card */}
<Card className="border-primary/20 bg-primary/5">
<CardHeader>
<CardTitle className="flex items-center gap-2">
{t('subscriptions.rssHub.title')}
</CardTitle>
<CardDescription>{t('subscriptions.rssHub.description')}</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="secondary"
size="sm"
onClick={() => void handleOpenRSSHubDocs()}
className="gap-2"
>
{t('subscriptions.rssHub.openDocs')}
</Button>
</CardContent>
</Card>
</div>
{/* RSSHub Info Card */}
<Card className="border-primary/20 bg-primary/5">
<CardHeader>
<CardTitle className="flex items-center gap-2">
{t('subscriptions.rssHub.title')}
</CardTitle>
<CardDescription>{t('subscriptions.rssHub.description')}</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="secondary"
size="sm"
onClick={() => void handleOpenRSSHubDocs()}
className="gap-2"
>
{t('subscriptions.rssHub.openDocs')}
</Button>
</CardContent>
</Card>
</div>
</ScrollArea>
<SubscriptionFormDialog
mode="add"