@@ -24,34 +24,48 @@ export function ScreenshotPreview({
2424 className
2525} : ScreenshotPreviewProps ) {
2626 const [ showActions , setShowActions ] = useState ( false ) ;
27+ const [ isDragging , setIsDragging ] = useState ( false ) ;
2728 const { isLoading } = useScreenshot ( ) ;
2829
2930 const DRAG_THRESHOLD = 5 ;
3031 const dragStateRef = useRef < { startX : number ; startY : number ; pending : boolean } | null > ( null ) ;
3132
3233 const handlePointerDown = useCallback ( ( e : React . PointerEvent < HTMLDivElement > ) => {
33- if ( e . button !== 0 || ! dragFilePath ) return ;
34+ if ( e . button !== 0 || ! dragFilePath || isDragging ) return ;
3435 if ( ( e . target as HTMLElement ) . closest ( 'button' ) ) return ;
3536 dragStateRef . current = { startX : e . clientX , startY : e . clientY , pending : true } ;
36- } , [ dragFilePath ] ) ;
37+ } , [ dragFilePath , isDragging ] ) ;
3738
3839 const handlePointerMove = useCallback ( ( e : React . PointerEvent < HTMLDivElement > ) => {
3940 const state = dragStateRef . current ;
40- if ( ! state ?. pending || ! dragFilePath ) return ;
41+ if ( ! state ?. pending || ! dragFilePath || isDragging ) return ;
4142 const dx = e . clientX - state . startX ;
4243 const dy = e . clientY - state . startY ;
4344 if ( Math . abs ( dx ) > DRAG_THRESHOLD || Math . abs ( dy ) > DRAG_THRESHOLD ) {
44- dragStateRef . current = null ;
45- startDrag ( { item : [ dragFilePath ] , icon : dragFilePath } ) . catch ( ( err ) => {
46- debugLog ( 'Native drag failed:' , err ) ;
47- } ) ;
45+ setIsDragging ( true ) ;
46+ startDrag ( { item : [ dragFilePath ] , icon : dragFilePath } )
47+ . then ( ( ) => {
48+ debugLog ( 'Drag completed successfully' ) ;
49+ } )
50+ . catch ( ( err ) => {
51+ debugLog ( 'Native drag failed:' , err ) ;
52+ } )
53+ . finally ( ( ) => {
54+ setIsDragging ( false ) ;
55+ dragStateRef . current = null ;
56+ } ) ;
4857 }
49- } , [ dragFilePath ] ) ;
58+ } , [ dragFilePath , isDragging ] ) ;
5059
5160 const handlePointerUp = useCallback ( ( ) => {
5261 dragStateRef . current = null ;
5362 } , [ ] ) ;
5463
64+ const handlePointerCancel = useCallback ( ( ) => {
65+ dragStateRef . current = null ;
66+ setIsDragging ( false ) ;
67+ } , [ ] ) ;
68+
5569 return (
5670 < div className = { className } >
5771 < div
@@ -60,13 +74,15 @@ export function ScreenshotPreview({
6074 onPointerDown = { handlePointerDown }
6175 onPointerMove = { handlePointerMove }
6276 onPointerUp = { handlePointerUp }
63- onMouseEnter = { ( ) => setShowActions ( true ) }
77+ onPointerCancel = { handlePointerCancel }
78+ onMouseEnter = { ( ) => ! isDragging && setShowActions ( true ) }
6479 onMouseLeave = { ( ) => setShowActions ( false ) }
6580 >
6681 { /* Delete button - top right */ }
6782 < button
6883 onClick = { onDelete }
69- className = "absolute top-2 right-2 z-20 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-black hover:bg-red-50 hover:border-red-500 hover:scale-110 transition-all duration-200"
84+ disabled = { isDragging }
85+ className = "absolute top-2 right-2 z-20 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-black hover:bg-red-50 hover:border-red-500 hover:scale-110 transition-all duration-200 disabled:opacity-50"
7086 aria-label = "Delete screenshot"
7187 title = "Delete"
7288 >
@@ -76,7 +92,7 @@ export function ScreenshotPreview({
7692 { /* Edit button - top left */ }
7793 < button
7894 onClick = { onEdit }
79- disabled = { isLoading }
95+ disabled = { isLoading || isDragging }
8096 className = "absolute top-2 left-2 z-20 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-black hover:bg-blue-50 hover:border-blue-500 hover:scale-110 transition-all duration-200 disabled:opacity-50"
8197 aria-label = "Edit screenshot"
8298 title = "Edit"
@@ -87,7 +103,9 @@ export function ScreenshotPreview({
87103 { /* Image container with drag support */ }
88104 < div className = { cn (
89105 "relative w-full h-full select-none" ,
90- dragFilePath ? "cursor-grab active:cursor-grabbing" : "cursor-default"
106+ dragFilePath && ! isDragging ? "cursor-grab" : "" ,
107+ isDragging ? "cursor-grabbing" : "" ,
108+ ! dragFilePath ? "cursor-default" : ""
91109 ) } >
92110 < div
93111 className = "w-full h-full bg-center bg-cover bg-no-repeat"
@@ -109,7 +127,7 @@ export function ScreenshotPreview({
109127 >
110128 < button
111129 onClick = { onCopy }
112- disabled = { isLoading }
130+ disabled = { isLoading || isDragging }
113131 className = "flex items-center justify-center px-5 py-1.5 bg-white text-black rounded-full hover:bg-white/90 hover:scale-105 active:scale-95 transition-all duration-200 disabled:opacity-50"
114132 aria-label = "Copy to clipboard"
115133 title = "Copy"
@@ -119,7 +137,7 @@ export function ScreenshotPreview({
119137
120138 < button
121139 onClick = { onSave }
122- disabled = { isLoading }
140+ disabled = { isLoading || isDragging }
123141 className = "flex items-center justify-center px-5 py-1.5 bg-white text-black rounded-full hover:bg-white/90 hover:scale-105 active:scale-95 transition-all duration-200 disabled:opacity-50"
124142 aria-label = "Save screenshot"
125143 title = "Save"
@@ -135,6 +153,15 @@ export function ScreenshotPreview({
135153 < div className = "w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin" />
136154 </ div >
137155 ) }
156+
157+ { /* Drag indicator overlay */ }
158+ { isDragging && (
159+ < div className = "absolute inset-0 bg-blue-500/20 flex items-center justify-center z-20 pointer-events-none" >
160+ < div className = "bg-white text-black px-3 py-1.5 rounded-full text-xs font-medium shadow-lg" >
161+ Release to drag
162+ </ div >
163+ </ div >
164+ ) }
138165 </ div >
139166 </ div >
140167 ) ;
0 commit comments