Files
iiEasy/components/PostCard.tsx

147 lines
6.1 KiB
TypeScript
Executable File

import React, { useState, useRef } from 'react';
import { Post, CurrentView } from '../types';
import { PlayIcon, PauseIcon } from './icons';
import { UI_TEXTS } from '../constants';
import { ViewMode } from './FilterSortBar';
interface PostCardProps {
post: Post;
isFeatured?: boolean;
setCurrentView: (view: CurrentView) => void;
setSelectedItemId: (id: string | null) => void;
viewMode?: ViewMode;
}
const PostCard: React.FC<PostCardProps> = ({ post, isFeatured = false, setCurrentView, setSelectedItemId, viewMode = 'grid' }) => {
const [isVideoPlaying, setIsVideoPlaying] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const toggleVideoPlay = (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent card click event when clicking video button
if (videoRef.current) {
if (videoRef.current.paused) {
videoRef.current.play();
setIsVideoPlaying(true);
} else {
videoRef.current.pause();
setIsVideoPlaying(false);
}
}
};
const handleCardClick = () => {
setCurrentView('blogPostDetail');
setSelectedItemId(post.id);
};
if (viewMode === 'list' && !isFeatured) {
return (
<div
onClick={handleCardClick}
className="group block p-4 rounded-lg bg-white transition-all duration-300 ease-in-out hover:bg-slate-50 cursor-pointer w-full"
role="article"
aria-labelledby={`post-title-${post.id}`}
>
<div className="flex items-start space-x-4">
<div className="flex-shrink-0 w-24 h-24 md:w-28 md:h-28 rounded-md overflow-hidden bg-slate-200">
<img
src={post.imageUrl}
alt={post.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300 ease-in-out"
loading="lazy"
/>
</div>
<div className="flex-1">
<h3 id={`post-title-${post.id}`} className="text-base md:text-lg font-quicksand font-semibold text-slate-800 group-hover:text-black mb-1 transition-colors duration-200 leading-tight">
{post.title}
</h3>
<div className="font-inter text-xs md:text-sm text-slate-500 mb-2">
<span className="font-semibold text-slate-600">{post.category}</span>
{(post.date || post.readTime) && <span className="mx-1.5"></span>}
{post.date && <span>{post.date}</span>}
{post.date && post.readTime && <span className="mx-1.5"></span>}
{post.readTime && <span>{post.readTime}</span>}
</div>
{post.description && (
<p className="text-sm text-slate-600 line-clamp-2 font-inter">{post.description}</p>
)}
</div>
</div>
</div>
);
}
// Grid View (or Featured Card)
const imageAspectRatio = isFeatured ? "aspect-[4/5] md:aspect-[16/9]" : "aspect-[1/1]";
const titleSize = isFeatured ? "text-2xl md:text-3xl lg:text-4xl" : "text-xl md:text-lg";
const scaleEffect = isFeatured ? "group-hover:scale-[1.0125]" : "group-hover:scale-[1.025]";
return (
<div
className={`group relative rounded-xl overflow-hidden transition-all duration-300 bg-white ${isFeatured ? 'flex flex-col' : ''} cursor-pointer shadow-sm hover:shadow-md`}
onClick={handleCardClick}
role="article"
aria-labelledby={`post-title-${post.id}`}
>
<div className={`relative w-full overflow-hidden ${imageAspectRatio} ${scaleEffect} transition-transform duration-300`}>
{post.videoUrl && !isFeatured ? (
<>
<video
ref={videoRef}
loop
playsInline
muted
className="absolute inset-0 w-full h-full object-cover"
poster={post.imageUrl}
onPlay={() => setIsVideoPlaying(true)}
onPause={() => setIsVideoPlaying(false)}
onClick={(e) => e.stopPropagation()} // Prevent card click on video itself
>
<source src={post.videoUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
<button
onClick={toggleVideoPlay}
className="absolute top-2 right-2 z-10 p-2 bg-black bg-opacity-50 rounded-full text-white hover:bg-opacity-75 transition-opacity"
aria-label={isVideoPlaying ? UI_TEXTS.pauseVideoAriaLabel : UI_TEXTS.playVideoAriaLabel}
>
{isVideoPlaying ? <PauseIcon className="w-5 h-5" /> : <PlayIcon className="w-5 h-5" />}
</button>
</>
) : (
<img
src={post.imageUrl}
alt={post.title}
className="absolute inset-0 w-full h-full object-cover"
loading="lazy"
/>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</div>
<div className={`p-4 md:p-6 ${isFeatured ? 'flex-grow flex flex-col justify-between' : ''}`}>
<div>
<h3 id={`post-title-${post.id}`} className={`font-quicksand font-bold ${titleSize} mb-2 text-slate-800 transition-colors`}>
{post.title}
</h3>
{isFeatured && post.description && (
<p className="font-inter text-sm md:text-base text-slate-600 mb-3 line-clamp-3">{post.description}</p>
)}
{!isFeatured && post.description && (
<p className="font-inter text-sm text-slate-600 mb-3 line-clamp-2">{post.description}</p>
)}
</div>
<div className="font-inter text-xs md:text-sm text-slate-500">
<span className="font-semibold text-slate-600">{post.category}</span>
{(post.date || post.readTime) && <span className="mx-1.5"></span>}
{post.date && <span>{post.date}</span>}
{post.date && post.readTime && <span className="mx-1.5"></span>}
{post.readTime && <span>{post.readTime}</span>}
</div>
</div>
</div>
);
};
export default PostCard;