File Upload
FileUpload là vùng kéo thả kèm danh sách tệp: chọn bằng chuột, bàn phím, kéo thả hoặc dán; tự kiểm tra định dạng, dung lượng, số lượng.
Import
Cài từ npm, chép mã nguồn vào dự án bằng @yugen-ui/cli hoặc shadcn CLI, hoặc import thẳng khi làm việc trong repo này.
// npm install yugen-uiimport { FileUpload, FileUploadItem, createFileEntry, isFileAccepted, type FileUploadEntry } from "yugen-ui";Biến thể
Mỗi ô là một mẫu độc lập: hover để Copy, hoặc bấm Code để xem mã nguồn.
Tải lên có tiến trình, lỗi và Thử lại
Chế độ điều khiển: files + onFilesChange; mỗi tệp mới được tải lên ngay, cập nhật progress (0 → 1) và status. Bật “Giả lập lỗi mạng” để thấy nút Thử lại.
Một ảnh, có thumbnail và dán từ clipboard
multiple mặc định false: chọn ảnh mới sẽ thay ảnh cũ. allowPaste nhận ảnh dán bằng Ctrl + V khi vùng thả đang focus.
Bắt buộc trong Field
Field gắn nhãn, hint và validationMessage; aria-invalid làm viền vùng thả chuyển sang màu lỗi. Bấm Gửi hồ sơ khi chưa chọn tệp.
Cách dùng
- Ghi rõ định dạng và dung lượng cho phép (tự sinh từ accept / maxSize hoặc qua description) trước khi người dùng chọn tệp.
- Hiện tiến trình cho tệp lớn và luôn có Thử lại khi tải lỗi — đừng bắt chọn lại từ đầu.
- Bọc trong Field để có nhãn, hint và thông báo lỗi liên kết đúng với control.
- Không chỉ dựa vào accept phía client — server vẫn phải kiểm tra loại và dung lượng tệp.
- Không dùng FileUpload cho một nút “Đính kèm” nhỏ trong thanh soạn tin — dùng PromptInput / Button + input ẩn.
- Không tự động gửi form khi vừa chọn tệp; để người dùng xem lại danh sách trước.
Props
| Prop · Kiểu | Mặc định | Mô tả |
|---|---|---|
| acceptstring | Mặc định: — | Như input gốc: ".pdf,image/*". Áp dụng cả với tệp kéo thả / dán. |
| multipleboolean | Mặc định: false | Chọn nhiều tệp. Tắt → tệp mới thay tệp cũ. |
| maxFilesnumber | Mặc định: — | Tổng số tệp tối đa (khi multiple). Tệp vượt bị từ chối với lý do too-many-files. |
| maxSizenumber | Mặc định: — | Dung lượng tối đa mỗi tệp (byte). |
| validate(file: File) => string | null | undefined | Mặc định: — | Kiểm tra riêng; trả về chuỗi lỗi để từ chối. |
| files / defaultFilesFileUploadEntry[] | Mặc định: — | Danh sách tệp (điều khiển / ban đầu). Entry: { id, name, size, type?, file?, previewUrl?, status?, progress?, error? }. |
| onFilesChange(files, { added, removed }) => void | Mặc định: — | Gọi khi thêm / xoá tệp. |
| onReject(rejections: FileUploadRejection[]) => void | Mặc định: — | Tệp bị từ chối: { file, code: file-invalid-type | file-too-large | too-many-files | custom, message }. |
| onRetry(entry: FileUploadEntry) => void | Mặc định: — | Có → tệp status="error" hiện nút Thử lại. |
| allowPasteboolean | Mặc định: false | Nhận tệp dán bằng Ctrl + V khi vùng thả đang focus. |
| showThumbnailsboolean | Mặc định: true | Ảnh thu nhỏ cho tệp ảnh (tạo trong effect, thu hồi khi gỡ / unmount). |
| label / descriptionReactNode | Mặc định: — | Dòng chính / dòng phụ trong vùng thả. |
| disabled / requiredboolean | Mặc định: — | Field tự truyền. required → aria-required (không dùng required gốc vì input được xoá giá trị sau mỗi lần chọn). |
| refRef<HTMLInputElement> | Mặc định: — | Trỏ tới <input type="file"> — ref.current.click() mở hộp thoại, focus() cho react-hook-form. |
| FileUploadItemcomponent (<li>) | Mặc định: — | Dòng tệp dùng riêng được: name, size, type, file, previewUrl, status, progress, error, onRemove, onRetry. |
| createFileEntry / isFileAccepted(file, init?) => FileUploadEntry / (file, accept?) => boolean | Mặc định: — | Tiện ích tạo entry và kiểm tra accept. Định dạng dung lượng dùng formatFileSize (dùng chung với PromptInput). |
Messages
Chữ có sẵn của component: nhãn, aria-label, thông báo cho trình đọc màn hình.
Mặc định là tiếng Anh. Đổi qua prop messages của YugenProvider, theo namespace bên dưới; messagesVi chứa bản tiếng Việt. Xem Ngôn ngữ giao diện.
messages.fileUpload
| Khoá · Kiểu | Mặc định (tiếng Anh) | messagesVi |
|---|---|---|
| dropzonestring Dòng chính của vùng thả: | Mặc định (tiếng Anh)"Drag files here or" | messagesVi"Kéo thả tệp vào đây hoặc" |
| browsestring | Mặc định (tiếng Anh)"browse" | messagesVi"chọn tệp" |
| droppingstring Dòng chính khi đang kéo tệp vào vùng thả. | Mặc định (tiếng Anh)"Drop files to upload" | messagesVi"Thả tệp để tải lên" |
| selectedFilesstring aria-label danh sách tệp đã chọn. | Mặc định (tiếng Anh)"Selected files" | messagesVi"Tệp đã chọn" |
| typeImagestring Tên nhóm MIME trong mô tả định dạng ( | Mặc định (tiếng Anh)"images" | messagesVi"ảnh" |
| typeVideostring | Mặc định (tiếng Anh)"videos" | messagesVi"video" |
| typeAudiostring | Mặc định (tiếng Anh)"audio" | messagesVi"âm thanh" |
| typeTextstring | Mặc định (tiếng Anh)"text" | messagesVi"văn bản" |
| maxSizeHint(size: string) => string Phần mô tả tự sinh; | Mặc định (tiếng Anh)(size) => `up to ${size} per file` | messagesVi(size) => `tối đa ${size} mỗi tệp` |
| maxFilesHint(count: number) => string | Mặc định (tiếng Anh)(count) => (count === 1 ? "up to 1 file" : `up to ${count} files`) | messagesVi(count) => `tối đa ${count} tệp` |
| invalidType(name: string, accepted: string) => string Lỗi từ chối tệp; | Mặc định (tiếng Anh)(name, accepted) => `"${name}" isn't a supported file type (accepted: ${accepted}).` | messagesVi(name, accepted) => `"${name}" không đúng định dạng (chỉ nhận ${accepted}).` |
| tooLarge(name: string, size: string) => string | Mặc định (tiếng Anh)(name, size) => `"${name}" is larger than the ${size} limit.` | messagesVi(name, size) => `"${name}" vượt quá dung lượng tối đa ${size}.` |
| tooMany(name: string, limit: number) => string | Mặc định (tiếng Anh)(name, limit) => `You can select up to ${limit === 1 ? "1 file" : `${limit} files`}. "${name}" was skipped.` | messagesVi(name, limit) => `Chỉ được chọn tối đa ${limit} tệp — đã bỏ qua "${name}".` |
| onlyOne(name: string) => string Khi không bật | Mặc định (tiếng Anh)(name) => `You can select only 1 file. "${name}" was skipped.` | messagesVi(name) => `Chỉ được chọn 1 tệp — đã bỏ qua "${name}".` |
| added(name: string) => string Thông báo (aria-live) khi thêm / xoá tệp. | Mặc định (tiếng Anh)(name) => `Added ${name}.` | messagesVi(name) => `Đã thêm tệp ${name}.` |
| addedCount(count: number) => string | Mặc định (tiếng Anh)(count) => `Added ${count} files.` | messagesVi(count) => `Đã thêm ${count} tệp.` |
| removed(name: string) => string | Mặc định (tiếng Anh)(name) => `Removed ${name}.` | messagesVi(name) => `Đã xoá tệp ${name}.` |
| uploadProgress(name: string) => string FileUploadItem. | Mặc định (tiếng Anh)(name) => `Upload progress for ${name}` | messagesVi(name) => `Tiến trình tải lên ${name}` |
| uploadingstring | Mặc định (tiếng Anh)"Uploading…" | messagesVi"Đang tải lên…" |
| uploadingPercent(percent: number) => string | Mặc định (tiếng Anh)(percent) => `Uploading… ${percent}%` | messagesVi(percent) => `Đang tải lên… ${percent}%` |
| uploadedstring | Mặc định (tiếng Anh)"Uploaded" | messagesVi"Đã tải lên" |
| failedstring | Mặc định (tiếng Anh)"Upload failed" | messagesVi"Tải lên thất bại" |
| retry(name: string) => string | Mặc định (tiếng Anh)(name) => `Retry uploading ${name}` | messagesVi(name) => `Thử lại tải lên ${name}` |
| remove(name: string) => string | Mặc định (tiếng Anh)(name) => `Remove ${name}` | messagesVi(name) => `Xoá tệp ${name}` |