/* * @Author: ZhaoYing * @Date: 2026-02-02 15:24:23 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-03-25 11:15:26 */ /** * SearchInput Component * * A search input component with debounce and throttle support: * - Configurable debounce delay for search optimization * - Optional throttle mode for rate limiting * - Search icon prefix * - Clear button * - Internationalized placeholder * * @component */ import { useState, type FC, useCallback, useRef } from 'react'; import { Input, type InputProps } from 'antd'; import { useTranslation } from 'react-i18next'; /** Props interface for SearchInput component */ interface SearchInputProps extends InputProps { /** Placeholder text */ placeholder?: string; /** Callback fired when search value changes */ onSearch?: (value: string) => void; /** Debounce delay in milliseconds (default: 300) */ debounceDelay?: number; /** Throttle delay in milliseconds (overrides debounce if set) */ throttleDelay?: number; /** Default input value */ defaultValue?: string; /** Custom styles */ style?: Record; /** Additional CSS classes */ className?: string; /** Input size */ size?: InputProps['size'] /** Maximum length of the input value */ maxLength?: number; } /** Search input component with debounce and throttle support */ const SearchInput: FC = ({ placeholder, onSearch, debounceDelay = 300, throttleDelay, defaultValue = undefined, className = '', variant = 'filled', ...props }) => { const { t } = useTranslation(); const [value, setValue] = useState(defaultValue); const timerRef = useRef(null); const throttleRef = useRef(false); const lastCallRef = useRef(0); /** Debounce function - delays callback execution until after delay period */ const debounce = useCallback( void>(callback: T, delay: number) => { return (...args: Parameters) => { if (timerRef.current) { window.clearTimeout(timerRef.current); } timerRef.current = window.setTimeout(() => { callback(...args); }, delay); }; }, []); /** Throttle function - limits callback execution to once per delay period */ const throttle = useCallback( void>(callback: T, delay: number) => { return (...args: Parameters) => { const now = Date.now(); if (!throttleRef.current && now - lastCallRef.current >= delay) { lastCallRef.current = now; throttleRef.current = true; callback(...args); window.setTimeout(() => { throttleRef.current = false; }, delay); } }; }, []); /** Handle input change with debounce or throttle based on configuration */ const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value; setValue(newValue); /** Decide whether to use debounce or throttle based on throttleDelay setting */ if (onSearch) { if (throttleDelay) { const throttledSearch = throttle(() => { onSearch(newValue); }, throttleDelay); throttledSearch(); } else { const debouncedSearch = debounce(() => { onSearch(newValue); }, debounceDelay); debouncedSearch(); } } }; return ( } placeholder={placeholder || t('user.searchPlaceholder')} value={value} onChange={handleChange} style={{ width: '300px' }} className={className} variant={variant} {...props} /> ); }; export default SearchInput;