| 1 | /** |
| 2 | * VideoCard 组件 |
| 3 | * 展示视频封面和标题的卡片组件,支持悬停效果 |
| 4 | */ |
| 5 | 'use client' |
| 6 | |
| 7 | import type { VideoCardProps } from '../types' |
| 8 | import { Play } from 'lucide-react' |
| 9 | import Image from 'next/image' |
| 10 | import { memo } from 'react' |
| 11 | import { cn } from '@/utils/className' |
| 12 | |
| 13 | export const VideoCard = memo(({ item, onClick, size = 'horizontal' }: VideoCardProps) => { |
| 14 | return ( |
| 15 | <div |
| 16 | className={cn( |
| 17 | 'group relative cursor-pointer overflow-hidden rounded-lg bg-card', |
| 18 | 'transition-all duration-300 ease-out', |
| 19 | 'hover:scale-[1.02] hover:shadow-lg hover:shadow-black/10', |
| 20 | 'dark:hover:shadow-black/30', |
| 21 | // 根据 size 设置不同的高度 |
| 22 | size === 'vertical' ? 'h-full' : '', |
| 23 | )} |
| 24 | onClick={onClick} |
| 25 | role="button" |
| 26 | tabIndex={0} |
| 27 | onKeyDown={(e) => { |
| 28 | if (e.key === 'Enter' || e.key === ' ') { |
| 29 | e.preventDefault() |
| 30 | onClick() |
| 31 | } |
| 32 | }} |
| 33 | > |
| 34 | {/* 封面图 */} |
| 35 | <div |
| 36 | className={cn( |
| 37 | 'relative w-full overflow-hidden', |
| 38 | // 竖视频使用全高度,横视频使用 16:9 比例 |
| 39 | size === 'vertical' ? 'h-full' : 'aspect-video', |
| 40 | )} |
| 41 | > |
| 42 | <Image |
| 43 | src={item.cover} |
| 44 | alt={item.title} |
| 45 | fill |
| 46 | className="object-cover transition-transform duration-300 group-hover:scale-105" |
| 47 | sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" |
| 48 | /> |
| 49 | |
| 50 | {/* 悬停时的播放图标 */} |
| 51 | <div |
| 52 | className={cn( |
| 53 | 'absolute inset-0 flex items-center justify-center', |
| 54 | 'bg-black/30 opacity-0 transition-opacity duration-300', |
| 55 | 'group-hover:opacity-100', |
| 56 | )} |
| 57 | > |
| 58 | <div |
| 59 | className={cn( |
| 60 | 'flex h-14 w-14 items-center justify-center rounded-full', |
| 61 | 'bg-white/90 text-foreground shadow-lg', |
| 62 | 'transition-transform duration-300 group-hover:scale-110', |
| 63 | )} |
| 64 | > |
| 65 | <Play className="h-6 w-6 fill-current" /> |
| 66 | </div> |
| 67 | </div> |
| 68 | |
| 69 | {/* 底部标题遮罩 */} |
| 70 | <div |
| 71 | className={cn( |
| 72 | 'absolute inset-x-0 bottom-0 p-3', |
| 73 | 'bg-gradient-to-t from-black/70 to-transparent', |
| 74 | )} |
| 75 | > |
| 76 | <h3 |
| 77 | className={cn( |
| 78 | 'line-clamp-2 text-sm font-medium text-white', |
| 79 | 'transition-colors duration-200', |
| 80 | )} |
| 81 | > |
| 82 | {item.title} |
| 83 | </h3> |
| 84 | </div> |
| 85 | </div> |
| 86 | </div> |
| 87 | ) |
| 88 | }) |
| 89 |