Files
app-261004/components/post/post-card.tsx
T
2026-10-08 11:09:37 +08:00

80 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from "@/i18n/navigation";
import { getLocale, getTranslations } from "next-intl/server";
import { CalendarDays, Clock } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { formatDate, toDateTimeAttribute } from "@/lib/utils";
import type { PostMeta } from "@/lib/posts/types";
/**
* 文章卡片(服务端组件)。
*
* DESIGN.md 的 card-base:白底 + 1px hairline 描边 + rounded-lg,
* 内部留白 {spacing.xl}。展示标题、日期、阅读时长、摘要与标签。
*/
export interface PostCardProps {
post: PostMeta;
/** 是否展示摘要;紧凑列表可关闭 */
showSummary?: boolean;
className?: string;
}
export async function PostCard({
post,
showSummary = true,
className,
}: PostCardProps) {
const t = await getTranslations("Post");
const locale = await getLocale();
return (
<article className={className}>
<Link
href={`/posts/${post.slug}`}
className="group flex h-full flex-col gap-sm rounded-lg border border-hairline bg-canvas p-xl transition-colors duration-150 hover:border-hairline-soft hover:bg-surface-soft active:bg-surface"
>
{/* 元信息:日期 + 阅读时长 */}
<div className="flex flex-wrap items-center gap-sm text-caption text-steel">
<span className="inline-flex items-center gap-[6px]">
<CalendarDays className="size-3.5" aria-hidden="true" />
<time dateTime={toDateTimeAttribute(post.date)}>
{formatDate(post.date, locale)}
</time>
</span>
<span aria-hidden="true" className="text-muted">
·
</span>
<span className="inline-flex items-center gap-[6px]">
<Clock className="size-3.5" aria-hidden="true" />
{t("readingTime", { minutes: post.readingMinutes })}
</span>
</div>
{/* 标题 */}
<h3 className="text-heading-4 font-semibold text-ink transition-colors duration-150 group-hover:text-brand-green-deep">
{post.title}
</h3>
{/* 摘要 */}
{showSummary ? (
<p className="flex-1 text-body-sm text-steel text-balance-pretty">
{post.summary}
</p>
) : null}
{/* 标签 */}
{post.tags && post.tags.length > 0 ? (
<ul className="mt-xs flex flex-wrap gap-xs">
{post.tags.map((tag) => (
<li key={tag}>
<Badge variant="tag">{tag}</Badge>
</li>
))}
</ul>
) : null}
</Link>
</article>
);
}