feat: optimize TopBar search with Cmd+K shortcut and improved styling

- Add global Cmd/Ctrl+K keyboard shortcut to focus search input
- Show keyboard shortcut hint (⌘K) in search input when empty
- Improve input styling: rounded-lg, softer borders, focus transitions
- Improve dropdown: rounded-lg, larger shadow, better spacing
- Larger result icons (h-8 w-8) with rounded-lg containers
- Refined history section with uppercase label and muted icon
- Search icon fades in on focus via group-focus-within

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
雨霖铃
2026-05-28 21:34:16 +08:00
parent 4955f617ee
commit 3400ffc9dc
+34 -11
View File
@@ -49,6 +49,19 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
}, []); }, []);
// Cmd/Ctrl+K 快捷键聚焦搜索框
useEffect(() => {
const handleGlobalKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
inputRef.current?.focus();
setShowResults(true);
}
};
document.addEventListener('keydown', handleGlobalKeyDown);
return () => document.removeEventListener('keydown', handleGlobalKeyDown);
}, []);
useEffect(() => { useEffect(() => {
storageUtil storageUtil
.get('app/searchHistory', []) .get('app/searchHistory', [])
@@ -153,8 +166,8 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
{/* 中间:搜索容器 */} {/* 中间:搜索容器 */}
<div ref={containerRef} className="flex-1 mx-4 max-w-md relative"> <div ref={containerRef} className="flex-1 mx-4 max-w-md relative">
<div className="relative"> <div className="relative group">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" /> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground/60 group-focus-within:text-muted-foreground transition-colors pointer-events-none" />
<input <input
ref={inputRef} ref={inputRef}
type="text" type="text"
@@ -168,8 +181,13 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
onFocus={() => setShowResults(true)} onFocus={() => setShowResults(true)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
aria-label={t('common_buttons_search')} aria-label={t('common_buttons_search')}
className="w-full h-9 pl-9 pr-8 text-sm rounded-md border border-input bg-muted/50 transition-all placeholder:text-muted-foreground focus:bg-background focus:outline-none focus:ring-1 focus:ring-ring focus:border-input" className="w-full h-9 pl-9 pr-16 text-sm rounded-lg border border-border/60 bg-muted/40 transition-all placeholder:text-muted-foreground/50 focus:bg-background focus:outline-none focus:ring-1 focus:ring-ring focus:border-input"
/> />
{!searchQuery && (
<kbd className="absolute right-3 top-1/2 -translate-y-1/2 hidden sm:inline-flex h-5 items-center gap-0.5 rounded border border-border/60 bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground/60 pointer-events-none">
K
</kbd>
)}
{searchQuery && ( {searchQuery && (
<button <button
type="button" type="button"
@@ -187,8 +205,8 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
{/* 动态联想结果卡片 */} {/* 动态联想结果卡片 */}
{showResults && (searchQuery.trim() || displayedHistory.length > 0) && ( {showResults && (searchQuery.trim() || displayedHistory.length > 0) && (
<div className="absolute top-full left-0 right-0 mt-1 bg-popover text-popover-foreground rounded-md shadow-md border border-border max-h-80 overflow-y-auto z-50 animate-in fade-in slide-in-from-top-1 duration-150"> <div className="absolute top-full left-0 right-0 mt-1.5 bg-popover text-popover-foreground rounded-lg shadow-lg border border-border max-h-80 overflow-y-auto z-50 animate-in fade-in slide-in-from-top-2 duration-150">
<ul role="listbox" className="p-1"> <ul role="listbox" className="p-1.5">
{searchQuery.trim() ? ( {searchQuery.trim() ? (
searchResults.length > 0 ? ( searchResults.length > 0 ? (
searchResults.map((feature, index) => ( searchResults.map((feature, index) => (
@@ -198,20 +216,25 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
aria-selected={selectedIndex === index} aria-selected={selectedIndex === index}
onClick={() => handleSelectFeature(feature)} onClick={() => handleSelectFeature(feature)}
className={cn( className={cn(
'flex items-center gap-3 px-2.5 py-2 rounded-sm cursor-pointer text-sm transition-colors', 'flex items-center gap-3 px-3 py-2.5 rounded-md cursor-pointer text-sm transition-colors',
selectedIndex === index selectedIndex === index
? 'bg-accent text-accent-foreground' ? 'bg-accent text-accent-foreground'
: 'hover:bg-muted/60', : 'hover:bg-muted/60',
)} )}
> >
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground"> <div
className={cn(
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
'bg-muted/80 text-muted-foreground',
)}
>
{feature.icon && <feature.icon className="h-4 w-4" />} {feature.icon && <feature.icon className="h-4 w-4" />}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="font-medium text-foreground truncate"> <p className="font-medium text-foreground truncate">
{t(feature.labelKey)} {t(feature.labelKey)}
</p> </p>
<p className="text-xs text-muted-foreground truncate"> <p className="text-xs text-muted-foreground truncate mt-0.5">
{t(feature.descriptionKey)} {t(feature.descriptionKey)}
</p> </p>
</div> </div>
@@ -224,7 +247,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
) )
) : ( ) : (
<> <>
<div className="px-2.5 py-1.5 text-xs font-semibold tracking-wider text-muted-foreground/80"> <div className="px-3 py-1.5 text-[11px] font-semibold tracking-wider text-muted-foreground/60 uppercase">
{t('common:buttons.recentSearch')} {t('common:buttons.recentSearch')}
</div> </div>
{displayedHistory.map((item, index) => ( {displayedHistory.map((item, index) => (
@@ -237,13 +260,13 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
setSelectedIndex(-1); setSelectedIndex(-1);
}} }}
className={cn( className={cn(
'flex items-center gap-3 px-2.5 py-2 rounded-sm 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
? 'bg-accent text-accent-foreground' ? 'bg-accent text-accent-foreground'
: 'hover:bg-muted/60', : 'hover:bg-muted/60',
)} )}
> >
<History className="h-4 w-4 text-muted-foreground shrink-0" /> <History className="h-4 w-4 text-muted-foreground/60 shrink-0" />
<span className="truncate">{item}</span> <span className="truncate">{item}</span>
</li> </li>
))} ))}