upgrade to different layout
This commit is contained in:
205
src/components/Navigation.astro
Normal file
205
src/components/Navigation.astro
Normal file
@@ -0,0 +1,205 @@
|
||||
---
|
||||
import ThemeToggle from './ThemeToggle.astro';
|
||||
|
||||
import directus from "../../lib/directus"
|
||||
import { readSingleton } from "@directus/sdk";
|
||||
|
||||
const global = await directus.request(readSingleton("global"));
|
||||
|
||||
const navItems = [
|
||||
{ text: 'Home', href: '/' },
|
||||
{ text: 'Blog', href: '/blog' },
|
||||
{ text: 'Topics', href: '/topics' },
|
||||
{ text: 'About', href: '/about' },
|
||||
{ text: 'RSS', href: 'rss.xml' },
|
||||
];
|
||||
|
||||
const pathname = new URL(Astro.request.url).pathname;
|
||||
const currentPath = pathname.slice(1); // remove the first "/"
|
||||
---
|
||||
|
||||
<header class="py-4 fixed top-0 left-0 right-0 z-40 bg-white dark:bg-zinc-900 border-b border-zinc-100 dark:border-zinc-800">
|
||||
<div class="max-w-3xl mx-auto px-4 flex items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="font-bold text-xl text-zinc-900 dark:text-white">{global.initals}</a>
|
||||
|
||||
<!-- Desktop navigation -->
|
||||
<nav class="hidden sm:flex items-center space-x-6">
|
||||
{navItems.map(item => {
|
||||
const isActive = currentPath === (item.href === '/' ? '' : item.href.slice(1));
|
||||
return (
|
||||
<a
|
||||
href={item.href}
|
||||
class={`text-sm font-medium ${isActive
|
||||
? 'text-zinc-900 dark:text-white'
|
||||
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white'}`}
|
||||
>
|
||||
{item.text}
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
<ThemeToggle />
|
||||
</nav>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<button id="mobile-menu-button" class="sm:hidden flex items-center" aria-label="Menu">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-zinc-900 dark:text-white">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Mobile menu overlay -->
|
||||
<div id="mobile-menu" class="fixed inset-0 z-50 bg-white dark:bg-zinc-900 flex flex-col opacity-0 pointer-events-none transition-all duration-300 ease-in-out">
|
||||
<div class="flex justify-between items-center p-4 border-b border-zinc-100 dark:border-zinc-800">
|
||||
<a href="/" class="font-bold text-xl text-zinc-900 dark:text-white">JD</a>
|
||||
<button id="close-menu-button" class="text-zinc-900 dark:text-white p-2 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" aria-label="Close menu">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 flex flex-col items-center justify-center space-y-6 text-center">
|
||||
{navItems.map((item, index) => {
|
||||
const isActive = currentPath === (item.href === '/' ? '' : item.href.slice(1));
|
||||
return (
|
||||
<a
|
||||
href={item.href}
|
||||
class={`text-lg font-medium mobile-nav-item opacity-0 translate-y-4 ${isActive
|
||||
? 'text-zinc-900 dark:text-white'
|
||||
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white'}`}
|
||||
style={`transition-delay: ${index * 0.05}s;`}
|
||||
>
|
||||
{item.text}
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
<div class="pt-4 mobile-nav-item opacity-0 translate-y-4" style="transition-delay: 0.25s;">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Spacer to prevent content from hiding behind fixed header -->
|
||||
<div class="h-16"></div>
|
||||
|
||||
<script>
|
||||
// Mobile menu toggle with animations
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const closeMenuButton = document.getElementById('close-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const navItems = document.querySelectorAll('.mobile-nav-item');
|
||||
|
||||
// Open menu with animations
|
||||
mobileMenuButton?.addEventListener('click', () => {
|
||||
if (!mobileMenu) return;
|
||||
|
||||
// Prevent body scrolling
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Show menu with fade in
|
||||
mobileMenu.classList.remove('pointer-events-none');
|
||||
mobileMenu.classList.add('pointer-events-auto');
|
||||
|
||||
// Animate opacity
|
||||
setTimeout(() => {
|
||||
mobileMenu.style.opacity = '1';
|
||||
|
||||
// Animate each nav item with staggered delay
|
||||
navItems.forEach(item => {
|
||||
setTimeout(() => {
|
||||
item.classList.remove('opacity-0', 'translate-y-4');
|
||||
}, 150);
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
|
||||
// Close menu with animations
|
||||
const closeMenu = () => {
|
||||
if (!mobileMenu) return;
|
||||
|
||||
// Fade out nav items first
|
||||
navItems.forEach(item => {
|
||||
item.classList.add('opacity-0', 'translate-y-4');
|
||||
});
|
||||
|
||||
// Then fade out the menu
|
||||
setTimeout(() => {
|
||||
mobileMenu.style.opacity = '0';
|
||||
|
||||
// After animation completes, hide menu and restore scrolling
|
||||
setTimeout(() => {
|
||||
mobileMenu.classList.remove('pointer-events-auto');
|
||||
mobileMenu.classList.add('pointer-events-none');
|
||||
document.body.style.overflow = '';
|
||||
}, 300);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Close button event
|
||||
closeMenuButton?.addEventListener('click', closeMenu);
|
||||
|
||||
// Close menu when clicking a link
|
||||
const mobileLinks = mobileMenu?.querySelectorAll('a');
|
||||
mobileLinks?.forEach(link => {
|
||||
link.addEventListener('click', closeMenu);
|
||||
});
|
||||
|
||||
// Close menu on escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && mobileMenu?.classList.contains('pointer-events-auto')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Add smooth animation to header on scroll
|
||||
const header = document.querySelector('header');
|
||||
let lastScrollY = window.scrollY;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!header) return;
|
||||
|
||||
const currentScrollY = window.scrollY;
|
||||
|
||||
// Add shadow on scroll
|
||||
if (currentScrollY > 10) {
|
||||
header.classList.add('shadow-sm');
|
||||
} else {
|
||||
header.classList.remove('shadow-sm');
|
||||
}
|
||||
|
||||
// Update last scroll position
|
||||
lastScrollY = currentScrollY;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Smooth animations for mobile navigation */
|
||||
.mobile-nav-item {
|
||||
transition: opacity 0.5s ease, transform 0.5s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Header transition */
|
||||
header {
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Mobile menu button hover effect */
|
||||
#mobile-menu-button {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
#mobile-menu-button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Mobile menu transition */
|
||||
#mobile-menu {
|
||||
transition: opacity 0.3s ease;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user