146 lines
5.6 KiB
TypeScript
146 lines
5.6 KiB
TypeScript
import { Link, useLocation } from "wouter";
|
||
import { Menu, X, Search } from "lucide-react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { useState, useRef, useEffect } from "react";
|
||
import folxLogo from "@assets/folx_MT_poz_b_1772296729169.png";
|
||
import PushNotificationBell from "./push-notification-bell";
|
||
|
||
const navItems = [
|
||
{ label: "Start", href: "/" },
|
||
{ label: "News", href: "/category/News" },
|
||
{ label: "Video", href: "/videos" },
|
||
{ label: "Empfang", href: "/empfang-folx-tv" },
|
||
{ label: "Kontakt", href: "/kontakt" },
|
||
];
|
||
|
||
export default function Header() {
|
||
const [mobileOpen, setMobileOpen] = useState(false);
|
||
const [searchOpen, setSearchOpen] = useState(false);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
const [location, navigate] = useLocation();
|
||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||
|
||
useEffect(() => {
|
||
if (searchOpen && searchInputRef.current) {
|
||
searchInputRef.current.focus();
|
||
}
|
||
}, [searchOpen]);
|
||
|
||
const handleSearch = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (searchQuery.trim().length >= 2) {
|
||
const q = encodeURIComponent(searchQuery.trim());
|
||
setSearchOpen(false);
|
||
setSearchQuery("");
|
||
setMobileOpen(false);
|
||
navigate(`/search?q=${q}`);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<header className="sticky top-0 z-50 bg-background/95 backdrop-blur-md border-b border-card-border" data-testid="header">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="flex items-center justify-between gap-4 h-16">
|
||
<Link href="/">
|
||
<div className="flex items-center gap-2 cursor-pointer" data-testid="link-logo">
|
||
<img src={folxLogo} alt="FOLX TV – Schlager & Volksmusik News" className="h-32 w-auto object-contain py-1" />
|
||
</div>
|
||
</Link>
|
||
|
||
<nav className="hidden md:flex items-center gap-1" data-testid="nav-desktop">
|
||
{navItems.map((item) => (
|
||
<Link key={item.href} href={item.href}>
|
||
<Button
|
||
variant={location === item.href ? "default" : "ghost"}
|
||
size="sm"
|
||
data-testid={`link-nav-${item.label.toLowerCase().replace(/\s/g, "-")}`}
|
||
>
|
||
{item.label}
|
||
</Button>
|
||
</Link>
|
||
))}
|
||
</nav>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<PushNotificationBell />
|
||
{searchOpen ? (
|
||
<form onSubmit={handleSearch} className="flex items-center gap-1" data-testid="form-header-search">
|
||
<input
|
||
ref={searchInputRef}
|
||
type="text"
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
placeholder="Suchen..."
|
||
className="w-32 sm:w-48 px-3 py-1.5 bg-card border border-card-border rounded-md text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
||
data-testid="input-header-search"
|
||
/>
|
||
<Button
|
||
type="button"
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-8 w-8"
|
||
onClick={() => { setSearchOpen(false); setSearchQuery(""); }}
|
||
data-testid="button-close-search"
|
||
>
|
||
<X className="w-4 h-4" />
|
||
</Button>
|
||
</form>
|
||
) : (
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-8 w-8"
|
||
onClick={() => setSearchOpen(true)}
|
||
data-testid="button-open-search"
|
||
>
|
||
<Search className="w-4 h-4" />
|
||
</Button>
|
||
)}
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="md:hidden"
|
||
onClick={() => setMobileOpen(!mobileOpen)}
|
||
data-testid="button-mobile-menu"
|
||
>
|
||
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{mobileOpen && (
|
||
<div className="md:hidden border-t border-card-border bg-card" data-testid="nav-mobile">
|
||
<nav className="flex flex-col p-4 gap-1">
|
||
<form onSubmit={handleSearch} className="mb-2" data-testid="form-mobile-search">
|
||
<div className="relative">
|
||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||
<input
|
||
type="text"
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
placeholder="Artikel, Videos suchen..."
|
||
className="w-full pl-9 pr-3 py-2 bg-background border border-card-border rounded-md text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
||
data-testid="input-mobile-search"
|
||
/>
|
||
</div>
|
||
</form>
|
||
{navItems.map((item) => (
|
||
<Link key={item.href} href={item.href}>
|
||
<Button
|
||
variant={location === item.href ? "default" : "ghost"}
|
||
className="w-full justify-start"
|
||
onClick={() => setMobileOpen(false)}
|
||
data-testid={`link-mobile-${item.label.toLowerCase().replace(/\s/g, "-")}`}
|
||
>
|
||
{item.label}
|
||
</Button>
|
||
</Link>
|
||
))}
|
||
</nav>
|
||
</div>
|
||
)}
|
||
</header>
|
||
);
|
||
}
|