import { useState } from 'react'; import { Search, X } from 'lucide-react'; /** * A filter box for a list that is already in the browser. * * Client-side, deliberately. Both screens load a whole collection in one request and hold it — a * calendar of a few hundred events, an address book of a few hundred cards — so filtering is a string * comparison over data already in hand. A server round trip per keystroke would be slower, would need a * search endpoint on the sidecar, and would show a spinner where a person expects a list to narrow. * * Matching is CASE- AND ACCENT-INSENSITIVE, and that second half is not decoration here: this owner's * contacts are Portuguese. Typing "jose" must find "José" or the box is useless for half the address * book. `NFD` splits a letter from its accent and the range strips what is left behind. * * Every term must match SOMEWHERE, not all in one field — so "ana lisboa" finds the Ana whose address is * in Lisbon, which is how people actually search a contact list. */ const fold = (value: string) => value.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase(); export const matches = (query: string, ...fields: (string | undefined)[]): boolean => { const terms = fold(query).split(/\s+/).filter(Boolean); if (!terms.length) return true; const haystack = fold(fields.filter(Boolean).join(' ')); return terms.every((term) => haystack.includes(term)); }; export function useFilter(placeholder: string) { const [query, setQuery] = useState(''); const input = (