'use client'; import { useEffect, useMemo, useState } from 'react'; import type { Post } from 'contentlayer/generated'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowDownWideShort, faArrowUpWideShort, faMagnifyingGlass, faListUl } from '@fortawesome/free-solid-svg-icons'; import { siteConfig } from '@/lib/config'; import { PostListItem } from './post-list-item'; import { TimelineWrapper } from './timeline-wrapper'; interface Props { posts: Post[]; pageSize?: number; } type SortOrder = 'new' | 'old'; export function PostListWithControls({ posts, pageSize }: Props) { const [sortOrder, setSortOrder] = useState('new'); const [page, setPage] = useState(1); const [searchTerm, setSearchTerm] = useState(''); const size = pageSize ?? siteConfig.postsPerPage ?? 5; const normalizedQuery = searchTerm.trim().toLowerCase(); const filteredPosts = useMemo(() => { if (!normalizedQuery) return posts; return posts.filter((post) => { const haystack = [ post.title, post.description, post.custom_excerpt, post.tags?.join(' ') ] .filter(Boolean) .join(' ') .toLowerCase(); return haystack.includes(normalizedQuery); }); }, [posts, normalizedQuery]); const sortedPosts = useMemo(() => { const arr = [...filteredPosts]; arr.sort((a, b) => { const aDate = a.published_at ? new Date(a.published_at).getTime() : 0; const bDate = b.published_at ? new Date(b.published_at).getTime() : 0; return sortOrder === 'new' ? bDate - aDate : aDate - bDate; }); return arr; }, [filteredPosts, sortOrder]); const totalPages = Math.max(1, Math.ceil(sortedPosts.length / size)); const currentPage = Math.min(page, totalPages); const start = (currentPage - 1) * size; const currentPosts = sortedPosts.slice(start, start + size); useEffect(() => { setPage(1); }, [normalizedQuery]); const handleChangeSort = (order: SortOrder) => { setSortOrder(order); setPage(1); }; const goToPage = (p: number) => { if (p < 1 || p > totalPages) return; setPage(p); }; return (
排序
setSearchTerm(event.target.value)} className="w-full rounded-full border border-slate-200 bg-white py-1.5 pl-9 pr-3 text-sm text-slate-700 shadow-sm transition duration-180 ease-snappy focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:focus:ring-blue-500" />

第 {currentPage} / {totalPages} 頁 · 共 {sortedPosts.length} 篇 {normalizedQuery && `(搜尋「${searchTerm}」)`}

{normalizedQuery && sortedPosts.length === 0 && ( )}
{currentPosts.length === 0 ? (
找不到符合關鍵字的文章,換個詞再試試?
) : ( {currentPosts.map((post) => ( ))} )} {totalPages > 1 && currentPosts.length > 0 && ( )}
); }