fix: add collapse all threads functionality to email selection in thread list

This commit is contained in:
Max Hao
2026-06-16 13:34:56 +02:00
committed by Linus Rath
parent 0872d3dc8d
commit c0ca6d3102
2 changed files with 52 additions and 34 deletions
+13 -1
View File
@@ -101,6 +101,7 @@ export function EmailList({
toggleThreadExpansion, toggleThreadExpansion,
fetchThreadEmails, fetchThreadEmails,
markThreadAsRead, markThreadAsRead,
collapseAllThreads,
threadEmailCounts, threadEmailCounts,
searchFilters, searchFilters,
setSearchFilters, setSearchFilters,
@@ -477,7 +478,18 @@ export function EmailList({
isLoading={isLoadingThread === thread.threadId} isLoading={isLoadingThread === thread.threadId}
expandedEmails={threadEmailsCache.get(thread.threadId)} expandedEmails={threadEmailsCache.get(thread.threadId)}
onToggleExpand={() => handleToggleThreadExpansion(thread.threadId)} onToggleExpand={() => handleToggleThreadExpansion(thread.threadId)}
onEmailSelect={(email) => onEmailSelect?.(email)} onCollapseAllThreads={collapseAllThreads}
onEmailSelect={(email) => {
// Collapse expanded threads when selecting an email outside the expanded thread
const currentExpanded = useEmailStore.getState().expandedThreadIds;
if (currentExpanded.size > 0) {
// Check if the selected email belongs to any expanded thread via its threadId
if (!email.threadId || !currentExpanded.has(email.threadId)) {
collapseAllThreads();
}
}
onEmailSelect?.(email);
}}
onEmailDoubleClick={onEmailDoubleClick ? (email) => onEmailDoubleClick(email) : undefined} onEmailDoubleClick={onEmailDoubleClick ? (email) => onEmailDoubleClick(email) : undefined}
onContextMenu={openContextMenu} onContextMenu={openContextMenu}
onOpenConversation={onOpenConversation} onOpenConversation={onOpenConversation}
+25 -19
View File
@@ -24,6 +24,7 @@ interface ThreadListItemProps {
isLoading?: boolean; isLoading?: boolean;
expandedEmails?: Email[]; expandedEmails?: Email[];
onToggleExpand: () => void; onToggleExpand: () => void;
onCollapseAllThreads?: () => void;
onEmailSelect: (email: Email) => void; onEmailSelect: (email: Email) => void;
onEmailDoubleClick?: (email: Email) => void; onEmailDoubleClick?: (email: Email) => void;
onContextMenu?: (e: React.MouseEvent, email: Email) => void; onContextMenu?: (e: React.MouseEvent, email: Email) => void;
@@ -384,6 +385,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
isLoading = false, isLoading = false,
expandedEmails, expandedEmails,
onToggleExpand, onToggleExpand,
onCollapseAllThreads,
onEmailSelect, onEmailSelect,
onEmailDoubleClick, onEmailDoubleClick,
onContextMenu, onContextMenu,
@@ -515,6 +517,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
} else { } else {
if (selectedEmailIds.size > 0) clearSelection(); if (selectedEmailIds.size > 0) clearSelection();
if (!isExpanded) { if (!isExpanded) {
onCollapseAllThreads?.();
onToggleExpand(); onToggleExpand();
} }
onEmailSelect(latestEmail); onEmailSelect(latestEmail);
@@ -581,6 +584,21 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
</button> </button>
)} )}
{hasUnread && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
{density !== 'extra-compact' && (
<div className="relative flex-shrink-0">
<Avatar
name={avatarPerson?.name}
email={avatarPerson?.email}
size={isFocusedMailLayout ? "sm" : "md"}
className="shadow-sm"
disableImages={hideJunkAvatarImages}
/>
{!isMobile && !isFocusedMailLayout && ( {!isMobile && !isFocusedMailLayout && (
<button <button
data-expand-toggle data-expand-toggle
@@ -589,40 +607,28 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
onToggleExpand(); onToggleExpand();
}} }}
className={cn( className={cn(
"p-1 rounded mt-2 flex-shrink-0 transition-all duration-200", "absolute -bottom-2.5 left-1/2 -translate-x-1/2 p-0.5 rounded-full",
"transition-all duration-200",
"hover:bg-muted/50 hover:scale-110", "hover:bg-muted/50 hover:scale-110",
"active:scale-95", "active:scale-95",
"text-muted-foreground hover:text-foreground" "text-muted-foreground hover:text-foreground",
"bg-background border border-border"
)} )}
aria-expanded={isExpanded} aria-expanded={isExpanded}
aria-label={t('toggle_thread')} aria-label={t('toggle_thread')}
> >
{isLoading ? ( {isLoading ? (
<Loader2 className="w-4 h-4 animate-spin" /> <Loader2 className="w-3 h-3 animate-spin" />
) : isExpanded ? ( ) : isExpanded ? (
<ChevronDown className="w-4 h-4" /> <ChevronDown className="w-3 h-3" />
) : ( ) : (
<ChevronRight className="w-4 h-4" /> <ChevronRight className="w-3 h-3" />
)} )}
</button> </button>
)} )}
{hasUnread && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-unread text-unread" />
</div> </div>
)} )}
{density !== 'extra-compact' && (
<Avatar
name={avatarPerson?.name}
email={avatarPerson?.email}
size={isFocusedMailLayout ? "sm" : "md"}
className="flex-shrink-0 shadow-sm"
disableImages={hideJunkAvatarImages}
/>
)}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{isFocusedMailLayout ? ( {isFocusedMailLayout ? (
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">