返回 AiToEarn
VideoCard.tsx
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
89 lines Plain Text