feat: user control fluid search

This commit is contained in:
shinya
2025-08-17 17:32:42 +08:00
parent 07c1da2a6b
commit 4ffd59c5d1
20 changed files with 312 additions and 246 deletions
+2 -48
View File
@@ -22,7 +22,6 @@ export default function SearchSuggestions({
onClose,
}: SearchSuggestionsProps) {
const [suggestions, setSuggestions] = useState<SuggestionItem[]>([]);
const [selectedIndex, setSelectedIndex] = useState(-1);
const containerRef = useRef<HTMLDivElement>(null);
// 防抖定时器
@@ -55,7 +54,6 @@ export default function SearchSuggestions({
})
);
setSuggestions(apiSuggestions);
setSelectedIndex(-1);
}
} catch (err: unknown) {
// 类型保护判断 err 是否是 Error 类型
@@ -63,12 +61,10 @@ export default function SearchSuggestions({
if (err.name !== 'AbortError') {
// 不是取消请求导致的错误才清空
setSuggestions([]);
setSelectedIndex(-1);
}
} else {
// 如果 err 不是 Error 类型,也清空提示
setSuggestions([]);
setSelectedIndex(-1);
}
}
}, []);
@@ -84,7 +80,6 @@ export default function SearchSuggestions({
fetchSuggestionsFromAPI(searchQuery);
} else {
setSuggestions([]);
setSelectedIndex(-1);
}
}, 300); //300ms
},
@@ -94,7 +89,6 @@ export default function SearchSuggestions({
useEffect(() => {
if (!query.trim() || !isVisible) {
setSuggestions([]);
setSelectedIndex(-1);
return;
}
debouncedFetchSuggestions(query);
@@ -107,43 +101,6 @@ export default function SearchSuggestions({
};
}, [query, isVisible, debouncedFetchSuggestions]);
// 键盘导航
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!isVisible || suggestions.length === 0) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setSelectedIndex((prev) =>
prev < suggestions.length - 1 ? prev + 1 : 0
);
break;
case 'ArrowUp':
e.preventDefault();
setSelectedIndex((prev) =>
prev > 0 ? prev - 1 : suggestions.length - 1
);
break;
case 'Enter':
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < suggestions.length) {
onSelect(suggestions[selectedIndex].text);
} else {
onSelect(query);
}
break;
case 'Escape':
e.preventDefault();
onClose();
break;
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isVisible, query, suggestions, selectedIndex, onSelect, onClose]);
// 点击外部关闭
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
@@ -171,14 +128,11 @@ export default function SearchSuggestions({
ref={containerRef}
className='absolute top-full left-0 right-0 z-50 mt-1 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 max-h-80 overflow-y-auto'
>
{suggestions.map((suggestion, index) => (
{suggestions.map((suggestion) => (
<button
key={`related-${suggestion.text}`}
onClick={() => onSelect(suggestion.text)}
onMouseEnter={() => setSelectedIndex(index)}
className={`w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150 flex items-center gap-3 ${
selectedIndex === index ? 'bg-gray-100 dark:bg-gray-700' : ''
}`}
className="w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150 flex items-center gap-3"
>
<span className='flex-1 text-sm text-gray-700 dark:text-gray-300 truncate'>
{suggestion.text}