1+ import { BarChart3 , ChevronLeft , Download , Home , Menu , Moon , Package , Sun , X , Zap } from 'lucide-react' ;
2+ import { Link , useLocation } from 'react-router-dom' ;
3+ import { useState , useEffect } from 'react' ;
4+ import { Button } from '../ui/Button' ;
5+
6+ interface NavItem {
7+ path : string ;
8+ label : string ;
9+ icon : React . ReactNode ;
10+ badge ?: string ;
11+ }
12+
13+ const navItems : NavItem [ ] = [
14+ {
15+ path : '/' ,
16+ label : 'Dashboard' ,
17+ icon : < Home size = { 20 } />
18+ } ,
19+ {
20+ path : '/rolldown-stats' ,
21+ label : 'Rolldown Stats' ,
22+ icon : < Package size = { 20 } />
23+ } ,
24+ {
25+ path : '/minification' ,
26+ label : 'Minification' ,
27+ icon : < Zap size = { 20 } /> ,
28+ badge : 'Benchmarks'
29+ } ,
30+ {
31+ path : '/npm-downloads' ,
32+ label : 'NPM Downloads' ,
33+ icon : < Download size = { 20 } />
34+ }
35+ ] ;
36+
37+ export function Sidebar ( ) {
38+ const location = useLocation ( ) ;
39+ const [ collapsed , setCollapsed ] = useState ( false ) ;
40+ const [ mobileOpen , setMobileOpen ] = useState ( false ) ;
41+ const [ darkMode , setDarkMode ] = useState ( false ) ;
42+
43+ useEffect ( ( ) => {
44+ const isDark = localStorage . getItem ( 'theme' ) === 'dark' ||
45+ ( ! localStorage . getItem ( 'theme' ) && window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ) ;
46+ setDarkMode ( isDark ) ;
47+ if ( isDark ) {
48+ document . documentElement . classList . add ( 'dark' ) ;
49+ }
50+ } , [ ] ) ;
51+
52+ const toggleDarkMode = ( ) => {
53+ const newDarkMode = ! darkMode ;
54+ setDarkMode ( newDarkMode ) ;
55+ if ( newDarkMode ) {
56+ document . documentElement . classList . add ( 'dark' ) ;
57+ localStorage . setItem ( 'theme' , 'dark' ) ;
58+ } else {
59+ document . documentElement . classList . remove ( 'dark' ) ;
60+ localStorage . setItem ( 'theme' , 'light' ) ;
61+ }
62+ } ;
63+
64+ const isActive = ( path : string ) => {
65+ if ( path === '/' && location . pathname === '/' ) return true ;
66+ if ( path !== '/' && location . pathname . startsWith ( path ) ) return true ;
67+ return false ;
68+ } ;
69+
70+ return (
71+ < >
72+ { /* Mobile Menu Button */ }
73+ < button
74+ onClick = { ( ) => setMobileOpen ( ! mobileOpen ) }
75+ className = "lg:hidden fixed top-4 left-4 z-50 p-2 rounded-lg bg-white dark:bg-slate-800 shadow-md border border-slate-200 dark:border-slate-700"
76+ >
77+ { mobileOpen ? < X size = { 24 } /> : < Menu size = { 24 } /> }
78+ </ button >
79+
80+ { /* Mobile Overlay */ }
81+ { mobileOpen && (
82+ < div
83+ className = "lg:hidden fixed inset-0 bg-black/50 z-40"
84+ onClick = { ( ) => setMobileOpen ( false ) }
85+ />
86+ ) }
87+
88+ { /* Sidebar */ }
89+ < aside
90+ className = { `
91+ fixed top-0 left-0 h-full bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-700
92+ transition-all duration-300 z-40
93+ ${ collapsed ? 'w-20' : 'w-64' }
94+ ${ mobileOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0' }
95+ ` }
96+ >
97+ { /* Logo Header */ }
98+ < div className = "h-16 flex items-center justify-between px-4 border-b border-slate-200 dark:border-slate-700" >
99+ < div className = "flex items-center gap-3" >
100+ < div className = "w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center" >
101+ < BarChart3 size = { 24 } className = "text-white" />
102+ </ div >
103+ { ! collapsed && (
104+ < div >
105+ < h1 className = "font-bold text-slate-900 dark:text-white" > Vibe</ h1 >
106+ < p className = "text-xs text-slate-500 dark:text-slate-400" > Dashboard</ p >
107+ </ div >
108+ ) }
109+ </ div >
110+ < button
111+ onClick = { ( ) => setCollapsed ( ! collapsed ) }
112+ className = "hidden lg:flex p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
113+ >
114+ < ChevronLeft
115+ size = { 18 }
116+ className = { `text-slate-600 dark:text-slate-400 transition-transform ${ collapsed ? 'rotate-180' : '' } ` }
117+ />
118+ </ button >
119+ </ div >
120+
121+ { /* Navigation */ }
122+ < nav className = "flex-1 px-3 py-4" >
123+ < ul className = "space-y-1" >
124+ { navItems . map ( ( item ) => (
125+ < li key = { item . path } >
126+ < Link
127+ to = { item . path }
128+ onClick = { ( ) => setMobileOpen ( false ) }
129+ className = { `
130+ flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200
131+ ${ isActive ( item . path )
132+ ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 font-medium'
133+ : 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800'
134+ }
135+ ${ collapsed ? 'justify-center' : '' }
136+ ` }
137+ title = { collapsed ? item . label : undefined }
138+ >
139+ < span className = "flex-shrink-0" > { item . icon } </ span >
140+ { ! collapsed && (
141+ < >
142+ < span className = "flex-1" > { item . label } </ span >
143+ { item . badge && (
144+ < span className = "px-2 py-0.5 text-xs bg-slate-200 dark:bg-slate-700 rounded-full" >
145+ { item . badge }
146+ </ span >
147+ ) }
148+ </ >
149+ ) }
150+ </ Link >
151+ </ li >
152+ ) ) }
153+ </ ul >
154+ </ nav >
155+
156+ { /* Bottom Actions */ }
157+ < div className = "p-3 border-t border-slate-200 dark:border-slate-700" >
158+ < Button
159+ variant = "ghost"
160+ size = "sm"
161+ onClick = { toggleDarkMode }
162+ className = { `w-full ${ collapsed ? 'justify-center' : '' } ` }
163+ icon = { darkMode ? < Sun size = { 18 } /> : < Moon size = { 18 } /> }
164+ >
165+ { ! collapsed && ( darkMode ? 'Light Mode' : 'Dark Mode' ) }
166+ </ Button >
167+ </ div >
168+ </ aside >
169+ </ >
170+ ) ;
171+ }
0 commit comments