126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
/*
|
|
* @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<string, string | number>;
|
|
/** 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<SearchInputProps> = ({
|
|
placeholder,
|
|
onSearch,
|
|
debounceDelay = 300,
|
|
throttleDelay,
|
|
defaultValue = undefined,
|
|
className = '',
|
|
variant = 'filled',
|
|
...props
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const [value, setValue] = useState(defaultValue);
|
|
const timerRef = useRef<number | null>(null);
|
|
const throttleRef = useRef<boolean>(false);
|
|
const lastCallRef = useRef<number>(0);
|
|
|
|
/** Debounce function - delays callback execution until after delay period */
|
|
const debounce = useCallback(<T extends (...args: any[]) => void>(callback: T, delay: number) => {
|
|
return (...args: Parameters<T>) => {
|
|
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(<T extends (...args: any[]) => void>(callback: T, delay: number) => {
|
|
return (...args: Parameters<T>) => {
|
|
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<HTMLInputElement>) => {
|
|
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 (
|
|
<Input
|
|
allowClear
|
|
prefix={<div className="rb:size-4 rb:bg-[url('@/assets/images/search.svg')] rb:mr-1"></div>}
|
|
placeholder={placeholder || t('user.searchPlaceholder')}
|
|
value={value}
|
|
onChange={handleChange}
|
|
style={{ width: '300px' }}
|
|
className={className}
|
|
variant={variant}
|
|
{...props}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default SearchInput; |