SPB Git

spb/trouve-ka Public

Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com

Python 76.8% TypeScript 15.7% SQL 3.9% Shell 1.4% CSS 1.3% Dockerfile 0.7%
3.8 KB · 129 lines tsx
Raw Blame History
1/**2 * Trouve-KA — barre de recherche (homepage et en-tête des résultats)3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 */67"use client";89import { useRouter } from "next/navigation";10import { useEffect, useState, type FormEvent } from "react";11import { cn } from "@/lib/utils";1213interface SearchBarProps {14  initialQuery?: string;15  autoFocus?: boolean;16  size?: "md" | "lg";17  /** `inline` : bouton dans la barre; `below` : grand bouton sous la barre. */18  buttonPlacement?: "inline" | "below";19  /** Suggestions québécoises affichées en placeholder, en rotation douce. */20  placeholderSuggestions?: string[];21  className?: string;22}2324const SUGGESTION_INTERVAL_MS = 4000;2526export function SearchBar({27  initialQuery = "",28  autoFocus = false,29  size = "md",30  buttonPlacement = "inline",31  placeholderSuggestions,32  className,33}: SearchBarProps) {34  const router = useRouter();35  const [query, setQuery] = useState(initialQuery);36  const [suggestionIndex, setSuggestionIndex] = useState(0);3738  const suggestionCount = placeholderSuggestions?.length ?? 0;3940  useEffect(() => {41    if (suggestionCount < 2) return;42    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;43    const timer = window.setInterval(44      () => setSuggestionIndex((index) => (index + 1) % suggestionCount),45      SUGGESTION_INTERVAL_MS,46    );47    return () => window.clearInterval(timer);48  }, [suggestionCount]);4950  const placeholder =51    suggestionCount > 052      ? `Essaie « ${placeholderSuggestions![suggestionIndex % suggestionCount]} »…`53      : "Rechercher…";5455  function onSubmit(event: FormEvent) {56    event.preventDefault();57    const q = query.trim();58    if (!q) return;59    router.push(`/search?q=${encodeURIComponent(q)}`);60  }6162  const isLarge = size === "lg";6364  return (65    <form role="search" onSubmit={onSubmit} className={cn("w-full", className)}>66      <label htmlFor="champ-recherche" className="sr-only">67        Rechercher sur le web québécois68      </label>69      <div className="relative">70        <svg71          aria-hidden="true"72          viewBox="0 0 24 24"73          fill="none"74          stroke="currentColor"75          strokeWidth="2"76          strokeLinecap="round"77          strokeLinejoin="round"78          className={cn(79            "pointer-events-none absolute top-1/2 -translate-y-1/2 text-ink-3",80            isLarge ? "left-5 h-5 w-5" : "left-3.5 h-4 w-4",81          )}82        >83          <circle cx="11" cy="11" r="8" />84          <path d="m21 21-4.35-4.35" />85        </svg>86        <input87          id="champ-recherche"88          type="search"89          name="q"90          value={query}91          onChange={(event) => setQuery(event.target.value)}92          placeholder={placeholder}93          autoComplete="off"94          spellCheck={false}95          // eslint-disable-next-line jsx-a11y/no-autofocus96          autoFocus={autoFocus}97          className={cn(98            "field appearance-none",99            isLarge ? "h-14 pl-12 text-lg" : "h-10 pl-10 text-sm",100            buttonPlacement === "inline"101              ? isLarge102                ? "pr-32"103                : "pr-28"104              : isLarge105                ? "pr-6"106                : "pr-4",107          )}108        />109        {buttonPlacement === "inline" ? (110          <button111            type="submit"112            className={cn(113              "gk-display absolute right-1.5 top-1/2 -translate-y-1/2 rounded-[5px] border-[1.5px] border-ink bg-ink font-bold text-lime transition-colors hover:bg-green-deep active:translate-y-[calc(-50%+1px)]",114              isLarge ? "h-11 px-6 text-base" : "h-7 px-4 text-[13px]",115            )}116          >117            Chercher118          </button>119        ) : null}120      </div>121      {buttonPlacement === "below" ? (122        <button type="submit" className="btn btn-primary mt-8 px-9">123          Chercher124        </button>125      ) : null}126    </form>127  );128}129