diff --git a/docs/app/components/content/examples/file-upload/FileUploadExtendTypeExample.vue b/docs/app/components/content/examples/file-upload/FileUploadExtendTypeExample.vue new file mode 100644 index 0000000000..96b6c24e5d --- /dev/null +++ b/docs/app/components/content/examples/file-upload/FileUploadExtendTypeExample.vue @@ -0,0 +1,14 @@ + + + diff --git a/docs/app/components/content/examples/file-upload/FileUploadFileValidationExample.vue b/docs/app/components/content/examples/file-upload/FileUploadFileValidationExample.vue new file mode 100644 index 0000000000..9289910d52 --- /dev/null +++ b/docs/app/components/content/examples/file-upload/FileUploadFileValidationExample.vue @@ -0,0 +1,86 @@ + + + diff --git a/docs/app/components/content/examples/file-upload/FileUploadFormFieldExample.vue b/docs/app/components/content/examples/file-upload/FileUploadFormFieldExample.vue new file mode 100644 index 0000000000..143a296b08 --- /dev/null +++ b/docs/app/components/content/examples/file-upload/FileUploadFormFieldExample.vue @@ -0,0 +1,19 @@ + + + diff --git a/docs/content/3.components/file-upload.md b/docs/content/3.components/file-upload.md new file mode 100644 index 0000000000..1eb4288707 --- /dev/null +++ b/docs/content/3.components/file-upload.md @@ -0,0 +1,248 @@ +--- +title: FileUpload +description: A drag-and-drop file upload component. +category: form +links: + - label: GitHub + icon: i-simple-icons-github + to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/FileUpload.vue +navigation.badge: New +--- + +## Usage + +Use the `v-model` directive to control the value of the Input. + +::component-code +--- +ignore: + - modelValue +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] +--- +:: + +### Accept + +Use the `accept` prop to specify the types of files that can be uploaded. + +::component-code +--- +ignore: + - modelValue +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] + accept: 'image/*' +--- +:: + +### Label + +Use the `label` prop to set the label of the component. + +::component-code +--- +ignore: + - modelValue +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] + label: 'Upload your image' +--- +:: + +### Upload Icon + +Use the `uploadIcon` prop to set a custom upload icon. + +::component-code +--- +ignore: + - modelValue +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] + uploadIcon: 'i-heroicons-cloud-arrow-up-solid' +--- +:: + +::framework-only +#nuxt +:::tip{to="/getting-started/icons/nuxt#theme"} +You can customize this icon globally in your `app.config.ts` under `ui.icons.upload` key. +::: + +#vue +:::tip{to="/getting-started/icons/vue#theme"} +You can customize this icon globally in your `vite.config.ts` under `ui.icons.upload` key. +::: +:: + +### Size + +Use the `size` prop to set the size of the component. + +::component-code +--- +ignore: + - modelValue +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] + size: xl +--- +:: + +### Multiple + +Use the `multiple` prop to allow multiple file uploads. + +::component-code +--- +ignore: + - modelValue + - multiple +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] + multiple: true +--- +:: + +### File Icon + +Use the `fileIcon` prop to set a custom file icon. + +::component-code +--- +ignore: + - modelValue + - accept +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: + - file: + name: 'example.txt' + size: 3145728 + type: 'text/plain' + fileIcon: 'i-heroicons-document-text-solid' + accept: 'text/plain' +--- +:: + +::framework-only +#nuxt +:::tip{to="/getting-started/icons/nuxt#theme"} +You can customize this icon globally in your `app.config.ts` under `ui.icons.file` key. +::: + +#vue +:::tip{to="/getting-started/icons/vue#theme"} +You can customize this icon globally in your `vite.config.ts` under `ui.icons.file` key. +::: +:: + +### Disabled + +Use the `disabled` prop to disable the component. + +::component-code +--- +ignore: + - modelValue +external: + - modelValue +externalTypes: + - FileUploadItem[] +props: + modelValue: [] + disabled: true +--- +:: + +## Examples + +### With custom type + +You can extend the type to include additional properties. + +::component-example +--- +name: 'file-upload-extend-type-example' +--- +:: + +### Within a FormField + +You can use the FileUpload within a [FormField](/components/form-field) component to display a label, help text, required indicator, etc. + +::component-example +--- +name: 'file-upload-form-field-example' +--- +:: + +::tip{to="/components/form"} +It also provides validation and error handling when used within a **Form** component. +:: + +### With file validation + +You can build a custom validation function to check the file type and size. + +::component-example +--- +collapse: true +name: 'file-upload-file-validation-example' +--- +:: + + +## API + +### Props + +:component-props + +### Slots + +:component-slots + +### Emits + +:component-emits + +### Expose + +When accessing the component via a template ref, you can use the following: + +| Name | Type | +| ---- | ---- | +| `fileInputRef`{lang="ts-type"} | `Ref`{lang="ts-type"} | + +## Theme + +:component-theme diff --git a/playground-vue/src/app.vue b/playground-vue/src/app.vue index caaf932a0c..2e9476a1e0 100644 --- a/playground-vue/src/app.vue +++ b/playground-vue/src/app.vue @@ -37,6 +37,7 @@ const components = [ 'dropdown-menu', 'form', 'form-field', + 'file-upload', 'input', 'input-menu', 'input-number', diff --git a/playground/app/app.vue b/playground/app/app.vue index 5bfd85766e..5c5b9260d5 100644 --- a/playground/app/app.vue +++ b/playground/app/app.vue @@ -37,6 +37,7 @@ const components = [ 'dropdown-menu', 'form', 'form-field', + 'file-upload', 'input', 'input-menu', 'input-number', diff --git a/playground/app/pages/components/file-upload.vue b/playground/app/pages/components/file-upload.vue new file mode 100644 index 0000000000..89ddc76187 --- /dev/null +++ b/playground/app/pages/components/file-upload.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/runtime/components/FileUpload.vue b/src/runtime/components/FileUpload.vue new file mode 100644 index 0000000000..60851bf535 --- /dev/null +++ b/src/runtime/components/FileUpload.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/runtime/locale/ar.ts b/src/runtime/locale/ar.ts index 0c7de046b9..64adad7b4c 100644 --- a/src/runtime/locale/ar.ts +++ b/src/runtime/locale/ar.ts @@ -21,6 +21,9 @@ export default defineLocale({ increment: 'زيادة', decrement: 'تقليل' }, + fileUpload: { + empty: 'استعرض أو اسحب الملفات هنا' + }, commandPalette: { placeholder: 'اكتب أمرًا أو ابحث...', noMatch: 'لا توجد نتائج مطابقة', diff --git a/src/runtime/locale/az.ts b/src/runtime/locale/az.ts index baeca3ba25..caa99da448 100644 --- a/src/runtime/locale/az.ts +++ b/src/runtime/locale/az.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Artır', decrement: 'Azalt' }, + fileUpload: { + empty: 'Faylları buraya sürükləyin və ya seçin' + }, commandPalette: { placeholder: 'Əmr daxil edin və ya axtarın...', noMatch: 'Uyğun məlumat tapılmadı', diff --git a/src/runtime/locale/bg.ts b/src/runtime/locale/bg.ts index 9282ed294b..556dd15ac9 100644 --- a/src/runtime/locale/bg.ts +++ b/src/runtime/locale/bg.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Увеличаване', decrement: 'Намаляване' }, + fileUpload: { + empty: 'Прегледайте или пуснете файлове тук' + }, commandPalette: { placeholder: 'Въведете команда или потърсете...', noMatch: 'Няма съвпадение на данни', diff --git a/src/runtime/locale/bn.ts b/src/runtime/locale/bn.ts index a4abeecdf9..72ccff8587 100644 --- a/src/runtime/locale/bn.ts +++ b/src/runtime/locale/bn.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'বৃদ্ধি করুন', decrement: 'হ্রাস করুন' }, + fileUpload: { + empty: 'ফাইল ব্রাউজ করুন বা এখানে ড্রপ করুন' + }, commandPalette: { placeholder: 'কমান্ড টাইপ করুন বা অনুসন্ধান করুন...', noMatch: 'কোন মিল পাওয়া যায়নি', diff --git a/src/runtime/locale/ca.ts b/src/runtime/locale/ca.ts index 7660bca021..08d3b048aa 100644 --- a/src/runtime/locale/ca.ts +++ b/src/runtime/locale/ca.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Incrementar', decrement: 'Decrementar' }, + fileUpload: { + empty: 'Explora o arrossega fitxers aquí' + }, commandPalette: { placeholder: 'Escriu una ordre o cerca...', noMatch: 'No hi ha dades coincidents', diff --git a/src/runtime/locale/ckb.ts b/src/runtime/locale/ckb.ts index d4ade9688d..757f507d20 100644 --- a/src/runtime/locale/ckb.ts +++ b/src/runtime/locale/ckb.ts @@ -21,6 +21,9 @@ export default defineLocale({ increment: 'زیادکردن', decrement: 'کەمکردنەوە' }, + fileUpload: { + empty: 'فایلەکان بگەڕێنەوە یان لێرەوە بەرز بکەوە' + }, commandPalette: { placeholder: 'فەرمانێک بنووسە یان بگەڕێ...', noMatch: 'هیچ ئەنجامێک نەدۆزرایەوە', diff --git a/src/runtime/locale/cs.ts b/src/runtime/locale/cs.ts index 8a911d0e28..bd262e70ce 100644 --- a/src/runtime/locale/cs.ts +++ b/src/runtime/locale/cs.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Zvýšit', decrement: 'Snížit' }, + fileUpload: { + empty: 'Procházet nebo přetáhnout soubory sem' + }, commandPalette: { placeholder: 'Zadejte příkaz nebo hledejte...', noMatch: 'Žádná shoda', diff --git a/src/runtime/locale/da.ts b/src/runtime/locale/da.ts index 7a67a020c9..35da82bf0d 100644 --- a/src/runtime/locale/da.ts +++ b/src/runtime/locale/da.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Øg', decrement: 'Reducer' }, + fileUpload: { + empty: 'Gennemse eller træk filer her' + }, commandPalette: { placeholder: 'Skriv en kommando eller søg...', noMatch: 'Ingen matchende data', diff --git a/src/runtime/locale/de.ts b/src/runtime/locale/de.ts index 4664e6fca8..d71757bf59 100644 --- a/src/runtime/locale/de.ts +++ b/src/runtime/locale/de.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Erhöhen', decrement: 'Verringern' }, + fileUpload: { + empty: 'Dateien durchsuchen oder hier ablegen' + }, commandPalette: { placeholder: 'Geben Sie einen Befehl ein oder suchen Sie...', noMatch: 'Nichts gefunden', diff --git a/src/runtime/locale/el.ts b/src/runtime/locale/el.ts index 8cc526abf2..7ba917e840 100644 --- a/src/runtime/locale/el.ts +++ b/src/runtime/locale/el.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Αύξηση', decrement: 'Μείωση' }, + fileUpload: { + empty: 'Αναζητήστε ή σύρετε αρχεία εδώ' + }, commandPalette: { placeholder: 'Πληκτρολογήστε μια εντολή ή αναζητήστε...', noMatch: 'Δεν βρέθηκαν δεδομένα', diff --git a/src/runtime/locale/en.ts b/src/runtime/locale/en.ts index dc35884cf1..2eaaaa3418 100644 --- a/src/runtime/locale/en.ts +++ b/src/runtime/locale/en.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Increment', decrement: 'Decrement' }, + fileUpload: { + empty: 'Browse or drop files here' + }, commandPalette: { placeholder: 'Type a command or search...', noMatch: 'No matching data', diff --git a/src/runtime/locale/es.ts b/src/runtime/locale/es.ts index 31f8ddb6e9..d5f29dd101 100644 --- a/src/runtime/locale/es.ts +++ b/src/runtime/locale/es.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Incremento', decrement: 'Decremento' }, + fileUpload: { + empty: 'Explorar o arrastrar archivos aquí' + }, commandPalette: { placeholder: 'Escribe un comando o busca...', noMatch: 'No hay datos coincidentes', diff --git a/src/runtime/locale/et.ts b/src/runtime/locale/et.ts index 7fd252177d..e108262d89 100644 --- a/src/runtime/locale/et.ts +++ b/src/runtime/locale/et.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Suurenda', decrement: 'Vähenda' }, + fileUpload: { + empty: 'Sirvi või lohista failid siia' + }, commandPalette: { placeholder: 'Sisesta käsk või otsi...', noMatch: 'Pole vastavaid andmeid', diff --git a/src/runtime/locale/fa_ir.ts b/src/runtime/locale/fa_ir.ts index e932640793..bdc4d3885a 100644 --- a/src/runtime/locale/fa_ir.ts +++ b/src/runtime/locale/fa_ir.ts @@ -21,6 +21,9 @@ export default defineLocale({ increment: 'افزایش', decrement: 'کاهش' }, + fileUpload: { + empty: 'فایل‌ها را مرور کنید یا اینجا بکشید و رها کنید' + }, commandPalette: { placeholder: 'یک دستور وارد کنید یا جستجو کنید...', noMatch: 'داده‌ای یافت نشد', diff --git a/src/runtime/locale/fi.ts b/src/runtime/locale/fi.ts index 6588424d30..f4b7afc077 100644 --- a/src/runtime/locale/fi.ts +++ b/src/runtime/locale/fi.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Kasvata', decrement: 'Vähennä' }, + fileUpload: { + empty: 'Selaa tai vedä tiedostoja tähän' + }, commandPalette: { placeholder: 'Kirjoita komento tai hae...', noMatch: 'Ei vastaavia tietoja', diff --git a/src/runtime/locale/fr.ts b/src/runtime/locale/fr.ts index e823efc3ad..af0d09cf67 100644 --- a/src/runtime/locale/fr.ts +++ b/src/runtime/locale/fr.ts @@ -16,6 +16,9 @@ export default defineLocale({ prevMonth: 'Mois précédent', nextMonth: 'Mois suivant' }, + fileUpload: { + empty: 'Parcourir ou déposer des fichiers ici' + }, inputNumber: { increment: 'Augmenter', decrement: 'Diminuer' diff --git a/src/runtime/locale/he.ts b/src/runtime/locale/he.ts index dc305dcde8..16b76fd67c 100644 --- a/src/runtime/locale/he.ts +++ b/src/runtime/locale/he.ts @@ -21,6 +21,9 @@ export default defineLocale({ increment: 'הוסף', decrement: 'הפחת' }, + fileUpload: { + empty: 'עיין בקבצים או גרור ושחרר כאן' + }, commandPalette: { placeholder: 'הקלד פקודה...', noMatch: 'לא נמצאה התאמה', diff --git a/src/runtime/locale/hi.ts b/src/runtime/locale/hi.ts index 501cde1428..3b220a9508 100644 --- a/src/runtime/locale/hi.ts +++ b/src/runtime/locale/hi.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'बढ़ाना', decrement: 'घटाना' }, + fileUpload: { + empty: 'फाइल ब्राउज़ करें या यहाँ ड्रॉप करें' + }, commandPalette: { placeholder: 'एक आदेश या खोज टाइप करें...', noMatch: 'कोई मेल खाता डेटा नहीं', diff --git a/src/runtime/locale/hu.ts b/src/runtime/locale/hu.ts index d7e05fc57c..43599823b0 100644 --- a/src/runtime/locale/hu.ts +++ b/src/runtime/locale/hu.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Növel', decrement: 'Csökkent' }, + fileUpload: { + empty: 'Fájlok böngészése vagy ide húzása' + }, commandPalette: { placeholder: 'Írjon be egy parancsot vagy keressen...', noMatch: 'Nincs találat', diff --git a/src/runtime/locale/hy.ts b/src/runtime/locale/hy.ts index 591d1f1f6f..717157e616 100644 --- a/src/runtime/locale/hy.ts +++ b/src/runtime/locale/hy.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Ավելացնել', decrement: 'Պակասեցնել' }, + fileUpload: { + empty: 'Փնտրել ֆայլեր կամ քաշել այստեղ' + }, commandPalette: { placeholder: 'Մուտքագրեք հրաման կամ որոնեք...', noMatch: 'Համընկնումներ չեն գտնվել', diff --git a/src/runtime/locale/id.ts b/src/runtime/locale/id.ts index f9a212d201..cfc9045a8f 100644 --- a/src/runtime/locale/id.ts +++ b/src/runtime/locale/id.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Tambah', decrement: 'Kurangi' }, + fileUpload: { + empty: 'Telusuri file atau seret ke sini' + }, commandPalette: { placeholder: 'Ketik perintah atau cari...', noMatch: 'Tidak ada data yang cocok', diff --git a/src/runtime/locale/it.ts b/src/runtime/locale/it.ts index afc2ae65a9..872605a18a 100644 --- a/src/runtime/locale/it.ts +++ b/src/runtime/locale/it.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Aumenta', decrement: 'Diminuisci' }, + fileUpload: { + empty: 'Sfoglia o trascina i file qui' + }, commandPalette: { placeholder: 'Digita un comando o cerca...', noMatch: 'Nessun dato corrispondente', diff --git a/src/runtime/locale/ja.ts b/src/runtime/locale/ja.ts index 771674ead6..b3bccf563d 100644 --- a/src/runtime/locale/ja.ts +++ b/src/runtime/locale/ja.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: '増やす', decrement: '減らす' }, + fileUpload: { + empty: 'ファイルを参照するか、ここにドロップしてください' + }, commandPalette: { placeholder: 'コマンドを入力するか検索...', noMatch: '一致するデータがありません', diff --git a/src/runtime/locale/kk.ts b/src/runtime/locale/kk.ts index fd1dcbd793..6ac5000fe9 100644 --- a/src/runtime/locale/kk.ts +++ b/src/runtime/locale/kk.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Арттыру', decrement: 'Азайту' }, + fileUpload: { + empty: 'Файлдарды шолып, немесе мұнда тастау' + }, commandPalette: { placeholder: 'Команда енгізіңіз немесе іздеңіз...', noMatch: 'Сәйкес келетін деректер жоқ', diff --git a/src/runtime/locale/km.ts b/src/runtime/locale/km.ts index 6810ba46d7..fc08c59a9a 100644 --- a/src/runtime/locale/km.ts +++ b/src/runtime/locale/km.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'បង្កើន', decrement: 'បន្ថយ' }, + fileUpload: { + empty: 'ស្វែងរកឯកសារឬទាញយកទីនេះ' + }, commandPalette: { placeholder: 'វាយពាក្យបញ្ជា ឬស្វែងរក...', noMatch: 'មិនមានទិន្នន័យដែលត្រូវគ្នាទេ', diff --git a/src/runtime/locale/ko.ts b/src/runtime/locale/ko.ts index 54b872d2d3..ce8d9f699f 100644 --- a/src/runtime/locale/ko.ts +++ b/src/runtime/locale/ko.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: '증가', decrement: '감소' }, + fileUpload: { + empty: '파일을 선택하거나 여기에 드롭하세요' + }, commandPalette: { placeholder: '명령을 입력하거나 검색...', noMatch: '일치하는 데이터가 없습니다.', diff --git a/src/runtime/locale/ky.ts b/src/runtime/locale/ky.ts index 191de3e656..59ea7e6e13 100644 --- a/src/runtime/locale/ky.ts +++ b/src/runtime/locale/ky.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Кошуу', decrement: 'Азайтуу' }, + fileUpload: { + empty: 'Файлды тандаңыз же бул жерге таштаңыз' + }, commandPalette: { placeholder: 'Буйрук киргизиңиз же издөө…', noMatch: 'Эч нерсе табылган жок', diff --git a/src/runtime/locale/lt.ts b/src/runtime/locale/lt.ts index 3be6918a02..86c23b9da3 100644 --- a/src/runtime/locale/lt.ts +++ b/src/runtime/locale/lt.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Padidinti', decrement: 'Sumažinti' }, + fileUpload: { + empty: 'Naršyti failus arba vilkti čia' + }, commandPalette: { placeholder: 'Įveskite komandą arba ieškokite...', noMatch: 'Nėra atitinkančių duomenų', diff --git a/src/runtime/locale/mn.ts b/src/runtime/locale/mn.ts index 87bd7dc5e4..26f9d3a997 100644 --- a/src/runtime/locale/mn.ts +++ b/src/runtime/locale/mn.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Нэмэх', decrement: 'Хасах' }, + fileUpload: { + empty: 'Файлуудыг хайх эсвэл энд чирэх' + }, commandPalette: { placeholder: 'Комманд бичих эсвэл хайлт хийх...', noMatch: 'Тохирох мэдээлэл олдсонгүй', diff --git a/src/runtime/locale/ms.ts b/src/runtime/locale/ms.ts index 668ff3839f..91722f3c87 100644 --- a/src/runtime/locale/ms.ts +++ b/src/runtime/locale/ms.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Naikkan', decrement: 'Kurangkan' }, + fileUpload: { + empty: 'Semak fail atau seret ke sini' + }, commandPalette: { placeholder: 'Taip arahan atau carian...', noMatch: 'Tiada data yang sepadan', diff --git a/src/runtime/locale/nb_no.ts b/src/runtime/locale/nb_no.ts index 634b1cf050..6fe71f91ea 100644 --- a/src/runtime/locale/nb_no.ts +++ b/src/runtime/locale/nb_no.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Øk', decrement: 'Reduser' }, + fileUpload: { + empty: 'Bla gjennom filer eller slipp dem her' + }, commandPalette: { placeholder: 'Skriv inn en kommando eller søk...', noMatch: 'Ingen samsvarende data', diff --git a/src/runtime/locale/nl.ts b/src/runtime/locale/nl.ts index 6f116e829b..45da11ac47 100644 --- a/src/runtime/locale/nl.ts +++ b/src/runtime/locale/nl.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Verhogen', decrement: 'Verlagen' }, + fileUpload: { + empty: 'Blader door bestanden of sleep ze hierheen' + }, commandPalette: { placeholder: 'Typ een commando of zoek...', noMatch: 'Geen overeenkomende gegevens', diff --git a/src/runtime/locale/pl.ts b/src/runtime/locale/pl.ts index 2fec21365d..87b5805d63 100644 --- a/src/runtime/locale/pl.ts +++ b/src/runtime/locale/pl.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Zwiększ', decrement: 'Zmniejsz' }, + fileUpload: { + empty: 'Przeglądaj pliki lub przeciągnij je tutaj' + }, commandPalette: { placeholder: 'Wpisz polecenie lub wyszukaj...', noMatch: 'Brak pasujących danych', diff --git a/src/runtime/locale/pt.ts b/src/runtime/locale/pt.ts index fb5e72f3d7..cdd169a39d 100644 --- a/src/runtime/locale/pt.ts +++ b/src/runtime/locale/pt.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Incrementar', decrement: 'Decrementar' }, + fileUpload: { + empty: 'Navegue ou arraste arquivos aqui' + }, commandPalette: { placeholder: 'Digite um comando ou pesquise...', noMatch: 'Nenhum dado correspondente', diff --git a/src/runtime/locale/pt_br.ts b/src/runtime/locale/pt_br.ts index 052bae6a5e..fcb7348c3d 100644 --- a/src/runtime/locale/pt_br.ts +++ b/src/runtime/locale/pt_br.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Incrementar', decrement: 'Decrementar' }, + fileUpload: { + empty: 'Navegue ou arraste arquivos aqui' + }, commandPalette: { placeholder: 'Digite um comando ou pesquise...', noMatch: 'Nenhum dado correspondente', diff --git a/src/runtime/locale/ro.ts b/src/runtime/locale/ro.ts index dfd9b6a011..4d126c5d3e 100644 --- a/src/runtime/locale/ro.ts +++ b/src/runtime/locale/ro.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Crește', decrement: 'Scade' }, + fileUpload: { + empty: 'Răsfoiește sau trage fișiere aici' + }, commandPalette: { placeholder: 'Tastează o comandă sau caută...', noMatch: 'Nu există date corespunzătoare', diff --git a/src/runtime/locale/ru.ts b/src/runtime/locale/ru.ts index 2df96842ea..e5c9001262 100644 --- a/src/runtime/locale/ru.ts +++ b/src/runtime/locale/ru.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Увеличить', decrement: 'Уменьшить' }, + fileUpload: { + empty: 'Выберите файл или перетащите его сюда' + }, commandPalette: { placeholder: 'Введите команду или выполните поиск...', noMatch: 'Совпадений не найдено', diff --git a/src/runtime/locale/sk.ts b/src/runtime/locale/sk.ts index c57bba0ad8..c221cd557e 100644 --- a/src/runtime/locale/sk.ts +++ b/src/runtime/locale/sk.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Zvýšiť', decrement: 'Znížiť' }, + fileUpload: { + empty: 'Vyberte súbor alebo ho sem presuňte' + }, commandPalette: { placeholder: 'Zadajte príkaz alebo vyhľadajte...', noMatch: 'Žiadna zhoda', diff --git a/src/runtime/locale/sl.ts b/src/runtime/locale/sl.ts index 9df276955c..fec750bb19 100644 --- a/src/runtime/locale/sl.ts +++ b/src/runtime/locale/sl.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Povišaj', decrement: 'Zmanjšaj' }, + fileUpload: { + empty: 'Izberi datoteko ali jo povleci sem' + }, commandPalette: { placeholder: 'Vpiši ukaz ali išči...', noMatch: 'Ni ujemanj', diff --git a/src/runtime/locale/sv.ts b/src/runtime/locale/sv.ts index 3a66ccd821..986d2299e4 100644 --- a/src/runtime/locale/sv.ts +++ b/src/runtime/locale/sv.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Öka', decrement: 'Minska' }, + fileUpload: { + empty: 'Bläddra eller släpp filer här' + }, commandPalette: { placeholder: 'Skriv ett kommando eller sök...', noMatch: 'Inga matchande data', diff --git a/src/runtime/locale/th.ts b/src/runtime/locale/th.ts index 50f258b201..a26da47a47 100644 --- a/src/runtime/locale/th.ts +++ b/src/runtime/locale/th.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'เพิ่ม', decrement: 'ลด' }, + fileUpload: { + empty: 'เลือกไฟล์หรือวางที่นี่' + }, commandPalette: { placeholder: 'พิมพ์คำสั่งหรือค้นหา...', noMatch: 'ไม่พบข้อมูลที่ตรงกัน', diff --git a/src/runtime/locale/tj.ts b/src/runtime/locale/tj.ts index 5fd02adf27..d144a3a351 100644 --- a/src/runtime/locale/tj.ts +++ b/src/runtime/locale/tj.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Зиёд кардан', decrement: 'Кам кардан' }, + fileUpload: { + empty: 'Файлро интихоб кунед ё ин ҷо гузоред' + }, commandPalette: { placeholder: 'Фармонро нависед ё ҷустуҷӯ кунед...', noMatch: 'Маълумоти мувофиқ ёфт нашуд', diff --git a/src/runtime/locale/tr.ts b/src/runtime/locale/tr.ts index e57ed3cdc8..1f8b93f9bf 100644 --- a/src/runtime/locale/tr.ts +++ b/src/runtime/locale/tr.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Arttır', decrement: 'Azalt' }, + fileUpload: { + empty: 'Dosya seçin veya buraya bırakın' + }, commandPalette: { placeholder: 'Bir komut yazın veya arama yapın...', noMatch: 'Eşleşen veri yok', diff --git a/src/runtime/locale/ug_cn.ts b/src/runtime/locale/ug_cn.ts index 0f50b157e6..07db3a4f0d 100644 --- a/src/runtime/locale/ug_cn.ts +++ b/src/runtime/locale/ug_cn.ts @@ -21,6 +21,9 @@ export default defineLocale({ increment: 'كۆپەيتىش', decrement: 'ئازايتىش' }, + fileUpload: { + empty: 'فايىلنى كۆرۈش ياكى بۇ يەرگە تارتىش' + }, commandPalette: { placeholder: 'بۇيرۇق كىرگۈزۈڭ ياكى ئىزدەڭ...', noMatch: 'ماس كېلىدىغان سانلىق مەلۇمات يوق', diff --git a/src/runtime/locale/uk.ts b/src/runtime/locale/uk.ts index 45a3c1d4d5..d0de53f2a3 100644 --- a/src/runtime/locale/uk.ts +++ b/src/runtime/locale/uk.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Збільшити', decrement: 'Зменшити' }, + fileUpload: { + empty: 'Виберіть файл або перетягніть його сюди' + }, commandPalette: { placeholder: 'Введіть команду або шукайте...', noMatch: 'Збігів не знайдено', diff --git a/src/runtime/locale/ur.ts b/src/runtime/locale/ur.ts index f001f4b5df..2729058e5a 100644 --- a/src/runtime/locale/ur.ts +++ b/src/runtime/locale/ur.ts @@ -21,6 +21,9 @@ export default defineLocale({ increment: 'اضافہ', decrement: 'کمی' }, + fileUpload: { + empty: 'فائلوں کو براؤز کریں یا یہاں چھوڑیں' + }, commandPalette: { placeholder: 'کمانڈ ٹائپ کریں یا تلاش کریں...', noMatch: 'کوئی ملتا جلتا ڈیٹا نہیں ملا', diff --git a/src/runtime/locale/uz.ts b/src/runtime/locale/uz.ts index 4b939ee3bb..b15dea64dd 100644 --- a/src/runtime/locale/uz.ts +++ b/src/runtime/locale/uz.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Qoʻshish', decrement: 'Ayirish' }, + fileUpload: { + empty: 'Faylni tanlang yoki bu yerga joylashtiring' + }, commandPalette: { placeholder: 'Buyruq kiriting yoki qidiring...', noMatch: 'Mos keluvchi natija topilmadi', diff --git a/src/runtime/locale/vi.ts b/src/runtime/locale/vi.ts index ee53d8585c..0cbec2d267 100644 --- a/src/runtime/locale/vi.ts +++ b/src/runtime/locale/vi.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: 'Tăng', decrement: 'Giảm' }, + fileUpload: { + empty: 'Chọn tệp hoặc kéo và thả vào đây' + }, commandPalette: { placeholder: 'Nhập lệnh hoặc tìm kiếm...', noMatch: 'Không có kết quả phù hợp', diff --git a/src/runtime/locale/zh_cn.ts b/src/runtime/locale/zh_cn.ts index 4b67a3596f..603fb9e002 100644 --- a/src/runtime/locale/zh_cn.ts +++ b/src/runtime/locale/zh_cn.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: '增加', decrement: '减少' }, + fileUpload: { + empty: '选择文件或拖放到这里' + }, commandPalette: { placeholder: '输入命令或搜索...', noMatch: '没有匹配的数据', diff --git a/src/runtime/locale/zh_tw.ts b/src/runtime/locale/zh_tw.ts index 6f9868c6b5..a3264e47ee 100644 --- a/src/runtime/locale/zh_tw.ts +++ b/src/runtime/locale/zh_tw.ts @@ -20,6 +20,9 @@ export default defineLocale({ increment: '增加', decrement: '減少' }, + fileUpload: { + empty: '選擇檔案或拖放到這裡' + }, commandPalette: { placeholder: '輸入命令或搜尋...', noMatch: '沒有相符的資料', diff --git a/src/runtime/types/index.ts b/src/runtime/types/index.ts index c5e73f665b..f16aaea0c7 100644 --- a/src/runtime/types/index.ts +++ b/src/runtime/types/index.ts @@ -48,6 +48,7 @@ export * from '../components/Tabs.vue' export * from '../components/Textarea.vue' export * from '../components/Toast.vue' export * from '../components/Toaster.vue' +export * from '../components/FileUpload.vue' export * from '../components/Tooltip.vue' export * from '../components/Tree.vue' export * from './form' diff --git a/src/runtime/types/locale.ts b/src/runtime/types/locale.ts index 44d63534bb..abe5a9ddca 100644 --- a/src/runtime/types/locale.ts +++ b/src/runtime/types/locale.ts @@ -14,6 +14,9 @@ export type Messages = { increment: string decrement: string } + fileUpload: { + empty: string + } commandPalette: { placeholder: string noMatch: string diff --git a/src/theme/file-upload.ts b/src/theme/file-upload.ts new file mode 100644 index 0000000000..b18061de4b --- /dev/null +++ b/src/theme/file-upload.ts @@ -0,0 +1,68 @@ +export default { + slots: { + root: 'relative inline-flex items-center', + base: 'bg-default shadow-sm rounded-md divide-y divide-default overflow-y-auto border border-dashed border-accented', + dragging: 'bg-accented/20', + empty: 'flex flex-col items-center justify-center gap-2', + label: 'font-semibold text-highlighted text-center px-2 line-clamp-1', + uploadIcon: 'shrink-0 pointer-events-none', + files: 'divide-y divide-default', + file: 'text-default flex justify-between items-center gap-2 p-2', + fileLabel: 'text-default font-semibold line-clamp-1', + fileAvatar: 'shrink-0', + fileAvatarSize: '', + fileSize: 'text-muted' + }, + variants: { + size: { + xs: { + base: 'w-56', + empty: 'h-16', + label: 'text-xs', + uploadIcon: 'size-4', + files: 'h-10', + file: 'p-1 text-xs gap-1', + fileAvatarSize: 'xs' + }, + sm: { + base: 'w-60', + empty: 'h-20', + label: 'text-xs', + uploadIcon: 'size-4', + file: 'p-1.5 text-xs gap-1.5', + fileAvatarSize: 'sm' + }, + md: { + base: 'w-64', + empty: 'h-24', + label: 'text-sm', + uploadIcon: 'size-5', + file: 'p-1.5 text-sm gap-1.5', + fileAvatarSize: 'md' + }, + lg: { + base: 'w-72', + empty: 'h-26', + label: 'text-sm', + uploadIcon: 'size-5', + file: 'p-2 text-sm gap-2', + fileAvatarSize: 'lg' + }, + xl: { + base: 'w-82', + empty: 'h-32', + label: 'text-base', + uploadIcon: 'size-6', + file: 'p-2 text-base gap-2', + fileAvatarSize: 'xl' + } + }, + multiple: { + true: '', + false: '' + } + }, + defaultVariants: { + size: 'md' + } +} diff --git a/src/theme/icons.ts b/src/theme/icons.ts index b0d6357b65..d66a639087 100644 --- a/src/theme/icons.ts +++ b/src/theme/icons.ts @@ -13,6 +13,8 @@ export default { external: 'i-lucide-arrow-up-right', folder: 'i-lucide-folder', folderOpen: 'i-lucide-folder-open', + upload: 'i-lucide-upload', + file: 'i-lucide-file', loading: 'i-lucide-loader-circle', minus: 'i-lucide-minus', plus: 'i-lucide-plus', diff --git a/src/theme/index.ts b/src/theme/index.ts index 752065ec45..e0ca34cc8e 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -24,6 +24,7 @@ export { default as formField } from './form-field' export { default as input } from './input' export { default as inputMenu } from './input-menu' export { default as inputNumber } from './input-number' +export { default as fileUpload } from './file-upload' export { default as kbd } from './kbd' export { default as link } from './link' export { default as modal } from './modal' diff --git a/test/components/FileUpload.spec.ts b/test/components/FileUpload.spec.ts new file mode 100644 index 0000000000..8a88462ef2 --- /dev/null +++ b/test/components/FileUpload.spec.ts @@ -0,0 +1,271 @@ +import { describe, it, expect, test } from 'vitest' +import { mount } from '@vue/test-utils' +import FileUpload, { type FileUploadProps, type FileUploadSlots, type FileUploadItem } from '../../src/runtime/components/FileUpload.vue' +import theme from '#build/ui/file-upload' + +import { renderForm } from '../utils/form' +import type { FormInputEvents } from '~/src/module' + +async function setFilesOnInput(input: any, files: File[]) { + // Create a DataTransfer and add files + const data = new DataTransfer() + files.forEach(file => data.items.add(file)) + // Set files property via Object.defineProperty + Object.defineProperty(input.element, 'files', { + value: data.files, + writable: false, + configurable: true + }) + // Trigger change event + await input.trigger('change') +} + +describe('FileUpload', () => { + const sizes = Object.keys(theme.variants.size) as any + + it.each([ + // Props + ['with id', { props: { id: 'id' } }], + ['with name', { props: { name: 'name' } }], + ['with multiple', { props: { multiple: true } }], + ['with accept', { props: { accept: 'png,jpg' } }], + ['with disabled', { props: { disabled: true } }], + ['with required', { props: { required: true } }], + ['with label', { props: { label: 'Label' } }], + ['with placeholder', { props: { placeholder: 'Placeholder' } }], + ...sizes.map((size: string) => [`with size ${size}`, { props: { size } }]) + ])('renders %s correctly', async (nameOrHtml: string, options: { props?: FileUploadProps, slots?: Partial }) => { + const wrapper = mount(FileUpload, { + ...options + }) + expect(wrapper.html()).toMatchSnapshot() + }) + + describe('emits', () => { + test('update:modelValue event', async () => { + const wrapper = mount(FileUpload, { + props: { + modelValue: [] + } + }) + const input = wrapper.find('input[type="file"]') + const file1 = new File(['foo'], 'file1.txt', { type: 'text/plain' }) + const file2 = new File(['bar'], 'file2.txt', { type: 'text/plain' }) + await setFilesOnInput(input, [file1, file2]) + expect(wrapper.emitted('update:modelValue')).toBeTruthy() + }) + test('change event', async () => { + const wrapper = mount(FileUpload, { + props: { + modelValue: [] + } + }) + const input = wrapper.find('input[type="file"]') + const file1 = new File(['foo'], 'file1.txt', { type: 'text/plain' }) + const file2 = new File(['bar'], 'file2.txt', { type: 'text/plain' }) + await setFilesOnInput(input, [file1, file2]) + expect(wrapper.emitted('change')).toBeTruthy() + }) + test('dragover event', async () => { + const wrapper = mount(FileUpload, { + props: { + modelValue: [] + } + }) + const input = wrapper.find('input[type="file"]') + await input.trigger('dragover') + expect(wrapper.emitted('dragover')).toBeTruthy() + } + ) + test('dragleave event', async () => { + const wrapper = mount(FileUpload, { + props: { + modelValue: [] + } + }) + const input = wrapper.find('input[type="file"]') + await input.trigger('dragleave') + expect(wrapper.emitted('dragleave')).toBeTruthy() + } + ) + test('drop event', async () => { + const wrapper = mount(FileUpload, { + props: { + modelValue: [] + } + }) + const input = wrapper.find('input[type="file"]') + await input.trigger('drop') + expect(wrapper.emitted('drop')).toBeTruthy() + } + ) + }) + + describe('form integration', async () => { + async function createForm(validateOn?: FormInputEvents[], eagerValidation?: boolean) { + const wrapper = await renderForm({ + props: { + validateOn, + validateOnInputDelay: 0, + async validate(state: any) { + // state.value is expected to be an array of FileUploadItem(s) + const files: FileUploadItem[] = Array.isArray(state.value) ? state.value : [] + if (!files.length || files.some(f => f.file.name !== 'valid')) { + return [{ name: 'value', message: 'Error message' }] + } + return [] + } + }, + slotTemplate: ` + + + + `, + slotVars: { + eagerValidation + } + }) + const input = wrapper.find('input[type="file"]') + return { + wrapper, + input + } + } + + test('validate on blur works', async () => { + const { input, wrapper } = await createForm(['blur']) + await setFilesOnInput(input, [new File(['foo'], 'invalid.txt', { type: 'text/plain' })]) + await input.trigger('blur') + expect(wrapper.text()).toContain('Error message') + + await setFilesOnInput(input, [new File(['foo'], 'valid', { type: 'text/plain' })]) + await input.trigger('blur') + expect(wrapper.text()).not.toContain('Error message') + }) + + test('validate on change works', async () => { + const { input, wrapper } = await createForm(['change']) + await setFilesOnInput(input, [new File(['foo'], 'invalid.txt', { type: 'text/plain' })]) + await input.trigger('change') + expect(wrapper.text()).toContain('Error message') + + await setFilesOnInput(input, [new File(['foo'], 'valid', { type: 'text/plain' })]) + await input.trigger('change') + expect(wrapper.text()).not.toContain('Error message') + }) + + test('validate on input works', async () => { + const { input, wrapper } = await createForm(['input'], true) + await setFilesOnInput(input, [new File(['foo'], 'invalid.txt', { type: 'text/plain' })]) + expect(wrapper.text()).toContain('Error message') + + await setFilesOnInput(input, [new File(['foo'], 'valid', { type: 'text/plain' })]) + expect(wrapper.text()).not.toContain('Error message') + }) + + test('validate on input without eager validation works', async () => { + const { input, wrapper } = await createForm(['input']) + + await setFilesOnInput(input, [new File(['foo'], 'invalid.txt', { type: 'text/plain' })]) + expect(wrapper.text()).not.toContain('Error message') + + await input.trigger('blur') + + await setFilesOnInput(input, [new File(['foo'], 'invalid.txt', { type: 'text/plain' })]) + expect(wrapper.text()).toContain('Error message') + + await setFilesOnInput(input, [new File(['foo'], 'valid', { type: 'text/plain' })]) + expect(wrapper.text()).not.toContain('Error message') + }) + }) + + describe('FileUpload advanced behaviors', () => { + test('shows image preview and removes it when file is removed', async () => { + const file = new File(['dummy'], 'test.png', { type: 'image/png', lastModified: 1 }) + const wrapper = mount(FileUpload, { + props: { modelValue: [{ file }] } + }) + await wrapper.vm.$nextTick() + + expect(wrapper.html()).toContain('test.png') + const removeIcon = wrapper.find('#remove-file') + expect(removeIcon).toBeDefined() + await removeIcon!.trigger('click') + + // Check that update:modelValue was emitted with an empty array + const emits = wrapper.emitted('update:modelValue') + expect(emits).toBeTruthy() + expect(emits![emits!.length - 1][0]).toEqual([]) + }) + + test('does not allow interaction when disabled', async () => { + const wrapper = mount(FileUpload, { + props: { disabled: true } + }) + const input = wrapper.find('input[type="file"]') + expect(input.attributes('disabled')).toBeDefined() + await wrapper.find('div[role="presentation"],div').trigger('click') + expect(wrapper.emitted('change')).toBeFalsy() + }) + + test('handles multiple file uploads', async () => { + const file1 = new File(['foo'], 'foo.png', { type: 'image/png', lastModified: 1 }) + const file2 = new File(['bar'], 'bar.jpg', { type: 'image/jpeg', lastModified: 2 }) + const wrapper = mount(FileUpload, { + props: { multiple: true, modelValue: [] } + }) + const input = wrapper.find('input[type="file"]') + await setFilesOnInput(input, [file1, file2]) + expect(wrapper.emitted('update:modelValue')).toBeTruthy() + const lastEmitted = wrapper.emitted('update:modelValue')?.pop()?.[0] + expect(lastEmitted).toHaveLength(2) + }) + + test('accept prop restricts file types', async () => { + const file = new File(['foo'], 'foo.txt', { type: 'text/plain' }) + const wrapper = mount(FileUpload, { + props: { accept: 'image/*', modelValue: [] } + }) + const input = wrapper.find('input[type="file"]') + await setFilesOnInput(input, [file]) + expect(input.attributes('accept')).toBe('image/*') + }) + + test('renders custom empty slot', () => { + const wrapper = mount(FileUpload, { + slots: { + empty: '
Custom Empty
' + } + }) + expect(wrapper.html()).toContain('Custom Empty') + }) + + test('renders custom item slot', async () => { + const file = new File(['foo'], 'foo.png', { type: 'image/png', lastModified: 1 }) + const wrapper = mount(FileUpload, { + props: { modelValue: [{ file }] }, + slots: { + item: '
{{item.file.name}}
' + } + }) + expect(wrapper.html()).toContain('custom-item') + expect(wrapper.html()).toContain('foo.png') + }) + + test('renders custom item slots with correct type', async () => { + const file = new File(['foo'], 'foo.png', { type: 'image/png', lastModified: 1 }) + type UploadWithStatus = FileUploadItem<{ status: 'pending' | 'uploading' | 'done', progress?: number }> + const wrapper = mount(FileUpload, { + props: { + modelValue: [{ file, status: 'pending' }] + }, + slots: { + item: '
{{item.file.name}} - {{item.status}}
' + } + + }) + expect(wrapper.html()).toContain('custom-item') + expect(wrapper.html()).toContain('foo.png - pending') + }) + }) +}) diff --git a/test/components/__snapshots__/FileUpload-vue.spec.ts.snap b/test/components/__snapshots__/FileUpload-vue.spec.ts.snap new file mode 100644 index 0000000000..dada79a5ad --- /dev/null +++ b/test/components/__snapshots__/FileUpload-vue.spec.ts.snap @@ -0,0 +1,105 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`FileUpload > renders with accept correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with disabled correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with id correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with label correctly 1`] = ` +"
+
+
Label
+
+
" +`; + +exports[`FileUpload > renders with multiple correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with name correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with placeholder correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with required correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size lg correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size md correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size sm correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size xl correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size xs correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; diff --git a/test/components/__snapshots__/FileUpload.spec.ts.snap b/test/components/__snapshots__/FileUpload.spec.ts.snap new file mode 100644 index 0000000000..997ef532b1 --- /dev/null +++ b/test/components/__snapshots__/FileUpload.spec.ts.snap @@ -0,0 +1,105 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`FileUpload > renders with accept correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with disabled correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with id correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with label correctly 1`] = ` +"
+
+
Label
+
+
" +`; + +exports[`FileUpload > renders with multiple correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with name correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with placeholder correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with required correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size lg correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size md correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size sm correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size xl correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; + +exports[`FileUpload > renders with size xs correctly 1`] = ` +"
+
+
Browse or drop files here
+
+
" +`; diff --git a/test/utils/form.ts b/test/utils/form.ts index 1a8f3b6f3b..5f8d009510 100644 --- a/test/utils/form.ts +++ b/test/utils/form.ts @@ -13,6 +13,7 @@ import { USelectMenu, UInputMenu, UInputNumber, + UFileUpload, USwitch, USlider, UPinInput, @@ -44,6 +45,7 @@ export async function renderForm(options: { UForm, UInput, URadioGroup, + UFileUpload, UTextarea, UCheckbox, USelect,