This commit is contained in:
Andras Schmelczer 2026-06-10 22:25:15 +01:00
parent 1241132095
commit 54a5c3ca9a
28 changed files with 826 additions and 422 deletions

View file

@ -18,6 +18,12 @@ interface SearchHook {
handleInputChange: (value: string) => void;
handleKeyDown: (e: React.KeyboardEvent, onSelect: (result: SearchResult) => void) => void;
showEmptySearches: () => void;
close?: () => void;
}
/** Addresses arrive in raw ALL-CAPS Land Registry casing; title-case for display. */
function titleCaseAddress(address: string): string {
return address.toLowerCase().replace(/(^|[\s\-/(])([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
}
interface PlaceSearchInputProps {
@ -65,6 +71,9 @@ export function PlaceSearchInput({
const dropdown = showDropdown && (
<div
className={`bg-white dark:bg-warm-800 rounded shadow-lg border border-warm-200 dark:border-warm-700 ${sm ? 'max-h-64' : 'max-h-48'} overflow-y-auto`}
// Keep focus on the input while interacting with the list (incl. its
// scrollbar) so the blur-close below doesn't fire mid-click.
onMouseDown={(e) => e.preventDefault()}
style={
portal && dropdownPos
? {
@ -116,8 +125,11 @@ export function PlaceSearchInput({
) : result.type === 'address' ? (
<>
<HouseIcon className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`} />
<span className="min-w-0 text-warm-700 dark:text-warm-200">
<span className="block truncate">{result.address}</span>
<span
className="min-w-0 text-warm-700 dark:text-warm-200"
title={`${titleCaseAddress(result.address)}, ${result.postcode}`}
>
<span className="block truncate">{titleCaseAddress(result.address)}</span>
<span className="block truncate text-warm-400 dark:text-warm-500">
{result.postcode}
</span>
@ -126,7 +138,10 @@ export function PlaceSearchInput({
) : (
<>
<MapPinIcon className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`} />
<span className="text-warm-700 dark:text-warm-200">
<span
className="text-warm-700 dark:text-warm-200"
title={result.city ? `${result.name} (${result.city})` : result.name}
>
{result.name}
{result.city && (
<span className="text-warm-400 dark:text-warm-500"> ({result.city})</span>
@ -154,6 +169,9 @@ export function PlaceSearchInput({
onFocus={() => {
search.showEmptySearches();
}}
// Without this, instances whose parents lack outside-click handling
// leave a detached dropdown floating over the map.
onBlur={() => search.close?.()}
onKeyDown={(e) => search.handleKeyDown(e, onSelect)}
aria-label={ariaLabel ?? placeholder}
placeholder={placeholder}