feat(nav): collapsible sidebar + mobile drawer, user menu, breadcrumbs, polished cmd+k, header search
Build & Push Docker Image / build (push) Successful in 2m46s
Build & Push Docker Image / build (push) Successful in 2m46s
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { Fragment } from "react";
|
||||
import { useLocation, Link } from "wouter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { useGetTool, getGetToolQueryKey } from "@workspace/api-client-react";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
|
||||
type Crumb = { href?: string; label: string };
|
||||
|
||||
function buildCrumbs(location: string, t: TFunction): Crumb[] {
|
||||
const crumbs: Crumb[] = [];
|
||||
|
||||
if (location.startsWith("/tools")) {
|
||||
crumbs.push({ href: "/tools", label: t("nav.browseTools") });
|
||||
const match = location.match(/^\/tools\/(\d+)/);
|
||||
if (match) {
|
||||
crumbs.push({ href: `/tools/${match[1]}`, label: t("browse.tool") });
|
||||
if (location.includes("/edit")) crumbs.push({ label: t("common.edit") });
|
||||
} else if (location.startsWith("/tools/new")) {
|
||||
crumbs.push({ label: t("nav.addTool") });
|
||||
}
|
||||
} else if (location.startsWith("/admin")) {
|
||||
crumbs.push({ href: "/admin", label: t("nav.admin") });
|
||||
if (location.startsWith("/admin/redundancy")) crumbs.push({ label: t("nav.redundancy") });
|
||||
} else if (location.startsWith("/watchlist")) {
|
||||
crumbs.push({ label: t("nav.watchlist") });
|
||||
} else if (location.startsWith("/trash")) {
|
||||
crumbs.push({ label: t("nav.trash") });
|
||||
} else if (location.startsWith("/compare")) {
|
||||
crumbs.push({ label: t("compare.title") });
|
||||
} else if (location.startsWith("/analytics")) {
|
||||
crumbs.push({ label: t("nav.analytics") });
|
||||
} else if (location.startsWith("/login")) {
|
||||
crumbs.push({ label: t("auth.signIn") });
|
||||
}
|
||||
|
||||
return crumbs;
|
||||
}
|
||||
|
||||
export function Breadcrumbs() {
|
||||
const [location] = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const detailMatch = location.match(/^\/tools\/(\d+)/);
|
||||
const toolId = detailMatch ? parseInt(detailMatch[1], 10) : 0;
|
||||
const { data: tool } = useGetTool(Number.isFinite(toolId) ? toolId : 0, {
|
||||
query: {
|
||||
queryKey: getGetToolQueryKey(toolId),
|
||||
enabled: Number.isFinite(toolId) && toolId > 0,
|
||||
},
|
||||
});
|
||||
|
||||
const crumbs = buildCrumbs(location, t);
|
||||
if (crumbs.length <= 1) return null;
|
||||
|
||||
if (toolId > 0 && tool?.name) {
|
||||
const idx = crumbs.findIndex((c) => c.href === `/tools/${toolId}`);
|
||||
if (idx >= 0) crumbs[idx].label = tool.name;
|
||||
}
|
||||
|
||||
return (
|
||||
<Breadcrumb className="min-w-0">
|
||||
<BreadcrumbList className="flex-nowrap">
|
||||
{crumbs.map((crumb, i) => {
|
||||
const isLast = i === crumbs.length - 1;
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{i > 0 && <BreadcrumbSeparator />}
|
||||
<BreadcrumbItem className="min-w-0">
|
||||
{isLast || !crumb.href ? (
|
||||
<BreadcrumbPage className="text-sm truncate">{crumb.label}</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink asChild className="text-sm">
|
||||
<Link href={crumb.href} className="truncate">
|
||||
{crumb.label}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user