fix height problems
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ReactNode } from "react"
|
||||||
import CvEntry from "./CvEntry"
|
import CvEntry from "./CvEntry"
|
||||||
import type { RouterOutputs } from "~/server/routers/_app"
|
import type { RouterOutputs } from "~/server/routers/_app"
|
||||||
import { cn } from "~/lib/utils"
|
import { cn } from "~/lib/utils"
|
||||||
@@ -13,8 +14,9 @@ type CvCategoryProps = {
|
|||||||
category: CvCategoryData,
|
category: CvCategoryData,
|
||||||
layout: "row" | "col",
|
layout: "row" | "col",
|
||||||
position?: number,
|
position?: number,
|
||||||
|
descriptions: Record<string, ReactNode>,
|
||||||
}
|
}
|
||||||
export default function CvCategory({ category, layout, position = 0 }: CvCategoryProps) {
|
export default function CvCategory({ category, layout, position = 0, descriptions }: CvCategoryProps) {
|
||||||
const entries = category.cvEntry
|
const entries = category.cvEntry
|
||||||
return (
|
return (
|
||||||
<AnimatedCard position={position} className={cn(layout == "row" ? "w-full" : "", "h-screen")}>
|
<AnimatedCard position={position} className={cn(layout == "row" ? "w-full" : "", "h-screen")}>
|
||||||
@@ -28,7 +30,7 @@ export default function CvCategory({ category, layout, position = 0 }: CvCategor
|
|||||||
<ScrollArea>
|
<ScrollArea>
|
||||||
{entries.map((entry, i) => (
|
{entries.map((entry, i) => (
|
||||||
<AnimatePopUp position={position + 0.4 + i * 0.2} debugId={`cv-entry-wrapper:${category.name}:${entry.title}:${position + 0.4 + i * 0.2}`} key={entry.id}>
|
<AnimatePopUp position={position + 0.4 + i * 0.2} debugId={`cv-entry-wrapper:${category.name}:${entry.title}:${position + 0.4 + i * 0.2}`} key={entry.id}>
|
||||||
<CvEntry position={position + 0.4 + i * 0.2} entry={entry} className={layout == "row" ? "w-full lg:w-fit" : undefined} />
|
<CvEntry position={position + 0.4 + i * 0.2} entry={entry} description={descriptions[entry.id]} className={layout == "row" ? "w-full lg:w-fit" : undefined} />
|
||||||
</AnimatePopUp>
|
</AnimatePopUp>
|
||||||
))}
|
))}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import type { ReactNode } from "react"
|
||||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
|
||||||
import { cn } from "~/lib/utils"
|
import { cn } from "~/lib/utils"
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import type { ArrayElement } from "type-fest"
|
import type { ArrayElement } from "type-fest"
|
||||||
import AnimateTextIn from "~/app/_components/Animated/AnimateIn"
|
import AnimateTextIn from "~/app/_components/Animated/AnimateIn"
|
||||||
import AnimatePopUp from "~/app/_components/Animated/AnimatePopUp"
|
import AnimatedDiv from "~/app/_components/Animated/AnimatedDiv"
|
||||||
import { MDXRemote } from "next-mdx-remote/rsc";
|
|
||||||
import type { CvCategoryData } from "./CvCategory"
|
import type { CvCategoryData } from "./CvCategory"
|
||||||
import { ClientMdx } from "~/components/ClientMdx"
|
|
||||||
|
|
||||||
export type CvEntryData = ArrayElement<CvCategoryData['cvEntry']>
|
export type CvEntryData = ArrayElement<CvCategoryData['cvEntry']>
|
||||||
|
|
||||||
export default function CvEntry({ entry, className, position = 0 }: {
|
export default function CvEntry({ entry, description, className, position = 0 }: {
|
||||||
entry: CvEntryData,
|
entry: CvEntryData,
|
||||||
|
description?: ReactNode,
|
||||||
className?: string,
|
className?: string,
|
||||||
position?: number
|
position?: number
|
||||||
}) {
|
}) {
|
||||||
@@ -27,11 +27,15 @@ export default function CvEntry({ entry, className, position = 0 }: {
|
|||||||
}
|
}
|
||||||
{entry.description ?
|
{entry.description ?
|
||||||
<CardContent className="text-sm lg:text-base">
|
<CardContent className="text-sm lg:text-base">
|
||||||
<AnimatePopUp once position={position + 0.2} debugId={`cv-entry-description:${entry.title}:${position + 0.2}`}>
|
{/* 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. */}
|
||||||
|
<AnimatedDiv once position={position + 0.2} className="opacity-0" opacity={1} duration={0.5} debugId={`cv-entry-description:${entry.title}:${position + 0.2}`}>
|
||||||
<article className="prose prose-zinc dark:prose-invert max-w-none">
|
<article className="prose prose-zinc dark:prose-invert max-w-none">
|
||||||
<ClientMdx source={entry.description} fallback={entry.description} />
|
{description ?? entry.description}
|
||||||
</article>
|
</article>
|
||||||
</AnimatePopUp>
|
</AnimatedDiv>
|
||||||
</CardContent> :
|
</CardContent> :
|
||||||
<></>
|
<></>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { Sidebar, SidebarContent, SidebarProvider } from "~/components/ui/sidebar";
|
import { Sidebar, SidebarContent, SidebarProvider } from "~/components/ui/sidebar";
|
||||||
import type { RouterOutputs } from "~/server/routers/_app"
|
import type { RouterOutputs } from "~/server/routers/_app"
|
||||||
import SidebarTriggerDisappearsOnMobile from "./SidebarTriggerDisappearsOnMobile";
|
import SidebarTriggerDisappearsOnMobile from "./SidebarTriggerDisappearsOnMobile";
|
||||||
import CvCategory from "./CvCategory";
|
import CvCategory from "./CvCategory";
|
||||||
import { useTimeLine } from "~/app/_providers/GsapProvicer";
|
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<string, ReactNode>,
|
||||||
|
}) {
|
||||||
useTimeLine(props.cv)
|
useTimeLine(props.cv)
|
||||||
|
const { descriptions } = props
|
||||||
const byPosition = (pos: "sidebar" | "header" | "col1" | "col2") =>
|
const byPosition = (pos: "sidebar" | "header" | "col1" | "col2") =>
|
||||||
props.cv?.filter((c) => c.layoutPosition === pos) ?? []
|
props.cv?.filter((c) => c.layoutPosition === pos) ?? []
|
||||||
const sidebarCategories = byPosition("sidebar")
|
const sidebarCategories = byPosition("sidebar")
|
||||||
@@ -21,7 +26,7 @@ export default function CvPage(props: { cv: RouterOutputs['categoryv2']['listAll
|
|||||||
<Sidebar>
|
<Sidebar>
|
||||||
<SidebarContent className="p-2 lg:pt-[3.2rem]">
|
<SidebarContent className="p-2 lg:pt-[3.2rem]">
|
||||||
{sidebarCategories.map((cat, i) => (
|
{sidebarCategories.map((cat, i) => (
|
||||||
<CvCategory layout="col" position={i} category={cat} key={cat.id} />
|
<CvCategory layout="col" position={i} category={cat} descriptions={descriptions} key={cat.id} />
|
||||||
))}
|
))}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
@@ -31,18 +36,18 @@ export default function CvPage(props: { cv: RouterOutputs['categoryv2']['listAll
|
|||||||
<div id="mainwrap" className="flex w-full flex-col gap-4 lg:px-[15vw]">
|
<div id="mainwrap" className="flex w-full flex-col gap-4 lg:px-[15vw]">
|
||||||
<div id="header" className="flex w-full h-fit flex-row gap-4 flex-wrap">
|
<div id="header" className="flex w-full h-fit flex-row gap-4 flex-wrap">
|
||||||
{headerCategories.map((cat, i) => (
|
{headerCategories.map((cat, i) => (
|
||||||
<CvCategory layout="row" position={i} category={cat} key={cat.id} />
|
<CvCategory layout="row" position={i} category={cat} descriptions={descriptions} key={cat.id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div id="colwrapper" className="flex flex-col lg:flex-row w-full h-3/4 gap-4">
|
<div id="colwrapper" className="flex flex-col lg:flex-row w-full h-3/4 gap-4">
|
||||||
<div id="col1" className={`flex flex-col w-full ${col1Categories.length > 0 ? "lg:w-1/2" : ""} h-full gap-4`}>
|
<div id="col1" className={`flex flex-col w-full ${col1Categories.length > 0 ? "lg:w-1/2" : ""} h-full gap-4`}>
|
||||||
{col1Categories.map((cat, i) => (
|
{col1Categories.map((cat, i) => (
|
||||||
<CvCategory layout="col" position={i} category={cat} key={cat.id} />
|
<CvCategory layout="col" position={i} category={cat} descriptions={descriptions} key={cat.id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div id="col2" className={`flex flex-col w-full ${col2Categories.length > 0 ? "lg:w-1/2" : ""} h-full gap-4`}>
|
<div id="col2" className={`flex flex-col w-full ${col2Categories.length > 0 ? "lg:w-1/2" : ""} h-full gap-4`}>
|
||||||
{col2Categories.map((cat, i) => (
|
{col2Categories.map((cat, i) => (
|
||||||
<CvCategory layout="col" position={i} category={cat} key={cat.id} />
|
<CvCategory layout="col" position={i} category={cat} descriptions={descriptions} key={cat.id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 { servTrpc as trpc } from "../_trpc/ServerClient";
|
||||||
|
import { mdxComponents } from "~/components/mdx-components";
|
||||||
import Page from "./_components/Page";
|
import Page from "./_components/Page";
|
||||||
|
|
||||||
export default async function CvPage() {
|
export default async function CvPage() {
|
||||||
const cv = await trpc.categoryv2.listAllWithEntries();
|
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<string, ReactNode> = {};
|
||||||
|
for (const category of cv ?? []) {
|
||||||
|
for (const entry of category.cvEntry) {
|
||||||
|
if (!entry.description?.trim()) continue;
|
||||||
|
descriptions[entry.id] = (
|
||||||
|
<MDXRemote
|
||||||
|
source={entry.description}
|
||||||
|
components={mdxComponents}
|
||||||
|
options={{
|
||||||
|
mdxOptions: {
|
||||||
|
format: "md",
|
||||||
|
remarkPlugins: [remarkGfm],
|
||||||
|
rehypePlugins: [rehypeHighlight],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Page cv={cv}/>
|
<Page cv={cv} descriptions={descriptions} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user