prmbr-image-mksaas/src/components/blog/blog-card.tsx

115 lines
4.3 KiB
TypeScript

import { Skeleton } from '@/components/ui/skeleton';
import { LocaleLink } from '@/i18n/navigation';
import { formatDate } from '@/lib/formatter';
import { type BlogType, authorSource, categorySource } from '@/lib/source';
import Image from 'next/image';
import BlogImage from './blog-image';
interface BlogCardProps {
locale: string;
post: BlogType;
}
export default function BlogCard({ locale, post }: BlogCardProps) {
const { date, title, description, image, author, categories } = post.data;
const publishDate = formatDate(new Date(date));
const blogAuthor = authorSource.getPage([author], locale);
const blogCategories = categorySource
.getPages(locale)
.filter((category) => categories.includes(category.slugs[0] ?? ''));
return (
<LocaleLink href={`/blog/${post.slugs}`} className="block h-full">
<div className="group flex flex-col border border-border rounded-lg overflow-hidden h-full transition-all duration-300 ease-in-out hover:border-primary hover:shadow-lg hover:shadow-primary/20">
{/* Image container - fixed aspect ratio */}
<div className="group overflow-hidden relative aspect-16/9 w-full">
<div className="relative w-full h-full">
<BlogImage
src={image}
alt={title || 'image for blog post'}
title={title || 'image for blog post'}
/>
{/* categories */}
{blogCategories && blogCategories.length > 0 && (
<div className="absolute left-2 bottom-2 opacity-100 transition-opacity duration-300 z-20">
<div className="flex flex-wrap gap-1">
{blogCategories.map((category, index) => (
<span
key={`${category?.slugs[0]}-${index}`}
className="text-xs font-medium text-white bg-black/50 bg-opacity-50 px-2 py-1 rounded-md"
>
{category?.data.name}
</span>
))}
</div>
</div>
)}
</div>
</div>
{/* Post info container */}
<div className="flex flex-col justify-between p-4 flex-1">
<div>
{/* Post title */}
<h3 className="text-lg line-clamp-2 font-medium">{title}</h3>
{/* Post excerpt */}
<div className="mt-2">
{description && (
<p className="line-clamp-2 text-sm text-muted-foreground">
{description}
</p>
)}
</div>
</div>
{/* Author and date */}
<div className="mt-4 pt-4 border-t flex items-center justify-between space-x-4 text-muted-foreground">
<div className="flex items-center gap-2">
<div className="relative h-8 w-8 shrink-0">
{blogAuthor?.data.avatar && (
<Image
src={blogAuthor?.data.avatar}
alt={`avatar for ${blogAuthor?.data.name}`}
className="rounded-full object-cover border"
fill
/>
)}
</div>
<span className="truncate text-sm">{blogAuthor?.data.name}</span>
</div>
<time className="truncate text-sm" dateTime={date}>
{publishDate}
</time>
</div>
</div>
</div>
</LocaleLink>
);
}
export function BlogCardSkeleton() {
return (
<div className="border border-gray-200 dark:border-gray-800 rounded-lg overflow-hidden h-full">
<div className="overflow-hidden relative aspect-16/9 w-full">
<Skeleton className="h-full w-full rounded-b-none" />
</div>
<div className="p-4 flex flex-col justify-between flex-1">
<div>
<Skeleton className="h-6 w-full mb-2" />
<Skeleton className="h-4 w-full mb-4" />
</div>
<div className="pt-4 border-t border-gray-100 dark:border-gray-800 flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-4 w-24" />
</div>
<Skeleton className="h-4 w-20" />
</div>
</div>
</div>
);
}