From 177970988ac403aa80979b6a6ee19528818f8ab9 Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Wed, 11 Feb 2026 19:46:28 +0000 Subject: [PATCH] feat(sidebar): add session search filter with Ctrl+K shortcut - Search input appears when 4+ sessions exist - Filters sessions by label/key in real-time - Ctrl+K / Cmd+K keyboard shortcut to focus search - Clear button and 'no results' state - i18n support (EN + FR) --- src/components/Sidebar.tsx | 58 ++++++++++++++++++++++++++++++++++++-- src/lib/i18n.ts | 4 +++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index b3ecf7b..8ecc652 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,5 @@ -import { MessageSquare, X, Sparkles } from 'lucide-react'; +import { useState, useMemo, useRef, useEffect } from 'react'; +import { MessageSquare, X, Sparkles, Search } from 'lucide-react'; import type { Session } from '../types'; import { useT } from '../hooks/useLocale'; @@ -12,6 +13,29 @@ interface Props { export function Sidebar({ sessions, activeSession, onSwitch, open, onClose }: Props) { const t = useT(); + const [filter, setFilter] = useState(''); + const searchRef = useRef(null); + + // Keyboard shortcut: Ctrl+K or Cmd+K to focus search when sidebar is open + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + searchRef.current?.focus(); + } + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, []); + + const filtered = useMemo(() => { + if (!filter.trim()) return sessions; + const q = filter.toLowerCase(); + return sessions.filter(s => + (s.label || s.key).toLowerCase().includes(q) + ); + }, [sessions, filter]); + return ( <> {open &&
} @@ -30,11 +54,41 @@ export function Sidebar({ sessions, activeSession, onSwitch, open, onClose }: Pr
+ + {/* Session search */} + {sessions.length > 3 && ( +
+
+ + setFilter(e.target.value)} + placeholder={t('sidebar.search')} + aria-label={t('sidebar.search')} + className="w-full pl-8 pr-3 py-1.5 rounded-xl border border-white/8 bg-zinc-800/30 text-xs text-zinc-300 placeholder:text-zinc-500 outline-none focus:ring-1 focus:ring-cyan-400/30 transition-all" + /> + {filter && ( + + )} +
+
+ )} +
{sessions.length === 0 && (
{t('sidebar.empty')}
)} - {sessions.map(s => { + {sessions.length > 0 && filtered.length === 0 && ( +
{t('sidebar.noResults')}
+ )} + {filtered.map(s => { const isActive = s.key === activeSession; return (