Files
AfiS-Aktensystem/web/app/(app)/suche/SearchBox.tsx
T
Harlekin e0b03ea336
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
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

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>
)
}