Add a global search feature to find articles and videos
Introduces a global search functionality with a dedicated search page and integrates a search icon/input into the header and mobile menu. The search queries articles by title, excerpt, and content, and videos by title and description. Updates are made to `App.tsx` to include the new route, and `header.tsx` to implement the search UI and logic. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 90e58e4e-8d41-41e7-bc45-74196314bd78 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/jdAEdU5 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { Menu, X, Search } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useState } from "react";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import folxLogo from "@assets/folx_MT_poz_b_1772296729169.png";
|
||||
|
||||
const navItems = [
|
||||
@@ -15,7 +15,26 @@ const navItems = [
|
||||
|
||||
export default function Header() {
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [location] = useLocation();
|
||||
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) {
|
||||
navigate(`/search?q=${encodeURIComponent(searchQuery.trim())}`);
|
||||
setSearchOpen(false);
|
||||
setSearchQuery("");
|
||||
setMobileOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 bg-background/95 backdrop-blur-md border-b border-card-border" data-testid="header">
|
||||
@@ -42,6 +61,39 @@ export default function Header() {
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{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"
|
||||
@@ -58,6 +110,19 @@ export default function Header() {
|
||||
{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
|
||||
|
||||
Reference in New Issue
Block a user