|
2 | 2 | import { HiUpload } from "react-icons/hi"; |
3 | 3 | import { forwardRef, useState, useEffect } from "react"; |
4 | 4 | import { toast } from "react-hot-toast"; |
| 5 | +import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd"; |
5 | 6 | import PreviewItem from "./PreviewItem"; |
6 | 7 | import { cn } from "@/lib/tailwindUtil"; |
7 | 8 | import { ImageInputType } from "@/types/addform"; |
8 | 9 |
|
9 | 10 | interface ImageInputProps { |
10 | 11 | name: string; |
11 | | - onChange?: (files: File[]) => void; |
| 12 | + onChange?: (files: File[] | string[]) => void; |
12 | 13 | onDelete?: (id: string) => void; |
13 | 14 | initialImageList: ImageInputType[]; |
14 | 15 | } |
@@ -66,34 +67,61 @@ const ImageInput = forwardRef<HTMLInputElement, ImageInputProps>((props, ref) => |
66 | 67 | innerHoverColor: "hover:bg-background-300", |
67 | 68 | }; |
68 | 69 |
|
| 70 | + const handleDragEnd = (result: DropResult) => { |
| 71 | + if (!result.destination) return; |
| 72 | + |
| 73 | + const items = Array.from(imageList); |
| 74 | + const [reorderedItem] = items.splice(result.source.index, 1); |
| 75 | + items.splice(result.destination.index, 0, reorderedItem); |
| 76 | + |
| 77 | + setImageList(items); |
| 78 | + // 상위 컴포넌트에 변경된 이미지 URL 배열 전달 |
| 79 | + props.onChange?.(items.map((item) => item.url)); |
| 80 | + }; |
| 81 | + |
69 | 82 | return ( |
70 | | - <div className="flex gap-5 lg:gap-6"> |
71 | | - <div |
72 | | - onClick={handleOpenFileSelector} |
73 | | - className={cn( |
74 | | - "relative size-20 cursor-pointer rounded-lg lg:size-[116px]", |
75 | | - colorStyle.bgColor, |
76 | | - colorStyle.borderColor, |
77 | | - colorStyle.hoverColor |
78 | | - )} |
79 | | - > |
80 | | - <input |
81 | | - ref={ref} |
82 | | - type="file" |
83 | | - name={props.name} |
84 | | - accept="image/*" |
85 | | - onChange={(e) => handleFileChange(e.target.files)} |
86 | | - className="hidden" |
87 | | - multiple |
88 | | - /> |
89 | | - <div className="pointer-events-none absolute top-0 z-10 p-7 lg:p-10"> |
90 | | - <HiUpload className="text-[24px] text-grayscale-400 lg:text-[36px]" /> |
| 83 | + <DragDropContext onDragEnd={handleDragEnd}> |
| 84 | + <div className="flex gap-5 lg:gap-6"> |
| 85 | + <div |
| 86 | + onClick={handleOpenFileSelector} |
| 87 | + className={cn( |
| 88 | + "relative size-20 cursor-pointer rounded-lg lg:size-[116px]", |
| 89 | + colorStyle.bgColor, |
| 90 | + colorStyle.borderColor, |
| 91 | + colorStyle.hoverColor |
| 92 | + )} |
| 93 | + > |
| 94 | + <input |
| 95 | + ref={ref} |
| 96 | + type="file" |
| 97 | + name={props.name} |
| 98 | + accept="image/*" |
| 99 | + onChange={(e) => handleFileChange(e.target.files)} |
| 100 | + className="hidden" |
| 101 | + multiple |
| 102 | + /> |
| 103 | + <div className="pointer-events-none absolute top-0 z-10 p-7 lg:p-10"> |
| 104 | + <HiUpload className="text-[24px] text-grayscale-400 lg:text-[36px]" /> |
| 105 | + </div> |
91 | 106 | </div> |
| 107 | + <Droppable droppableId="image-list" direction="horizontal"> |
| 108 | + {(provided) => ( |
| 109 | + <div {...provided.droppableProps} ref={provided.innerRef} className="flex gap-5 lg:gap-6"> |
| 110 | + {imageList.map((image, index) => ( |
| 111 | + <Draggable key={image.id} draggableId={image.id} index={index}> |
| 112 | + {(provided) => ( |
| 113 | + <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> |
| 114 | + <PreviewItem image={image} handleDeleteImage={handleDeleteImage} placeholder={false} /> |
| 115 | + </div> |
| 116 | + )} |
| 117 | + </Draggable> |
| 118 | + ))} |
| 119 | + {provided.placeholder} |
| 120 | + </div> |
| 121 | + )} |
| 122 | + </Droppable> |
92 | 123 | </div> |
93 | | - {imageList.map((image) => ( |
94 | | - <PreviewItem key={image.id} image={image} handleDeleteImage={handleDeleteImage} placeholder={false} /> |
95 | | - ))} |
96 | | - </div> |
| 124 | + </DragDropContext> |
97 | 125 | ); |
98 | 126 | }); |
99 | 127 |
|
|
0 commit comments