diff --git a/src/app/cv/_components/CvCategory.tsx b/src/app/cv/_components/CvCategory.tsx index c8ad728..9d768da 100644 --- a/src/app/cv/_components/CvCategory.tsx +++ b/src/app/cv/_components/CvCategory.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from "react" import CvEntry from "./CvEntry" import type { RouterOutputs } from "~/server/routers/_app" import { cn } from "~/lib/utils" @@ -13,8 +14,9 @@ type CvCategoryProps = { category: CvCategoryData, layout: "row" | "col", position?: number, + descriptions: Record, } -export default function CvCategory({ category, layout, position = 0 }: CvCategoryProps) { +export default function CvCategory({ category, layout, position = 0, descriptions }: CvCategoryProps) { const entries = category.cvEntry return ( @@ -28,7 +30,7 @@ export default function CvCategory({ category, layout, position = 0 }: CvCategor {entries.map((entry, i) => ( - + ))} diff --git a/src/app/cv/_components/CvEntry.tsx b/src/app/cv/_components/CvEntry.tsx index cf24974..affe5cf 100644 --- a/src/app/cv/_components/CvEntry.tsx +++ b/src/app/cv/_components/CvEntry.tsx @@ -1,17 +1,17 @@ +import type { ReactNode } from "react" import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card" import { cn } from "~/lib/utils" import { format } from 'date-fns' import type { ArrayElement } from "type-fest" import AnimateTextIn from "~/app/_components/Animated/AnimateIn" -import AnimatePopUp from "~/app/_components/Animated/AnimatePopUp" -import { MDXRemote } from "next-mdx-remote/rsc"; +import AnimatedDiv from "~/app/_components/Animated/AnimatedDiv" import type { CvCategoryData } from "./CvCategory" -import { ClientMdx } from "~/components/ClientMdx" export type CvEntryData = ArrayElement -export default function CvEntry({ entry, className, position = 0 }: { +export default function CvEntry({ entry, description, className, position = 0 }: { entry: CvEntryData, + description?: ReactNode, className?: string, position?: number }) { @@ -27,11 +27,15 @@ export default function CvEntry({ entry, className, position = 0 }: { } {entry.description ? - + {/* Fade the description in place instead of collapsing its height: + the outer entry pop-up (CvCategory) measures height:auto when it + plays, so the description must stay laid out at full height or the + entry reveals too short. */} +
- + {description ?? entry.description}
-
+
: <> } diff --git a/src/app/cv/_components/Page.tsx b/src/app/cv/_components/Page.tsx index ae3d7b9..59edf68 100644 --- a/src/app/cv/_components/Page.tsx +++ b/src/app/cv/_components/Page.tsx @@ -1,11 +1,16 @@ 'use client' +import type { ReactNode } from "react"; import { Sidebar, SidebarContent, SidebarProvider } from "~/components/ui/sidebar"; import type { RouterOutputs } from "~/server/routers/_app" import SidebarTriggerDisappearsOnMobile from "./SidebarTriggerDisappearsOnMobile"; import CvCategory from "./CvCategory"; import { useTimeLine } from "~/app/_providers/GsapProvicer"; -export default function CvPage(props: { cv: RouterOutputs['categoryv2']['listAllWithEntries'] }) { +export default function CvPage(props: { + cv: RouterOutputs['categoryv2']['listAllWithEntries'], + descriptions: Record, +}) { useTimeLine(props.cv) + const { descriptions } = props const byPosition = (pos: "sidebar" | "header" | "col1" | "col2") => props.cv?.filter((c) => c.layoutPosition === pos) ?? [] const sidebarCategories = byPosition("sidebar") @@ -21,7 +26,7 @@ export default function CvPage(props: { cv: RouterOutputs['categoryv2']['listAll {sidebarCategories.map((cat, i) => ( - + ))} @@ -31,18 +36,18 @@ export default function CvPage(props: { cv: RouterOutputs['categoryv2']['listAll
0 ? "lg:w-1/2" : ""} h-full gap-4`}> {col1Categories.map((cat, i) => ( - + ))}
0 ? "lg:w-1/2" : ""} h-full gap-4`}> {col2Categories.map((cat, i) => ( - + ))}
diff --git a/src/app/cv/page.tsx b/src/app/cv/page.tsx index 760a26f..caf9f82 100644 --- a/src/app/cv/page.tsx +++ b/src/app/cv/page.tsx @@ -1,11 +1,42 @@ -import { Suspense } from "react"; +import { Suspense, type ReactNode } from "react"; +import { MDXRemote } from "next-mdx-remote/rsc"; +import rehypeHighlight from "rehype-highlight"; +import remarkGfm from "remark-gfm"; import { servTrpc as trpc } from "../_trpc/ServerClient"; +import { mdxComponents } from "~/components/mdx-components"; import Page from "./_components/Page"; + export default async function CvPage() { const cv = await trpc.categoryv2.listAllWithEntries(); + + // Render the MDX descriptions on the server so they exist at first paint. + // The client tree (which runs the GSAP entrance via useTimeLine) only places + // these already-rendered nodes — it never invokes the MDX renderer itself, so + // the 'use client' boundary stays intact and the animations no longer play + // against an un-rendered fallback. + const descriptions: Record = {}; + for (const category of cv ?? []) { + for (const entry of category.cvEntry) { + if (!entry.description?.trim()) continue; + descriptions[entry.id] = ( + + ); + } + } + return ( - + ) }