refactor: implement icon rail sidebar for Design System v4
72px icon rail with hover flyouts, pin-to-expand toggle (260px), keyboard accessible, mobile hamburger overlay. Flat TopBar styling (no blur/glass). New BrandLogo mark (gradient square + lightning bolt). BrandWordmark uses solid text color instead of gradient. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useLocation, useNavigate, Link } from 'react-router-dom'
|
||||
import { Menu, X, LayoutGrid, Clock, Network, AlertTriangle, Code2, Wand2, BarChart3, Settings, LogOut, Shield, Library } from 'lucide-react'
|
||||
import { Menu, X, LayoutGrid, Clock, AlertTriangle, GitBranch, Code2, Wand2, BarChart3, Settings, LogOut, Shield, Layers } from 'lucide-react'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { usePermissions } from '@/hooks/usePermissions'
|
||||
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
||||
@@ -16,7 +16,7 @@ export function AppLayout() {
|
||||
const navigate = useNavigate()
|
||||
const { user, logout } = useAuthStore()
|
||||
const { effectiveRole } = usePermissions()
|
||||
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
|
||||
const sidebarPinned = useUserPreferencesStore(s => s.sidebarPinned)
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
|
||||
// Close mobile menu on route change
|
||||
@@ -54,8 +54,8 @@ export function AppLayout() {
|
||||
{ path: '/', label: 'Dashboard', icon: LayoutGrid },
|
||||
{ path: '/sessions', label: 'Active Sessions', icon: Clock },
|
||||
{ path: '/escalations', label: 'Escalations', icon: AlertTriangle },
|
||||
{ path: '/trees', label: 'Flows', icon: Network },
|
||||
{ path: '/step-library', label: 'Step Library', icon: Library },
|
||||
{ path: '/trees', label: 'Flows', icon: GitBranch },
|
||||
{ path: '/step-library', label: 'Step Library', icon: Layers },
|
||||
{ path: '/scripts', label: 'Scripts', icon: Code2 },
|
||||
{ path: '/script-builder', label: 'Script Builder', icon: Wand2 },
|
||||
{ path: '/analytics', label: 'Analytics', icon: BarChart3 },
|
||||
@@ -64,34 +64,8 @@ export function AppLayout() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Atmosphere orbs — ambient light behind glass */}
|
||||
<div
|
||||
className="pointer-events-none fixed z-0"
|
||||
style={{
|
||||
top: '-120px',
|
||||
right: '-80px',
|
||||
width: '600px',
|
||||
height: '600px',
|
||||
borderRadius: '50%',
|
||||
background: 'radial-gradient(circle, rgba(6, 182, 212, 0.15) 0%, rgba(6, 182, 212, 0.04) 40%, transparent 70%)',
|
||||
filter: 'blur(60px)',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none fixed z-0"
|
||||
style={{
|
||||
bottom: '-100px',
|
||||
left: '-60px',
|
||||
width: '500px',
|
||||
height: '500px',
|
||||
borderRadius: '50%',
|
||||
background: 'radial-gradient(circle, rgba(99, 102, 241, 0.08) 0%, rgba(99, 102, 241, 0.02) 40%, transparent 70%)',
|
||||
filter: 'blur(50px)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={cn('app-shell relative z-1', sidebarCollapsed && 'app-shell--collapsed')}
|
||||
className={cn('app-shell relative z-1', sidebarPinned && 'app-shell--pinned')}
|
||||
data-testid="app-shell"
|
||||
>
|
||||
{/* Top Bar - spans full width */}
|
||||
@@ -104,8 +78,9 @@ export function AppLayout() {
|
||||
|
||||
{/* Mobile hamburger - overlaid on topbar */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
className="fixed left-4 top-3.5 z-50 rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors md:hidden"
|
||||
className="fixed left-4 top-3.5 z-50 rounded-lg p-2 text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb] transition-colors md:hidden"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<Menu size={20} />
|
||||
@@ -119,17 +94,19 @@ export function AppLayout() {
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-[var(--sidebar-bg)] shadow-2xl animate-slide-in-left">
|
||||
<div className="flex h-14 items-center justify-between border-b border-border px-4">
|
||||
<nav
|
||||
className="absolute inset-y-0 left-0 w-72 shadow-2xl animate-slide-in-left"
|
||||
style={{ background: '#0f1118', borderRight: '1px solid #1e2130' }}
|
||||
>
|
||||
<div className="flex h-14 items-center justify-between px-4" style={{ borderBottom: '1px solid #1e2130' }}>
|
||||
<Link to="/" className="flex items-center gap-2.5">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-brand">
|
||||
<BrandLogo size="sm" className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="text-sm font-heading font-bold">ResolutionFlow</span>
|
||||
<BrandLogo size="sm" />
|
||||
<span className="text-sm font-heading font-bold text-[#f0f2f5]">ResolutionFlow</span>
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground"
|
||||
className="rounded-lg p-2 text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb]"
|
||||
aria-label="Close menu"
|
||||
>
|
||||
<X size={18} />
|
||||
@@ -138,10 +115,10 @@ export function AppLayout() {
|
||||
|
||||
<div className="flex flex-col p-3">
|
||||
{/* User info */}
|
||||
<div className="mb-3 border-b border-border pb-3 px-3">
|
||||
<p className="text-sm font-medium text-foreground">{user?.name || user?.email}</p>
|
||||
<div className="mb-3 pb-3 px-3" style={{ borderBottom: '1px solid #1e2130' }}>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">{user?.name || user?.email}</p>
|
||||
{effectiveRole && effectiveRole !== 'engineer' && (
|
||||
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<span className="mt-1 inline-flex items-center gap-1 text-xs text-[#848b9b]">
|
||||
<Shield size={10} />
|
||||
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
|
||||
</span>
|
||||
@@ -162,8 +139,8 @@ export function AppLayout() {
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-[var(--sidebar-active)] text-foreground'
|
||||
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Icon size={18} />
|
||||
@@ -174,10 +151,11 @@ export function AppLayout() {
|
||||
</div>
|
||||
|
||||
{/* Logout */}
|
||||
<div className="mt-3 border-t border-border pt-3">
|
||||
<div className="mt-3 pt-3" style={{ borderTop: '1px solid #1e2130' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors"
|
||||
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
<LogOut size={18} />
|
||||
Logout
|
||||
|
||||
Reference in New Issue
Block a user