66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import Link from 'next/link'
|
|
import { formatDate, getExcerpt, getReadingTime, type Post } from '@/app/blog/utils'
|
|
import { cn } from '@/lib/utils'
|
|
import { Card } from '@/components/ui/card'
|
|
|
|
type PostCardProps = {
|
|
post: Post
|
|
className?: string
|
|
}
|
|
|
|
export default function PostCard({ post, className }: PostCardProps) {
|
|
const { metadata, slug, content } = post
|
|
const excerpt = getExcerpt(metadata.summary, content, 200)
|
|
const reading = getReadingTime(content)
|
|
|
|
return (
|
|
<Card
|
|
className={cn(
|
|
'group relative overflow-hidden transition-colors hover:bg-black/[0.04] dark:hover:bg-white/5',
|
|
className
|
|
)}
|
|
>
|
|
<Link
|
|
href={`/blog/${slug}`}
|
|
aria-label={`Read: ${metadata.title}`}
|
|
className="absolute inset-0"
|
|
/>
|
|
<div className="p-5">
|
|
<header className="mb-3">
|
|
<h3 className="text-lg font-semibold leading-tight tracking-tight text-neutral-900 dark:text-neutral-100 underline-offset-4 group-hover:underline">
|
|
{metadata.title}
|
|
</h3>
|
|
</header>
|
|
{excerpt && (
|
|
<p className="mb-4 line-clamp-3 text-sm leading-6 text-neutral-700 dark:text-neutral-300">
|
|
{excerpt}
|
|
</p>
|
|
)}
|
|
<footer className="flex flex-wrap items-center gap-2 text-xs text-neutral-600 dark:text-neutral-400">
|
|
<time dateTime={metadata.publishedAt}>{formatDate(metadata.publishedAt, false)}</time>
|
|
<span aria-hidden="true">•</span>
|
|
<span>{reading.text}</span>
|
|
{Array.isArray(metadata.tags) && metadata.tags.length > 0 && (
|
|
<>
|
|
<span aria-hidden="true">•</span>
|
|
<ul className="flex flex-wrap gap-1.5">
|
|
{metadata.tags.slice(0, 3).map((tag) => (
|
|
<li
|
|
key={tag}
|
|
className="rounded-full border border-black/10 bg-white/40 px-2 py-0.5 text-[11px] leading-5 dark:border-white/10 dark:bg-white/[0.06]"
|
|
>
|
|
{tag}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
<span className="ml-auto hidden text-neutral-400 transition-colors group-hover:text-neutral-600 dark:group-hover:text-neutral-300 sm:inline">
|
|
→
|
|
</span>
|
|
</footer>
|
|
</div>
|
|
</Card>
|
|
)
|
|
}
|