diff --git a/package.json b/package.json index 9915c9b..0229a60 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-scroll-area": "^1.2.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec29391..b7f9c85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@radix-ui/react-dropdown-menu': specifier: ^2.1.16 version: 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-hover-card': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-label': specifier: ^2.1.7 version: 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -802,6 +805,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -4162,6 +4178,23 @@ snapshots: '@types/react': 19.2.2 '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) diff --git a/src/renderer/src/components/ui/hover-card.tsx b/src/renderer/src/components/ui/hover-card.tsx new file mode 100644 index 0000000..8726488 --- /dev/null +++ b/src/renderer/src/components/ui/hover-card.tsx @@ -0,0 +1,35 @@ +import * as HoverCardPrimitive from '@radix-ui/react-hover-card' +import { cn } from '@renderer/lib/utils' +import type * as React from 'react' + +function HoverCard({ ...props }: React.ComponentProps) { + return +} + +function HoverCardTrigger({ ...props }: React.ComponentProps) { + return +} + +function HoverCardContent({ + className, + align = 'center', + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { HoverCard, HoverCardTrigger, HoverCardContent } diff --git a/src/renderer/src/components/ui/sidebar.tsx b/src/renderer/src/components/ui/sidebar.tsx index 5992bae..bf249d2 100644 --- a/src/renderer/src/components/ui/sidebar.tsx +++ b/src/renderer/src/components/ui/sidebar.tsx @@ -112,21 +112,15 @@ export function Sidebar({ currentPage, onPageChange }: SidebarProps) { return (
- - - - - -

{item.label}

-
-
+ + {showLabel && ( {item.label} diff --git a/src/renderer/src/pages/Home.tsx b/src/renderer/src/pages/Home.tsx index 36c4a97..53ea41f 100644 --- a/src/renderer/src/pages/Home.tsx +++ b/src/renderer/src/pages/Home.tsx @@ -142,6 +142,7 @@ export function Home({ onOpenSupportedSites, onOpenSettings }: HomeProps) { const [url, setUrl] = useState('') const inputRef = useRef(null) + const [activeTab, setActiveTab] = useState<'single' | 'playlist'>('single') const inlinePreviewSites = popularSites .slice(0, 3) .map((site) => t(`sites.popular.${site.id}.label`)) @@ -536,262 +537,275 @@ export function Home({ onOpenSupportedSites, onOpenSettings }: HomeProps) { className="container mx-auto max-w-7xl p-6 space-y-6 overflow-hidden w-full" style={{ maxWidth: '100%' }} > - - - - {t('download.singleVideo')} - - - {t('playlist.title')} - - - - {/* Single Video Download Tab */} - - {/* URL Input Card */} - {!videoInfo && ( - - - {t('download.enterUrl')} + + setActiveTab(value as 'single' | 'playlist')} + className="w-full gap-0" + > + +
+
+ + {activeTab === 'single' ? t('download.enterUrl') : t('playlist.enterPlaylistUrl')} + -
- {t('sites.homeInlineDescription', { sites: inlinePreviewSites })} - -
-
- - -
-
- setUrl(e.target.value)} - onKeyDown={handleKeyDown} - className="pr-10" - disabled={loading} - /> - {loading && ( -
- -
- )} -
- - {settings.oneClickDownload ? ( - - ) : ( - - )} -
- - {/* One-Click Download Info */} - {settings.oneClickDownload && ( -
-
- {t('download.oneClickDownloadEnabled')} -
- {onOpenSettings && ( + {activeTab === 'single' ? ( +
+ {t('sites.homeInlineDescription', { sites: inlinePreviewSites })} +
+ ) : ( + t('playlist.playlistUrlDescription') + )} + +
+ + + {t('download.singleVideo')} + + + {t('playlist.title')} + + +
+ + + + {/* Single Video Download Tab */} + + {/* URL Input Card */} + {!videoInfo && ( +
+
+
+ setUrl(e.target.value)} + onKeyDown={handleKeyDown} + className="pr-10" + disabled={loading} + /> + {loading && ( +
+ +
+ )} +
+ + {settings.oneClickDownload ? ( + + ) : ( + )}
- )} - {/* Error Display */} - {error && ( -
-
- -
-

- {t('errors.fetchInfoFailed')} -

-

{error}

+ {/* One-Click Download Info */} + {settings.oneClickDownload && ( +
+
+ {t('download.oneClickDownloadEnabled')} +
+ {onOpenSettings && ( + + )} +
+ )} + + {/* Error Display */} + {error && ( +
+
+ +
+

+ {t('errors.fetchInfoFailed')} +

+

{error}

+
-
- )} - - - )} - - {/* Video Info and Download Options */} - {videoInfo && !loading && } - - - {/* Playlist Download Tab */} - - - - {t('playlist.enterPlaylistUrl')} - {t('playlist.playlistUrlDescription')} - - -
- -
- { - setPlaylistUrl(e.target.value) - setPlaylistInfo(null) - setPlaylistPreviewError(null) - }} - className="flex-1" - disabled={playlistBusy} - /> - -
-
- -
-
- - -
- -
- -
- setStartIndex(e.target.value)} - min="1" - disabled={playlistBusy} - /> - setEndIndex(e.target.value)} - min="1" - disabled={playlistBusy} - /> -
-
-
- -
- - {playlistInfo && ( +
+ )} + + {/* Video Info and Download Options */} + {videoInfo && !loading && } +
+ + {/* Playlist Download Tab */} + +
+
+ +
+ { + setPlaylistUrl(e.target.value) + setPlaylistInfo(null) + setPlaylistPreviewError(null) + }} + className="flex-1" + disabled={playlistBusy} + /> + +
+
+ +
+
+ + +
+ +
+ +
+ setStartIndex(e.target.value)} + min="1" + disabled={playlistBusy} + /> + setEndIndex(e.target.value)} + min="1" + disabled={playlistBusy} + /> +
+
+
+ +
+ {playlistInfo && ( + + )} +
+ + {playlistUrl.trim() && !playlistInfo && !playlistPreviewError && !playlistBusy && ( +

{t('playlist.previewRequired')}

+ )} + + {playlistPreviewError && ( +
+ {playlistPreviewError} +
)}
+
+ + + - {playlistUrl.trim() && !playlistInfo && !playlistPreviewError && !playlistBusy && ( -

{t('playlist.previewRequired')}

- )} - - {playlistPreviewError && ( -
- {playlistPreviewError} -
- )} - - - {playlistInfo && ( - - )} - - + {/* Playlist Preview Card (outside main card) */} + {playlistInfo && ( + + )} {/* Unified Download History */} diff --git a/src/renderer/src/pages/Subscriptions.tsx b/src/renderer/src/pages/Subscriptions.tsx index 7017286..1818019 100644 --- a/src/renderer/src/pages/Subscriptions.tsx +++ b/src/renderer/src/pages/Subscriptions.tsx @@ -18,6 +18,7 @@ import { ContextMenuSeparator, ContextMenuTrigger } 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 { Tabs, TabsList, TabsTrigger } from '@renderer/components/ui/tabs' import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip' @@ -127,16 +128,16 @@ function SubscriptionTab({ return ( <> - - - - + + + +
-
- -

{statusDescription}

-

- {t('subscriptions.status.tooltip.updatedAt', { time: lastUpdatedLabel })} -

-
-
-
- - {subscription.title || t('subscriptions.labels.unknown')} - -
-
-
+
+ + {subscription.title || t('subscriptions.labels.unknown')} + +
+ + + + +

+ {subscription.title || t('subscriptions.labels.unknown')} +

+

{statusDescription}

+

+ {t('subscriptions.status.tooltip.updatedAt', { time: lastUpdatedLabel })} +

+
+ @@ -557,7 +561,7 @@ function SubscriptionCard({ subscription }: { subscription: SubscriptionRule }) {t('subscriptions.labels.noThumbnail')}
)} -
+
{subscription.coverUrl ? (
@@ -572,7 +576,7 @@ function SubscriptionCard({ subscription }: { subscription: SubscriptionRule }) {(subscription.title || t('subscriptions.labels.unknown')).slice(0, 1)}
)} - + {subscription.title || t('subscriptions.labels.unknown')}