@@ -82,6 +82,75 @@ const Header = () => {
8282 scrollToSection ( sectionId )
8383 }
8484 }
85+ const getNavHref = ( item ) => {
86+ if ( ! item ) return '#'
87+ if ( item . target ) {
88+ const target = item . target
89+ const value = target . value ?? target . path ?? target . href ?? target . slug
90+ switch ( target . type ) {
91+ case 'section' : {
92+ const sectionId = resolveSectionId ( { ...item , ...target } )
93+ return sectionId ? `#${ sectionId . replace ( / ^ # / , '' ) } ` : '#'
94+ }
95+ case 'route' :
96+ case 'page' :
97+ return typeof value === 'string' && value . trim ( )
98+ ? value . startsWith ( '/' )
99+ ? value
100+ : `/${ value . trim ( ) . replace ( / ^ \/ / , '' ) } `
101+ : '#'
102+ case 'external' :
103+ case 'href' : {
104+ const safeUrl = sanitizeExternalUrl ( value )
105+ return safeUrl || '#'
106+ }
107+ default :
108+ return '#'
109+ }
110+ }
111+ if ( item . href ) {
112+ const safeUrl = sanitizeExternalUrl ( item . href )
113+ return safeUrl || '#'
114+ }
115+ if ( ( item . type === 'route' || item . type === 'page' ) && item . path ) {
116+ return item . path
117+ }
118+ if ( item . type === 'section' ) {
119+ const sectionId = resolveSectionId ( item )
120+ return sectionId ? `#${ sectionId . replace ( / ^ # / , '' ) } ` : '#'
121+ }
122+ if ( item . slug ) {
123+ return `/pages/${ item . slug } `
124+ }
125+ return '#'
126+ }
127+ const isExternalHref = ( href ) =>
128+ typeof href === 'string' && ( href . startsWith ( 'http://' ) || href . startsWith ( 'https://' ) )
129+ const isSpecialProtocol = ( href ) =>
130+ typeof href === 'string' && ( href . startsWith ( 'mailto:' ) || href . startsWith ( 'tel:' ) )
131+ const handleNavClick = ( event , item ) => {
132+ if ( ! item ) return
133+ if ( event . metaKey || event . ctrlKey || event . altKey || event . shiftKey || event . button !== 0 ) {
134+ return
135+ }
136+ event . preventDefault ( )
137+ handleNavigation ( item )
138+ setMobileMenuOpen ( false )
139+ }
140+ const handleBrandClick = ( event ) => {
141+ if ( event . metaKey || event . ctrlKey || event . altKey || event . shiftKey || event . button !== 0 ) {
142+ return
143+ }
144+ event . preventDefault ( )
145+ setMobileMenuOpen ( false )
146+ if ( location . pathname === '/' ) {
147+ if ( typeof window !== 'undefined' ) {
148+ window . scrollTo ( { top : 0 , behavior : 'smooth' } )
149+ }
150+ return
151+ }
152+ navigate ( '/' )
153+ }
85154 const handleCtaClick = ( ) => {
86155 if ( ctaContent . target ) {
87156 navigateContentTarget ( ctaContent . target , { navigate, location } )
@@ -94,25 +163,44 @@ const Header = () => {
94163 < nav className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
95164 < div className = "flex justify-between items-center h-20" >
96165 { }
97- < div className = "flex items-center space-x-3" >
166+ < a
167+ href = "/"
168+ onClick = { handleBrandClick }
169+ className = "flex items-center space-x-3 rounded-xl focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500"
170+ aria-label = "Zur Startseite"
171+ >
98172 < div className = "flex h-12 w-12 items-center justify-center rounded-xl bg-primary-600 text-white shadow-sm" >
99173 < BrandIcon className = "w-6 h-6" />
100174 </ div >
101175 < span className = "text-xl font-semibold text-gray-900 dark:text-gray-100" >
102176 { headerContent ?. brand ?. name || 'Linux Tutorial' }
103177 </ span >
104- </ div >
178+ </ a >
105179 { }
106180 < div className = "hidden md:flex items-center space-x-8" >
107- { computedNavItems . map ( ( item , index ) => (
108- < button
109- key = { item . id || `${ item . label ?? 'nav' } -${ index } ` }
110- onClick = { ( ) => handleNavigation ( item ) }
111- className = "nav-link font-medium hover:text-primary-600 dark:text-gray-300 dark:hover:text-primary-400"
112- >
113- { item . label }
114- </ button >
115- ) ) }
181+ { computedNavItems . map ( ( item , index ) => {
182+ const href = getNavHref ( item )
183+ const external = isExternalHref ( href )
184+ const special = isSpecialProtocol ( href )
185+ const ariaCurrent = ! href || href . startsWith ( '#' ) || external || special
186+ ? undefined
187+ : location . pathname === href
188+ ? 'page'
189+ : undefined
190+ return (
191+ < a
192+ key = { item . id || `${ item . label ?? 'nav' } -${ index } ` }
193+ href = { href }
194+ onClick = { ( event ) => handleNavClick ( event , item ) }
195+ target = { external ? '_blank' : undefined }
196+ rel = { external ? 'noopener noreferrer' : undefined }
197+ className = "nav-link font-medium hover:text-primary-600 dark:text-gray-300 dark:hover:text-primary-400"
198+ aria-current = { ariaCurrent }
199+ >
200+ { item . label }
201+ </ a >
202+ )
203+ } ) }
116204 < button
117205 onClick = { ( ) => setSearchOpen ( true ) }
118206 className = "p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
@@ -139,40 +227,44 @@ const Header = () => {
139227 < button
140228 className = "p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800"
141229 onClick = { ( ) => setMobileMenuOpen ( ! mobileMenuOpen ) }
230+ aria-label = { mobileMenuOpen ? 'Navigation schließen' : 'Navigation öffnen' }
142231 >
143- { mobileMenuOpen ? < X className = "w-6 h-6" /> : < Menu className = "w-6 h-6" /> }
232+ { mobileMenuOpen ? (
233+ < X className = "w-5 h-5" />
234+ ) : (
235+ < Menu className = "w-5 h-5" />
236+ ) }
144237 </ button >
145238 </ div >
146239 </ div >
147- { }
148240 { mobileMenuOpen && (
149241 < div className = "md:hidden pb-4 space-y-2" >
150- { computedNavItems . map ( ( item , index ) => (
151- < button
152- key = { item . id || `${ item . label ?? 'nav' } -${ index } ` }
153- onClick = { ( ) => {
154- handleNavigation ( item )
155- setMobileMenuOpen ( false )
156- } }
157- className = "block w-full text-left px-4 py-2 rounded-md text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-primary-600 dark:hover:text-primary-400"
158- >
159- { item . label }
160- </ button >
161- ) ) }
242+ { computedNavItems . map ( ( item , index ) => {
243+ const href = getNavHref ( item )
244+ const external = isExternalHref ( href )
245+ return (
246+ < a
247+ key = { item . id || `${ item . label ?? 'nav' } -${ index } ` }
248+ href = { href }
249+ onClick = { ( event ) => handleNavClick ( event , item ) }
250+ target = { external ? '_blank' : undefined }
251+ rel = { external ? 'noopener noreferrer' : undefined }
252+ className = "block w-full text-left px-4 py-2 rounded-md text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-primary-600 dark:hover:text-primary-400"
253+ >
254+ { item . label }
255+ </ a >
256+ )
257+ } ) }
162258 < button
163- onClick = { ( ) => {
164- handleCtaClick ( )
165- setMobileMenuOpen ( false )
166- } }
167- className = "btn-primary w-full justify-center"
259+ onClick = { handleCtaClick }
260+ className = "w-full flex items-center justify-center gap-2 rounded-lg bg-gradient-to-r from-primary-600 to-primary-700 px-4 py-2 text-sm font-semibold text-white shadow-lg hover:from-primary-700 hover:to-primary-800"
168261 >
169262 < CTAIcon className = "w-4 h-4" />
170263 < span > { isAuthenticated ? ctaContent . authLabel || 'Admin' : ctaContent . guestLabel || 'Login' } </ span >
171264 </ button >
172265 </ div >
173266 ) }
174267 </ nav >
175- { }
176268 { searchOpen && < SearchBar onClose = { ( ) => setSearchOpen ( false ) } /> }
177269 </ header >
178270 )
0 commit comments