Redesigned nav bar #136

Merged
khwezi merged 1 commits from finishline into main 2026-10-04 18:27:35 +02:00
@@ -8,8 +8,6 @@ import {
ShieldAlert,
Settings,
LogOut,
Menu,
X,
ChevronRight,
ChevronDown,
ShieldCheck,
@@ -19,6 +17,10 @@ import {
Users,
GitPullRequest,
MapPin,
PanelLeftClose,
PanelLeftOpen,
ChevronsLeft,
ChevronsRight,
} from 'lucide-react';
import { useAuth } from '../../auth/AuthProvider';
import { BrandLogo } from '../ui/BrandLogo';
@@ -181,42 +183,115 @@ export const AppLayout: React.FC = () => {
{/* Sidebar */}
<aside
className={`${
sidebarOpen ? 'w-64' : 'w-20'
} shrink-0 bg-[#0b101b]/95 text-slate-200 border-r border-slate-800/80 transition-all duration-300 flex flex-col z-30 shadow-2xl backdrop-blur-md`}
sidebarOpen ? 'w-64' : 'w-[72px]'
} shrink-0 bg-gradient-to-b from-[#0b101b] via-[#090d16] to-[#0b101b] text-slate-200 border-r border-slate-800/80 transition-all duration-300 ease-in-out flex flex-col z-30 shadow-2xl backdrop-blur-md relative`}
>
{/* Brand Header */}
<div className="h-16 px-4 flex items-center justify-between border-b border-slate-800/80 bg-[#0d121f]/90">
<Link
to="/"
id="nav-brand-home-link"
title="Go to Home page"
className="flex items-center gap-3 overflow-hidden group cursor-pointer"
>
<BrandLogo size={36} showGlow animateOnHover />
{sidebarOpen && (
<div className="flex flex-col min-w-0">
<span className="font-bold text-sm tracking-tight text-white truncate group-hover:text-blue-400 transition">
PFM Console
</span>
<span className="text-[11px] text-blue-400 font-medium tracking-tight">Enterprise Management</span>
</div>
)}
</Link>
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="rounded-lg p-1.5 text-slate-400 hover:text-white hover:bg-slate-800/60 transition cursor-pointer"
title={sidebarOpen ? 'Collapse sidebar' : 'Expand sidebar'}
>
{sidebarOpen ? <X className="w-4 h-4" /> : <Menu className="w-4 h-4" />}
</button>
<div className={`h-16 border-b border-slate-800/80 bg-[#0d121f]/90 flex items-center transition-all ${
sidebarOpen ? 'px-4 justify-between' : 'px-2 justify-center'
}`}>
{sidebarOpen ? (
<>
<Link
to="/"
id="nav-brand-home-link"
title="Go to Home page"
className="flex items-center gap-3 overflow-hidden group cursor-pointer"
>
<BrandLogo size={32} showGlow animateOnHover />
<div className="flex flex-col min-w-0">
<span className="font-bold text-sm tracking-tight text-white truncate group-hover:text-blue-400 transition">
PFM Console
</span>
<span className="text-[11px] text-blue-400 font-medium tracking-tight">Enterprise Management</span>
</div>
</Link>
<button
onClick={() => setSidebarOpen(false)}
className="p-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800/80 border border-transparent hover:border-slate-700/60 transition cursor-pointer shadow-xs"
title="Collapse sidebar shelf"
aria-label="Collapse sidebar shelf"
>
<PanelLeftClose className="w-4 h-4" />
</button>
</>
) : (
<div className="flex items-center justify-center">
<Link
to="/"
id="nav-brand-home-link-collapsed"
title="PFM Console • Go to Home page"
className="cursor-pointer transition transform hover:scale-105"
>
<BrandLogo size={32} showGlow animateOnHover />
</Link>
</div>
)}
</div>
{/* Collapsed Shelf Expand Button */}
{!sidebarOpen && (
<div className="pt-2 pb-1 px-2 flex justify-center border-b border-slate-800/40">
<button
onClick={() => setSidebarOpen(true)}
className="w-10 h-8 flex items-center justify-center rounded-lg text-slate-400 hover:text-blue-400 hover:bg-blue-600/10 border border-slate-800 hover:border-blue-500/30 transition cursor-pointer shadow-xs group relative"
title="Expand sidebar shelf"
aria-label="Expand sidebar shelf"
>
<PanelLeftOpen className="w-4 h-4 transition-transform group-hover:scale-110" />
<div className="absolute left-full ml-3 px-2.5 py-1 bg-slate-900 text-white text-xs font-semibold rounded-lg shadow-2xl border border-slate-700 whitespace-nowrap z-50 pointer-events-none opacity-0 group-hover:opacity-100 transition-all duration-150">
Expand Navigation Shelf
</div>
</button>
</div>
)}
{/* Navigation Items */}
<nav className="flex-1 overflow-y-auto px-3 py-4 space-y-1.5">
<nav className={`flex-1 overflow-y-auto space-y-1.5 ${sidebarOpen ? 'px-3 py-4' : 'px-2 py-3'}`}>
{visibleNavItems.map((item) => {
const Icon = item.icon;
const hasSub = item.subItems && item.subItems.length > 0;
if (!sidebarOpen) {
return (
<div key={item.path} className="flex justify-center">
<NavLink
to={item.path}
end={hasSub}
className={({ isActive }) =>
`group relative flex items-center justify-center w-11 h-11 rounded-xl transition-all ${
isActive
? 'bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-lg shadow-blue-600/30 font-semibold ring-1 ring-blue-400/50'
: 'text-slate-400 hover:text-white hover:bg-slate-800/70 border border-transparent hover:border-slate-700/60'
}`
}
title={item.name}
>
<Icon className="w-5 h-5 shrink-0" />
{/* Hover Floating Tooltip Shelf */}
<div className="absolute left-full ml-3 px-3 py-2 bg-[#0d1322] text-slate-100 rounded-xl shadow-2xl border border-slate-700/90 whitespace-nowrap z-50 pointer-events-none opacity-0 group-hover:opacity-100 transition-all duration-150 translate-x-1 group-hover:translate-x-0 min-w-[160px]">
<div className="text-xs font-bold text-white flex items-center gap-1.5">
<Icon className="w-3.5 h-3.5 text-blue-400" />
<span>{item.name}</span>
</div>
<div className="text-[10px] text-slate-400 font-normal mt-0.5 max-w-[200px] line-clamp-2">
{item.help}
</div>
{hasSub && (
<div className="mt-1.5 pt-1.5 border-t border-slate-800 flex flex-col gap-1 text-[10px] text-blue-300">
{item.subItems?.map((s) => (
<span key={s.path} className="flex items-center gap-1 text-slate-300">
• {s.name}
</span>
))}
</div>
)}
</div>
</NavLink>
</div>
);
}
return (
<div key={item.path} className="space-y-1">
<NavLink
@@ -231,12 +306,12 @@ export const AppLayout: React.FC = () => {
}
title={item.name}
>
<Icon className="w-4 h-4 shrink-0" />
{sidebarOpen && <span className="truncate flex-1">{item.name}</span>}
<Icon className="w-4.5 h-4.5 shrink-0" />
<span className="truncate flex-1">{item.name}</span>
</NavLink>
{/* Sub-items under parent item */}
{hasSub && sidebarOpen && (
{hasSub && (
<div className="pl-6 pr-1 py-1 space-y-1 border-l border-slate-800/80 ml-5">
{item.subItems?.map((subItem) => {
const SubIcon = subItem.icon;
@@ -266,12 +341,13 @@ export const AppLayout: React.FC = () => {
</nav>
{/* User Card */}
<div className="p-3 border-t border-slate-800/80 bg-[#0d121f]/90">
<div className="flex items-center gap-3">
<div className="h-9 w-9 shrink-0 rounded-full bg-blue-600/20 border border-blue-500/40 flex items-center justify-center font-bold text-xs text-blue-400">
{tokenUserName ? tokenUserName.slice(0, 2).toUpperCase() : 'AU'}
</div>
{sidebarOpen && (
<div className={`border-t border-slate-800/80 bg-[#0d121f]/90 ${sidebarOpen ? 'p-3' : 'p-2'}`}>
{sidebarOpen ? (
<div className="flex items-center gap-3">
<div className="h-9 w-9 shrink-0 rounded-full bg-blue-600/20 border border-blue-500/40 flex items-center justify-center font-bold text-xs text-blue-400 relative shadow-xs">
{tokenUserName ? tokenUserName.slice(0, 2).toUpperCase() : 'AU'}
<span className="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-emerald-500 border-2 border-[#0d121f]" title="Active Online Session" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-semibold text-white truncate" title={tokenUserName}>
{tokenUserName}
@@ -280,16 +356,38 @@ export const AppLayout: React.FC = () => {
{tokenUserEmail || 'Authenticated User'}
</p>
</div>
)}
<button
onClick={handleLogout}
id="sidebar-logout-btn"
className="rounded-lg p-1.5 text-slate-400 hover:text-rose-400 hover:bg-rose-950/30 transition cursor-pointer"
title="Sign Out of Session"
>
<LogOut className="w-4 h-4" />
</button>
</div>
<button
onClick={handleLogout}
id="sidebar-logout-btn"
className="rounded-lg p-1.5 text-slate-400 hover:text-rose-400 hover:bg-rose-950/30 transition cursor-pointer"
title="Sign Out of Session"
>
<LogOut className="w-4 h-4" />
</button>
</div>
) : (
<div className="flex flex-col items-center gap-2">
<div
className="h-10 w-10 shrink-0 rounded-xl bg-blue-600/20 border border-blue-500/40 flex items-center justify-center font-bold text-xs text-blue-400 relative group cursor-pointer shadow-xs"
title={`${tokenUserName} (${tokenUserEmail})`}
>
{tokenUserName ? tokenUserName.slice(0, 2).toUpperCase() : 'AU'}
<span className="absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full bg-emerald-500 border-2 border-[#0d121f]" />
<div className="absolute left-full ml-3 px-3 py-2 bg-[#0d1322] text-white text-xs font-medium rounded-xl shadow-2xl border border-slate-700 whitespace-nowrap z-50 pointer-events-none opacity-0 group-hover:opacity-100 transition-all duration-150">
<div className="font-semibold">{tokenUserName}</div>
<div className="text-[10px] text-slate-400 font-mono mt-0.5">{tokenUserEmail}</div>
</div>
</div>
<button
onClick={handleLogout}
id="sidebar-logout-btn-collapsed"
className="w-10 h-9 rounded-xl flex items-center justify-center text-slate-400 hover:text-rose-400 hover:bg-rose-950/30 border border-transparent hover:border-rose-900/40 transition cursor-pointer"
title="Sign Out of Session"
>
<LogOut className="w-4 h-4" />
</button>
</div>
)}
</div>
</aside>