11import { useState , useEffect } from 'react'
22import { Link , useMatchRoute , useNavigate } from '@tanstack/react-router'
33import clsx from 'clsx'
4- import { Sun , Moon , LogIn , LogOut , Shield , User } from 'lucide-react'
4+ import { Sun , Moon , LogIn , LogOut , Shield , User , Menu , X } from 'lucide-react'
55import { useAuth } from '@/hooks/useAuth'
66
7- function NavLink ( { to, children } : { to : string ; children : React . ReactNode } ) {
7+ function NavLink ( { to, children, onClick } : { to : string ; children : React . ReactNode ; onClick ?: ( ) => void } ) {
88 const matchRoute = useMatchRoute ( )
99 const isActive = matchRoute ( { to, fuzzy : true } )
1010
1111 return (
1212 < Link
1313 to = { to }
14+ onClick = { onClick }
1415 className = { clsx (
1516 'rounded-sm px-3 py-1.5 text-sm/6 font-medium transition-colors' ,
1617 isActive
@@ -50,7 +51,7 @@ function ThemeSwitcher() {
5051 )
5152}
5253
53- function AuthControls ( ) {
54+ function AuthControls ( { onNavigate } : { onNavigate ?: ( ) => void } ) {
5455 const { user, isApiEnabled, isAdmin, logout } = useAuth ( )
5556 const navigate = useNavigate ( )
5657
@@ -60,6 +61,7 @@ function AuthControls() {
6061 return (
6162 < Link
6263 to = "/login"
64+ onClick = { onNavigate }
6365 className = "flex items-center gap-1.5 rounded-sm px-3 py-1.5 text-sm font-medium text-gray-600 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-700/50 dark:hover:text-gray-100"
6466 >
6567 < LogIn className = "size-4" />
@@ -70,12 +72,13 @@ function AuthControls() {
7072
7173 const handleLogout = async ( ) => {
7274 await logout ( )
75+ onNavigate ?.( )
7376 navigate ( { to : '/runs' } )
7477 }
7578
7679 return (
7780 < div className = "flex items-center gap-2" >
78- { isAdmin && < NavLink to = "/admin" > Admin</ NavLink > }
81+ { isAdmin && < NavLink to = "/admin" onClick = { onNavigate } > Admin</ NavLink > }
7982 < div className = "flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-300" >
8083 < User className = "size-4" />
8184 < span > { user . username } </ span >
@@ -93,6 +96,10 @@ function AuthControls() {
9396}
9497
9598export function Header ( ) {
99+ const [ mobileOpen , setMobileOpen ] = useState ( false )
100+
101+ const closeMobile = ( ) => setMobileOpen ( false )
102+
96103 return (
97104 < header className = "border-b border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800" >
98105 < div className = "mx-auto flex max-w-7xl items-center gap-8 px-4 py-2" >
@@ -101,15 +108,40 @@ export function Header() {
101108 < img src = "/img/logo_white.png" alt = "Benchmarkoor" className = "hidden h-12 dark:block" />
102109 < span className = "text-lg/7 font-semibold text-gray-900 dark:text-gray-100" > Benchmarkoor</ span >
103110 </ Link >
104- < nav className = "flex items-center gap-1" >
111+
112+ { /* Desktop nav */ }
113+ < nav className = "hidden items-center gap-1 md:flex" >
105114 < NavLink to = "/runs" > Runs</ NavLink >
106115 < NavLink to = "/suites" > Suites</ NavLink >
107116 </ nav >
108- < div className = "ml-auto flex items-center gap-2" >
117+ < div className = "ml-auto hidden items-center gap-2 md:flex " >
109118 < AuthControls />
110119 < ThemeSwitcher />
111120 </ div >
121+
122+ { /* Mobile hamburger */ }
123+ < button
124+ onClick = { ( ) => setMobileOpen ( ! mobileOpen ) }
125+ className = "ml-auto rounded-sm p-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 md:hidden"
126+ aria-label = "Toggle menu"
127+ >
128+ { mobileOpen ? < X className = "size-5" /> : < Menu className = "size-5" /> }
129+ </ button >
112130 </ div >
131+
132+ { /* Mobile menu */ }
133+ { mobileOpen && (
134+ < div className = "border-t border-gray-200 px-4 py-3 dark:border-gray-700 md:hidden" >
135+ < nav className = "flex flex-col gap-1" >
136+ < NavLink to = "/runs" onClick = { closeMobile } > Runs</ NavLink >
137+ < NavLink to = "/suites" onClick = { closeMobile } > Suites</ NavLink >
138+ </ nav >
139+ < div className = "mt-3 flex items-center gap-2 border-t border-gray-200 pt-3 dark:border-gray-700" >
140+ < AuthControls onNavigate = { closeMobile } />
141+ < ThemeSwitcher />
142+ </ div >
143+ </ div >
144+ ) }
113145 </ header >
114146 )
115147}
0 commit comments