@@ -11,6 +11,7 @@ import { ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
1111import { useCommandPalette } from '../context/command-palette-context' ;
1212import { useHotkeys , useHotkeysContext } from 'react-hotkeys-hook' ;
1313import { ThreadDisplay } from '@/components/mail/thread-display' ;
14+ import { useCallback , useEffect , useRef , useState } from 'react' ;
1415import { useActiveConnection } from '@/hooks/use-connections' ;
1516import { Check , ChevronDown , RefreshCcw } from 'lucide-react' ;
1617import { useMediaQuery } from '../../hooks/use-media-query' ;
@@ -22,7 +23,6 @@ import { useMail } from '@/components/mail/use-mail';
2223import { SidebarToggle } from '../ui/sidebar-toggle' ;
2324import { PricingDialog } from '../ui/pricing-dialog' ;
2425import { clearBulkSelectionAtom } from './use-mail' ;
25- import { useEffect , useRef , useState } from 'react' ;
2626import AISidebar from '@/components/ui/ai-sidebar' ;
2727import { useThreads } from '@/hooks/use-threads' ;
2828import AIToggleButton from '../ai-toggle-button' ;
@@ -392,6 +392,26 @@ export function MailLayout() {
392392 const defaultCategoryId = useDefaultCategoryId ( ) ;
393393 const [ category ] = useQueryState ( 'category' , { defaultValue : defaultCategoryId } ) ;
394394
395+ const handleClearFilters = useCallback (
396+ ( e : React . MouseEvent ) => {
397+ e . stopPropagation ( ) ;
398+ clearAllFilters ( ) ;
399+ } ,
400+ [ clearAllFilters ] ,
401+ ) ;
402+
403+ const handleExitBulkSelection = useCallback ( ( ) => {
404+ setMail ( { ...mail , bulkSelected : [ ] } ) ;
405+ } , [ mail , setMail ] ) ;
406+
407+ const handleRefetchThreads = useCallback ( ( ) => {
408+ refetchThreads ( ) ;
409+ } , [ refetchThreads ] ) ;
410+
411+ const handleOpenCommandPalette = useCallback ( ( ) => {
412+ setIsCommandPaletteOpen ( 'true' ) ;
413+ } , [ setIsCommandPaletteOpen ] ) ;
414+
395415 return (
396416 < TooltipProvider delayDuration = { 0 } >
397417 < PricingDialog />
@@ -413,113 +433,106 @@ export function MailLayout() {
413433 // onMouseLeave={handleMailListMouseLeave}
414434 >
415435 < div className = "w-full md:h-[calc(100dvh-10px)]" >
416- < div
417- className = { cn (
418- 'z-15 sticky top-0 flex items-center justify-between gap-1.5 p-2 pb-0 transition-colors' ,
419- ) }
420- >
421- < div className = "w-full" >
422- < div className = "mt-1 grid grid-cols-12 gap-2" >
423- < SidebarToggle className = "col-span-1 h-fit px-2" />
424- { mail . bulkSelected . length === 0 ? (
425- < div className = "col-span-10 flex gap-2" >
426- < Button
427- variant = "outline"
428- className = { cn (
429- 'text-muted-foreground relative flex h-8 w-full select-none items-center justify-start overflow-hidden rounded-lg border bg-white pl-2 text-left text-sm font-normal shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:border-none dark:bg-[#141414]' ,
430- ) }
431- onClick = { ( ) => setIsCommandPaletteOpen ( 'true' ) }
432- >
433- < Search className = "fill-[#71717A] dark:fill-[#6F6F6F]" />
434-
435- < span className = "hidden truncate pr-20 lg:inline-block" >
436- { activeFilters . length > 0
437- ? activeFilters . map ( ( f ) => f . display ) . join ( ', ' )
438- : 'Search' }
439- </ span >
440- < span className = "inline-block truncate pr-20 lg:hidden" >
441- { activeFilters . length > 0
442- ? `${ activeFilters . length } filter${ activeFilters . length > 1 ? 's' : '' } `
443- : 'Search' }
444- </ span >
445-
446- < span className = "absolute right-[0rem] flex items-center gap-1" >
447- { /* {activeFilters.length > 0 && (
436+ < div className = "z-15 sticky top-0 p-4 pb-0" >
437+ < div className = "flex items-center gap-2" >
438+ < SidebarToggle className = "h-10 w-10" />
439+
440+ { mail . bulkSelected . length === 0 ? (
441+ < >
442+ < Button
443+ variant = "outline"
444+ className = { cn (
445+ 'text-muted-foreground border-border/40 bg-background/50 hover:bg-accent/30 focus-visible:ring-ring dark:border-border/20 dark:bg-background/40 relative flex h-10 flex-1 select-none items-center justify-start overflow-hidden rounded-lg border pl-3 text-left text-sm font-normal shadow-none ring-0 backdrop-blur-sm transition-all focus-visible:ring-2 focus-visible:ring-offset-2' ,
446+ ) }
447+ onClick = { handleOpenCommandPalette }
448+ >
449+ < Search className = "fill-muted-foreground h-4 w-4" />
450+
451+ < span className = "ml-3 hidden truncate pr-20 lg:inline-block" >
452+ { activeFilters . length > 0
453+ ? activeFilters . map ( ( f ) => f . display ) . join ( ', ' )
454+ : 'Search' }
455+ </ span >
456+ < span className = "ml-3 inline-block truncate pr-20 lg:hidden" >
457+ { activeFilters . length > 0
458+ ? `${ activeFilters . length } filter${ activeFilters . length > 1 ? 's' : '' } `
459+ : 'Search' }
460+ </ span >
461+
462+ < div className = "absolute right-2 flex items-center gap-2" >
463+ { /* {activeFilters.length > 0 && (
448464 <Badge variant="secondary" className="ml-2 h-5 rounded px-1">
449465 {activeFilters.length}
450466 </Badge>
451467 )} */ }
452- { activeFilters . length > 0 && (
453- < Button
454- variant = "ghost"
455- size = "sm"
456- className = "my-auto h-5 rounded-xl px-1.5 text-xs"
457- onClick = { ( e ) => {
458- e . stopPropagation ( ) ;
459- clearAllFilters ( ) ;
460- } }
461- >
462- Clear
463- </ Button >
464- ) }
465- < kbd className = "bg-muted text-md leading-[0]! pointer-events-none mr-0.5 hidden h-7 select-none flex-row items-center gap-1 rounded-md border-none px-2 font-medium opacity-100 sm:flex dark:bg-[#262626] dark:text-[#929292]" >
466- < span
467- className = { cn (
468- 'leading-[0.2]! h-min' ,
469- isMac ? 'mt-px text-lg' : 'text-sm' ,
470- ) }
471- >
472- { isMac ? '⌘' : 'Ctrl' } { ' ' }
473- </ span >
474- < span className = "leading-[0.2]! h-min text-sm" > K</ span >
475- </ kbd >
476- </ span >
477- </ Button >
478- { activeConnection ?. providerId === 'google' && folder === 'inbox' && (
479- < CategoryDropdown isMultiSelectMode = { mail . bulkSelected . length > 0 } />
480- ) }
481- </ div >
482- ) : null }
483- < Button
484- onClick = { ( ) => {
485- refetchThreads ( ) ;
486- } }
487- variant = "ghost"
488- className = "md:h-fit md:px-2 hover:bg-accent/50"
489- >
490- < RefreshCcw className = "text-muted-foreground h-4 w-4" />
491- </ Button >
492- { mail . bulkSelected . length > 0 ? (
493- < div className = "flex items-center gap-2" >
494- < Tooltip >
495- < TooltipTrigger asChild >
496- < button
497- onClick = { ( ) => {
498- setMail ( { ...mail , bulkSelected : [ ] } ) ;
499- } }
500- className = "flex h-6 items-center gap-1 rounded-md bg-[#313131] px-2 text-xs text-[#A0A0A0] hover:bg-[#252525]"
468+ { activeFilters . length > 0 && (
469+ < Button
470+ variant = "secondary"
471+ size = "sm"
472+ className = "h-6 rounded-md px-2 text-xs"
473+ onClick = { handleClearFilters }
501474 >
502- < X className = "h-3 w-3 fill-[#A0A0A0]" />
503- < span > esc</ span >
504- </ button >
505- </ TooltipTrigger >
506- < TooltipContent >
507- { m [ 'common.actions.exitSelectionModeEsc' ] ( ) }
508- </ TooltipContent >
509- </ Tooltip >
475+ Clear
476+ </ Button >
477+ ) }
478+ < kbd className = "bg-muted border-border/40 dark:bg-muted/40 pointer-events-none hidden h-6 select-none items-center gap-1 rounded border px-2 text-xs font-medium opacity-80 sm:flex" >
479+ < span className = { cn ( 'text-xs' , isMac ? 'text-sm' : 'text-xs' ) } >
480+ { isMac ? '⌘' : 'Ctrl' }
481+ </ span >
482+ < span className = "text-xs" > K</ span >
483+ </ kbd >
484+ </ div >
485+ </ Button >
486+
487+ { activeConnection ?. providerId === 'google' && folder === 'inbox' && (
488+ < CategoryDropdown isMultiSelectMode = { mail . bulkSelected . length > 0 } />
489+ ) }
490+ </ >
491+ ) : (
492+ < div className = "flex flex-1 items-center justify-between" >
493+ < div className = "text-foreground text-sm font-medium" >
494+ { mail . bulkSelected . length } selected
510495 </ div >
511- ) : null }
512- </ div >
496+ < Tooltip >
497+ < TooltipTrigger asChild >
498+ < Button
499+ variant = "secondary"
500+ size = "sm"
501+ onClick = { handleExitBulkSelection }
502+ className = "h-8 gap-2 rounded-lg"
503+ >
504+ < X className = "h-3 w-3" />
505+ < span className = "text-xs" > ESC</ span >
506+ </ Button >
507+ </ TooltipTrigger >
508+ < TooltipContent >
509+ { m [ 'common.actions.exitSelectionModeEsc' ] ( ) }
510+ </ TooltipContent >
511+ </ Tooltip >
512+ </ div >
513+ ) }
514+
515+ < Button
516+ onClick = { handleRefetchThreads }
517+ variant = "ghost"
518+ size = "icon"
519+ className = "border-none bg-transparent hover:bg-accent/50 h-10 w-10 rounded-lg backdrop-blur-sm"
520+ >
521+ < RefreshCcw className = "text-muted-foreground h-4 w-4" />
522+ </ Button >
513523 </ div >
514524 </ div >
515525
516- < div
517- className = { cn (
518- `${ category === 'Important' ? 'bg-[#F59E0D]' : category === 'All Mail' ? 'bg-[#006FFE]' : category === 'Personal' ? 'bg-[#39ae4a]' : category === 'Updates' ? 'bg-[#8B5CF6]' : category === 'Promotions' ? 'bg-[#F43F5E]' : category === 'Unread' ? 'bg-[#FF4800]' : 'bg-[#F59E0D]' } ` ,
519- 'z-5 relative h-0.5 w-full transition-opacity' ,
520- isFetching ? 'opacity-100' : 'opacity-0' ,
521- ) }
522- />
526+ < div className = "px-4 pt-2" >
527+ < div
528+ className = { cn (
529+ `${ category === 'Important' ? 'bg-[#F59E0D]' : category === 'All Mail' ? 'bg-[#006FFE]' : category === 'Personal' ? 'bg-[#39ae4a]' : category === 'Updates' ? 'bg-[#8B5CF6]' : category === 'Promotions' ? 'bg-[#F43F5E]' : category === 'Unread' ? 'bg-[#FF4800]' : 'bg-[#F59E0D]' } ` ,
530+ 'h-0.5 w-full rounded-full transition-opacity' ,
531+ isFetching ? 'opacity-100' : 'opacity-0' ,
532+ ) }
533+ />
534+ </ div >
535+
523536 < div className = "z-1 relative h-[calc(100dvh-(2px+2px))] overflow-hidden pt-0 md:h-[calc(100dvh-4rem)]" >
524537 < MailList />
525538 </ div >
@@ -563,6 +576,14 @@ export function MailLayout() {
563576 ) ;
564577}
565578
579+ interface CategoryItem {
580+ id : string ;
581+ name : string ;
582+ searchValue : string ;
583+ icon ?: React . ReactNode ;
584+ colors ?: string ;
585+ }
586+
566587export const Categories = ( ) => {
567588 const defaultCategoryIdInner = useDefaultCategoryId ( ) ;
568589 const categorySettings = useCategorySettings ( ) ;
@@ -661,11 +682,11 @@ export const Categories = () => {
661682 ) ,
662683 } ;
663684 default :
664- return base as any ;
685+ return base ;
665686 }
666687 } ) ;
667688
668- return categories ;
689+ return categories as CategoryItem [ ] ;
669690} ;
670691interface CategoryDropdownProps {
671692 isMultiSelectMode ?: boolean ;
@@ -737,32 +758,35 @@ function CategoryDropdown({ isMultiSelectMode }: CategoryDropdownProps) {
737758 < Button
738759 variant = "outline"
739760 className = { cn (
740- 'black: text-white text- muted-foreground flex h-8 min-w-fit items-center gap-1 rounded-md border-none px-2 ' ,
761+ 'text-muted-foreground border-border/40 bg-background/50 hover:bg-accent/30 dark:border-border/20 dark:bg-background/40 flex h-10 min-w-fit items-center gap-2 rounded-lg border px-3 backdrop-blur-sm transition-all ' ,
741762 ) }
742763 aria-label = "Filter by labels"
743764 aria-expanded = { isOpen }
744765 aria-haspopup = "menu"
745766 >
746- < span className = "text-xs font-medium" >
767+ < span className = "text-sm font-medium" >
747768 { labels . length > 0
748769 ? `${ labels . length } View${ labels . length > 1 ? 's' : '' } `
749770 : m [ 'navigation.settings.categories' ] ( ) }
750771 </ span >
751772 < ChevronDown
752- className = { `black:text-white text-muted-foreground h-2 w-2 transition-transform duration-200 ${ isOpen ? 'rotate-180' : 'rotate-0' } ` }
773+ className = { cn (
774+ 'text-muted-foreground h-4 w-4 transition-transform duration-200' ,
775+ isOpen ? 'rotate-180' : 'rotate-0' ,
776+ ) }
753777 />
754778 </ Button >
755779 </ DropdownMenuTrigger >
756780 < DropdownMenuContent
757- className = "bg-muted w-48 font-medium dark:bg-[#2C2C2C ]"
781+ className = "border-border/50 bg-muted w-48 rounded-xl border p-2 dark:bg-[#232323 ]"
758782 align = "start"
759783 role = "menu"
760784 aria-label = "Label filter options"
761785 >
762786 { categorySettings . map ( ( category ) => (
763787 < DropdownMenuItem
764788 key = { category . id }
765- className = "flex cursor-pointer items-center gap-2 hover:bg-white/10 "
789+ className = "hover:bg-accent/50 flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors "
766790 onClick = { ( e ) => {
767791 e . preventDefault ( ) ;
768792 e . stopPropagation ( ) ;
@@ -771,12 +795,14 @@ function CategoryDropdown({ isMultiSelectMode }: CategoryDropdownProps) {
771795 role = "menuitemcheckbox"
772796 aria-checked = { labels . includes ( category . id ) }
773797 >
774- < span className = "text-muted-foreground capitalize" > { category . name . toLowerCase ( ) } </ span >
798+ < span className = "text-foreground font-medium capitalize" >
799+ { category . name . toLowerCase ( ) }
800+ </ span >
775801 { /* Special case: empty searchValue means "All Mail" - shows everything */ }
776802 { ( category . searchValue === ''
777803 ? labels . length === 0
778804 : category . searchValue . split ( ',' ) . some ( ( val ) => labels . includes ( val ) ) ) && (
779- < Check className = "ml-auto h-3 w-3 " />
805+ < Check className = "text-primary ml-auto h-4 w-4 " />
780806 ) }
781807 </ DropdownMenuItem >
782808 ) ) }
0 commit comments