"use client"; import { useEffect, useId, useRef, useState, type ChangeEvent, type CSSProperties, type DragEvent, } from "react"; import { FoxFormLabel, type FoxFormLabelRequirement } from "../fox-form-label"; import { cx } from "../../utils"; import { FoxButton } from "../fox-button"; import { FoxIconButton } from "../fox-icon-button"; import { FoxInput } from "../fox-input"; import { FoxTextButton } from "../fox-text-button"; export type FoxFileUploadMode = "default" | "area" | "image"; /** 목록에 그릴 파일 한 줄. 업로드 상태는 호출부가 이 모양으로 바꿔서 넘긴다. */ export interface FoxFileItem { /** 목록에서 구분하는 키. */ id: string; /** 확장자를 뺀 이름. 길면 말줄임된다. */ name: string; /** 이름 뒤에 붙는 표기 — 시안은 `.PDF (20KB)`. 줄어들지 않는다. */ meta?: string; /** 참이면 줄이 붉어지고 `message`가 아래에 붙는다. */ error?: boolean; message?: string; /** image 모드에서 썸네일로 쓸 주소. */ previewUrl?: string; } export interface FoxFileUploadProps { mode?: FoxFileUploadMode; /** 주면 제어 컴포넌트가 된다. 없으면 고른 파일을 안에서 들고 있는다. */ files?: FoxFileItem[]; defaultFiles?: FoxFileItem[]; /** 목록이 바뀔 때(고르기·삭제) 부른다. */ onFilesChange?: (files: FoxFileItem[]) => void; /** 파일을 고르면 원본 `File`을 그대로 넘긴다 — 업로드는 호출부가 한다. */ onSelect?: (picked: File[]) => void; onRemove?: (id: string) => void; accept?: string; multiple?: boolean; /** * 네이티브 파일 입력의 `name`. 주면 고른 파일이 감싼 `
`의 제출에 그대로 실린다 — * 값을 따로 들고 있지 않아도 서버로 넘어간다. */ name?: string; /** * `default`·`area`에서 목록을 보여준다. `image`에는 목록이 없다. * 넘기지 않으면 `area`이거나 여러 개를 받을 때만 켜진다 — 파일 하나는 칸이 곧 목록이다. */ showList?: boolean; /** 끌어다 놓기. `area`는 안내문이 그렇게 말하므로 이 값과 무관하게 항상 켜져 있다. */ droppable?: boolean; label?: string; /** 라벨 뒤 필수·선택 표시. `label`이 없으면 의미 없다(`FoxInput`·`FoxSelect`와 같은 규약). */ requirement?: FoxFormLabelRequirement; description?: string; placeholder?: string; areaText?: string; selectLabel?: string; removeLabel?: string; /** `default` 모드 입력칸 폭. 시안은 320px이고 토큰에 없어 여기서 받는다. */ inputWidth?: string | number; /** `image` 모드 썸네일 크기. 시안은 213×120px(16:9)이고 토큰에 없어 여기서 받는다. */ thumbnailWidth?: string | number; thumbnailHeight?: string | number; /** * `image` 모드에서 썸네일 상자만 그리고 **상자 자체를 파일선택 버튼**으로 쓴다 — 파일선택 * 버튼·설명·삭제 오버레이가 없다(시안 tbl-form의 컷 업로드). 채워진 뒤에도 상자를 누르면 * 다시 고른다. 비우기·삭제가 필요하면 호출부가 바깥에 만든다. */ thumbnailPicker?: boolean; disabled?: boolean; /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ hidden?: boolean; /** 배치 조정용. */ className?: string; } const DEFAULT_AREA_TEXT = "첨부할 파일을 여기에 끌어다 놓거나 파일 선택 버튼을 직접 선택해주세요."; /* ── 아이콘 ──────────────────────────────────────────────────────────────── @fox에 아이콘 세트가 없어 시안의 모양을 직접 그린다. 세트가 들어오면 교체할 자리다. 전부 `currentColor`라 감싼 쪽이 색을 정한다. */ function UploadIcon() { return