Initial commit for iiEasy: all files included
This commit is contained in:
146
components/PostCard.tsx
Executable file
146
components/PostCard.tsx
Executable file
@@ -0,0 +1,146 @@
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user