67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
"use client";
|
||
|
||
import { useMemo, useState } from "react";
|
||
import Link from "next/link";
|
||
import type { BlogPost } from "@/lib/types";
|
||
|
||
export default function BlogSection({ posts }: { posts: BlogPost[] }) {
|
||
const allTags = useMemo(() => {
|
||
const tags = new Set<string>();
|
||
posts.forEach((p) => p.tags.forEach((t) => tags.add(t)));
|
||
return Array.from(tags);
|
||
}, [posts]);
|
||
|
||
const [activeTag, setActiveTag] = useState("all");
|
||
|
||
const filtered = useMemo(() => {
|
||
if (activeTag === "all") return posts;
|
||
return posts.filter((p) => p.tags.includes(activeTag));
|
||
}, [posts, activeTag]);
|
||
|
||
return (
|
||
<section className="section blog-section" id="blog">
|
||
<div className="container">
|
||
<div className="section-header reveal">
|
||
<span className="section-tag">📝 BLOG</span>
|
||
<h2>游民博客</h2>
|
||
<p>深度攻略、签证指南与旅居经验分享</p>
|
||
</div>
|
||
|
||
{allTags.length > 0 && (
|
||
<div className="blog-filters reveal">
|
||
<button className={`filter-btn${activeTag === "all" ? " active" : ""}`} onClick={() => setActiveTag("all")}>
|
||
📚 全部
|
||
</button>
|
||
{allTags.map((t) => (
|
||
<button key={t} className={`filter-btn${activeTag === t ? " active" : ""}`} onClick={() => setActiveTag(t)}>
|
||
{t}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<div className="blog-grid">
|
||
{filtered.map((post) => (
|
||
<Link key={post.slug} href={`/blog/${post.slug}`} className="blog-card reveal">
|
||
<div className="blog-emoji">{post.emoji}</div>
|
||
<div className="blog-meta">
|
||
<span>📅 {post.published_at}</span>
|
||
<span>⏱️ {post.read_time} 分钟阅读</span>
|
||
</div>
|
||
<h3>{post.title}</h3>
|
||
<p>{post.excerpt}</p>
|
||
<div className="blog-tags">
|
||
{post.tags.map((t) => <span key={t} className="blog-tag">{t}</span>)}
|
||
</div>
|
||
<span className="blog-read-more">阅读全文 →</span>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
{filtered.length === 0 && (
|
||
<p className="dest-empty reveal">该分类暂无文章</p>
|
||
)}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|