11"use client" ;
22
33import { useState , useRef } from "react" ;
4- import Button from "./shared/Button" ;
5- import { EllipsisVerticalIcon } from "@heroicons/react/24/outline" ;
64import { getFileIcon , formatFileSize , formatDate , type FileType } from "../lib/helpers" ;
5+ import FileItemMenu from "./FileItemMenu" ;
76
87export type { FileType } ;
98
@@ -22,6 +21,7 @@ interface FileItemProps {
2221 view : "grid" | "list" ;
2322 onSelect : ( file : FileItemData ) => void ;
2423 onDoubleClick : ( file : FileItemData ) => void ;
24+ onRename ?: ( file : FileItemData ) => void ;
2525 isSelected ?: boolean ;
2626}
2727
@@ -30,13 +30,22 @@ export default function FileItem({
3030 view,
3131 onSelect,
3232 onDoubleClick,
33+ onRename,
3334 isSelected = false ,
3435} : FileItemProps ) {
3536 const [ isHovered , setIsHovered ] = useState ( false ) ;
3637 const clickCountRef = useRef ( 0 ) ;
3738 const clickTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
39+ const lastTapRef = useRef ( 0 ) ;
40+ const touchHandledRef = useRef ( false ) ;
3841
3942 const handleClick = ( e : React . MouseEvent ) => {
43+ // Prevent click handler from running if we just handled a touch event
44+ if ( touchHandledRef . current ) {
45+ touchHandledRef . current = false ;
46+ return ;
47+ }
48+
4049 clickCountRef . current += 1 ;
4150
4251 if ( clickCountRef . current === 1 ) {
@@ -59,20 +68,68 @@ export default function FileItem({
5968 }
6069 } ;
6170
71+ const handleTouchStart = ( e : React . TouchEvent ) => {
72+ touchHandledRef . current = true ;
73+ // Reset the flag after a delay to allow click events to be ignored
74+ setTimeout ( ( ) => {
75+ touchHandledRef . current = false ;
76+ } , 400 ) ;
77+
78+ const currentTime = new Date ( ) . getTime ( ) ;
79+ const tapLength = currentTime - lastTapRef . current ;
80+
81+ if ( tapLength < 300 && tapLength > 0 ) {
82+ // Double tap detected
83+ e . preventDefault ( ) ;
84+ e . stopPropagation ( ) ;
85+ if ( clickTimeoutRef . current ) {
86+ clearTimeout ( clickTimeoutRef . current ) ;
87+ clickTimeoutRef . current = null ;
88+ }
89+ clickCountRef . current = 0 ;
90+ onDoubleClick ( file ) ;
91+ } else {
92+ // Single tap - wait to see if there's a second tap
93+ clickCountRef . current = 1 ;
94+ clickTimeoutRef . current = setTimeout ( ( ) => {
95+ if ( clickCountRef . current === 1 ) {
96+ onSelect ( file ) ;
97+ }
98+ clickCountRef . current = 0 ;
99+ clickTimeoutRef . current = null ;
100+ } , 300 ) ;
101+ }
102+
103+ lastTapRef . current = currentTime ;
104+ } ;
105+
62106 if ( view === "grid" ) {
63107 return (
64108 < section
65109 className = { `group relative flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 p-2 transition-colors sm:p-4 ${ isSelected
66110 ? "border-[#7B42F6] bg-[#F9F6FF]"
67111 : "border-transparent bg-white hover:border-gray-300 hover:bg-gray-50"
68112 } `}
113+ style = { { touchAction : 'manipulation' } }
69114 onMouseEnter = { ( ) => setIsHovered ( true ) }
70115 onMouseLeave = { ( ) => setIsHovered ( false ) }
71116 onClick = { handleClick }
117+ onTouchStart = { handleTouchStart }
72118 role = "button"
73119 tabIndex = { 0 }
74120 aria-label = { `${ file . type === "folder" ? "Folder" : "File" } : ${ file . name } ` }
75121 >
122+ { isHovered && (
123+ < FileItemMenu
124+ file = { file }
125+ position = "top-right"
126+ onRename = { onRename }
127+ onDownload = { ( f ) => console . log ( "Download:" , f . name ) }
128+ onCopy = { ( f ) => console . log ( "Copy:" , f . name ) }
129+ onMove = { ( f ) => console . log ( "Move:" , f . name ) }
130+ onDelete = { ( f ) => console . log ( "Delete:" , f . name ) }
131+ />
132+ ) }
76133 < div className = "mb-1 flex h-12 w-12 items-center justify-center sm:mb-2 sm:h-16 sm:w-16" >
77134 { getFileIcon ( file . type , file . mimeType ) }
78135 </ div >
@@ -88,9 +145,11 @@ export default function FileItem({
88145 < section
89146 className = { `group flex cursor-pointer items-center gap-2 border-b border-gray-100 px-2 py-2 transition-colors sm:gap-4 sm:px-4 sm:py-3 ${ isSelected ? "bg-[#F9F6FF]" : "bg-white hover:bg-gray-50"
90147 } `}
148+ style = { { touchAction : 'manipulation' } }
91149 onMouseEnter = { ( ) => setIsHovered ( true ) }
92150 onMouseLeave = { ( ) => setIsHovered ( false ) }
93151 onClick = { handleClick }
152+ onTouchStart = { handleTouchStart }
94153 role = "button"
95154 tabIndex = { 0 }
96155 aria-label = { `${ file . type === "folder" ? "Folder" : "File" } : ${ file . name } ` }
@@ -108,17 +167,15 @@ export default function FileItem({
108167 { file . size && formatFileSize ( file . size ) }
109168 </ div >
110169 { isHovered && (
111- < div className = "flex-shrink-0" >
112- < Button
113- variant = "icon"
114- aria-label = "More options"
115- onClick = { ( e ) => {
116- e . stopPropagation ( ) ;
117- } }
118- >
119- < EllipsisVerticalIcon className = "h-4 w-4 sm:h-5 sm:w-5" />
120- </ Button >
121- </ div >
170+ < FileItemMenu
171+ file = { file }
172+ position = "right"
173+ onRename = { onRename }
174+ onDownload = { ( f ) => console . log ( "Download:" , f . name ) }
175+ onCopy = { ( f ) => console . log ( "Copy:" , f . name ) }
176+ onMove = { ( f ) => console . log ( "Move:" , f . name ) }
177+ onDelete = { ( f ) => console . log ( "Delete:" , f . name ) }
178+ />
122179 ) }
123180 </ section >
124181 ) ;
0 commit comments