@@ -30,72 +30,74 @@ const bottomNavItems: NavItem[] = [
3030 { name : 'Documentation' , href : '/docs' , icon : < BookOpen className = "w-5 h-5" /> , external : true } ,
3131]
3232
33- export function Sidebar ( { collapsed, onToggle, mobileOpen, onMobileClose } : SidebarProps ) {
34- const location = useLocation ( )
35-
36- const isActive = ( href : string ) => {
37- if ( href === '/dashboard' ) {
38- return location . pathname === '/dashboard' || location . pathname . startsWith ( '/dashboard/repo/' )
39- }
40- return location . pathname === href
41- }
42-
43- const handleNavClick = ( ) => {
44- // Close mobile menu when navigating
45- onMobileClose ?.( )
46- }
47-
48- const NavLink = ( { item } : { item : NavItem } ) => {
49- const active = isActive ( item . href )
50-
51- const baseClasses = `
52- flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group
53- ${ active
54- ? 'bg-primary/10 text-primary'
55- : 'text-muted-foreground hover:text-foreground hover:bg-muted'
56- }
57- `
58-
59- // On mobile, always show full labels (not collapsed)
60- const showLabels = ! collapsed || mobileOpen
61-
62- if ( item . external ) {
63- return (
64- < a
65- href = { item . href }
66- target = "_blank"
67- rel = "noopener noreferrer"
68- className = { baseClasses }
69- onClick = { handleNavClick }
70- >
71- < span className = { active ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground' } >
72- { item . icon }
73- </ span >
74- { showLabels && (
75- < >
76- < span className = "text-sm font-medium truncate" > { item . name } </ span >
77- < ExternalLink className = "w-3 h-3 ml-auto opacity-50" />
78- </ >
79- ) }
80- </ a >
81- )
33+ // defined at module scope so React can reconcile it across renders
34+ function NavLink ( {
35+ item,
36+ active,
37+ showLabels,
38+ onClick,
39+ } : {
40+ item : NavItem
41+ active : boolean
42+ showLabels : boolean
43+ onClick ?: ( ) => void
44+ } ) {
45+ const baseClasses = `
46+ flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group
47+ ${ active
48+ ? 'bg-primary/10 text-primary'
49+ : 'text-muted-foreground hover:text-foreground hover:bg-muted'
8250 }
51+ `
8352
53+ if ( item . external ) {
8454 return (
85- < Link to = { item . href } className = { baseClasses } onClick = { handleNavClick } >
55+ < a
56+ href = { item . href }
57+ target = "_blank"
58+ rel = "noopener noreferrer"
59+ className = { baseClasses }
60+ onClick = { onClick }
61+ >
8662 < span className = { active ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground' } >
8763 { item . icon }
8864 </ span >
8965 { showLabels && (
90- < span className = "text-sm font-medium truncate" > { item . name } </ span >
66+ < >
67+ < span className = "text-sm font-medium truncate" > { item . name } </ span >
68+ < ExternalLink className = "w-3 h-3 ml-auto opacity-50" />
69+ </ >
9170 ) }
92- </ Link >
71+ </ a >
9372 )
9473 }
9574
96- // Determine sidebar width class
75+ return (
76+ < Link to = { item . href } className = { baseClasses } onClick = { onClick } >
77+ < span className = { active ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground' } >
78+ { item . icon }
79+ </ span >
80+ { showLabels && (
81+ < span className = "text-sm font-medium truncate" > { item . name } </ span >
82+ ) }
83+ </ Link >
84+ )
85+ }
86+
87+ export function Sidebar ( { collapsed, onToggle, mobileOpen, onMobileClose } : SidebarProps ) {
88+ const location = useLocation ( )
89+
90+ const isActive = ( href : string ) => {
91+ if ( href === '/dashboard' ) {
92+ return location . pathname === '/dashboard' || location . pathname . startsWith ( '/dashboard/repo/' )
93+ }
94+ return location . pathname === href
95+ }
96+
97+ const showLabels = ! collapsed || ! ! mobileOpen
98+
9799 const getWidthClass = ( ) => {
98- if ( mobileOpen ) return 'w-[var(--sidebar-width)]' // Always full width on mobile when open
100+ if ( mobileOpen ) return 'w-[var(--sidebar-width)]'
99101 if ( collapsed ) return 'w-[var(--sidebar-width-collapsed)]'
100102 return 'w-[var(--sidebar-width)]'
101103 }
@@ -111,7 +113,7 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
111113 lg:translate-x-0
112114 ` }
113115 >
114- { /* Mobile close button */ }
116+ { /* mobile close */ }
115117 < div className = "lg:hidden flex items-center justify-between p-3 border-b border-border" >
116118 < span className = "font-semibold text-foreground" > Menu</ span >
117119 < button
@@ -125,16 +127,27 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
125127
126128 < nav className = "flex-1 p-3 space-y-1" >
127129 { mainNavItems . map ( ( item ) => (
128- < NavLink key = { item . href } item = { item } />
130+ < NavLink
131+ key = { item . href }
132+ item = { item }
133+ active = { isActive ( item . href ) }
134+ showLabels = { showLabels }
135+ onClick = { onMobileClose }
136+ />
129137 ) ) }
130138 </ nav >
131139
132140 < div className = "p-3 border-t border-border space-y-1" >
133141 { bottomNavItems . map ( ( item ) => (
134- < NavLink key = { item . href } item = { item } />
142+ < NavLink
143+ key = { item . href }
144+ item = { item }
145+ active = { isActive ( item . href ) }
146+ showLabels = { showLabels }
147+ onClick = { onMobileClose }
148+ />
135149 ) ) }
136150
137- { /* Collapse toggle - desktop only */ }
138151 < button
139152 onClick = { onToggle }
140153 className = "hidden lg:flex items-center gap-3 px-3 py-2.5 w-full rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-all"
0 commit comments