import React, { useState, useEffect, useRef } from 'react' import { SearchIcon } from './SearchIcon' import { ClearButton } from './ClearButton' import { Input } from './ui' import { SearchSuggestions } from './SearchSuggestions' import { saveSearchQuery } from '@/lib/searchHistory' import { t } from '@/lib/i18n' interface SearchBarProps { value: string onChange: (value: string) => void placeholder?: string } function useSearchBarLocalValue(value: string): [string, (value: string) => void] { const [localValue, setLocalValue] = useState(value) useEffect(() => { setLocalValue(value) }, [value]) return [localValue, setLocalValue] } function useSearchBarFocusState(): [boolean, () => void, () => void] { const [isFocused, setIsFocused] = useState(false) const handleFocus = (): void => { setIsFocused(true) } const handleBlur = (): void => { setIsFocused(false) } return [isFocused, handleFocus, handleBlur] } interface UseSearchBarHandlersParams { setLocalValue: (value: string) => void onChange: (value: string) => void isFocused: boolean setShowSuggestions: (show: boolean) => void } function useSearchBarHandlers(params: UseSearchBarHandlersParams): { handleChange: (e: React.ChangeEvent) => void handleClear: () => void handleFocusWithSuggestions: () => void handleSelectSuggestion: (query: string) => void handleCloseSuggestions: () => void } { const handleChange = (e: React.ChangeEvent): void => { const newValue = e.target.value params.setLocalValue(newValue) params.onChange(newValue) if (newValue.trim() && params.isFocused) { params.setShowSuggestions(true) } } const handleClear = (): void => { params.setLocalValue('') params.onChange('') params.setShowSuggestions(false) } const handleFocusWithSuggestions = (): void => { params.setShowSuggestions(true) } const handleSelectSuggestion = (query: string): void => { params.setLocalValue(query) params.onChange(query) params.setShowSuggestions(false) void saveSearchQuery(query) } const handleCloseSuggestions = (): void => { params.setShowSuggestions(false) } return { handleChange, handleClear, handleFocusWithSuggestions, handleSelectSuggestion, handleCloseSuggestions, } } function useSearchBarState(value: string, onChange: (value: string) => void): { localValue: string showSuggestions: boolean handleChange: (e: React.ChangeEvent) => void handleClear: () => void handleFocus: () => void handleBlur: () => void handleSelectSuggestion: (query: string) => void handleCloseSuggestions: () => void setShowSuggestions: (show: boolean) => void } { const [localValue, setLocalValue] = useSearchBarLocalValue(value) const [showSuggestions, setShowSuggestions] = useState(false) const [isFocused, , handleBlur] = useSearchBarFocusState() const handlers = useSearchBarHandlers({ setLocalValue, onChange, isFocused, setShowSuggestions }) return { localValue, showSuggestions, handleChange: handlers.handleChange, handleClear: handlers.handleClear, handleFocus: handlers.handleFocusWithSuggestions, handleBlur, handleSelectSuggestion: handlers.handleSelectSuggestion, handleCloseSuggestions: handlers.handleCloseSuggestions, setShowSuggestions, } } function useClickOutsideHandler(containerRef: React.RefObject, showSuggestions: boolean, setShowSuggestions: (show: boolean) => void): void { useEffect(() => { if (!showSuggestions) { return } const handleClickOutside = (e: MouseEvent): void => { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setShowSuggestions(false) } } document.addEventListener('mousedown', handleClickOutside) return () => { document.removeEventListener('mousedown', handleClickOutside) } }, [showSuggestions, setShowSuggestions, containerRef]) } export const SearchBar = React.forwardRef( ({ value, onChange, placeholder }, ref): React.ReactElement => { const defaultPlaceholder = placeholder ?? t('search.placeholder') const containerRef = useRef(null) const { localValue, showSuggestions, handleChange, handleClear, handleFocus, handleBlur, handleSelectSuggestion, handleCloseSuggestions, setShowSuggestions, } = useSearchBarState(value, onChange) useClickOutsideHandler(containerRef, showSuggestions, setShowSuggestions) return (
} rightIcon={localValue ? : undefined} className="pr-10" role="search" aria-label={t('search.placeholder')} aria-autocomplete="list" aria-expanded={showSuggestions} /> {showSuggestions && ( )}
) } ) SearchBar.displayName = 'SearchBar'