Files
KVideo/components/search/VideoGrid.tsx
T
kuekhaoyang a7480fb47a feat: add MobileVideoPlayer component with advanced controls and hooks
- Implemented MobileVideoPlayer component for mobile video playback.
- Added features such as play/pause, skip forward/backward, volume control, and fullscreen support.
- Integrated double-tap gesture handling for skipping video.
- Included hooks for screen orientation management and mobile detection.
- Enhanced user experience with loading indicators and playback speed options.
2025-11-18 12:40:50 +08:00

141 lines
5.1 KiB
TypeScript

'use client';
import { useState } from 'react';
import Link from 'next/link';
import { Card } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { Icons } from '@/components/ui/Icon';
interface Video {
vod_id: string;
vod_name: string;
vod_pic?: string;
vod_remarks?: string;
vod_year?: string;
type_name?: string;
source: string;
sourceName?: string;
isNew?: boolean;
}
interface VideoGridProps {
videos: Video[];
className?: string;
}
export function VideoGrid({ videos, className = '' }: VideoGridProps) {
const [activeCardId, setActiveCardId] = useState<string | null>(null);
if (videos.length === 0) {
return null;
}
const handleCardClick = (e: React.MouseEvent, videoId: string, videoUrl: string) => {
// Check if it's a mobile device
const isMobile = window.innerWidth < 1024; // lg breakpoint
if (isMobile) {
// On mobile, first click shows details, second click navigates
if (activeCardId === videoId) {
// Already active, allow navigation
window.location.href = videoUrl;
} else {
// First click, show details
e.preventDefault();
setActiveCardId(videoId);
}
}
// On desktop, let the Link work normally
};
return (
<div className={`grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-3 md:gap-4 lg:gap-6 ${className}`}>
{videos.map((video, index) => {
const videoUrl = `/player?${new URLSearchParams({
id: video.vod_id,
source: video.source,
title: video.vod_name,
}).toString()}`;
const cardId = `${video.vod_id}-${index}`;
const isActive = activeCardId === cardId;
return (
<Link
key={cardId}
href={videoUrl}
onClick={(e) => handleCardClick(e, cardId, videoUrl)}
>
<Card
className={`p-0 overflow-hidden group cursor-pointer flex flex-col h-full ${video.isNew ? 'animate-scale-in' : ''}`}
>
{/* Poster */}
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden" style={{ borderRadius: 'var(--radius-2xl)' }}>
{video.vod_pic ? (
<img
src={video.vod_pic}
alt={video.vod_name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
style={{ borderRadius: 'var(--radius-2xl)' }}
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Icons.Film size={64} className="text-[var(--text-color-secondary)]" />
</div>
)}
{/* Source Badge - Top Left */}
{video.sourceName && (
<div className="absolute top-2 left-2 z-10">
<Badge variant="primary" className="text-xs backdrop-blur-md bg-[var(--accent-color)]/90">
{video.sourceName}
</Badge>
</div>
)}
{/* Overlay - Show on hover (desktop) or when active (mobile) */}
<div className={`absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent transition-opacity duration-300 ${
isActive ? 'opacity-100' : 'opacity-0 lg:group-hover:opacity-100'
}`}>
<div className="absolute bottom-0 left-0 right-0 p-3">
{/* Mobile indicator when active */}
{isActive && (
<div className="lg:hidden text-white/90 text-xs mb-2 font-medium">
再次点击播放
</div>
)}
{video.type_name && (
<Badge variant="secondary" className="text-xs mb-2">
{video.type_name}
</Badge>
)}
{video.vod_year && (
<div className="flex items-center gap-1 text-white/80 text-xs">
<Icons.Calendar size={12} />
<span>{video.vod_year}</span>
</div>
)}
</div>
</div>
</div>
{/* Info - Fixed height section */}
<div className="p-3 flex-1 flex flex-col">
<h4 className="font-semibold text-sm text-[var(--text-color)] line-clamp-2 min-h-[2.5rem] group-hover:text-[var(--accent-color)] transition-colors">
{video.vod_name}
</h4>
{video.vod_remarks && (
<p className="text-xs text-[var(--text-color-secondary)] mt-1 line-clamp-1">
{video.vod_remarks}
</p>
)}
</div>
</Card>
</Link>
);
})}
</div>
);
}