mobile nav implemented
dark mode need rifinements
This commit is contained in:
1 parent
b8779e5a35
commit
6d65d5975c
4 files changed
+253
-20
No files matched your search
@@ -1,44 +1,71 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { Button } from '../ui/button';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
|
||||
export function Header() {
|
||||
const { user, logout, isAuthenticated, hasRole } = useAuth();
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
const closeMobileMenu = () => {
|
||||
setIsMobileMenuOpen(false);
|
||||
};
|
||||
|
||||
const navLinks = [
|
||||
{ to: '/', label: 'Home' },
|
||||
{ to: '/articles', label: 'Articles' },
|
||||
{ to: '/live-blogs', label: 'Live' },
|
||||
];
|
||||
|
||||
const adminLinks = [
|
||||
{ to: '/admin', label: 'Admin' },
|
||||
{ to: '/admin/live-blogs/create', label: '+ New Live Blog' },
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="border-b">
|
||||
<header className="border-b sticky top-0 z-50 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-3xl font-bold">
|
||||
<h1 className="text-2xl md:text-3xl font-bold">
|
||||
<Link to="/" className="hover:underline">Placebo.mk</Link>
|
||||
</h1>
|
||||
|
||||
<nav className="flex items-center gap-4">
|
||||
<Link to="/" className="text-sm font-medium hover:underline">
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/articles" className="text-sm font-medium hover:underline">
|
||||
Articles
|
||||
</Link>
|
||||
<Link to="/live-blogs" className="text-sm font-medium hover:underline">
|
||||
Live
|
||||
</Link>
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-4">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.to}
|
||||
to={link.to}
|
||||
className="text-sm font-medium hover:underline transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
{(hasRole('admin') || hasRole('contributor')) && (
|
||||
<>
|
||||
<Link to="/admin" className="text-sm font-medium hover:underline text-primary">
|
||||
Admin
|
||||
</Link>
|
||||
<Link to="/admin/live-blogs/create" className="text-sm font-medium hover:underline text-primary">
|
||||
+ New Live Blog
|
||||
</Link>
|
||||
{adminLinks.map((link) => (
|
||||
<Link
|
||||
key={link.to}
|
||||
to={link.to}
|
||||
className="text-sm font-medium hover:underline text-primary transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 ml-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{user?.username}
|
||||
</span>
|
||||
@@ -53,12 +80,97 @@ export function Header() {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Link to="/auth" className="text-sm font-medium hover:underline text-primary">
|
||||
<Link to="/auth" className="text-sm font-medium hover:underline text-primary transition-colors">
|
||||
Login / Register
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<ThemeToggle />
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<div className="flex items-center gap-2 md:hidden">
|
||||
<ThemeToggle />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleMobileMenu}
|
||||
className="h-9 w-9"
|
||||
aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{isMobileMenuOpen ? (
|
||||
<X className="h-5 w-5" />
|
||||
) : (
|
||||
<Menu className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden mt-4 pb-4 border-t pt-4 animate-in slide-in-from-top-2">
|
||||
<div className="flex flex-col gap-3">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.to}
|
||||
to={link.to}
|
||||
className="text-sm font-medium hover:underline py-2 transition-colors"
|
||||
onClick={closeMobileMenu}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
{(hasRole('admin') || hasRole('contributor')) && (
|
||||
<div className="border-t pt-3 mt-2">
|
||||
<p className="text-xs text-muted-foreground mb-2">Admin</p>
|
||||
{adminLinks.map((link) => (
|
||||
<Link
|
||||
key={link.to}
|
||||
to={link.to}
|
||||
className="text-sm font-medium hover:underline text-primary py-2 block transition-colors"
|
||||
onClick={closeMobileMenu}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t pt-3 mt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Logged in as: {user?.username}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
logout();
|
||||
closeMobileMenu();
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
to="/auth"
|
||||
className="text-sm font-medium hover:underline text-primary py-2 transition-colors"
|
||||
onClick={closeMobileMenu}
|
||||
>
|
||||
Login / Register
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Sun, Moon } from 'lucide-react';
|
||||
import { getInitialTheme, toggleTheme, watchSystemTheme } from '@/lib/theme';
|
||||
|
||||
export function ThemeToggle() {
|
||||
const [theme, setTheme] = useState<'light' | 'dark'>(getInitialTheme);
|
||||
|
||||
useEffect(() => {
|
||||
// Watch for system theme changes
|
||||
const cleanup = watchSystemTheme((newTheme) => {
|
||||
setTheme(newTheme);
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
const handleToggle = () => {
|
||||
const newTheme = toggleTheme();
|
||||
setTheme(newTheme);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleToggle}
|
||||
className="h-9 w-9"
|
||||
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<Moon className="h-4 w-4" />
|
||||
) : (
|
||||
<Sun className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user