1- import { useState , useRef , useEffect } from 'react' ;
1+ import { useState , useRef , useEffect , type DragEvent } from 'react' ;
22import { useTranslation } from 'react-i18next' ;
33import { useQuery , useMutation , useQueryClient } from '@tanstack/react-query' ;
44import { pluginsApi } from '../../shared/api/plugins' ;
@@ -459,9 +459,11 @@ function InstallPluginModal({
459459 const { t } = useTranslation ( ) ;
460460 const { toast } = useToast ( ) ;
461461 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
462+ const dragCounterRef = useRef ( 0 ) ;
462463
463464 const [ installTab , setInstallTab ] = useState < 'upload' | 'github' > ( 'upload' ) ;
464465 const [ selectedFile , setSelectedFile ] = useState < File | null > ( null ) ;
466+ const [ dragActive , setDragActive ] = useState ( false ) ;
465467 const [ pluginName , setPluginName ] = useState ( '' ) ;
466468 const [ githubRepo , setGithubRepo ] = useState ( '' ) ;
467469
@@ -491,14 +493,46 @@ function InstallPluginModal({
491493 setSelectedFile ( null ) ;
492494 setPluginName ( '' ) ;
493495 setGithubRepo ( '' ) ;
496+ dragCounterRef . current = 0 ;
494497 if ( fileInputRef . current ) fileInputRef . current . value = '' ;
495498 }
496499
497500 function handleClose ( ) {
498501 resetForm ( ) ;
502+ setDragActive ( false ) ;
499503 onClose ( ) ;
500504 }
501505
506+ function handleFileSelect ( file : File | null ) {
507+ setSelectedFile ( file ) ;
508+ setDragActive ( false ) ;
509+ }
510+
511+ function handleDragEvent ( e : DragEvent < HTMLDivElement > ) {
512+ e . preventDefault ( ) ;
513+ e . stopPropagation ( ) ;
514+ }
515+
516+ function handleDragEnter ( e : DragEvent < HTMLDivElement > ) {
517+ handleDragEvent ( e ) ;
518+ dragCounterRef . current += 1 ;
519+ setDragActive ( true ) ;
520+ }
521+
522+ function handleDragLeave ( e : DragEvent < HTMLDivElement > ) {
523+ handleDragEvent ( e ) ;
524+ dragCounterRef . current = Math . max ( 0 , dragCounterRef . current - 1 ) ;
525+ if ( dragCounterRef . current === 0 ) {
526+ setDragActive ( false ) ;
527+ }
528+ }
529+
530+ function handleDrop ( e : DragEvent < HTMLDivElement > ) {
531+ handleDragEvent ( e ) ;
532+ dragCounterRef . current = 0 ;
533+ handleFileSelect ( e . dataTransfer . files ?. [ 0 ] || null ) ;
534+ }
535+
502536 const installing = uploadMutation . isPending || githubMutation . isPending ;
503537
504538 const installTabs = [
@@ -564,18 +598,23 @@ function InstallPluginModal({
564598 { t ( 'plugins.plugin_file' ) } < span className = "text-danger" > *</ span >
565599 </ label >
566600 < div
567- className = { `border-2 border-dashed rounded-md p-6 text-center cursor-pointer transition-colors ${
568- selectedFile
601+ className = { `border-2 border-dashed rounded-md p-6 text-center cursor-pointer transition-colors ${ selectedFile
569602 ? 'border-primary bg-primary-subtle'
570- : 'border-glass-border hover:border-border-focus'
571- } `}
603+ : dragActive
604+ ? 'border-border-focus bg-[var(--ag-bg-muted)]'
605+ : 'border-glass-border hover:border-border-focus'
606+ } `}
572607 onClick = { ( ) => fileInputRef . current ?. click ( ) }
608+ onDragOver = { handleDragEvent }
609+ onDragEnter = { handleDragEnter }
610+ onDragLeave = { handleDragLeave }
611+ onDrop = { handleDrop }
573612 >
574613 < input
575614 ref = { fileInputRef }
576615 type = "file"
577616 className = "hidden"
578- onChange = { ( e ) => setSelectedFile ( e . target . files ?. [ 0 ] || null ) }
617+ onChange = { ( e ) => handleFileSelect ( e . target . files ?. [ 0 ] || null ) }
579618 />
580619 { selectedFile ? (
581620 < div className = "flex items-center justify-center gap-2" >
@@ -587,7 +626,7 @@ function InstallPluginModal({
587626 </ div >
588627 ) : (
589628 < div >
590- < Upload className = " w-8 h-8 mx-auto mb-2 text-text-tertiary" />
629+ < Upload className = { ` w-8 h-8 mx-auto mb-2 ${ dragActive ? ' text-primary' : ' text-text- tertiary' } ` } />
591630 < p className = "text-sm text-text-tertiary" >
592631 { t ( 'plugins.upload_hint' ) }
593632 </ p >
0 commit comments