@@ -2,6 +2,14 @@ import { useState, useEffect } from 'react'
22import { Link , useRouterState } from '@tanstack/react-router'
33import { useTranslation } from 'react-i18next'
44import { Button } from '@/components/ui/button'
5+ import {
6+ NavigationMenu ,
7+ NavigationMenuContent ,
8+ NavigationMenuItem ,
9+ NavigationMenuLink ,
10+ NavigationMenuList ,
11+ NavigationMenuTrigger ,
12+ } from '@/components/ui/navigation-menu'
513import { HugeiconsIcon } from '@hugeicons/react'
614import {
715 GridViewIcon ,
@@ -12,6 +20,7 @@ import {
1220 GitPullRequestIcon ,
1321 BrowserIcon ,
1422 ChartLineData01Icon ,
23+ More03Icon ,
1524} from '@hugeicons/core-free-icons'
1625import { CreateTaskModal } from '@/components/kanban/create-task-modal'
1726
@@ -20,119 +29,83 @@ interface HeaderProps {
2029 onOpenCommandPalette ?: ( ) => void
2130}
2231
32+ const NAV_ITEMS = [
33+ { to : '/tasks' , icon : GridViewIcon , labelKey : 'header.tasks' , matchPrefix : true } ,
34+ { to : '/terminals' , icon : CommandLineIcon , labelKey : 'header.terminals' , matchPrefix : false } ,
35+ { to : '/worktrees' , icon : FolderSyncIcon , labelKey : 'header.worktrees' , matchPrefix : true } ,
36+ { to : '/repositories' , icon : Database01Icon , labelKey : 'header.repositories' , matchPrefix : true } ,
37+ { to : '/review' , icon : GitPullRequestIcon , labelKey : 'header.review' , matchPrefix : true } ,
38+ { to : '/monitoring' , icon : ChartLineData01Icon , labelKey : 'header.monitoring' , matchPrefix : true } ,
39+ ] as const
40+
2341export function Header ( { onNewTaskRef, onOpenCommandPalette } : HeaderProps ) {
2442 const { t } = useTranslation ( 'navigation' )
2543 const { location } = useRouterState ( )
2644 const pathname = location . pathname
2745 const [ createTaskOpen , setCreateTaskOpen ] = useState ( false )
2846
47+ const isActive = ( to : string , matchPrefix : boolean ) =>
48+ matchPrefix ? pathname . startsWith ( to ) : pathname === to
49+
2950 // Expose the open function to parent via callback ref pattern
3051 useEffect ( ( ) => {
3152 onNewTaskRef ?.( ( ) => setCreateTaskOpen ( true ) )
3253 } , [ onNewTaskRef ] )
3354
3455 return (
3556 < header className = "sticky top-0 z-10 flex h-12 shrink-0 items-center justify-between border-b border-border bg-black px-4 max-sm:px-2" >
36- < div className = "flex min-w-0 items-center gap-4 max-sm:gap-1 " >
37- < Link to = "/tasks" className = "hidden items-center sm:flex " >
57+ < div className = "flex min-w-0 items-center gap-4 max-sm:gap-2 " >
58+ < Link to = "/tasks" className = "flex items-center" >
3859 < img src = "/vibora-logo.jpeg" alt = "Vibora" className = "h-7" />
3960 </ Link >
4061
41- < nav className = "flex items-center gap-1 max-sm:gap-0" >
42- < Link to = "/tasks" >
43- < Button
44- variant = { pathname . startsWith ( '/tasks' ) ? 'secondary' : 'ghost' }
45- size = "sm"
46- className = "max-sm:px-2"
47- >
48- < HugeiconsIcon
49- icon = { GridViewIcon }
50- size = { 16 }
51- strokeWidth = { 2 }
52- data-slot = "icon"
53- />
54- < span className = "max-sm:hidden" > { t ( 'header.tasks' ) } </ span >
55- </ Button >
56- </ Link >
57- < Link to = "/terminals" >
58- < Button
59- variant = { pathname === '/terminals' ? 'secondary' : 'ghost' }
60- size = "sm"
61- className = "max-sm:px-2"
62- >
63- < HugeiconsIcon
64- icon = { CommandLineIcon }
65- size = { 16 }
66- strokeWidth = { 2 }
67- data-slot = "icon"
68- />
69- < span className = "max-sm:hidden" > { t ( 'header.terminals' ) } </ span >
70- </ Button >
71- </ Link >
72- < Link to = "/worktrees" >
73- < Button
74- variant = { pathname . startsWith ( '/worktrees' ) ? 'secondary' : 'ghost' }
75- size = "sm"
76- className = "max-sm:px-2"
77- >
78- < HugeiconsIcon
79- icon = { FolderSyncIcon }
80- size = { 16 }
81- strokeWidth = { 2 }
82- data-slot = "icon"
83- />
84- < span className = "max-sm:hidden" > { t ( 'header.worktrees' ) } </ span >
85- </ Button >
86- </ Link >
87- < Link to = "/repositories" >
88- < Button
89- variant = { pathname . startsWith ( '/repositories' ) ? 'secondary' : 'ghost' }
90- size = "sm"
91- className = "max-sm:px-2"
92- >
93- < HugeiconsIcon
94- icon = { Database01Icon }
95- size = { 16 }
96- strokeWidth = { 2 }
97- data-slot = "icon"
98- />
99- < span className = "max-sm:hidden" > { t ( 'header.repositories' ) } </ span >
100- </ Button >
101- </ Link >
102- < Link to = "/review" >
103- < Button
104- variant = { pathname . startsWith ( '/review' ) ? 'secondary' : 'ghost' }
105- size = "sm"
106- className = "max-sm:px-2"
107- >
108- < HugeiconsIcon
109- icon = { GitPullRequestIcon }
110- size = { 16 }
111- strokeWidth = { 2 }
112- data-slot = "icon"
113- />
114- < span className = "max-sm:hidden" > { t ( 'header.review' ) } </ span >
115- </ Button >
116- </ Link >
117- < Link to = "/monitoring" >
118- < Button
119- variant = { pathname . startsWith ( '/monitoring' ) ? 'secondary' : 'ghost' }
120- size = "sm"
121- className = "max-sm:px-2"
122- >
123- < HugeiconsIcon
124- icon = { ChartLineData01Icon }
125- size = { 16 }
126- strokeWidth = { 2 }
127- data-slot = "icon"
128- />
129- < span className = "max-sm:hidden" > { t ( 'header.monitoring' ) } </ span >
130- </ Button >
131- </ Link >
62+ { /* Mobile navigation menu */ }
63+ < NavigationMenu className = "lg:hidden" >
64+ < NavigationMenuList >
65+ < NavigationMenuItem >
66+ < NavigationMenuTrigger className = "bg-transparent hover:bg-muted/50 data-open:bg-muted/50 gap-1 px-2" >
67+ < HugeiconsIcon icon = { More03Icon } size = { 16 } strokeWidth = { 2 } />
68+ < span className = "sr-only" > Menu</ span >
69+ </ NavigationMenuTrigger >
70+ < NavigationMenuContent className = "min-w-48" >
71+ { NAV_ITEMS . map ( ( item ) => (
72+ < NavigationMenuLink
73+ key = { item . to }
74+ href = { item . to }
75+ active = { isActive ( item . to , item . matchPrefix ) }
76+ render = { < Link to = { item . to } /> }
77+ >
78+ < HugeiconsIcon icon = { item . icon } size = { 16 } strokeWidth = { 2 } />
79+ { t ( item . labelKey ) }
80+ </ NavigationMenuLink >
81+ ) ) }
82+ </ NavigationMenuContent >
83+ </ NavigationMenuItem >
84+ </ NavigationMenuList >
85+ </ NavigationMenu >
86+
87+ { /* Desktop navigation */ }
88+ < nav className = "hidden items-center gap-1 lg:flex" >
89+ { NAV_ITEMS . map ( ( item ) => (
90+ < Link key = { item . to } to = { item . to } >
91+ < Button
92+ variant = { isActive ( item . to , item . matchPrefix ) ? 'secondary' : 'ghost' }
93+ size = "sm"
94+ >
95+ < HugeiconsIcon
96+ icon = { item . icon }
97+ size = { 16 }
98+ strokeWidth = { 2 }
99+ data-slot = "icon"
100+ />
101+ { t ( item . labelKey ) }
102+ </ Button >
103+ </ Link >
104+ ) ) }
132105 </ nav >
133106 </ div >
134107
135- < div className = "flex shrink-0 items-center gap-2 max-sm:gap-1 " >
108+ < div className = "flex shrink-0 items-center gap-2" >
136109 < CreateTaskModal open = { createTaskOpen } onOpenChange = { setCreateTaskOpen } />
137110 < Button
138111 variant = "ghost"
0 commit comments