import React, { forwardRef, useCallback, useState } from 'react' import { AppIcon } from '@codeleap/styles' import { ForwardRefComponentWithDefaultProps } from '../../types' import { TextInputProps, TextInput } from '../TextInput' import { TypeGuards } from '@codeleap/types' import { TextInput as RNTextInput } from 'react-native' /** * `onSearchChange` fires after the debounce delay (default 500 ms), while `onValueChange` * fires on every keystroke — use `onTypingChange` to show a loading indicator between the two. * If `debounce` is `undefined` (not 0), `onSearchChange` fires synchronously with no timer. */ export type SearchInputProps = { onTypingChange?: (isTyping: boolean) => void onSearchChange: (search: string) => void onValueChange?: (search: string) => void onClear?: () => void showClear?: (search: string) => boolean debugName: string debounce?: number clearIcon?: AppIcon searchIcon?: AppIcon placeholder?: string } & Partial type Component = React.ForwardRefExoticComponent & { defaultProps?: Partial } export const SearchInput: Component = forwardRef((props: SearchInputProps, ref) => { const { debugName, onClear, onSearchChange, onTypingChange, clearIcon, searchIcon, debounce, placeholder, value, onValueChange, showClear, ...others } = { ...SearchInput.defaultProps, ...props, } /** When `value` + `onValueChange` are both provided the component is fully controlled — the internal `useState` is skipped entirely, so the caller owns the displayed text and must update it on every keystroke. */ const [search, setSearch] = !TypeGuards.isNil(value) && !!onValueChange ? [value, onValueChange] : useState('') const setSearchTimeout = React.useRef | null>(null) const handleChangeSearch = useCallback((value: string) => { onTypingChange?.(true) setSearch(value) if (TypeGuards.isNil(debounce)) { onSearchChange?.(value) } else { if (setSearchTimeout.current) { clearTimeout(setSearchTimeout.current) setSearchTimeout.current = null } setSearchTimeout.current = setTimeout(() => { onSearchChange(value) onTypingChange?.(false) }, debounce ?? 0) } }, [onSearchChange, onTypingChange]) const handleClear = useCallback(() => { setSearch('') onSearchChange?.('') onClear?.() }, []) return ( ) }) SearchInput.defaultProps = { debounce: 500, clearIcon: 'x' as AppIcon, searchIcon: 'search' as AppIcon, showClear: (s) => !!s?.trim?.() } as Partial