@@ -4,12 +4,11 @@ import { isToday, isYesterday, subMonths, subWeeks } from 'date-fns';
44import Link from 'next/link' ;
55import { useRouter , usePathname } from 'next/navigation' ;
66import type { User } from '@/lib/auth' ;
7- import { memo , useCallback , useEffect , useState } from 'react' ;
7+ import { memo , useCallback , useEffect , useRef , useState } from 'react' ;
88import { toast } from 'sonner' ;
99import useSWR from 'swr' ;
1010import { cn , fetcher } from '@/lib/utils' ;
1111import {
12- FileIcon ,
1312 MoreHorizontalIcon ,
1413 PlusIcon ,
1514 TrashIcon ,
@@ -24,12 +23,6 @@ import {
2423 AlertDialogHeader ,
2524 AlertDialogTitle ,
2625} from '@/components/ui/alert-dialog' ;
27- import {
28- DropdownMenu ,
29- DropdownMenuContent ,
30- DropdownMenuItem ,
31- DropdownMenuTrigger ,
32- } from '@/components/ui/dropdown-menu' ;
3326import {
3427 SidebarGroup ,
3528 SidebarGroupContent ,
@@ -45,7 +38,6 @@ import { Input } from '@/components/ui/input';
4538import { Button } from '@/components/ui/button' ;
4639import { Checkbox } from '@/components/ui/checkbox' ;
4740import useSWRInfinite from 'swr/infinite' ;
48- import { motion } from 'framer-motion' ;
4941
5042type GroupedDocuments = {
5143 today : Document [ ] ;
@@ -104,9 +96,21 @@ const PureDocumentItem = ({
10496 } , [ document , isActive , isSelectionMode , isSelected , onToggleSelect , setOpenMobile , onSelect ] ) ;
10597
10698 const router = useRouter ( ) ;
99+ const buttonRef = useRef < HTMLButtonElement > ( null ) ;
100+ const menuRef = useRef < HTMLDivElement > ( null ) ;
101+ const closeTimerRef = useRef < number | null > ( null ) ;
102+ const [ showCustomMenu , setShowCustomMenu ] = useState ( false ) ;
107103
104+ useEffect ( ( ) => {
105+ return ( ) => {
106+ if ( closeTimerRef . current ) {
107+ clearTimeout ( closeTimerRef . current ) ;
108+ }
109+ } ;
110+ } , [ ] ) ;
111+
108112 return (
109- < SidebarMenuItem >
113+ < SidebarMenuItem className = "relative" >
110114 < div className = "flex items-center w-full" >
111115 { isSelectionMode && (
112116 < div className = "flex items-center pl-2 pr-1" >
@@ -132,27 +136,70 @@ const PureDocumentItem = ({
132136 </ div >
133137
134138 { ! isSelectionMode && (
135- < DropdownMenu modal = { true } >
136- < DropdownMenuTrigger asChild >
137- < SidebarMenuAction
138- className = "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground mr-0.5"
139- showOnHover = { ! isActive }
140- >
141- < MoreHorizontalIcon />
142- < span className = "sr-only" > More</ span >
143- </ SidebarMenuAction >
144- </ DropdownMenuTrigger >
145-
146- < DropdownMenuContent side = "bottom" align = "end" >
147- < DropdownMenuItem
148- className = "cursor-pointer text-destructive focus:bg-destructive/15 focus:text-destructive dark:text-red-500"
149- onSelect = { ( ) => onDelete ( document . id ) }
150- >
151- < TrashIcon />
152- < span > Delete</ span >
153- </ DropdownMenuItem >
154- </ DropdownMenuContent >
155- </ DropdownMenu >
139+ < SidebarMenuAction
140+ ref = { buttonRef }
141+ className = "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground mr-0.5"
142+ showOnHover = { ! isActive }
143+ data-state = { showCustomMenu ? 'open' : 'closed' }
144+ aria-haspopup = "menu"
145+ aria-expanded = { showCustomMenu }
146+ aria-controls = { `doc-menu-${ document . id } ` }
147+ onBlur = { ( e ) => {
148+ if ( closeTimerRef . current ) {
149+ clearTimeout ( closeTimerRef . current ) ;
150+ closeTimerRef . current = null ;
151+ }
152+ const next = e . relatedTarget as Node | null ;
153+ if ( menuRef . current && next && menuRef . current . contains ( next ) ) return ;
154+ closeTimerRef . current = window . setTimeout ( ( ) => setShowCustomMenu ( false ) , 100 ) ;
155+ } }
156+ onClick = { ( ) => setShowCustomMenu ( ( v ) => ! v ) }
157+ onKeyDown = { ( e ) => {
158+ if ( e . key === 'Escape' ) {
159+ e . stopPropagation ( ) ;
160+ setShowCustomMenu ( false ) ;
161+ }
162+ if ( e . key === 'Enter' || e . key === ' ' ) {
163+ e . preventDefault ( ) ;
164+ setShowCustomMenu ( ( v ) => ! v ) ;
165+ }
166+ } }
167+ >
168+ < MoreHorizontalIcon />
169+ < span className = "sr-only" > More</ span >
170+ </ SidebarMenuAction >
171+ ) }
172+
173+ { ! isSelectionMode && showCustomMenu && (
174+ < div
175+ ref = { menuRef }
176+ id = { `doc-menu-${ document . id } ` }
177+ role = "menu"
178+ aria-labelledby = { `doc-menu-${ document . id } ` }
179+ className = "absolute right-0 top-full z-50 min-w-[8rem] overflow-visible rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
180+ style = { {
181+ marginTop : '2px'
182+ } }
183+ onMouseDown = { ( e ) => e . preventDefault ( ) }
184+ onKeyDown = { ( e ) => {
185+ if ( e . key === 'Escape' ) {
186+ e . stopPropagation ( ) ;
187+ setShowCustomMenu ( false ) ;
188+ buttonRef . current ?. focus ( ) ;
189+ }
190+ } }
191+ >
192+ < Button
193+ variant = "destructive"
194+ size = "sm"
195+ role = "menuitem"
196+ className = "h-7 text-sm w-full"
197+ onClick = { ( ) => { setShowCustomMenu ( false ) ; onDelete ( document . id ) ; } }
198+ >
199+ Delete
200+ < TrashIcon />
201+ </ Button >
202+ </ div >
156203 ) }
157204 </ SidebarMenuItem >
158205 ) ;
0 commit comments