123 lines
4.5 KiB
TypeScript
123 lines
4.5 KiB
TypeScript
'use client';
|
|
|
|
import {usePathname} from 'next/navigation';
|
|
import {
|
|
Breadcrumb,
|
|
BreadcrumbItem,
|
|
BreadcrumbLink,
|
|
BreadcrumbList,
|
|
BreadcrumbPage,
|
|
BreadcrumbSeparator,
|
|
} from '@/components/ui/breadcrumb';
|
|
import {Skeleton} from "@/components/ui/skeleton";
|
|
import React, {useEffect, useState} from 'react';
|
|
import {getBreadcrumbs} from '@/utils/BreadcrumbUtils';
|
|
import {breadcrumbResolvers} from "@/lib/breadcrumb-map";
|
|
|
|
interface ResolvedBreadcrumb {
|
|
href: string;
|
|
label: string;
|
|
isCurrentPage: boolean;
|
|
}
|
|
|
|
export function DynamicBreadcrumb() {
|
|
const pathname = usePathname();
|
|
const [resolvedBreadcrumbs, setResolvedBreadcrumbs] = useState<ResolvedBreadcrumb[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
// Get initial segments count for skeleton
|
|
const pathSegments = pathname.split('/').filter(Boolean);
|
|
|
|
useEffect(() => {
|
|
const resolveDynamicLabels = async () => {
|
|
setIsLoading(true);
|
|
const raw = getBreadcrumbs(pathname);
|
|
|
|
const enhanced = await Promise.all(
|
|
raw.map(async (b) => {
|
|
const pathSegments = b.href.split('/').filter(Boolean);
|
|
const lastSegment = pathSegments[pathSegments.length - 1];
|
|
const parentSegment = pathSegments[pathSegments.length - 2];
|
|
|
|
const isUUID = /^[0-9a-fA-F-]{36}$/.test(lastSegment);
|
|
const resolve = breadcrumbResolvers[parentSegment];
|
|
|
|
if (isUUID && resolve) {
|
|
try {
|
|
const dynamicLabel = await resolve(lastSegment);
|
|
return {
|
|
href: b.href,
|
|
label: dynamicLabel,
|
|
isCurrentPage: b.isCurrentPage ?? false,
|
|
};
|
|
} catch {
|
|
return {
|
|
href: b.href,
|
|
label: lastSegment,
|
|
isCurrentPage: b.isCurrentPage ?? false,
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
href: b.href,
|
|
label: b.label,
|
|
isCurrentPage: b.isCurrentPage ?? false,
|
|
};
|
|
})
|
|
);
|
|
|
|
setResolvedBreadcrumbs(enhanced);
|
|
setIsLoading(false);
|
|
};
|
|
|
|
resolveDynamicLabels();
|
|
}, [pathname]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Breadcrumb>
|
|
<BreadcrumbList>
|
|
{/* Always show Home for empty path */}
|
|
<BreadcrumbItem className="hidden md:block">
|
|
<Skeleton className="h-4 w-[60px]"/>
|
|
</BreadcrumbItem>
|
|
{pathSegments.length > 0 && <BreadcrumbSeparator className="hidden md:block"/>}
|
|
|
|
{/* Show skeleton for each path segment */}
|
|
{pathSegments.map((_, index) => (
|
|
<React.Fragment key={index}>
|
|
<BreadcrumbItem className="hidden md:block">
|
|
<Skeleton className={`h-4 w-[${index === pathSegments.length - 1 ? '120' : '80'}px]`}/>
|
|
</BreadcrumbItem>
|
|
{index < pathSegments.length - 1 && (
|
|
<BreadcrumbSeparator className="hidden md:block"/>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</BreadcrumbList>
|
|
</Breadcrumb>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Breadcrumb>
|
|
<BreadcrumbList>
|
|
{resolvedBreadcrumbs.map((breadcrumb, index) => (
|
|
<React.Fragment key={breadcrumb.href}>
|
|
<BreadcrumbItem className="hidden md:block">
|
|
{breadcrumb.isCurrentPage ? (
|
|
<BreadcrumbPage>{breadcrumb.label}</BreadcrumbPage>
|
|
) : (
|
|
<BreadcrumbLink href={breadcrumb.href}>{breadcrumb.label}</BreadcrumbLink>
|
|
)}
|
|
</BreadcrumbItem>
|
|
{index < resolvedBreadcrumbs.length - 1 && (
|
|
<BreadcrumbSeparator className="hidden md:block"/>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</BreadcrumbList>
|
|
</Breadcrumb>
|
|
);
|
|
} |