This commit is contained in:
Andras Schmelczer 2026-02-14 12:53:29 +00:00
parent 3a3f899ea2
commit 128b3191e7
68 changed files with 28060 additions and 1152 deletions

View file

@ -0,0 +1,123 @@
import type React from 'react';
import type { SearchResult } from '../../hooks/useLocationSearch';
import { SearchIcon } from './icons/SearchIcon';
import { MapPinIcon } from './icons/MapPinIcon';
interface SearchHook {
query: string;
results: SearchResult[];
activeIndex: number;
setActiveIndex: (idx: number) => void;
open: boolean;
setOpen: (open: boolean) => void;
handleInputChange: (value: string) => void;
handleKeyDown: (
e: React.KeyboardEvent,
onSelect: (result: SearchResult) => void,
) => void;
}
interface PlaceSearchInputProps {
search: SearchHook;
onSelect: (result: SearchResult) => void;
loading?: boolean;
placeholder?: string;
size?: 'sm' | 'xs';
inputClassName?: string;
inputRef?: React.Ref<HTMLInputElement>;
onInputChange?: () => void;
}
export function PlaceSearchInput({
search,
onSelect,
loading,
placeholder,
size = 'sm',
inputClassName,
inputRef,
onInputChange,
}: PlaceSearchInputProps) {
const sm = size === 'sm';
const iconSize = sm ? 'w-4 h-4' : 'w-3 h-3';
const spinnerSize = sm ? 'w-4 h-4' : 'w-3 h-3';
return (
<div className="relative flex-1 min-w-0">
<input
ref={inputRef}
type="text"
value={search.query}
onChange={(e) => {
search.handleInputChange(e.target.value);
onInputChange?.();
}}
onFocus={() => {
if (search.results.length > 0) search.setOpen(true);
}}
onKeyDown={(e) => search.handleKeyDown(e, onSelect)}
placeholder={placeholder}
className={inputClassName}
/>
{loading && (
<div
className={`absolute right-2 top-1/2 -translate-y-1/2 ${spinnerSize} border-2 border-warm-300 dark:border-warm-600 border-t-teal-500 rounded-full animate-spin`}
/>
)}
{search.open && search.results.length > 0 && (
<div
className={`absolute top-full left-0 right-0 mt-1 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 z-20`}
>
{search.results.map((result, idx) => (
<button
key={
result.type === 'postcode'
? `pc-${result.label}`
: `pl-${result.name}-${result.lat}`
}
type="button"
className={`w-full text-left flex items-center cursor-pointer ${
sm ? 'px-3 py-2 gap-2 text-sm' : 'px-2 py-1.5 gap-1.5 text-xs'
} ${
idx === search.activeIndex
? 'bg-teal-50 dark:bg-teal-900/30'
: 'hover:bg-warm-50 dark:hover:bg-warm-700'
}`}
onMouseEnter={() => search.setActiveIndex(idx)}
onMouseDown={(e) => {
e.preventDefault();
onSelect(result);
}}
>
{result.type === 'postcode' ? (
<>
<SearchIcon
className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`}
/>
<span className="text-warm-700 dark:text-warm-200">{result.label}</span>
</>
) : (
<>
<MapPinIcon
className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`}
/>
<span className="text-warm-700 dark:text-warm-200">
{result.name}
{result.city && (
<span className="text-warm-400 dark:text-warm-500">
{' '}
({result.city})
</span>
)}
</span>
</>
)}
</button>
))}
</div>
)}
</div>
);
}