@@ -12,6 +12,8 @@ import {
1212 RiFileLine ,
1313 RiEyeLine ,
1414 RiEdit2Line ,
15+ RiFileCopyLine ,
16+ RiFolderTransferLine ,
1517 RiArrowUpSLine ,
1618 RiArrowDownSLine ,
1719} from "@remixicon/react"
@@ -77,6 +79,7 @@ import {
7779 type ObjectRenameValidation ,
7880} from "@/lib/object-rename"
7981import { TaskStatsButton } from "@/components/tasks/stats-button"
82+ import { ObjectTransferDialog } from "@/components/object/transfer-dialog"
8083import { useAddDeleteKeys , useAddDeleteFolder , useTasks } from "@/contexts/task-context"
8184import type { ColumnDef } from "@tanstack/react-table"
8285
@@ -151,6 +154,12 @@ export function ObjectList({
151154 const [ renameSourceKey , setRenameSourceKey ] = React . useState ( "" )
152155 const [ renameName , setRenameName ] = React . useState ( "" )
153156 const [ renameSubmitting , setRenameSubmitting ] = React . useState ( false )
157+ const [ transfer , setTransfer ] = React . useState < {
158+ mode : "copy" | "move"
159+ key : string
160+ trigger : HTMLElement
161+ } | null > ( null )
162+ const refreshButtonRef = React . useRef < HTMLButtonElement > ( null )
154163
155164 const prefix = decodeURIComponent ( path )
156165 const resolvedPageSize = resolveObjectListPageSize ( pageSize )
@@ -522,7 +531,7 @@ export function ObjectList({
522531 header : ( ) => t ( "Actions" ) ,
523532 enableSorting : false ,
524533 cell : ( { row } ) => (
525- < div className = "flex items-center gap-2" >
534+ < div className = "flex flex-wrap items-center gap-2 [&_button]:min-h-11 sm:[&_button]:min-h-0 " >
526535 { row . original . type === "object" ? (
527536 < >
528537 { canCapability ( "objects.preview" , { bucket, objectKey : row . original . Key } ) ? (
@@ -549,6 +558,30 @@ export function ObjectList({
549558 < span > { t ( "Rename" ) } </ span >
550559 </ Button >
551560 ) : null }
561+ { canCapability ( "objects.copy" , { bucket, objectKey : row . original . Key } ) ? (
562+ < Button
563+ variant = "outline"
564+ size = "sm"
565+ onClick = { ( event ) =>
566+ setTransfer ( { mode : "copy" , key : row . original . Key , trigger : event . currentTarget } )
567+ }
568+ >
569+ < RiFileCopyLine data-icon = "inline-start" aria-hidden />
570+ < span > { t ( "Copy" ) } </ span >
571+ </ Button >
572+ ) : null }
573+ { canCapability ( "objects.move" , { bucket, objectKey : row . original . Key } ) ? (
574+ < Button
575+ variant = "outline"
576+ size = "sm"
577+ onClick = { ( event ) =>
578+ setTransfer ( { mode : "move" , key : row . original . Key , trigger : event . currentTarget } )
579+ }
580+ >
581+ < RiFolderTransferLine data-icon = "inline-start" aria-hidden />
582+ < span > { t ( "Move" ) } </ span >
583+ </ Button >
584+ ) : null }
552585 </ >
553586 ) : null }
554587 { canCapability ( "objects.delete" , { bucket, objectKey : row . original . Key } ) ? (
@@ -834,7 +867,11 @@ export function ObjectList({
834867 </ span >
835868 </ Button >
836869 ) : null }
837- < Button variant = "outline" onClick = { ( ) => ( onRefresh ? onRefresh ( ) : resetAndFetchObjects ( ) ) } >
870+ < Button
871+ ref = { refreshButtonRef }
872+ variant = "outline"
873+ onClick = { ( ) => ( onRefresh ? onRefresh ( ) : resetAndFetchObjects ( ) ) }
874+ >
838875 < RiRefreshLine className = "size-4" aria-hidden />
839876 < span > { t ( "Refresh" ) } </ span >
840877 </ Button >
@@ -988,6 +1025,17 @@ export function ObjectList({
9881025 </ AlertDialogContent >
9891026 </ AlertDialog >
9901027
1028+ { transfer ? (
1029+ < ObjectTransferDialog
1030+ mode = { transfer . mode }
1031+ bucket = { bucket }
1032+ objectKey = { transfer . key }
1033+ returnFocus = { ( ) => ( transfer . trigger . isConnected ? transfer . trigger : refreshButtonRef . current ) }
1034+ onClose = { ( ) => setTransfer ( null ) }
1035+ onRefresh = { resetAndFetchObjects }
1036+ />
1037+ ) : null }
1038+
9911039 < Dialog open = { renameDialogOpen } onOpenChange = { handleRenameOpenChange } disablePointerDismissal >
9921040 < DialogContent className = "sm:max-w-md" >
9931041 < DialogHeader >
0 commit comments