feat(dashboard): refactor navigation items to use NavItem component and enhance styling

This commit is contained in:
null 2026-05-22 16:45:09 -05:00
parent 2d91325937
commit f0f53bcc73
1 changed files with 192 additions and 136 deletions

View File

@ -2,16 +2,17 @@
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import type { ReactNode } from "react";
import { import {
Activity, Activity,
BarChart3, BarChart3,
Bot, Bot,
Boxes, Boxes,
Building2,
CheckCircle2, CheckCircle2,
CircleDot,
Folder, Folder,
FolderGit, FolderGit,
CircleDot,
Building2,
LayoutGrid, LayoutGrid,
Network, Network,
Settings, Settings,
@ -19,15 +20,85 @@ import {
Tags, Tags,
} from "lucide-react"; } from "lucide-react";
import { useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator"; import { ApiError } from "@/api/mutator";
import { useOrganizationMembership } from "@/lib/use-organization-membership";
import { import {
type healthzHealthzGetResponse, type healthzHealthzGetResponse,
useHealthzHealthzGet, useHealthzHealthzGet,
} from "@/api/generated/default/default"; } from "@/api/generated/default/default";
import { useAuth } from "@/auth/clerk";
import { useOrganizationMembership } from "@/lib/use-organization-membership";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type NavTone = "blue" | "cyan" | "emerald" | "violet" | "amber" | "rose";
const iconToneClass: Record<NavTone, string> = {
blue: "bg-blue-500/15 text-blue-300 ring-blue-400/20",
cyan: "bg-cyan-500/15 text-cyan-300 ring-cyan-400/20",
emerald: "bg-emerald-500/15 text-emerald-300 ring-emerald-400/20",
violet: "bg-violet-500/15 text-violet-300 ring-violet-400/20",
amber: "bg-amber-500/15 text-amber-300 ring-amber-400/20",
rose: "bg-rose-500/15 text-rose-300 ring-rose-400/20",
};
const sectionHeaderClass =
"px-3 text-[13px] font-bold uppercase tracking-[0.18em] text-[color:var(--text)]";
const navItemClass = (active: boolean) =>
cn(
"group relative flex items-center gap-3 rounded-lg px-3 py-2.5 text-[15px] font-semibold text-[color:var(--text-muted)] transition duration-200",
"before:absolute before:inset-y-2 before:left-0 before:w-1 before:rounded-full before:bg-transparent before:transition",
active
? "bg-[color:var(--accent-soft)] text-[color:var(--text)] shadow-sm before:bg-[color:var(--accent)]"
: "hover:bg-[color:var(--surface-muted)] hover:text-[color:var(--text)] hover:before:bg-[color:var(--border-strong)]",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--surface)]",
);
const iconClass = (active: boolean, tone: NavTone) =>
cn(
"flex h-8 w-8 shrink-0 items-center justify-center rounded-md ring-1 transition duration-200",
iconToneClass[tone],
active &&
"bg-[color:var(--accent)] text-[color:var(--primary-foreground)] ring-[color:var(--accent)]",
!active && "group-hover:scale-105 group-hover:ring-[color:var(--accent)]",
);
function isNavActive(pathname: string, href: string) {
if (href === "/git-projects") {
return (
pathname === href ||
pathname.startsWith("/git-projects/connections") ||
pathname.startsWith("/git-projects/repositories")
);
}
return pathname === href || pathname.startsWith(`${href}/`);
}
function NavItem({
href,
label,
icon,
tone,
active,
}: {
href: string;
label: string;
icon: ReactNode;
tone: NavTone;
active: boolean;
}) {
return (
<Link
href={href}
className={navItemClass(active)}
aria-current={active ? "page" : undefined}
>
<span className={iconClass(active, tone)}>{icon}</span>
<span className="truncate">{label}</span>
</Link>
);
}
export function DashboardSidebar() { export function DashboardSidebar() {
const pathname = usePathname(); const pathname = usePathname();
const { isSignedIn } = useAuth(); const { isSignedIn } = useAuth();
@ -58,177 +129,162 @@ export function DashboardSidebar() {
: systemStatus === "unknown" : systemStatus === "unknown"
? "System status unavailable" ? "System status unavailable"
: "System degraded"; : "System degraded";
const navItemClass = (active: boolean) => const isActive = (href: string) => isNavActive(pathname, href);
cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-[color:var(--text-muted)] transition",
active
? "bg-[color:var(--accent-soft)] font-medium text-[color:var(--accent-strong)]"
: "hover:bg-[color:var(--surface-muted)] hover:text-[color:var(--text)]",
);
return ( return (
<aside className="fixed inset-y-0 left-0 z-40 flex w-[280px] -translate-x-full flex-col border-r border-[color:var(--border)] bg-[color:var(--surface)] pt-16 shadow-lg transition-transform duration-200 ease-in-out [[data-sidebar=open]_&]:translate-x-0 md:relative md:inset-auto md:z-auto md:w-[260px] md:translate-x-0 md:pt-0 md:shadow-none md:transition-none"> <aside className="fixed inset-y-0 left-0 z-40 flex w-[280px] -translate-x-full flex-col border-r border-[color:var(--border)] bg-[linear-gradient(180deg,var(--surface)_0%,var(--surface-muted)_100%)] pt-16 shadow-lg transition-transform duration-200 ease-in-out [[data-sidebar=open]_&]:translate-x-0 md:relative md:inset-auto md:z-auto md:w-[260px] md:translate-x-0 md:pt-0 md:shadow-none md:transition-none">
<div className="flex-1 px-3 py-4"> <div className="flex-1 overflow-y-auto px-3 py-5">
<p className="px-3 text-xs font-semibold uppercase tracking-wider text-[color:var(--text-muted)]"> <p className="px-3 font-heading text-lg font-bold text-[color:var(--text)]">
Navigation Navigation
</p> </p>
<nav className="mt-3 space-y-4 text-sm"> <nav className="mt-5 space-y-5">
<div> <div>
<p className="px-3 text-[11px] font-semibold uppercase tracking-wider text-[color:var(--text-quiet)]"> <p className={sectionHeaderClass}>Overview</p>
Overview <div className="mt-2 space-y-1.5">
</p> <NavItem
<div className="mt-1 space-y-1">
<Link
href="/dashboard" href="/dashboard"
className={navItemClass(pathname === "/dashboard")} label="Dashboard"
> icon={<BarChart3 className="h-4 w-4" />}
<BarChart3 className="h-4 w-4" /> tone="blue"
Dashboard active={isActive("/dashboard")}
</Link> />
<Link <NavItem
href="/activity" href="/activity"
className={navItemClass(pathname === "/activity")} label="Live feed"
> icon={<Activity className="h-4 w-4" />}
<Activity className="h-4 w-4" /> tone="cyan"
Live feed active={isActive("/activity")}
</Link> />
</div> </div>
</div> </div>
<div> <div>
<p className="px-3 text-[11px] font-semibold uppercase tracking-wider text-[color:var(--text-quiet)]"> <p className={sectionHeaderClass}>Boards</p>
Boards <div className="mt-2 space-y-1.5">
</p> <NavItem
<div className="mt-1 space-y-1">
<Link
href="/board-groups" href="/board-groups"
className={navItemClass(pathname === "/board-groups")} label="Board groups"
> icon={<Folder className="h-4 w-4" />}
<Folder className="h-4 w-4" /> tone="emerald"
Board groups active={isActive("/board-groups")}
</Link> />
<Link <NavItem
href="/boards" href="/boards"
className={navItemClass(pathname === "/boards")} label="Boards"
> icon={<LayoutGrid className="h-4 w-4" />}
<LayoutGrid className="h-4 w-4" /> tone="blue"
Boards active={isActive("/boards")}
</Link> />
<Link <NavItem
href="/git-projects" href="/git-projects"
className={navItemClass(pathname === "/git-projects")} label="Git projects"
> icon={<FolderGit className="h-4 w-4" />}
<FolderGit className="h-4 w-4" /> tone="violet"
Git Projects active={isActive("/git-projects")}
</Link> />
<Link <NavItem
href="/git-projects/issues" href="/git-projects/issues"
className={navItemClass(pathname === "/git-projects/issues")} label="Issues"
> icon={<CircleDot className="h-4 w-4" />}
<CircleDot className="h-4 w-4" /> tone="amber"
Issues active={isActive("/git-projects/issues")}
</Link> />
<Link <NavItem
href="/tags" href="/tags"
className={navItemClass(pathname === "/tags")} label="Tags"
> icon={<Tags className="h-4 w-4" />}
<Tags className="h-4 w-4" /> tone="cyan"
Tags active={isActive("/tags")}
</Link> />
<Link <NavItem
href="/approvals" href="/approvals"
className={navItemClass(pathname === "/approvals")} label="Approvals"
> icon={<CheckCircle2 className="h-4 w-4" />}
<CheckCircle2 className="h-4 w-4" /> tone="emerald"
Approvals active={isActive("/approvals")}
</Link> />
{isAdmin ? ( {isAdmin ? (
<Link <NavItem
href="/custom-fields" href="/custom-fields"
className={navItemClass(pathname === "/custom-fields")} label="Custom fields"
> icon={<Settings className="h-4 w-4" />}
<Settings className="h-4 w-4" /> tone="rose"
Custom fields active={isActive("/custom-fields")}
</Link> />
) : null} ) : null}
</div> </div>
</div> </div>
<div> {isAdmin ? (
{isAdmin ? ( <div>
<> <p className={sectionHeaderClass}>Skills</p>
<p className="px-3 text-[11px] font-semibold uppercase tracking-wider text-[color:var(--text-quiet)]"> <div className="mt-2 space-y-1.5">
Skills <NavItem
</p> href="/skills/marketplace"
<div className="mt-1 space-y-1"> label="Marketplace"
<Link icon={<Store className="h-4 w-4" />}
href="/skills/marketplace" tone="violet"
className={navItemClass(pathname === "/skills/marketplace")} active={isActive("/skills/marketplace")}
> />
<Store className="h-4 w-4" /> <NavItem
Marketplace href="/skills/packs"
</Link> label="Packs"
<Link icon={<Boxes className="h-4 w-4" />}
href="/skills/packs" tone="cyan"
className={navItemClass(pathname === "/skills/packs")} active={isActive("/skills/packs")}
> />
<Boxes className="h-4 w-4" /> </div>
Packs </div>
</Link> ) : null}
</div>
</>
) : null}
</div>
<div> <div>
<p className="px-3 text-[11px] font-semibold uppercase tracking-wider text-[color:var(--text-quiet)]"> <p className={sectionHeaderClass}>Administration</p>
Administration <div className="mt-2 space-y-1.5">
</p> <NavItem
<div className="mt-1 space-y-1">
<Link
href="/organization" href="/organization"
className={navItemClass(pathname === "/organization")} label="Organization"
> icon={<Building2 className="h-4 w-4" />}
<Building2 className="h-4 w-4" /> tone="blue"
Organization active={isActive("/organization")}
</Link> />
<Link <NavItem
href="/settings" href="/settings"
className={navItemClass(pathname === "/settings")} label="Settings"
> icon={<Settings className="h-4 w-4" />}
<Settings className="h-4 w-4" /> tone="amber"
Settings active={isActive("/settings")}
</Link> />
{isAdmin ? ( {isAdmin ? (
<Link <NavItem
href="/gateways" href="/gateways"
className={navItemClass(pathname === "/gateways")} label="Gateways"
> icon={<Network className="h-4 w-4" />}
<Network className="h-4 w-4" /> tone="emerald"
Gateways active={isActive("/gateways")}
</Link> />
) : null} ) : null}
{isAdmin ? ( {isAdmin ? (
<Link <NavItem
href="/agents" href="/agents"
className={navItemClass(pathname === "/agents")} label="Agents"
> icon={<Bot className="h-4 w-4" />}
<Bot className="h-4 w-4" /> tone="violet"
Agents active={isActive("/agents")}
</Link> />
) : null} ) : null}
</div> </div>
</div> </div>
</nav> </nav>
</div> </div>
<div className="border-t border-[color:var(--border)] p-4"> <div className="border-t border-[color:var(--border)] bg-[color:var(--surface)]/70 p-4">
<div className="flex items-center gap-2 text-xs text-[color:var(--text-muted)]"> <div className="flex items-center gap-2 text-xs font-medium text-[color:var(--text-muted)]">
<span <span
className={cn( className={cn(
"h-2 w-2 rounded-full", "h-2.5 w-2.5 rounded-full shadow-[0_0_18px_currentColor]",
systemStatus === "operational" && "bg-emerald-500", systemStatus === "operational" && "bg-emerald-500 text-emerald-500",
systemStatus === "degraded" && "bg-rose-500", systemStatus === "degraded" && "bg-rose-500 text-rose-500",
systemStatus === "unknown" && "bg-[color:var(--text-quiet)]", systemStatus === "unknown" &&
"bg-[color:var(--text-quiet)] text-[color:var(--text-quiet)]",
)} )}
/> />
{statusLabel} {statusLabel}