/**
* SidebarNav — WealthX Design System
*
* Presentational sidebar navigation organism.
* Used by both Backoffice (Wealth Pro) and Frontend (WealthX app).
*
* Background: bg-brand-secondary (tenant dark navy — set via ThemeProvider).
* Text: text-brand-secondary-foreground (white on dark navy).
*
* - All icons must be Lucide icons (LucideIcon type).
* - Supports collapsible sub-items (accordion).
* - Hover mode (default): sidebar is icon-only; hovering expands it temporarily.
* - Lock mode: clicking the Pin button keeps the sidebar expanded after mouse leaves.
* - metricsGroups: optional financial summary rows (Frontend sidebar only).
* - No internal navigation — consumers wire onNavigate / onLogout.
*/
import * as React from "react";
import {
ChevronDown,
ChevronRight,
Info,
LogOut,
Pin,
PinOff,
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
import { cn, getInitials } from "@/lib/utils";
import { formatCurrency } from "@/lib/format-currency";
import { Accordion, AccordionContent, AccordionItem } from "./accordion";
import { Button } from "./button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
// ─── Types ─────────────────────────────────────────────────────────────────────
export interface SidebarNavSubItem {
title: string;
href: string;
isActive?: boolean;
}
export interface SidebarNavItem {
/** Lucide icon component */
icon: LucideIcon;
title: string;
href: string;
isActive?: boolean;
/** When true, renders an expandable group with subItems */
isCollapsible?: boolean;
subItems?: SidebarNavSubItem[];
}
export interface SidebarNavMetricItem {
name: string;
/** Numeric value in dollars — formatted as currency automatically */
value: number;
/** Net items show +/- prefix and a primary-color underline */
isNetItem?: boolean;
/** Optional tooltip text shown via Info icon */
info?: string;
}
export interface SidebarNavMetricsGroup {
items: SidebarNavMetricItem[];
}
export interface SidebarNavProps {
items: SidebarNavItem[];
/** Display name for the current user */
userName?: string;
/**
* Optional logo URL rendered at the top of the sidebar.
* Hidden when collapsed (unless logoCollapsed is provided).
* Use a horizontal/landscape logo (max-height 32px).
* The image is rendered white via CSS filter — use a logo with non-transparent paths.
*/
logo?: string;
/**
* Optional icon-only logo URL shown when the sidebar is collapsed.
* Use a square icon variant of the logo (rendered at 32×32px, white filter applied).
*/
logoCollapsed?: string;
/**
* Optional financial metric groups rendered between the user section and
* nav items. Hidden when sidebar is collapsed. Used by the Frontend (WealthX app) sidebar.
*/
metricsGroups?: SidebarNavMetricsGroup[];
onNavigate?: (href: string) => void;
onLogout?: () => void;
/**
* Initial locked (pinned) state. When true, sidebar starts expanded and stays
* open even without hover. Defaults to false (hover-to-expand mode).
*/
defaultLocked?: boolean;
/**
* Called when the user toggles the Pin/Unpin button.
* Use to persist the preference (e.g. localStorage).
*/
onLockedChange?: (locked: boolean) => void;
className?: string;
}
// ─── Helpers ───────────────────────────────────────────────────────────────────
function navIconCn(isActive: boolean): string {
return cn(
"shrink-0 transition-colors",
isActive
? "text-primary"
: "text-brand-secondary-foreground/50 group-hover:text-brand-secondary-foreground",
);
}
// ─── NavTooltip ────────────────────────────────────────────────────────────────
// Wraps content in a right-side tooltip only when the sidebar is collapsed.
interface NavTooltipProps {
label: string;
collapsed: boolean;
children: React.ReactElement;
}
function NavTooltip({ label, collapsed, children }: NavTooltipProps) {
if (!collapsed) return children;
return (
{label}
);
}
// ─── MetricsGroup ──────────────────────────────────────────────────────────────
interface MetricsGroupProps {
group: SidebarNavMetricsGroup;
}
function MetricsGroup({ group }: MetricsGroupProps) {
return (