first commit
This commit is contained in:
101
src/components/MediaListItem.tsx
Normal file
101
src/components/MediaListItem.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Media } from '@/types';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { motion } from 'motion/react';
|
||||
import { Star, Play, Bookmark } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
interface MediaListItemProps {
|
||||
key?: string;
|
||||
media: Media;
|
||||
onClick: (media: Media) => void;
|
||||
}
|
||||
|
||||
export default function MediaListItem({ media, onClick }: MediaListItemProps) {
|
||||
const statusColors = {
|
||||
watching: 'bg-blue-500',
|
||||
completed: 'bg-green-500',
|
||||
planned: 'bg-gray-500',
|
||||
dropped: 'bg-red-500',
|
||||
reading: 'bg-amber-500',
|
||||
listening: 'bg-purple-500',
|
||||
playing: 'bg-indigo-500',
|
||||
'on-hold': 'bg-orange-500',
|
||||
};
|
||||
|
||||
const getAspectRatio = () => {
|
||||
if (media.aspectRatio) return media.aspectRatio;
|
||||
switch (media.category) {
|
||||
case 'Music': return '1/1';
|
||||
case 'Games':
|
||||
case 'Adult': return '16/9';
|
||||
default: return '2/3';
|
||||
}
|
||||
};
|
||||
|
||||
const aspectRatioClass = {
|
||||
'2/3': 'w-24 h-32',
|
||||
'16/9': 'w-48 h-27', // 16:9 ratio for w-48 is approx h-27
|
||||
'1/1': 'w-24 h-24',
|
||||
}[getAspectRatio()];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="group flex items-center gap-6 p-4 rounded-xl hover:bg-zinc-50 transition-colors cursor-pointer border border-transparent hover:border-zinc-200"
|
||||
onClick={() => onClick(media)}
|
||||
>
|
||||
<div className={cn(
|
||||
"relative rounded-lg overflow-hidden shrink-0 shadow-md bg-zinc-800 transition-all duration-300",
|
||||
aspectRatioClass
|
||||
)}>
|
||||
<img
|
||||
src={media.poster}
|
||||
alt={media.title}
|
||||
className="w-full h-full object-cover"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
{media.status && (
|
||||
<div className={cn(
|
||||
"absolute top-2 left-2 w-3 h-3 rounded-full border border-white/20 shadow-sm",
|
||||
statusColors[media.status]
|
||||
)} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<h3 className="text-lg font-black text-zinc-900 truncate group-hover:text-[#6d28d9] transition-colors">
|
||||
{media.title}
|
||||
</h3>
|
||||
<span className="text-sm font-bold text-zinc-400">({media.year})</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mb-3">
|
||||
<div className="flex items-center gap-1 text-xs font-bold text-zinc-500">
|
||||
<Star size={14} className="text-yellow-500" fill="currentColor" />
|
||||
{media.rating || 'N/A'}
|
||||
</div>
|
||||
<div className="text-xs font-bold text-zinc-400 uppercase tracking-wider">
|
||||
{media.genres?.slice(0, 3).join(' • ') || 'Anime'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-zinc-500 line-clamp-2 max-w-2xl">
|
||||
{media.description || "No description available for this title."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<Button size="icon" variant="ghost" className="rounded-full text-zinc-400 hover:text-[#6d28d9] hover:bg-[#6d28d9]/10">
|
||||
<Play size={18} fill="currentColor" />
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" className="rounded-full text-zinc-400 hover:text-[#6d28d9] hover:bg-[#6d28d9]/10">
|
||||
<Bookmark size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user