56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
"use client";
|
|||
|
|
|
||
|
|
import Link from "next/link";
|
||
|
|
import { usePathname } from "next/navigation";
|
||
|
|
import { ChevronRight, House } from "lucide-react";
|
||
|
|
|
||
|
|
const SECTION_LABELS: Record<string, string> = {
|
||
|
|
control: "Control",
|
||
|
|
recordings: "Recordings",
|
||
|
|
sessions: "Sessions",
|
||
|
|
stats: "Stats",
|
||
|
|
devices: "Devices",
|
||
|
|
};
|
||
|
|
|
||
|
|
const LEAF_LABELS: Record<string, string> = {
|
||
|
|
replay: "Replay",
|
||
|
|
};
|
||
|
|
|
||
|
|
export function Breadcrumbs() {
|
||
|
|
const pathname = usePathname();
|
||
|
|
const segments = pathname.split("/").filter(Boolean);
|
||
|
|
|
||
|
|
if (segments.length === 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const crumbs = segments.reduce<{ label: string; href: string }[]>((acc, segment) => {
|
||
|
|
const href = `${acc.at(-1)?.href ?? ""}/${segment}`;
|
||
|
|
const label = /^\d+$/.test(segment) ? `#${segment}` : (LEAF_LABELS[segment] ?? SECTION_LABELS[segment] ?? segment);
|
||
|
|
return [...acc, { label, href }];
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<nav aria-label="Breadcrumb" className="bp-glass mb-6 flex w-fit items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-muted-foreground">
|
||
|
|
<Link href="/" className="flex items-center hover:text-foreground" aria-label="Dashboard">
|
||
|
|
<House className="size-3.5" />
|
||
|
|
</Link>
|
||
|
|
{crumbs.map((crumb, i) => {
|
||
|
|
const isLast = i === crumbs.length - 1;
|
||
|
|
return (
|
||
|
|
<span key={crumb.href} className="flex items-center gap-1.5">
|
||
|
|
<ChevronRight className="size-3.5 text-muted-foreground/50" />
|
||
|
|
{isLast ? (
|
||
|
|
<span className="font-medium text-foreground">{crumb.label}</span>
|
||
|
|
) : (
|
||
|
|
<Link href={crumb.href} className="hover:text-foreground">
|
||
|
|
{crumb.label}
|
||
|
|
</Link>
|
||
|
|
)}
|
||
|
|
</span>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</nav>
|
||
|
|
);
|
||
|
|
}
|