feat: implement dynamic breadcrumbs with custom titles for pages and update dependencies

This commit is contained in:
2025-12-10 03:15:53 -05:00
parent 49243758c9
commit 82b32b70bd
8 changed files with 391 additions and 119 deletions
+2
View File
@@ -5,6 +5,7 @@ import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge";
import fs from "fs";
import path from "path";
import { BreadcrumbUpdater } from "~/components/BreadcrumbUpdater";
interface PageProps {
params: Promise<{ slug: string }>;
@@ -48,6 +49,7 @@ export default async function BlogPost({ params }: PageProps) {
return (
<article className="animate-fade-in-up space-y-8">
<BreadcrumbUpdater title={metadata.title} />
<div className="mb-8">
{/* <Button variant="ghost" asChild className="-ml-4 text-muted-foreground mb-4">
<Link href="/blog">
+13 -10
View File
@@ -6,6 +6,7 @@ import { Footer } from "~/components/Footer";
import { Navigation } from "~/components/Navigation";
import { Sidebar } from "~/components/Sidebar";
import { BreadcrumbWrapper } from "~/components/BreadcrumbWrapper";
import { BreadcrumbProvider } from "~/context/BreadcrumbContext";
import { inter, playfair } from "~/lib/fonts";
import { description, name } from "~/lib/data";
@@ -45,18 +46,20 @@ export default function RootLayout({ children }: React.PropsWithChildren) {
/>
)}
<Navigation />
<div className="flex flex-1 pt-24 flex-col lg:flex-row">
<Sidebar />
<div className="flex-1 min-w-0 lg:pl-96">
<div className="mx-auto max-w-screen-xl px-6 sm:px-8 lg:pl-0 lg:pr-8">
<main className="pb-8 pt-4">
<BreadcrumbWrapper />
{children}
</main>
<BreadcrumbProvider>
<Navigation />
<div className="flex flex-1 pt-24 flex-col lg:flex-row">
<Sidebar />
<div className="flex-1 min-w-0 lg:pl-96">
<div className="mx-auto max-w-screen-xl px-6 sm:px-8 lg:pl-0 lg:pr-8">
<main className="pb-8 pt-4">
<BreadcrumbWrapper />
{children}
</main>
</div>
</div>
</div>
</div>
</BreadcrumbProvider>
<Footer />
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
"use client";
import { useEffect } from "react";
import { useBreadcrumb } from "~/context/BreadcrumbContext";
export function BreadcrumbUpdater({ title }: { title: string }) {
const { setCustomTitle } = useBreadcrumb();
// Use effect to set title on mount and clear on unmount
useEffect(() => {
setCustomTitle(title);
return () => setCustomTitle(null);
}, [title, setCustomTitle]);
return null;
}
+11
View File
@@ -22,6 +22,7 @@ import {
BreadcrumbPage,
BreadcrumbSeparator,
} from "~/components/ui/breadcrumb";
import { useBreadcrumb } from "~/context/BreadcrumbContext";
interface BreadcrumbItem {
href: string;
@@ -32,6 +33,7 @@ interface BreadcrumbItem {
export function PageBreadcrumb() {
const pathname = usePathname();
const { customTitle } = useBreadcrumb();
// Generate breadcrumb items based on current path
const breadcrumbItems: BreadcrumbItem[] = [
@@ -96,6 +98,15 @@ export function PageBreadcrumb() {
icon = <File className="mr-1 h-3.5 w-3.5" />;
}
// Override label for the last segment if customTitle is available and it's a blog post
if (isLastSegment && customTitle && pathname.startsWith("/blog/")) {
label = customTitle;
// Truncate if too long (e.g., > 30 chars)
if (label.length > 30) {
label = label.substring(0, 30) + "...";
}
}
breadcrumbItems.push({
href: currentPath,
label,
+28
View File
@@ -0,0 +1,28 @@
"use client";
import React, { createContext, useContext, useState } from "react";
interface BreadcrumbContextType {
customTitle: string | null;
setCustomTitle: (title: string | null) => void;
}
const BreadcrumbContext = createContext<BreadcrumbContextType | undefined>(undefined);
export function BreadcrumbProvider({ children }: { children: React.ReactNode }) {
const [customTitle, setCustomTitle] = useState<string | null>(null);
return (
<BreadcrumbContext.Provider value={{ customTitle, setCustomTitle }}>
{children}
</BreadcrumbContext.Provider>
);
}
export function useBreadcrumb() {
const context = useContext(BreadcrumbContext);
if (context === undefined) {
throw new Error("useBreadcrumb must be used within a BreadcrumbProvider");
}
return context;
}