Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThe changes remove legacy context menu components by deleting the Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant ML as MailList
participant NM as useMailNavigation
participant T as Thread Component
participant QA as MailQuickActions
U->>ML: Hover/Click on a thread
ML->>NM: Invoke handleMouseEnter
NM->>T: Set focused & hovered state
T->>QA: Render quick action buttons if active
U->>QA: Trigger quick action (archive, delete, etc.)
QA->>T: Execute thread action update
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
apps/mail/components/mail/mail-quick-actions.tsx (2)
60-87: Consider matching error message to action type.The error handling in
handleArchiveuses a generic error message regardless of whether it's an archive or unarchive action.- console.error('Error archiving thread', error); + console.error(`Error ${isArchiveFolder ? 'unarchiving' : 'archiving'} thread`, error);
162-167: Localize action labels for internationalization support.The
quickActionsarray uses string literals for labels instead of using the translation functiontwhich is already imported and used elsewhere in the component.const quickActions = [ - { action: handleArchive, icon: Archive, label: isArchiveFolder || !isInbox ? 'Unarchive' : 'Archive' }, - { action: handleToggleRead, icon: Mail, label: message.unread ? 'Mark as read' : 'Mark as unread' }, - { action: handleDelete, icon: Trash, label: 'Delete' }, - { action: handleQuickReply, icon: Reply, label: 'Reply' } + { action: handleArchive, icon: Archive, label: isArchiveFolder || !isInbox ? t('common.mail.unarchive') : t('common.mail.archive') }, + { action: handleToggleRead, icon: Mail, label: message.unread ? t('common.mail.markAsRead') : t('common.mail.markAsUnread') }, + { action: handleDelete, icon: Trash, label: t('common.mail.delete') }, + { action: handleQuickReply, icon: Reply, label: t('common.mail.reply') } ];apps/mail/hooks/use-mail-navigation.ts (3)
20-24: Consider handling keyboard focus persistence on item changes.The effect resets focus when items change, but only if keyboard navigation is not active. This might lead to unexpected behavior if items are filtered or sorted while navigating.
Consider adding a more robust approach to maintaining focus position when items change, such as tracking the focused item's ID rather than just its index.
33-36: Fragile DOM element selection method.The
getThreadElementfunction relies on CSS selectors to find DOM elements, which can be brittle if the DOM structure changes.Consider using React refs passed down to thread components instead of relying on DOM queries, or at least adding error handling for cases when elements aren't found.
38-52: Potential performance issue with scroll calculation.The
scrollIntoViewfunction usesgetBoundingClientRect()which can cause layout thrashing when scrolling quickly through a large list.Consider using the IntersectionObserver API for better performance:
const createObserver = (index: number) => { const threadElement = getThreadElement(index); if (!threadElement || !containerRef.current) return; const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (!entry.isIntersecting) { threadElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); observer.disconnect(); } }); }, { root: containerRef.current, threshold: 1.0 } ); observer.observe(threadElement); return observer; };apps/mail/components/mail/mail-list.tsx (2)
67-92: Duplicate hover state tracking.The component uses both
isHovering.current(a ref) andisHovered(state) to track hovering, which could lead to inconsistencies.Consider consolidating the hover tracking to use only the state variable, which would simplify the code and ensure consistent behavior:
const handleMouseEnter = () => { if (demo) return; - isHovering.current = true; setIsHovered(true); // Prefetch only in single select mode - if (selectMode === 'single' && sessionData?.userId && !hasPrefetched.current) { + if (selectMode === 'single' && sessionData?.userId && !hasPrefetched.current) { // Clear any existing timeout if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current); } // Set new timeout for prefetch hoverTimeoutRef.current = setTimeout(() => { - if (isHovering.current) { + if (isHovered) { const messageId = message.threadId ?? message.id; // Only prefetch if still hovering and hasn't been prefetched console.log( `🕒 Hover threshold reached for email ${messageId}, initiating prefetch...`, ); void preloadThread(sessionData.userId, messageId, sessionData.connectionId ?? ''); hasPrefetched.current = true; } }, HOVER_DELAY); } };
373-376: Potential side effect in mail click handler.Adding
handleMouseEnter(message.id)to the click handler might cause unexpected side effects if the behavior ofhandleMouseEnterchanges.Consider separating the concerns by not mixing mouse enter handler behavior with click handling, or add a comment explaining the intentional behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/mail/components/context/thread-context.tsx(0 hunks)apps/mail/components/mail/mail-list.tsx(11 hunks)apps/mail/components/mail/mail-quick-actions.tsx(1 hunks)apps/mail/components/ui/context-menu.tsx(0 hunks)apps/mail/hooks/use-mail-navigation.ts(1 hunks)
💤 Files with no reviewable changes (2)
- apps/mail/components/context/thread-context.tsx
- apps/mail/components/ui/context-menu.tsx
🔇 Additional comments (29)
apps/mail/components/mail/mail-quick-actions.tsx (8)
3-15: Appropriate imports and dependencies.The component has all the necessary imports for its functionality, with a good organization of React core imports first, followed by UI components, utilities, and type definitions.
16-23: Well-structured props interface with good type definitions.The
MailQuickActionsPropsinterface provides clear typing for the component with proper optional properties, making it flexible and reusable.
25-44: Good component declaration with proper memoization and default values.The component is memoized for performance optimization, and default values are provided for optional props. The parameter destructuring with defaults is a clean pattern.
45-58: Effective thread closing logic.The
closeThreadIfOpenfunction properly handles the cleanup by removing the threadId parameter from the URL and resetting navigation state when provided.
89-123: Comprehensive read/unread toggling with proper error handling.The
handleToggleReadfunction properly handles both marking as read and unread with appropriate user feedback and error handling.
125-148: Effective thread deletion implementation.The
handleDeletefunction correctly moves threads to trash with proper error handling and user feedback.
169-199: Well-structured conditional rendering with appropriate styling.The component renders nothing when not hovered or in quick action mode, and otherwise displays a clean, properly styled container with the action buttons. The styling conditionally applies focus styles when in quick action mode.
202-202: Good practice setting displayName for debugging.Setting
displayNamehelps with component identification in React DevTools and error messages.apps/mail/hooks/use-mail-navigation.ts (11)
1-11: Well-defined hook interface with appropriate imports.The hook imports necessary React hooks and defines a clear interface for its props.
12-19: Good state management setup with appropriate refs.The hook initializes state variables and refs for tracking focused index, quick action mode, hover state, and keyboard navigation state.
26-31: Clean navigation reset function.The
resetNavigationfunction properly clears all navigation state.
54-66: Effective thread navigation with appropriate mail state updates.The
navigateToThreadfunction correctly handles thread navigation and mail state updates.
68-116: Well-implemented arrow key navigation with hover integration.The arrow key navigation functions properly handle both keyboard and hover states, with good prevention of default browser behavior.
117-132: Robust Enter key handling for selection and action triggering.The
handleEnterfunction correctly handles both regular selection and quick action activation.
134-158: Well-implemented quick action mode toggling and navigation.The Tab and arrow key handlers for quick action mode provide good navigation between quick actions.
160-170: Clean escape key handling for progressive state reset.The
handleEscapefunction properly handles different states, first exiting quick action mode and then resetting focus.
172-178: Comprehensive keyboard shortcut registration.All necessary keyboard shortcuts are registered using the
useHotKeyhook.
180-187: Mouse interaction handling with keyboard state awareness.The
handleMouseEnterfunction properly updates the hover state and resets keyboard navigation when the mouse is used.
189-197: Clean hook return value with all necessary state and handlers.The hook returns all the required state and handlers for integrating with components.
apps/mail/components/mail/mail-list.tsx (10)
5-5: Good import organization with type imports separated.The import statement correctly uses
typefor TypeScript imports and separates component imports from React hook imports.
24-25: Appropriate import of new components and hooks.The new
MailQuickActionscomponent anduseMailNavigationhook are properly imported.
28-45: Updated Thread component props with keyboard navigation support.The
Threadcomponent now accepts additional props to support keyboard navigation and quick actions.
54-54: Added state for hover tracking.The
isHoveredstate is added to track hover state for the thread.
94-100: Updated mouse leave handler with hover state.The mouse leave handler now also updates the
isHoveredstate.
116-144: Well-integrated MailQuickActions component.The
MailQuickActionscomponent is properly integrated with appropriate props for hover state, quick action mode, and keyboard navigation.
244-248: Clean thread navigation handler implementation.The
handleNavigateToThreadfunction correctly updates URL parameters when navigating to a thread.
250-261: Well-structured mail navigation hook integration.The
useMailNavigationhook is properly initialized with the necessary props and its return values are destructured for use in the component.
445-445: Updated dependencies array with new handler.The dependencies array for
handleMailClicknow includeshandleMouseEnter.
468-480: Properly updated Thread component props in Virtuoso.The Thread component in the Virtuoso renderer now receives the necessary props for keyboard navigation and quick actions.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
apps/mail/locales/en.json (1)
123-123: Inconsistent capitalization between similar entries.I noticed the added
markAsReadentries have different capitalizations:
"markAsRead": "Mark as read"inthreadDisplay(line 123)"markAsRead": "Mark as Read"inThis follows the existing pattern where
threadDisplayuses lowercase for the second word whileAlso applies to: 212-212
apps/mail/components/mail/mail-quick-actions.tsx (4)
3-14: Add tests for the new component.This new component handles critical mail actions like archiving, marking as read/unread, and deletion. Consider adding unit tests to ensure these features work correctly across different states and edge cases.
60-87: Consider refactoring shared logic in action handlers.The action handlers (
handleArchive,handleToggleRead,handleDelete) share similar patterns for error handling, state management, and thread ID extraction. Consider extracting common functionality into a higher-order function to reduce code duplication.+ const performThreadAction = useCallback( + async ( + e: React.MouseEvent | undefined, + actionFn: () => Promise<any>, + errorMessage: string + ) => { + e?.stopPropagation(); + if (isProcessing || isLoading) return; + + setIsProcessing(true); + try { + await actionFn(); + } catch (error) { + console.error(errorMessage, error); + toast.error(t('common.mail.errorMoving')); + } finally { + setIsProcessing(false); + } + }, + [isProcessing, isLoading, t] + ); const handleArchive = useCallback(async (e?: React.MouseEvent) => { - e?.stopPropagation(); - if (isProcessing || isLoading) return; - - setIsProcessing(true); - try { + return performThreadAction(e, async () => { const threadId = message.threadId ?? message.id; const destination = isArchiveFolder ? FOLDERS.INBOX : FOLDERS.ARCHIVE; await moveThreadsTo({ threadIds: [`thread:${threadId}`], currentFolder: currentFolder, destination: destination as ThreadDestination }).then(async () => { await Promise.all([mutate(), mutateStats()]); const actionType = isArchiveFolder ? 'unarchive' : 'archive'; toast.success(t(`common.mail.${actionType}`)); closeThreadIfOpen(); }); - } catch (error) { - console.error('Error archiving thread', error); - toast.error(t('common.mail.errorMoving')); - } finally { - setIsProcessing(false); - } + }, 'Error archiving thread'); }, [message, currentFolder, isArchiveFolder, mutate, mutateStats, t, performThreadAction, closeThreadIfOpen]);
170-172: Consider showing button outlines for better discoverability.Currently, quick actions are completely hidden until hover. Consider showing subtle button outlines even when not hovered to improve discoverability, especially for users who might not realize these actions exist.
if (!isHovered && !isInQuickActionMode) { - return null; + return ( + <div className={cn( + 'absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1 bg-transparent rounded-md p-1 z-10 opacity-30', + className + )}> + {quickActions.map((quickAction, index) => ( + <Button + key={index} + variant="ghost" + size="icon" + className="h-7 w-7 mail-quick-action-button opacity-0" + disabled={true} + aria-hidden={true} + > + <quickAction.icon className="h-4 w-4" /> + </Button> + ))} + </div> + ); }
174-201: Consider keyboard focus management enhancements.The component provides good visual feedback for keyboard navigation with the
isInQuickActionModeandselectedQuickActionIndexprops, but could be enhanced with explicit keyboard event handlers and focus management.return ( <div className={cn( 'absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1 bg-background/80 backdrop-blur-sm rounded-md p-1 z-10 overflow-visible', className, isInQuickActionMode && 'bg-background/95 ring-2 ring-primary/20' )} + role="toolbar" + aria-label="Quick actions" > {quickActions.map((quickAction, index) => ( <Button key={index} variant={isInQuickActionMode && selectedQuickActionIndex === index ? "secondary" : "ghost"} size="icon" className={cn( "h-7 w-7 mail-quick-action-button", isInQuickActionMode && selectedQuickActionIndex === index && "border border-primary/60 shadow-sm" )} onClick={(e) => quickAction.action(e)} disabled={isLoading || isProcessing} aria-label={quickAction.label} + tabIndex={isInQuickActionMode && selectedQuickActionIndex === index ? 0 : -1} > <quickAction.icon className="h-4 w-4" /> </Button> ))} </div> );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/mail/components/mail/mail-quick-actions.tsx(1 hunks)apps/mail/locales/en.json(2 hunks)
🔇 Additional comments (3)
apps/mail/components/mail/mail-quick-actions.tsx (3)
150-161: Incomplete reply functionality implementation.The
handleQuickReplyfunction only shows a toast notification without implementing actual reply functionality.Consider implementing the actual reply functionality or adding a TODO comment if this is intended to be a placeholder for future implementation:
const handleQuickReply = useCallback(async (e?: React.MouseEvent) => { e?.stopPropagation(); if (isProcessing) return; setIsProcessing(true); try { + // TODO: Implement quick reply toast.info(t('common.mail.reply')); } finally { setIsProcessing(false); } }, [t, isProcessing]);
32-59: LGTM: Good implementation of closeThreadIfOpen.The
closeThreadIfOpenfunction effectively manages the URL parameters and navigation state when a thread is closed, ensuring a smooth user experience when performing quick actions.
163-168: Well-organized quick actions array.The quick actions array is well-structured, making it easy to maintain and extend with additional actions in the future.
Summary by CodeRabbit
New Features
Bug Fixes
Localization