1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-06-05 20:15:31 -04:00
Files
jarv.is/app/notes/page.tsx
T
jake b2416ff0db refactor: overhaul view transitions with granular per-page animation components
- Replace single `<ViewTransition>` wrapper in layout with `FadeTransition` and `DirectionalTransition` components applied per page
- Add `components/page-transition.tsx` with reusable transition wrappers
- Expand view transition CSS with named classes: fade, slide, nav-forward/back, morph, text-morph, scale — all driven by CSS custom property durations
- Use React `<ViewTransition name=... share="text-morph">` for shared note title element between list and detail views
- Wrap comments suspense boundary with enter/exit slide transitions
- Add `persistent-nav` and `persistent-footer` view-transition-name groups to keep chrome static during navigation
- Fix reduced-motion override to target delay and duration instead of `animation: none`
- Add tracking-tight and letter-spacing tweaks to home page typography
2026-04-25 10:50:31 -04:00

105 lines
3.2 KiB
TypeScript

import Link from "next/link";
import { ViewTransition } from "react";
import { PageTitle } from "@/components/layout/page-title";
import { DirectionalTransition } from "@/components/page-transition";
import { PostStats, PostStatsProvider } from "@/components/post-stats";
import authorConfig from "@/lib/config/author";
import { createMetadata } from "@/lib/metadata";
import { type FrontMatter, getFrontMatter, POSTS_DIR } from "@/lib/posts";
export const metadata = createMetadata({
title: "Notes",
description: `Recent posts by ${authorConfig.name}.`,
canonical: `/${POSTS_DIR}`,
});
const PostsList = async () => {
const posts = await getFrontMatter();
const formattedPosts = posts.map((post) => {
const d = new Date(post.date);
return Object.assign(post, {
year: d.getUTCFullYear(),
dateISO: d.toISOString(),
dateTitle: d.toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
}),
dateDisplay: d.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
}),
});
});
const postsByYear: {
[year: string]: (FrontMatter & {
year: number;
dateISO: string;
dateTitle: string;
dateDisplay: string;
})[];
} = {};
for (const post of formattedPosts) {
if (!postsByYear[post.year]) {
postsByYear[post.year] = [];
}
postsByYear[post.year].push(post);
}
const sections: React.ReactNode[] = [];
Object.entries(postsByYear).forEach(([year, yearPosts]) => {
sections.push(
<section className="my-8 first-of-type:mt-0 last-of-type:mb-0" key={year}>
<h2 id={year} className="mt-0 mb-4 text-xl font-semibold tracking-tight">
{year}
</h2>
<ul className="space-y-4">
{yearPosts.map(({ slug, dateISO, dateTitle, dateDisplay, title, htmlTitle }) => (
<li className="flex text-sm leading-relaxed" key={slug}>
<span className="text-muted-foreground w-18 shrink-0 md:w-22">
<time dateTime={dateISO} title={dateTitle} suppressHydrationWarning>
{dateDisplay}
</time>
</span>
<div className="space-x-2">
<ViewTransition name={`note-title-${slug}`} share="text-morph" default="none">
<Link
href={`/${POSTS_DIR}/${slug}`}
transitionTypes={["nav-forward"]}
dangerouslySetInnerHTML={{ __html: htmlTitle || title }}
className="mr-2.5 underline-offset-4 hover:underline"
/>
</ViewTransition>
<PostStats slug={`${POSTS_DIR}/${slug}`} />
</div>
</li>
))}
</ul>
</section>,
);
});
// grouped posts enter this component ordered chronologically -- we want reverse chronological
return <>{sections.toReversed()}</>;
};
const Page = async () => (
<DirectionalTransition>
<PageTitle canonical="/notes">Notes</PageTitle>
<PostStatsProvider>
<PostsList />
</PostStatsProvider>
</DirectionalTransition>
);
export default Page;