Files
starr-philproperties/apps/web/src/components/generic/Breadcrumb/AppBreadcrumb.jsx
T
kennethobsequio b37218f90e merge new_starr_app@qas history into apps/web
git-subtree-dir: apps/web
git-subtree-mainline: eaa6d2276a
git-subtree-split: 459af9d46a
2026-08-24 12:21:16 +08:00

117 lines
4.6 KiB
React

import { useNavigate } from "react-router-dom";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { cn } from "@/lib/utils";
/**
* AppBreadcrumb
*
* Generic breadcrumb driven entirely by the `items` prop.
* The last item is always rendered as the current page (non-clickable).
* All preceding items are rendered as clickable links.
*
* ─── Item definition shape ────────────────────────────────────────────────────
*
* @field {string} label Display text
* @field {ReactNode} [icon] Optional icon rendered before the label
* @field {string} [to] navigate() path — if omitted, item is non-navigable
* @field {Function} [onClick] Custom click handler (e, navigate) => void
* Overrides `to` when provided.
*
* ─────────────────────────────────────────────────────────────────────────────
*
* @param {Array} items Ordered list of breadcrumb item definitions
* @param {Object} [color] Independent color overrides per element
* @param {Object} [color.link] Override for non-last (clickable) items
* @param {string} [color.link.color] className applied to BreadcrumbLink
* @param {Object} [color.page] Override for the last (current page) item
* @param {string} [color.page.color] className applied to BreadcrumbPage
*
* ─── Usage ───────────────────────────────────────────────────────────────────
*
* import { House, Users } from "lucide-react";
* import AppBreadcrumb from "@/components/generic/AppBreadcrumb";
*
* // Basic — just paths
* <AppBreadcrumb
* items={[
* { label: "Home", icon: <House className="size-4" />, to: `/admin/${adminId}/users` },
* { label: "Users" },
* ]}
* />
*
* // With custom click handler
* <AppBreadcrumb
* items={[
* { label: "Home", icon: <House className="size-4" />, onClick: (e, navigate) => navigate("/admin") },
* { label: "Settings", to: `/admin/${adminId}/settings` },
* { label: "Profile" },
* ]}
* />
*
* // With per-element color overrides
* <AppBreadcrumb
* color={{ link: { color: "text-muted-foreground" }, page: { color: "text-[#000000]" } }}
* items={items}
* />
*
* ─────────────────────────────────────────────────────────────────────────────
*/
const AppBreadcrumb = ({ items = [], color = {} }) => {
const navigate = useNavigate();
const linkColor = color.link?.color ?? "";
const pageColor = color.page?.color ?? "";
if (!items.length) return null;
return (
<Breadcrumb>
<BreadcrumbList>
{items.map((item, index) => {
const isLast = index === items.length - 1;
return (
<span key={index} className="flex items-center gap-1.5">
<BreadcrumbItem>
{isLast ? (
<BreadcrumbPage className={cn("flex items-center gap-2 max-w-[300px] truncate", pageColor)}>
{item.icon}
<span className="hidden lg:inline truncate">{item.label}</span>
<span className="lg:hidden">...</span>
</BreadcrumbPage>
) : (
<BreadcrumbLink asChild>
<div
className={cn("flex items-center gap-2 select-none cursor-pointer max-w-[300px]", linkColor)}
onClick={(e) => {
if (item.onClick) {
item.onClick(e, navigate);
} else if (item.to) {
e.preventDefault();
navigate(item.to);
}
}}
>
{item.icon}
<span className="truncate">{item.label}</span>
</div>
</BreadcrumbLink>
)}
</BreadcrumbItem>
{!isLast && <BreadcrumbSeparator />}
</span>
);
})}
</BreadcrumbList>
</Breadcrumb>
);
};
export default AppBreadcrumb;