1- import { useState , useMemo } from "react" ;
2- import { Input } from "@/webview/components/ui/input" ;
3- import { ScrollArea , ScrollBar } from "@/webview/components/ui/scroll-area" ;
1+ import { useState , useMemo , useCallback } from "react" ;
42import type { SessionSummary } from "@/webview/types" ;
5- import { MessageCircle , Search } from "lucide-react" ;
3+ import { MessageCircle , PanelRight , Search } from "lucide-react" ;
64import { Empty , EmptyDescription , EmptyMedia , EmptyHeader , EmptyTitle } from "@/webview/components/ui/empty" ;
75import { cn } from "@/webview/lib/utils" ;
86import { Item , ItemActions , ItemContent , ItemGroup } from "./ui/item" ;
7+ import {
8+ Drawer ,
9+ DrawerClose ,
10+ DrawerContent ,
11+ DrawerFooter ,
12+ DrawerHeader ,
13+ DrawerTitle ,
14+ DrawerTrigger ,
15+ } from "@/webview/components/ui/drawer" ;
16+ import { Button } from "@/webview/components/ui/button" ;
17+ import { InputGroup , InputGroupAddon , InputGroupInput } from "@/webview/components/ui/input-group" ;
918
10- interface SessionDropdownProps {
19+ interface SessionListProps {
1120 sessions : SessionSummary [ ] ;
1221 activeSessionId : string | null ;
1322 onSelect : ( sessionId : string ) => void ;
14- onClose : ( ) => void ;
23+ onCreateNewSession : ( ) => void ;
1524}
1625
1726function formatTime ( dateString : string ) : string {
@@ -27,8 +36,13 @@ function formatTime(dateString: string): string {
2736 return `${ date . getMonth ( ) + 1 } /${ date . getDate ( ) } ` ;
2837}
2938
30- export default function SessionList ( { sessions, activeSessionId, onSelect, onClose } : SessionDropdownProps ) {
39+ export default function SessionList ( { sessions, activeSessionId, onSelect, onCreateNewSession } : SessionListProps ) {
3140 const [ query , setQuery ] = useState ( "" ) ;
41+ const [ drawerOpen , setDrawerOpen ] = useState ( false ) ;
42+
43+ const handleCloseDrawer = useCallback ( ( ) => {
44+ setDrawerOpen ( false ) ;
45+ } , [ ] ) ;
3246
3347 const filtered = useMemo ( ( ) => {
3448 const q = query . toLowerCase ( ) . trim ( ) ;
@@ -69,68 +83,85 @@ export default function SessionList({ sessions, activeSessionId, onSelect, onClo
6983 grouped [ "Today" ] . length + grouped [ "Yesterday" ] . length + grouped [ "Past Week" ] . length + grouped [ "Older" ] . length ;
7084
7185 return (
72- < div className = "flex flex-col h-full w-full" >
73- < div className = "p-2 shrink-0" >
74- < div className = "relative" >
75- < Search className = "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
76- < Input
77- className = "pl-8 h-8 text-sm"
78- placeholder = "Search sessions..."
79- value = { query }
80- onChange = { ( e ) => setQuery ( e . target . value ) }
81- autoFocus
82- />
83- </ div >
84- </ div >
85- < ScrollArea className = "flex-1 min-h-0" >
86- < div className = "p-2 pt-0" >
87- { totalCount === 0 ? (
88- < Empty className = "h-full bg-muted/30" >
89- < EmptyHeader >
90- < EmptyMedia variant = "icon" >
91- < MessageCircle />
92- </ EmptyMedia >
93- < EmptyTitle > { query ? "No sessions found" : "No sessions yet" } </ EmptyTitle >
94- < EmptyDescription className = "max-w-xs text-pretty" >
95- You're all caught up. New notifications will appear here.
96- </ EmptyDescription >
97- </ EmptyHeader >
98- </ Empty >
99- ) : (
100- Object . entries ( grouped ) . map ( ( [ group , items ] ) =>
101- items . length > 0 ? (
102- < ItemGroup key = { group } className = "mb-3 last:mb-0" >
103- < div className = "text-xs uppercase font-medium tracking-wide px-1.5 py-1 text-muted-foreground" >
104- { group }
105- </ div >
106- { items . map ( ( s ) => (
107- < Item
108- key = { s . id }
109- size = "xs"
110- className = { cn (
111- "w-full text-left px-2 py-1.5 rounded-md text-sm flex items-center gap-3 transition-colors cursor-pointer border-none" ,
112- s . id === activeSessionId
113- ? "bg-primary text-primary-foreground font-medium"
114- : "text-foreground hover:bg-accent"
115- ) }
116- onClick = { ( ) => {
117- onSelect ( s . id ) ;
118- onClose ( ) ;
119- } }
120- >
121- < ItemContent className = "flex-1 truncate" > { s . summary || "Untitled" } </ ItemContent >
122- < ItemActions className = "shrink-0 text-xs text-muted-foreground" >
123- { formatTime ( s . updateTime ) }
124- </ ItemActions >
125- </ Item >
126- ) ) }
127- </ ItemGroup >
128- ) : null
129- )
130- ) }
86+ < Drawer direction = "right" open = { drawerOpen } onOpenChange = { setDrawerOpen } >
87+ < DrawerTrigger asChild >
88+ < Button variant = "ghost" size = "icon" className = "shrink-0 cursor-pointer" title = "Show Agent Sessions Sidebar" >
89+ < PanelRight className = "h-4 w-4" />
90+ </ Button >
91+ </ DrawerTrigger >
92+ < DrawerContent >
93+ < DrawerHeader >
94+ < DrawerTitle > Sessions</ DrawerTitle >
95+ < InputGroup className = "w-full mt-4" >
96+ < InputGroupInput
97+ placeholder = "Search sessions..."
98+ value = { query }
99+ onChange = { ( e ) => setQuery ( e . target . value ) }
100+ />
101+ < InputGroupAddon >
102+ < Search />
103+ </ InputGroupAddon >
104+ < InputGroupAddon align = "inline-end" >
105+ { Object . entries ( grouped ) . reduceRight ( ( acc , [ _ , items ] ) => acc + items . length , 0 ) } results
106+ </ InputGroupAddon >
107+ </ InputGroup >
108+ </ DrawerHeader >
109+ < div className = "no-scrollbar overflow-y-auto" >
110+ < div className = "py-2 px-4 pt-0" >
111+ { totalCount === 0 ? (
112+ < Empty className = "h-full bg-muted/30" >
113+ < EmptyHeader >
114+ < EmptyMedia variant = "icon" >
115+ < MessageCircle />
116+ </ EmptyMedia >
117+ < EmptyTitle > { query ? "No sessions found" : "No sessions yet" } </ EmptyTitle >
118+ < EmptyDescription className = "max-w-xs text-pretty" >
119+ You're all caught up. New notifications will appear here.
120+ </ EmptyDescription >
121+ </ EmptyHeader >
122+ </ Empty >
123+ ) : (
124+ Object . entries ( grouped ) . map ( ( [ group , items ] ) =>
125+ items . length > 0 ? (
126+ < ItemGroup key = { group } className = "mb-3 last:mb-0" >
127+ < div className = "text-xs uppercase font-medium tracking-wide px-1.5 py-1 text-muted-foreground" >
128+ { group }
129+ </ div >
130+ { items . map ( ( s ) => (
131+ < Item
132+ key = { s . id }
133+ size = "xs"
134+ className = { cn (
135+ "w-full text-left px-2 py-1.5 rounded-md text-sm flex items-center gap-3 transition-colors cursor-pointer border-none" ,
136+ s . id === activeSessionId
137+ ? "bg-primary text-primary-foreground font-medium"
138+ : "text-foreground hover:bg-accent"
139+ ) }
140+ onClick = { ( ) => {
141+ onSelect ( s . id ) ;
142+ handleCloseDrawer ( ) ;
143+ } }
144+ >
145+ < ItemContent className = "flex-1 truncate" > { s . summary || "Untitled" } </ ItemContent >
146+ < ItemActions className = "shrink-0 text-xs text-muted-foreground" >
147+ { formatTime ( s . updateTime ) }
148+ </ ItemActions >
149+ </ Item >
150+ ) ) }
151+ </ ItemGroup >
152+ ) : null
153+ )
154+ ) }
155+ </ div >
131156 </ div >
132- < ScrollBar />
133- </ ScrollArea >
134- </ div >
157+ < DrawerFooter >
158+ < DrawerClose asChild >
159+ < Button onClick = { onCreateNewSession } variant = "outline" className = "cursor-pointer" >
160+ New Session
161+ </ Button >
162+ </ DrawerClose >
163+ </ DrawerFooter >
164+ </ DrawerContent >
165+ </ Drawer >
135166 ) ;
136167}
0 commit comments