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}
+39 -33
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,32 +584,6 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
</button> </button>
)} )}
{!isMobile && !isFocusedMailLayout && (
<button
data-expand-toggle
onClick={(e) => {
e.stopPropagation();
onToggleExpand();
}}
className={cn(
"p-1 rounded mt-2 flex-shrink-0 transition-all duration-200",
"hover:bg-muted/50 hover:scale-110",
"active:scale-95",
"text-muted-foreground hover:text-foreground"
)}
aria-expanded={isExpanded}
aria-label={t('toggle_thread')}
>
{isLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : isExpanded ? (
<ChevronDown className="w-4 h-4" />
) : (
<ChevronRight className="w-4 h-4" />
)}
</button>
)}
{hasUnread && ( {hasUnread && (
<div className="absolute left-1 top-1/2 -translate-y-1/2"> <div className="absolute left-1 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-unread text-unread" /> <Circle className="w-2 h-2 fill-unread text-unread" />
@@ -614,13 +591,42 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
)} )}
{density !== 'extra-compact' && ( {density !== 'extra-compact' && (
<Avatar <div className="relative flex-shrink-0">
name={avatarPerson?.name} <Avatar
email={avatarPerson?.email} name={avatarPerson?.name}
size={isFocusedMailLayout ? "sm" : "md"} email={avatarPerson?.email}
className="flex-shrink-0 shadow-sm" size={isFocusedMailLayout ? "sm" : "md"}
disableImages={hideJunkAvatarImages} className="shadow-sm"
/> disableImages={hideJunkAvatarImages}
/>
{!isMobile && !isFocusedMailLayout && (
<button
data-expand-toggle
onClick={(e) => {
e.stopPropagation();
onToggleExpand();
}}
className={cn(
"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",
"active:scale-95",
"text-muted-foreground hover:text-foreground",
"bg-background border border-border"
)}
aria-expanded={isExpanded}
aria-label={t('toggle_thread')}
>
{isLoading ? (
<Loader2 className="w-3 h-3 animate-spin" />
) : isExpanded ? (
<ChevronDown className="w-3 h-3" />
) : (
<ChevronRight className="w-3 h-3" />
)}
</button>
)}
</div>
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">