|
1 | | -import { useCallback, useState, type KeyboardEvent, type MouseEvent } from 'react' |
| 1 | +import { useCallback, useEffect, useState, type KeyboardEvent, type MouseEvent } from 'react' |
2 | 2 | import { useTranslation } from 'react-i18next' |
3 | 3 | import { |
4 | 4 | MessageSquare, |
@@ -71,14 +71,46 @@ export function Sidebar({ |
71 | 71 | [activeSessionId, setActiveSession, onNavigate], |
72 | 72 | ) |
73 | 73 |
|
| 74 | + const [confirmDeleteSessionId, setConfirmDeleteSessionId] = useState<string | null>(null) |
| 75 | + const [confirmClearAll, setConfirmClearAll] = useState(false) |
| 76 | + |
74 | 77 | const handleDelete = useCallback( |
| 78 | + (e: MouseEvent, id: string) => { |
| 79 | + e.stopPropagation() |
| 80 | + setConfirmDeleteSessionId(id) |
| 81 | + }, |
| 82 | + [], |
| 83 | + ) |
| 84 | + |
| 85 | + const handleConfirmDelete = useCallback( |
75 | 86 | (e: MouseEvent, id: string) => { |
76 | 87 | e.stopPropagation() |
77 | 88 | deleteSession(id) |
| 89 | + setConfirmDeleteSessionId(null) |
78 | 90 | }, |
79 | 91 | [deleteSession], |
80 | 92 | ) |
81 | 93 |
|
| 94 | + const handleCancelDelete = useCallback( |
| 95 | + (e: MouseEvent) => { |
| 96 | + e.stopPropagation() |
| 97 | + setConfirmDeleteSessionId(null) |
| 98 | + }, |
| 99 | + [], |
| 100 | + ) |
| 101 | + |
| 102 | + useEffect(() => { |
| 103 | + if (!confirmDeleteSessionId && !confirmClearAll) return |
| 104 | + const onKey = (e: globalThis.KeyboardEvent) => { |
| 105 | + if (e.key === 'Escape') { |
| 106 | + setConfirmDeleteSessionId(null) |
| 107 | + setConfirmClearAll(false) |
| 108 | + } |
| 109 | + } |
| 110 | + window.addEventListener('keydown', onKey) |
| 111 | + return () => window.removeEventListener('keydown', onKey) |
| 112 | + }, [confirmDeleteSessionId, confirmClearAll]) |
| 113 | + |
82 | 114 | const applyWorkspaceDir = useCallback((dir: string) => { |
83 | 115 | setWorkspaceDir(dir) |
84 | 116 | setShowPathInput(false) |
@@ -192,17 +224,33 @@ export function Sidebar({ |
192 | 224 | </div> |
193 | 225 | <div className="flex items-center gap-1.5"> |
194 | 226 | {!isFilesMode && ( |
195 | | - <button |
196 | | - onClick={() => { |
197 | | - if (window.confirm(t('sidebar.clearConfirm'))) { |
198 | | - useAppStore.getState().clearSessions() |
199 | | - } |
200 | | - }} |
201 | | - className="flex h-7 w-7 items-center justify-center rounded-md text-text-quaternary transition-all duration-300 hover:bg-red-500/10 hover:text-red-500" |
202 | | - title={t('sidebar.clearTitle')} |
203 | | - > |
204 | | - <Trash2 size={11} strokeWidth={2} /> |
205 | | - </button> |
| 227 | + confirmClearAll ? ( |
| 228 | + <div className="flex items-center gap-1"> |
| 229 | + <button |
| 230 | + onClick={() => { |
| 231 | + useAppStore.getState().clearSessions() |
| 232 | + setConfirmClearAll(false) |
| 233 | + }} |
| 234 | + className="rounded-md bg-red-500 px-2 py-1 text-[10px] font-black text-white transition-all duration-300 hover:bg-red-600" |
| 235 | + > |
| 236 | + {t('common.confirm')} |
| 237 | + </button> |
| 238 | + <button |
| 239 | + onClick={() => setConfirmClearAll(false)} |
| 240 | + className="rounded-md bg-white/75 px-2 py-1 text-[10px] font-black text-text-tertiary transition-all duration-300 hover:text-text dark:bg-white/10" |
| 241 | + > |
| 242 | + {t('common.cancel')} |
| 243 | + </button> |
| 244 | + </div> |
| 245 | + ) : ( |
| 246 | + <button |
| 247 | + onClick={() => setConfirmClearAll(true)} |
| 248 | + className="flex h-7 w-7 items-center justify-center rounded-md text-text-quaternary transition-all duration-300 hover:bg-red-500/10 hover:text-red-500" |
| 249 | + title={t('sidebar.clearTitle')} |
| 250 | + > |
| 251 | + <Trash2 size={11} strokeWidth={2} /> |
| 252 | + </button> |
| 253 | + ) |
206 | 254 | )} |
207 | 255 | <motion.button |
208 | 256 | whileHover={{ scale: 1.05, x: -2 }} |
@@ -327,27 +375,41 @@ export function Sidebar({ |
327 | 375 | )} |
328 | 376 | </div> |
329 | 377 |
|
330 | | - <div |
331 | | - onClick={(e) => { |
332 | | - e.stopPropagation() |
333 | | - handleDelete(e, session.id) |
334 | | - }} |
335 | | - role="button" |
336 | | - tabIndex={0} |
337 | | - onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => { |
338 | | - if (e.key === 'Enter' || e.key === ' ') { |
339 | | - e.stopPropagation() |
340 | | - handleDelete(e as unknown as MouseEvent, session.id) |
341 | | - } |
342 | | - }} |
343 | | - className={`cursor-pointer rounded-md p-1 transition-all duration-300 ${ |
344 | | - isActive |
345 | | - ? 'text-accent/60 hover:text-red-500 hover:bg-red-500/10' |
346 | | - : 'text-text-quaternary hover:text-red-500 hover:bg-red-500/10 opacity-0 group-hover:opacity-100' |
347 | | - }`} |
348 | | - > |
349 | | - <Trash2 size={12} strokeWidth={1.5} /> |
350 | | - </div> |
| 378 | + {confirmDeleteSessionId === session.id ? ( |
| 379 | + <div className="flex shrink-0 items-center gap-1"> |
| 380 | + <button |
| 381 | + onClick={(e) => handleConfirmDelete(e, session.id)} |
| 382 | + className="rounded-md bg-red-500 px-2 py-1 text-[10px] font-black text-white transition-all duration-300 hover:bg-red-600" |
| 383 | + > |
| 384 | + {t('common.confirm')} |
| 385 | + </button> |
| 386 | + <button |
| 387 | + onClick={handleCancelDelete} |
| 388 | + className="rounded-md bg-white/75 px-2 py-1 text-[10px] font-black text-text-tertiary transition-all duration-300 hover:text-text dark:bg-white/10" |
| 389 | + > |
| 390 | + {t('common.cancel')} |
| 391 | + </button> |
| 392 | + </div> |
| 393 | + ) : ( |
| 394 | + <div |
| 395 | + onClick={(e) => handleDelete(e, session.id)} |
| 396 | + role="button" |
| 397 | + tabIndex={0} |
| 398 | + onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => { |
| 399 | + if (e.key === 'Enter' || e.key === ' ') { |
| 400 | + e.stopPropagation() |
| 401 | + handleDelete(e as unknown as MouseEvent, session.id) |
| 402 | + } |
| 403 | + }} |
| 404 | + className={`cursor-pointer rounded-md p-1 transition-all duration-300 ${ |
| 405 | + isActive |
| 406 | + ? 'text-accent/60 hover:text-red-500 hover:bg-red-500/10' |
| 407 | + : 'text-text-quaternary hover:text-red-500 hover:bg-red-500/10 opacity-0 group-hover:opacity-100' |
| 408 | + }`} |
| 409 | + > |
| 410 | + <Trash2 size={12} strokeWidth={1.5} /> |
| 411 | + </div> |
| 412 | + )} |
351 | 413 |
|
352 | 414 | {isActive && ( |
353 | 415 | <div className="absolute left-0 top-2 bottom-2 z-20 w-1 rounded-r-full bg-accent shadow-[2px_0_8px_rgba(var(--color-accent-rgb),0.3)]" /> |
|
0 commit comments