Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.
This commit is contained in:
119
src/components/Header.astro
Normal file
119
src/components/Header.astro
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
import { NAVIGATION } from '../site.config';
|
||||
import ThemeToggle from './ThemeToggle.astro';
|
||||
import LanguageSwitcher from './LanguageSwitcher.astro';
|
||||
import { t } from '../utils/i18n';
|
||||
---
|
||||
|
||||
<header class="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<nav class="container-custom">
|
||||
<div class="flex h-16 items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<div class="h-8 w-8 flex items-center justify-center">
|
||||
<img
|
||||
src="/images/TIBER365.png"
|
||||
alt="Tiber365 Logo"
|
||||
class="h-6 w-6 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<span class="font-display font-bold text-xl text-foreground">Tiber365</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex items-center space-x-6">
|
||||
{NAVIGATION.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
target={item.type === 'external' ? '_blank' : undefined}
|
||||
rel={item.type === 'external' ? 'noopener noreferrer' : undefined}
|
||||
class="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors relative group"
|
||||
>
|
||||
{t(item.label)}
|
||||
{item.type === 'external' && (
|
||||
<svg class="inline h-3 w-3 ml-1 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
)}
|
||||
<span class="absolute inset-x-0 -bottom-1 h-0.5 bg-primary scale-x-0 group-hover:scale-x-100 transition-transform origin-left"></span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Theme Toggle & Language Switcher -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<LanguageSwitcher />
|
||||
<ThemeToggle />
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<button
|
||||
id="mobile-menu-button"
|
||||
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle mobile menu"
|
||||
>
|
||||
<svg id="mobile-menu-icon" class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
<svg id="mobile-close-icon" class="h-6 w-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation -->
|
||||
<div id="mobile-menu" class="md:hidden hidden border-t border-border">
|
||||
<div class="px-2 pt-2 pb-3 space-y-1">
|
||||
{NAVIGATION.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
target={item.type === 'external' ? '_blank' : undefined}
|
||||
rel={item.type === 'external' ? 'noopener noreferrer' : undefined}
|
||||
class="block px-3 py-2 text-base font-medium text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors"
|
||||
>
|
||||
{t(item.label)}
|
||||
{item.type === 'external' && (
|
||||
<svg class="inline h-4 w-4 ml-1 opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<script>
|
||||
// Mobile menu functionality
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const menuIcon = document.getElementById('mobile-menu-icon');
|
||||
const closeIcon = document.getElementById('mobile-close-icon');
|
||||
|
||||
if (mobileMenuButton && mobileMenu && menuIcon && closeIcon) {
|
||||
mobileMenuButton.addEventListener('click', () => {
|
||||
const isExpanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
mobileMenuButton.setAttribute('aria-expanded', (!isExpanded).toString());
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
menuIcon.classList.toggle('hidden');
|
||||
closeIcon.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!mobileMenuButton.contains(event.target as Node) && !mobileMenu.contains(event.target as Node)) {
|
||||
mobileMenuButton.setAttribute('aria-expanded', 'false');
|
||||
mobileMenu.classList.add('hidden');
|
||||
menuIcon.classList.remove('hidden');
|
||||
closeIcon.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
Reference in New Issue
Block a user