103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import Link from "next/link";
|
|
import type { LucideIcon } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
|
|
import { AppCard } from "@/components/ui/app-card";
|
|
import { CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type SidebarItem = {
|
|
label: string;
|
|
href: string;
|
|
icon: LucideIcon;
|
|
active?: boolean;
|
|
children?: SidebarItem[];
|
|
};
|
|
|
|
type AppSidebarProps = {
|
|
title: string;
|
|
description: string;
|
|
items: SidebarItem[];
|
|
footer?: ReactNode;
|
|
};
|
|
|
|
export function AppSidebar({
|
|
title,
|
|
description,
|
|
items,
|
|
footer,
|
|
}: AppSidebarProps) {
|
|
const portfolioItem = items.find((item) => item.href === "/root/portfolio");
|
|
const systemOrder = ["/root", "/root/media", "/root/maintenance", "/root/ui-kit"];
|
|
const systemItems = items
|
|
.filter((item) => item.href !== "/root/portfolio")
|
|
.sort((left, right) => systemOrder.indexOf(left.href) - systemOrder.indexOf(right.href));
|
|
|
|
function renderItem(item: SidebarItem, nested = false) {
|
|
const Icon = item.icon;
|
|
|
|
return (
|
|
<div key={item.label} className="space-y-1">
|
|
<Link
|
|
href={item.href}
|
|
className={cn(
|
|
"flex items-center gap-2 rounded-nested px-3 py-2 text-sm transition-colors",
|
|
item.active
|
|
? "bg-sidebar-primary text-sidebar-primary-foreground"
|
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
nested ? "text-sidebar-foreground/72" : undefined,
|
|
)}
|
|
>
|
|
<Icon className="h-4 w-4" />
|
|
<span>{item.label}</span>
|
|
</Link>
|
|
|
|
{item.children?.length ? (
|
|
<div className="ml-3 space-y-1 border-l border-sidebar-border pl-3">
|
|
{item.children.map((child) => renderItem(child, true))}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="sticky top-6 space-y-4">
|
|
<AppCard className="overflow-hidden bg-sidebar text-sidebar-foreground shadow-sidebar">
|
|
<CardHeader className="pb-4">
|
|
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-sidebar-foreground/56">
|
|
Root Access
|
|
</p>
|
|
<CardTitle className="text-base font-semibold">{title}</CardTitle>
|
|
<p className="text-sm leading-6 text-sidebar-foreground/72">{description}</p>
|
|
</CardHeader>
|
|
{footer ? <div className="border-t border-sidebar-border px-4 py-3">{footer}</div> : null}
|
|
</AppCard>
|
|
|
|
<AppCard className="overflow-hidden bg-sidebar text-sidebar-foreground shadow-sidebar">
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-semibold uppercase tracking-[0.2em] text-sidebar-foreground/72">
|
|
Overview
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-1 px-3 pb-3 pt-0">
|
|
{systemItems.map((item) => renderItem(item))}
|
|
</CardContent>
|
|
</AppCard>
|
|
|
|
{portfolioItem ? (
|
|
<AppCard className="overflow-hidden bg-sidebar text-sidebar-foreground shadow-sidebar">
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-semibold uppercase tracking-[0.2em] text-sidebar-foreground/72">
|
|
Portfolio
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-1 px-3 pb-3 pt-0">
|
|
{renderItem(portfolioItem)}
|
|
</CardContent>
|
|
</AppCard>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|