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:
+30
-31
@@ -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>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user