FileUpload
파일 선택·드래그앤드롭 표면 — dropzone/button 변형, list/picture-card 목록. 업로드 전송은 소비자 책임(엔드포인트 결합 없음), items 주입으로 제어형 업로드 상태(진행률/성공/실패+재시도) 표현. 제약 위반은 인라인 오류(role=alert) + onReject(한국어 reason + 표준 code). 붙여넣기(enablePaste)·폴더(directory) 추가 지원 (DK File/* 사양의 결합 제거판).
마지막 업데이트 2026-06-12
한눈에#
파일 선택·드래그앤드롭 표면입니다 — 업로드 전송은 결합하지 않고(엔드포인트 비결합) 표현만 담당합니다.
- dropzone·button 변형
- list·picture-card 목록
- 전송 비결합
- 제약 위반 인라인 오류
선택 파일은 내부 목록으로 유지되고 onFilesChange로 통지 — items 주입 시 진행률/성공/실패 제어
업로드 전송은 결합하지 않습니다 — 선택된 파일은 내부 목록으로만 유지되고
onFilesChange로 통지합니다(데모는 핸들러 없이도 목록 추가/제거가 동작).
같은 파일(이름+크기 동일)을 다시 넣으면 목록에 추가되지 않고
onReject(코드 FILE_EXISTS)로만 통지됩니다.
플레이그라운드#
컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.
import { FileUpload } from '@wds/ui-web';
<FileUpload />변형#
variant(표면)와 listType(목록 표현)이 변형 축입니다 —
variant='dropzone'(기본)은 드래그앤드롭 영역 + 클릭 선택, 'button'은 클릭
선택만. listType='list'(기본)는 행 목록, 'picture-card'는 정사각 썸네일
카드 그리드입니다.
items를 주입하면 제어 모드가 됩니다 — 내부 목록 대신 items를 렌더하고,
uploading은 Progress 합성+퍼센트, success는 체크 아이콘, error는 경고
아이콘+errorMessage+재시도 버튼(onRetry 제공 시)을 표시합니다. 전송·상태
전이는 전부 소비자 소유이며 컴포넌트는 표현만 담당합니다.
picture-card의 썸네일 Object URL은 항목 제거·언마운트 시 자동으로
revokeObjectURL 정리됩니다. 카드에도 상태 오버레이(업로드 중/실패)와 제거
버튼이 합성됩니다.
enablePaste는 드롭존이 포커스된 상태의 붙여넣기를 받습니다 — 이름 없는
클립보드 이미지(스크린샷)는 clipboard-{n}.png로 보정됩니다. directory는
보조 버튼(webkitdirectory)과 폴더 드롭 재귀 수집을 켭니다 — 폴더 안의
accept 불일치 파일은 소음을 줄이기 위해 조용히 건너뜁니다. directory는
multiple과 함께 쓰는 것을 권장합니다.
제약 위반은 role="alert" 인라인 오류로 안내되고 onReject로 통지됩니다.
거부 사유는 형식 불일치(accept) · 파일 용량 초과(maxFileSize) ·
총 용량 초과(maxTotalSize) · 개수 초과(maxFiles) · 중복 파일이며,
프로그래밍 분기용 표준 코드(FILE_INVALID_TYPE · FILE_TOO_LARGE ·
TOTAL_TOO_LARGE · TOO_MANY_FILES · FILE_EXISTS)가 함께 실립니다.
단일 모드(기본, multiple 미지정)는 새 파일이 기존 선택을 대체합니다.
크기#
dropzone은 최소 높이 space.16에 점선 보더(radius.lg)이고, button 변형은
Button(secondary)을 그대로 씁니다. list 항목은 이름(말줄임)·크기·제거
버튼(+상태 블록)으로 구성되며 폭은 부모 100%, picture-card는 7rem 이상
정사각 카드(radius.md)의 자동 채움 그리드입니다.
상태#
- Default — dropzone
color.surface-muted배경 + 점선color.border-strong - Hover —
color.surface-hover - Focus — dropzone(role=button)
:focus-visible에color.primary2px 아웃라인 - Dragging(드래그 오버) —
color.primary실선 보더 +color.primary-subtle배경 - 오류 —
role="alert"영역에color.error-text메시지 - 목록 항목 —
color.surface카드 + 제거 버튼(hovercolor.surface-hover) - 업로드 중(items) — Progress(primary 바) + 퍼센트
- 업로드 완료(items) —
color.success-text체크 아이콘 - 업로드 실패(items) —
color.error-text경고 아이콘 + 메시지 + 재시도(primary 텍스트 버튼)
Props#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
variant | 'dropzone' | 'button' | 'dropzone' | dropzone: 드래그앤드롭 + 클릭 / button: 클릭 선택만 |
listType | 'list' | 'picture-card' | 'list' | list: 행 목록 / picture-card: 썸네일 정사각 카드 그리드 |
multiple | boolean | false | 다중 선택 — false면 새 파일이 기존 선택을 대체 |
accept | string | — | 네이티브 accept 문법 — '.png', 'image/png', 'image/*' 콤마 구분 |
maxFileSize | number | — | 파일당 최대 바이트 — 초과 시 '파일 용량 초과' 거부 |
maxTotalSize | number | — | 목록 합계 최대 바이트 — 초과 시 '총 용량 초과' 거부 |
maxFiles | number | — | 최대 파일 수 — 초과 시 '개수 초과' 거부 |
items | ReadonlyArray<FileUploadItem> | — | 제어형 업로드 상태 — 제공 시 내부 목록 대신 items를 렌더(상태는 소비자 소유) |
onRetry | (file: File) => void | — | error 항목 재시도 — 제공 시에만 '재시도' 버튼 렌더 |
enablePaste | boolean | false | 드롭존 포커스 중 Ctrl+V 추가 — 이름 없는 이미지는 'clipboard-{n}.png' 보정 |
directory | boolean | false | 폴더 업로드 — 보조 버튼(webkitdirectory) + 드롭 시 하위 폴더 재귀 수집 |
directoryLabel | string | '폴더 선택' | 폴더 선택 보조 버튼 라벨 |
onFilesChange | (files: File[]) => void | — | 목록 변경 통지 — 항상 새 배열 (제어 모드에서는 items 갱신의 단일 입력) |
onReject | (rejections: FileRejection[]) => void | — | 제약 위반 통지 — { file, reason, code }[] |
label | string | 변형별 기본 문구 | 안내 라벨 (dropzone '파일을 끌어다 놓거나 클릭해 선택' / button '파일 선택') |
removeFileLabel | (file: File) => string | '{파일명} 제거' | 제거 버튼 aria-label |
…rest | HTMLAttributes<HTMLDivElement> | — | aria-label · className 등은 루트 div로 전달 |
FileUploadItem(items 항목):
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
file | File | — | 네이티브 File |
status | 'idle' | 'uploading' | 'success' | 'error' | — | 미지정이면 상태 표시 없음(비제어 목록과 동일한 외형) |
progress | number | — | 0-100 — 'uploading'일 때 진행률 바에 반영 |
errorMessage | string | '업로드 실패' | 'error'일 때 표시 |
FileRejection(onReject 인자):
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
file | File | — | 거부된 네이티브 File |
reason | FileRejectionReason | — | '파일 용량 초과' | '총 용량 초과' | '개수 초과' | '형식 불일치' | '중복 파일' |
code | FileRejectionCode | — | 'FILE_TOO_LARGE' | 'TOTAL_TOO_LARGE' | 'TOO_MANY_FILES' | 'FILE_INVALID_TYPE' | 'FILE_EXISTS' |
접근성#
dropzone은 role="button" + tabIndex=0으로 포커스·키보드 경로를 가지고,
네이티브 <input type="file">은 시각 숨김 + 탭 순서 제외(aria-hidden)로
영역/버튼 클릭과 연결됩니다.
| 키 | 동작 |
|---|---|
dropzone Enter / Space | 파일 선택 창 열기 |
dropzone Ctrl+V / ⌘V | 클립보드 파일 추가 (enablePaste일 때) |
| Tab | dropzone(또는 button) → 폴더 선택 → 재시도/제거 버튼 순회 |
제거·재시도 버튼 Enter / Space | 해당 동작 실행 (네이티브 button) |
- 제약 위반 안내는
role="alert"로 즉시 낭독되고, 파일 목록은aria-label="선택된 파일"리스트입니다 - 제어 모드 상태 변화는
role="status"라이브 리전으로 요약 낭독됩니다 — 진행률 틱은 요약을 바꾸지 않아 소음이 없습니다(예: '업로드 중 1건, 업로드 실패 1건') - 업로드 진행률은
role="progressbar"(aria-valuenow, 라벨 '{파일명} 업로드 진행률')로 전달됩니다 - 재시도 버튼은 '{파일명} 재시도', 제거 버튼은 '{파일명} 제거' 라벨을 갖고, 둘 다 시각 크기보다 넓은 44px 히트 영역으로 확장됩니다
토큰#
component 토큰 없이 semantic(+Button·Progress 합성)을 직접 소비합니다(신설 기준 §4 미충족).
| 속성 | 토큰 |
|---|---|
| dropzone | color.surface-muted · 점선 color.border-strong · radius.lg · 최소 높이 space.16 |
| dropzone hover/active | color.surface-hover / color.surface-pressed |
| dragging | color.primary(실선) · color.primary-subtle |
| Focus | color.focus-ring(2px 아웃라인) |
| 오류 | color.error-text · font-size.caption |
| 목록 항목 | color.surface · color.border · radius.md |
| picture-card | color.surface-muted · color.border · radius.md · 카드명/오버레이 color.surface 반투명 |
| 업로드 완료 | color.success-text |
| 업로드 실패 | color.error-text · 재시도 color.primary |
| 제거 버튼 | color.text-muted — hover color.surface-hover + color.text → active color.surface-pressed |
| 모션 | duration.fast + ease.standard |