wr: 파일명 잘리는 현상
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import { CloudUpload, Paperclip } from "lucide-react";
|
||||
import { FileInput, FileUploader, FileUploaderContent, FileUploaderItem } from "./lib/file_upload";
|
||||
|
||||
const dropZoneConfig = {
|
||||
maxFiles: 5,
|
||||
maxSize: 1024 * 1024 * 100,
|
||||
@@ -9,14 +10,19 @@ const dropZoneConfig = {
|
||||
|
||||
|
||||
interface FileUploadProps {
|
||||
files: File[]; // 파일 배열
|
||||
setFiles: (files: File[] | null) => void; // 파일 배열 세터
|
||||
fileTypes?: string; // 옵션 (파일 확장자 문자열)
|
||||
onFileClick?:(index:number)=> void;
|
||||
maxFiles?: number; // 파일 클릭 핸들러
|
||||
|
||||
files: File[]; // 화면에 표시할 현재 파일 목록
|
||||
setFiles: (files: File[] | null) => void; // 추가/삭제 후 변경된 파일 목록을 부모에 전달
|
||||
fileTypes?: string; // 허용할 확장자 문자열(ex: ".pdf,.jpg")
|
||||
onFileClick?:(index:number)=> void; // 파일명 영역 클릭 시 현재 목록 기준 index 전달
|
||||
maxFiles?: number; // 업로드 가능한 최대 파일 수
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통 파일 업로드 UI
|
||||
* - 실제 파일 상태는 부모가 관리하고, 이 컴포넌트는 목록 표시/추가/삭제 이벤트만 위임한다.
|
||||
* - 삭제 아이콘 클릭 시 FileUploader 내부에서 index 기준으로 파일을 제외한 배열을 만든 뒤 setFiles로 전달한다.
|
||||
*/
|
||||
export const FileUpload =
|
||||
({ files, setFiles, fileTypes ,onFileClick, maxFiles}: FileUploadProps) => {
|
||||
if(maxFiles){
|
||||
@@ -51,6 +57,7 @@ export const FileUpload =
|
||||
{files&&
|
||||
files.length > 0 &&
|
||||
files.map((file:File, i:number) => (
|
||||
// i는 현재 files 배열 순번이며, 삭제/클릭 이벤트에서 같은 순번값을 사용한다.
|
||||
<FileUploaderItem key={i} index={i} onFileClick={onFileClick }>
|
||||
<Paperclip className="h-4 w-4 stroke-current" />
|
||||
<span className="w-72 truncate">{file.name}</span>
|
||||
@@ -59,4 +66,4 @@ export const FileUpload =
|
||||
</FileUploaderContent>
|
||||
</FileUploader>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ type FileUploaderContextType = {
|
||||
|
||||
const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
|
||||
|
||||
//현재 value 배열에서 해당 index만 뺀 newFiles 생성
|
||||
// FileUploader 하위 컴포넌트들이 공통 상태와 핸들러를 사용할 수 있도록 context를 노출한다.
|
||||
export const useFileUpload = () => {
|
||||
const context = useContext(FileUploaderContext);
|
||||
if (!context) {
|
||||
@@ -55,7 +55,10 @@ type FileUploaderProps = {
|
||||
};
|
||||
|
||||
/**
|
||||
* File upload Docs: {@link: https://localhost:3000/docs/file-upload}
|
||||
* 파일 업로드의 공통 동작을 담당하는 컨테이너 컴포넌트
|
||||
* - 현재 파일 목록(value)을 기준으로 drag & drop / 선택 / 삭제를 처리한다.
|
||||
* - 실제 파일 상태는 부모가 관리하며, 변경 결과는 onValueChange로만 전달한다.
|
||||
* - FileInput, FileUploaderContent, FileUploaderItem은 이 context를 통해 같은 상태를 공유한다.
|
||||
*/
|
||||
|
||||
export const FileUploader = forwardRef<
|
||||
@@ -81,7 +84,7 @@ export const FileUploader = forwardRef<
|
||||
const [isLOF, setIsLOF] = useState(false);
|
||||
const [activeIndex, setActiveIndex] = useState(-1);
|
||||
|
||||
// MIME 타입 매핑 함수 추가
|
||||
// ".pdf,.jpg" 형태의 확장자 문자열을 react-dropzone accept 객체로 변환한다.
|
||||
const getAcceptObject = (fileTypes?: string): Record<string, string[]> => {
|
||||
if (!fileTypes) {
|
||||
return { "image/*": [".jpg", ".jpeg", ".png", ".gif"] };
|
||||
@@ -132,6 +135,7 @@ export const FileUploader = forwardRef<
|
||||
const reSelectAll = maxFiles === 1 ? true : reSelect;
|
||||
const direction: DirectionOptions = dir === "rtl" ? "rtl" : "ltr";
|
||||
|
||||
// 현재 value 배열에서 index 위치 파일만 제외한 새 배열을 부모로 전달한다.
|
||||
const removeFileFromSet = useCallback(
|
||||
(i: number) => {
|
||||
if (!value) return;
|
||||
@@ -141,6 +145,7 @@ export const FileUploader = forwardRef<
|
||||
[value, onValueChange],
|
||||
);
|
||||
|
||||
// 키보드로 파일 목록 탐색 및 삭제를 지원한다.
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
@@ -197,6 +202,7 @@ export const FileUploader = forwardRef<
|
||||
[value, activeIndex, removeFileFromSet],
|
||||
);
|
||||
|
||||
// 드롭/선택된 파일을 현재 목록과 합쳐서 부모 상태로 반영한다.
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
||||
const files = acceptedFiles;
|
||||
@@ -239,6 +245,7 @@ export const FileUploader = forwardRef<
|
||||
[reSelectAll, value],
|
||||
);
|
||||
|
||||
// 최대 파일 수에 도달하면 입력 영역을 비활성화한다.
|
||||
useEffect(() => {
|
||||
if (!value) return;
|
||||
if (value.length === maxFiles) {
|
||||
@@ -248,6 +255,7 @@ export const FileUploader = forwardRef<
|
||||
setIsLOF(false);
|
||||
}, [value, maxFiles]);
|
||||
|
||||
// react-dropzone에 넘길 최종 옵션 구성
|
||||
const opts = dropzoneOptions
|
||||
? { ...dropzoneOptions, accept: getAcceptObject(acceptName) }
|
||||
: { accept, maxFiles, maxSize, multiple };
|
||||
@@ -312,7 +320,7 @@ export const FileUploaderContent = forwardRef<
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"w-full flex rounded-xl gap-1",
|
||||
"w-full flex rounded-xl gap-0",
|
||||
orientation === "horizontal" ? "flex-raw flex-wrap" : "flex-col",
|
||||
className,
|
||||
)}
|
||||
@@ -336,14 +344,14 @@ export const FileUploaderItem = forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-6 p-1 justify-between cursor-pointer relative w-full",
|
||||
"h-9 justify-between cursor-pointer relative w-full",
|
||||
className,
|
||||
isSelected ? "bg-muted" : "",
|
||||
)}
|
||||
{...props}
|
||||
|
||||
>
|
||||
<div className="font-medium leading-none tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
|
||||
<div className="font-medium leading-normal tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
|
||||
onClick={() =>{onFileClick?.(index)
|
||||
}}>
|
||||
{children}
|
||||
@@ -353,7 +361,9 @@ export const FileUploaderItem = forwardRef<
|
||||
className={cn(
|
||||
"absolute",
|
||||
"!w-1/5 z-50",
|
||||
direction === "rtl" ? "top-1 left-1" : "top-1 right-1",
|
||||
direction === "rtl"
|
||||
? "left-1 top-1/2 -translate-y-1/2"
|
||||
: "right-1 top-1/2 -translate-y-1/2",
|
||||
|
||||
)}
|
||||
onClick={(e) =>{
|
||||
|
||||
Reference in New Issue
Block a user