feat: improve search history to show recently used tools with direct navigation

- Store feature keys (PageType) in history instead of translated labels
- Show icon + name + description in history items (same as search results)
- Click or Enter on history item navigates directly to the tool page
- Remove History icon import (no longer needed)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
雨霖铃
2026-05-28 21:38:18 +08:00
parent 3400ffc9dc
commit 5175677b24
+30 -31
View File
@@ -1,15 +1,5 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { import { ArrowLeft, ExternalLink, Monitor, Moon, Search, Settings, Sun, X } from 'lucide-react';
ArrowLeft,
ExternalLink,
History,
Monitor,
Moon,
Search,
Settings,
Sun,
X,
} from 'lucide-react';
import { useRouter } from '@/providers/RouterProvider'; import { useRouter } from '@/providers/RouterProvider';
import { useThemeMode } from '@/providers/ThemeModeProvider'; import { useThemeMode } from '@/providers/ThemeModeProvider';
import { FeatureConfig, FEATURES } from '@/config/features'; import { FeatureConfig, FEATURES } from '@/config/features';
@@ -85,12 +75,15 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
const displayedHistory = useMemo(() => { const displayedHistory = useMemo(() => {
if (searchQuery.trim()) return []; if (searchQuery.trim()) return [];
return searchHistory.slice(0, SEARCH_HISTORY_DISPLAY); return searchHistory
.slice(0, SEARCH_HISTORY_DISPLAY)
.map((key) => ({ key, feature: FEATURES.find((f) => f.key === key) }))
.filter((item) => item.feature && item.feature.key !== 'dashboard');
}, [searchHistory, searchQuery]); }, [searchHistory, searchQuery]);
const saveToHistory = async (query: string) => { const saveToHistory = async (featureKey: string) => {
if (!query.trim()) return; if (!featureKey.trim()) return;
const nextHistory = [query, ...searchHistory.filter((h) => h !== query)].slice( const nextHistory = [featureKey, ...searchHistory.filter((h) => h !== featureKey)].slice(
0, 0,
SEARCH_HISTORY_LIMIT, SEARCH_HISTORY_LIMIT,
); );
@@ -100,7 +93,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
const handleSelectFeature = (feature: FeatureConfig) => { const handleSelectFeature = (feature: FeatureConfig) => {
navigateTo(feature.key); navigateTo(feature.key);
saveToHistory(t(feature.labelKey)); saveToHistory(feature.key);
setSearchQuery(''); setSearchQuery('');
setShowResults(false); setShowResults(false);
}; };
@@ -127,14 +120,9 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
if (searchQuery.trim()) { if (searchQuery.trim()) {
handleSelectFeature(searchResults[selectedIndex]); handleSelectFeature(searchResults[selectedIndex]);
} else { } else {
const selectedQuery = displayedHistory[selectedIndex]; const selected = displayedHistory[selectedIndex];
if (selectedQuery) { if (selected?.feature) {
setSearchQuery(selectedQuery); handleSelectFeature(selected.feature);
setSelectedIndex(-1);
const matched = FEATURES.find(
(f) => f.key !== 'dashboard' && t(f.labelKey) === selectedQuery,
);
if (matched) handleSelectFeature(matched);
} }
} }
} else if (searchQuery.trim() && searchResults.length > 0) { } else if (searchQuery.trim() && searchResults.length > 0) {
@@ -252,13 +240,10 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
</div> </div>
{displayedHistory.map((item, index) => ( {displayedHistory.map((item, index) => (
<li <li
key={item} key={item.key}
role="option" role="option"
aria-selected={selectedIndex === index} aria-selected={selectedIndex === index}
onClick={() => { onClick={() => item.feature && handleSelectFeature(item.feature)}
setSearchQuery(item);
setSelectedIndex(-1);
}}
className={cn( className={cn(
'flex items-center gap-3 px-3 py-2 rounded-md cursor-pointer text-sm transition-colors', 'flex items-center gap-3 px-3 py-2 rounded-md cursor-pointer text-sm transition-colors',
selectedIndex === index selectedIndex === index
@@ -266,8 +251,22 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
: 'hover:bg-muted/60', : 'hover:bg-muted/60',
)} )}
> >
<History className="h-4 w-4 text-muted-foreground/60 shrink-0" /> <div
<span className="truncate">{item}</span> className={cn(
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
'bg-muted/80 text-muted-foreground',
)}
>
{item.feature?.icon && <item.feature.icon className="h-4 w-4" />}
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-foreground truncate">
{item.feature && t(item.feature.labelKey)}
</p>
<p className="text-xs text-muted-foreground truncate mt-0.5">
{item.feature && t(item.feature.descriptionKey)}
</p>
</div>
</li> </li>
))} ))}
</> </>