Add aesthetic timeline to post lists

This commit is contained in:
2025-11-19 00:54:58 +08:00
parent 10e4e7e21e
commit fe191752da
5 changed files with 26 additions and 5 deletions

View File

@@ -1,5 +1,6 @@
import { getAllPostsSorted } from '@/lib/posts'; import { getAllPostsSorted } from '@/lib/posts';
import { PostListWithControls } from '@/components/post-list-with-controls'; import { PostListWithControls } from '@/components/post-list-with-controls';
import { TimelineWrapper } from '@/components/timeline-wrapper';
export const metadata = { export const metadata = {
title: '所有文章' title: '所有文章'

View File

@@ -2,6 +2,7 @@ import Link from 'next/link';
import { getAllPostsSorted } from '@/lib/posts'; import { getAllPostsSorted } from '@/lib/posts';
import { siteConfig } from '@/lib/config'; import { siteConfig } from '@/lib/config';
import { PostListItem } from '@/components/post-list-item'; import { PostListItem } from '@/components/post-list-item';
import { TimelineWrapper } from '@/components/timeline-wrapper';
export default function HomePage() { export default function HomePage() {
const posts = getAllPostsSorted().slice(0, siteConfig.postsPerPage); const posts = getAllPostsSorted().slice(0, siteConfig.postsPerPage);
@@ -29,11 +30,11 @@ export default function HomePage() {
</Link> </Link>
</div> </div>
<ul className="space-y-3"> <TimelineWrapper>
{posts.map((post) => ( {posts.map((post) => (
<PostListItem key={post._id} post={post} /> <PostListItem key={post._id} post={post} />
))} ))}
</ul> </TimelineWrapper>
</div> </div>
</section> </section>
); );

View File

@@ -18,7 +18,8 @@ export function PostListItem({ post }: Props) {
post.description || post.custom_excerpt || post.body?.raw?.slice(0, 120); post.description || post.custom_excerpt || post.body?.raw?.slice(0, 120);
return ( return (
<li> <li className="relative pl-6">
<span className="pointer-events-none absolute left-0 top-6 z-10 h-3 w-3 rounded-full border-2 border-white bg-gradient-to-br from-blue-500 via-sky-400 to-indigo-400 shadow-md dark:border-slate-900" aria-hidden="true" />
<article className="motion-card group relative flex gap-4 rounded-lg border border-slate-200/70 bg-white/90 p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900/80"> <article className="motion-card group relative flex gap-4 rounded-lg border border-slate-200/70 bg-white/90 p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900/80">
<div className="pointer-events-none absolute inset-x-0 top-0 h-0.5 origin-left scale-x-0 bg-gradient-to-r from-blue-500 via-sky-400 to-indigo-500 opacity-80 transition-transform duration-300 ease-out group-hover:scale-x-100 dark:from-blue-400 dark:via-sky-300 dark:to-indigo-400" /> <div className="pointer-events-none absolute inset-x-0 top-0 h-0.5 origin-left scale-x-0 bg-gradient-to-r from-blue-500 via-sky-400 to-indigo-500 opacity-80 transition-transform duration-300 ease-out group-hover:scale-x-100 dark:from-blue-400 dark:via-sky-300 dark:to-indigo-400" />
{cover && ( {cover && (

View File

@@ -151,11 +151,11 @@ export function PostListWithControls({ posts, pageSize }: Props) {
</div> </div>
) : ( ) : (
<ul className="space-y-3"> <div className="space-y-3">
{currentPosts.map((post) => ( {currentPosts.map((post) => (
<PostListItem key={post._id} post={post} /> <PostListItem key={post._id} post={post} />
))} ))}
</ul> </div>
)} )}
{totalPages > 1 && currentPosts.length > 0 && ( {totalPages > 1 && currentPosts.length > 0 && (

View File

@@ -0,0 +1,18 @@
import { ReactNode } from 'react';
import clsx from 'clsx';
interface TimelineWrapperProps {
children: ReactNode;
className?: string;
}
export function TimelineWrapper({ children, className }: TimelineWrapperProps) {
return (
<div className={clsx('relative pl-8', className)}>
<div className="pointer-events-none absolute left-3 top-0 h-full w-px bg-gradient-to-b from-transparent via-slate-200 to-transparent dark:via-slate-700" aria-hidden="true" />
<div className="space-y-4">
{children}
</div>
</div>
);
}