import Link from 'next/link'; import type { Post } from 'contentlayer/generated'; import { siteConfig } from '@/lib/config'; interface Props { current: Post; newer?: Post; older?: Post; } interface StationConfig { key: 'older' | 'current' | 'newer'; label: string; post?: Post; hint: string; align: 'start' | 'center' | 'end'; rel?: 'prev' | 'next'; } export function PostStorylineNav({ current, newer, older }: Props) { const stations: StationConfig[] = [ { key: 'older', label: '回程站', post: older, hint: older ? `發表於 ${formatDate(older.published_at)}` : '這裡已是最早的文章', align: 'start', rel: 'prev' }, { key: 'current', label: '你在這裡', post: current, hint: current.published_at ? `本篇發表於 ${formatDate(current.published_at)}` : '草稿狀態', align: 'center' }, { key: 'newer', label: '前進站', post: newer, hint: newer ? `發表於 ${formatDate(newer.published_at)}` : '還沒有更新的文章', align: 'end', rel: 'next' } ]; return ( ); } function Station({ station }: { station: StationConfig }) { const { post, label, hint, align, key, rel } = station; const alignClass = align === 'start' ? 'items-start text-left' : align === 'end' ? 'items-end text-right' : 'items-center text-center'; const baseCard = `group relative flex w-full flex-col gap-2 rounded-3xl border border-slate-200/70 bg-white/95 px-4 py-5 text-slate-800 shadow-sm transition duration-300 hover:-translate-y-0.5 hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400/70 dark:border-slate-800/70 dark:bg-slate-900/80 dark:text-slate-100 ${alignClass}`; const circleClass = (() => { if (!post && key !== 'current') { return 'border-dashed border-slate-300 dark:border-slate-700'; } if (key === 'current') { return 'border-blue-500 bg-blue-500 shadow-[0_0_0_6px_rgba(59,130,246,0.15)] dark:border-blue-400 dark:bg-blue-400'; } return 'border-blue-500 bg-white shadow-[0_0_0_4px_rgba(59,130,246,0.08)] dark:border-blue-400 dark:bg-slate-900'; })(); const content = post ? (

{post.title}

{post.published_at && (

{formatDate(post.published_at)}

)} {post.tags && post.tags.length > 0 && (
{post.tags.slice(0, 3).map((tag) => ( #{tag} ))}
)} ) : (

{hint}

); return (
{content}
); } function StationHeader({ label, hint, circleClass, align, hasPost }: { label: string; hint: string; circleClass: string; align: 'start' | 'center' | 'end'; hasPost?: boolean; }) { return (
{align === 'end' && ( {hint} )} {hasPost && } {label} {align !== 'end' && ( {hint} )}
); } function formatDate(input?: string | Date) { if (!input) return ''; const date = input instanceof Date ? input : new Date(input); return date.toLocaleDateString(siteConfig.defaultLocale ?? 'zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit' }); } function getJustifyClass(align: 'start' | 'center' | 'end') { if (align === 'end') return 'justify-end'; if (align === 'center') return 'justify-center'; return 'justify-start'; }