Presentational only — it fetches nothing and owns no state, so the fetching rules and the ARIA rules can be read separately. onMouseDown, not onClick. The input's blur handler closes the list and blur fires before click, so a click handler never runs: the classic bug where a dropdown works perfectly by keyboard and is dead to the mouse. The plan's CSS guessed at token names like --color-surface. The real tokens are --bg-card, --border, --text-muted, --bg-secondary and --shadow-soft, and all five are redefined in the dark theme — invented names would have silently fallen back to hardcoded light values and broken dark mode. Local authority is rendered because there are many schools called 'St Mary's'; a list without it is unusable for exactly the query autosuggest exists to serve, which is what the test asserts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj
54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* The autosuggest dropdown. Presentational only — it fetches nothing and owns
|
|
* no state, so the fetching rules and the ARIA rules can be read separately.
|
|
*/
|
|
|
|
import type { Suggestion } from '@/lib/suggest';
|
|
import styles from './SuggestList.module.css';
|
|
|
|
/** The id the input's aria-activedescendant points at. */
|
|
export function suggestOptionId(id: string, index: number): string {
|
|
return `${id}-option-${index}`;
|
|
}
|
|
|
|
interface Props {
|
|
/** Shared with the input's aria-controls. */
|
|
id: string;
|
|
suggestions: Suggestion[];
|
|
activeIndex: number;
|
|
onPick: (s: Suggestion) => void;
|
|
onHover: (index: number) => void;
|
|
}
|
|
|
|
export function SuggestList({ id, suggestions, activeIndex, onPick, onHover }: Props) {
|
|
if (suggestions.length === 0) return null;
|
|
|
|
return (
|
|
<ul className={styles.list} id={id} role="listbox">
|
|
{suggestions.map((s, i) => (
|
|
<li
|
|
key={s.urn}
|
|
id={suggestOptionId(id, i)}
|
|
role="option"
|
|
aria-selected={i === activeIndex}
|
|
className={`${styles.option} ${i === activeIndex ? styles.active : ''}`}
|
|
/*
|
|
* onMouseDown, not onClick: the input's blur handler closes the list,
|
|
* and blur fires before click — so a click handler never runs. This
|
|
* is the classic autosuggest bug where the dropdown is unclickable
|
|
* with a mouse while working perfectly with a keyboard.
|
|
*/
|
|
onMouseDown={(e) => { e.preventDefault(); onPick(s); }}
|
|
onMouseEnter={() => onHover(i)}
|
|
>
|
|
<span className={styles.name}>{s.school_name}</span>
|
|
{/* Not decoration: there are many "St Mary's". */}
|
|
<span className={styles.meta}>{s.local_authority}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|