diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 9d25a7f7..0a809721 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo, useRef } from "react"; import DOMPurify from "dompurify"; import { Email, ContactCard } from "@/lib/jmap/types"; import { EMAIL_SANITIZE_CONFIG, collapseBlockedImageContainers } from "@/lib/email-sanitization"; @@ -441,8 +441,33 @@ export function EmailViewer({ const [isQuickReplyFocused, setIsQuickReplyFocused] = useState(false); const [isSendingQuickReply, setIsSendingQuickReply] = useState(false); const [showSourceModal, setShowSourceModal] = useState(false); + const [moreMenuOpen, setMoreMenuOpen] = useState(false); + const [tagMenuOpen, setTagMenuOpen] = useState(false); + const moreMenuRef = useRef(null); + const tagMenuRef = useRef(null); const currentColor = getCurrentColor(email?.keywords); + // Close dropdown menus on click outside + useEffect(() => { + if (!moreMenuOpen && !tagMenuOpen) return; + function handleClickOutside(e: MouseEvent) { + if (moreMenuOpen && moreMenuRef.current && !moreMenuRef.current.contains(e.target as Node)) { + setMoreMenuOpen(false); + } + if (tagMenuOpen && tagMenuRef.current && !tagMenuRef.current.contains(e.target as Node)) { + setTagMenuOpen(false); + } + } + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [moreMenuOpen, tagMenuOpen]); + + // Close dropdowns when email changes + useEffect(() => { + setMoreMenuOpen(false); + setTagMenuOpen(false); + }, [email?.id]); + // Contact sidebar state const [contactSidebarEmail, setContactSidebarEmail] = useState(null); const contacts = useContactStore((s) => s.contacts); @@ -1001,10 +1026,10 @@ export function EmailViewer({ "bg-background border-b border-border", "max-lg:sticky max-lg:top-0 max-lg:z-10" )}> -
-
+
+
{/* Left: Back + Reply actions */} -
+
{isTablet && !tabletListVisible && onBack && (
{/* Right: Organize actions */} -
+
{isLoading && (
)} + {/* Archive - hidden on mobile, available in More menu */} + {/* Spam - hidden on mobile, available in More menu */} {(onMarkAsSpam || onUndoSpam) && ( -
+
- {/* Tag Picker */} -
+ {/* Tag Picker - click-based, hidden on mobile (available in More menu) */} +
-
- {colorOptions.map((option) => ( - - ))} - {currentColor && ( - <> -
+ {tagMenuOpen && ( +
+ {colorOptions.map((option) => ( - - )} -
+ ))} + {currentColor && ( + <> +
+ + + )} +
+ )}
+ {/* Print - hidden on mobile, available in More menu */} - {/* More menu */} -
+ {/* More menu - click-based */} +
-
- - {onShowShortcuts && ( + {moreMenuOpen && ( +
+ {/* Mobile-only actions */} - )} -
+ {(onMarkAsSpam || onUndoSpam) && ( + + )} + {/* Tag submenu on mobile */} + {colorOptions.length > 0 && ( +
+
+
{t('tag')}
+ {colorOptions.map((option) => ( + + ))} + {currentColor && ( + + )} +
+
+ )} + + + {onShowShortcuts && ( + + )} +
+ )}
@@ -1281,66 +1376,68 @@ export function EmailViewer({ {/* === TOOLBAR (below-subject position) === */} {toolbarPosition === 'below-subject' && (
-
-
+
+
{/* Left: Reply actions */} -
+
{/* Right: Organize actions */} -
+
{isLoading && (
)} + {/* Archive - hidden on mobile, available in More menu */} + {/* Spam - hidden on mobile, available in More menu */} {(onMarkAsSpam || onUndoSpam) && ( -
+
- {/* Tag Picker */} -
+ {/* Tag Picker - click-based, hidden on mobile (available in More menu) */} +
-
- {colorOptions.map((option) => ( - - ))} - {currentColor && ( - <> -
+ {tagMenuOpen && ( +
+ {colorOptions.map((option) => ( - - )} -
+ ))} + {currentColor && ( + <> +
+ + + )} +
+ )}
+ {/* Print - hidden on mobile, available in More menu */} - {/* More Actions */} -
+ {/* More menu - click-based */} +
-
- - {onShowShortcuts && ( + {moreMenuOpen && ( +
+ {/* Mobile-only actions */} - )} -
+ {(onMarkAsSpam || onUndoSpam) && ( + + )} + {/* Tag submenu on mobile */} + {colorOptions.length > 0 && ( +
+
+
{t('tag')}
+ {colorOptions.map((option) => ( + + ))} + {currentColor && ( + + )} +
+
+ )} + + + {onShowShortcuts && ( + + )} +
+ )}