Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef, useState, useTransition } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { searchText } from '@/lib/i18n/shell'
|
|
import { toSearchParams, type SearchFilters } from '@/lib/search/query'
|
|
|
|
const DEBOUNCE_MS = 250
|
|
|
|
/**
|
|
* Das Suchfeld.
|
|
*
|
|
* Tippen schreibt in die Adresse, mit 250 ms Verzögerung - dadurch ist jeder
|
|
* Zustand teilbar und der Zurück-Knopf tut das Richtige. Während die neue
|
|
* Seite lädt, bleiben die alten Treffer stehen und werden gedimmt, statt zu
|
|
* verschwinden. Siehe docs/09-performance-und-ux.md.
|
|
*
|
|
* Einen `AbortController` braucht es hier nicht: der Router verwirft eine
|
|
* überholte Navigation von selbst, und `useTransition` sagt uns, ob noch eine
|
|
* läuft.
|
|
*/
|
|
export function SearchBox({ filters }: { filters: SearchFilters }) {
|
|
const router = useRouter()
|
|
const [text, setText] = useState(filters.q)
|
|
const [pending, startTransition] = useTransition()
|
|
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
|
|
// Kommt der Begriff von aussen (Zurück-Knopf, gespeicherte Ansicht), muss
|
|
// das Feld mitgehen.
|
|
useEffect(() => {
|
|
setText(filters.q)
|
|
}, [filters.q])
|
|
|
|
function suchen(wert: string) {
|
|
setText(wert)
|
|
if (timer.current) clearTimeout(timer.current)
|
|
timer.current = setTimeout(() => {
|
|
startTransition(() => {
|
|
router.replace(`/suche?${toSearchParams({ ...filters, q: wert, cursor: undefined })}`)
|
|
})
|
|
}, DEBOUNCE_MS)
|
|
}
|
|
|
|
return (
|
|
<div className="mt-4" data-pending={pending ? '' : undefined}>
|
|
<input
|
|
type="search"
|
|
value={text}
|
|
onChange={(event) => suchen(event.target.value)}
|
|
placeholder={searchText.placeholder}
|
|
aria-label={searchText.placeholder}
|
|
autoFocus
|
|
className="w-full max-w-xl field"
|
|
/>
|
|
</div>
|
|
)
|
|
}
|