Bestand uploaden
Gebruik
Gebruik de v-model-richtlijn om de waarde van de FileUpload te bepalen.
<script setup lang="ts">
const value = ref(null)
</script>
<template>
<UFileUpload v-model="value" class="w-96 min-h-48" />
</template><script setup lang="ts">
import { ref } from 'vue'
const value = ref(null)
</script>
<template>
<UFileUpload v-model="value" class="w-96 min-h-48" />
</template>Meerdere
Gebruik de multiple prop om meerdere bestanden te selecteren.
<template>
<UFileUpload multiple class="w-96 min-h-48" />
</template>Dropzone
Gebruik de dropzone-prop om het droppable-gebied in / uit te schakelen. Standaard true.
<template>
<UFileUpload :dropzone="false" class="w-96 min-h-48" />
</template>Interactief
Gebruik de interactive prop om het klikbare gebied in / uit te schakelen. Standaard true.
<template>
<UFileUpload :interactive="false" class="w-96 min-h-48" />
</template>Accepteren
Gebruik de accept prop om de toegestane bestandstypen voor de invoer op te geven. Geef een door komma 's gescheiden lijst van MIME types of bestandsextensies (bijv. image/png,application/pdf,.jpg).
Standaard * (alle bestandstypen).
<template>
<UFileUpload accept="image/*" class="w-96 min-h-48" />
</template>Label
Gebruik de label prop om het label van de FileUpload in te stellen.
<template>
<UFileUpload label="Drop your image here" class="w-96 min-h-48" />
</template>Beschrijving
Gebruik de description prop om de beschrijving van de FileUpload in te stellen.
<template>
<UFileUpload
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96 min-h-48"
/>
</template>Icoon
Gebruik de icon prop om het pictogram van de FileUpload in te stellen. Standaard i-lucide-upload.
<template>
<UFileUpload
icon="i-lucide-image"
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96 min-h-48"
/>
</template>Kleur
Gebruik de color prop om de kleur van de FileUpload te wijzigen.
<template>
<UFileUpload
color="neutral"
highlight
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96 min-h-48"
/>
</template>highlight prop wordt hier gebruikt om de focusstatus weer te geven. Het wordt intern gebruikt wanneer er een validatiefout optreedt.Variant
Gebruik de variant prop om de variant van de FileUpload te wijzigen.
<template>
<UFileUpload variant="button" />
</template>Grootte
Gebruik de size prop om de grootte van de FileUpload te wijzigen.
<template>
<UFileUpload
size="xl"
variant="area"
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
/>
</template>Opmaak
Gebruik de layout prop om te wijzigen hoe de bestanden worden weergegeven in de FileUpload. Standaard is grid.
variantarea is.<template>
<UFileUpload
layout="list"
multiple
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96"
:ui="{
base: 'min-h-48'
}"
/>
</template>Positie
Gebruik de position prop om de positie van de bestanden in de FileUpload te wijzigen. Standaard is outside.
variantarea is en als layoutlist is.<template>
<UFileUpload
position="inside"
layout="list"
multiple
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96"
:ui="{
base: 'min-h-48'
}"
/>
</template>Voorbeelden
Met formuliervalidatie
U kunt de FileUpload binnen een Form en FormField componenten gebruiken om validatie en foutafhandeling af te handelen.
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
const MIN_DIMENSIONS = { width: 200, height: 200 }
const MAX_DIMENSIONS = { width: 4096, height: 4096 }
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
const formatBytes = (bytes: number, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
const schema = z.object({
image: z
.instanceof(File, {
message: 'Please select an image file.'
})
.refine((file) => file.size <= MAX_FILE_SIZE, {
message: `The image is too large. Please choose an image smaller than ${formatBytes(MAX_FILE_SIZE)}.`
})
.refine((file) => ACCEPTED_IMAGE_TYPES.includes(file.type), {
message: 'Please upload a valid image file (JPEG, PNG, or WebP).'
})
.refine(
(file) =>
new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image()
img.onload = () => {
const meetsDimensions =
img.width >= MIN_DIMENSIONS.width &&
img.height >= MIN_DIMENSIONS.height &&
img.width <= MAX_DIMENSIONS.width &&
img.height <= MAX_DIMENSIONS.height
resolve(meetsDimensions)
}
img.src = e.target?.result as string
}
reader.readAsDataURL(file)
}),
{
message: `The image dimensions are invalid. Please upload an image between ${MIN_DIMENSIONS.width}x${MIN_DIMENSIONS.height} and ${MAX_DIMENSIONS.width}x${MAX_DIMENSIONS.height} pixels.`
}
)
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
image: undefined
})
async function onSubmit(event: FormSubmitEvent<Schema>) {
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4 w-96" @submit="onSubmit">
<UFormField name="image" label="Image" description="JPG, GIF or PNG. 2MB Max.">
<UFileUpload v-model="state.image" accept="image/*" class="min-h-48" />
</UFormField>
<UButton type="submit" label="Submit" color="neutral" />
</UForm>
</template><script setup lang="ts">
import { reactive } from 'vue'
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
const MIN_DIMENSIONS = { width: 200, height: 200 }
const MAX_DIMENSIONS = { width: 4096, height: 4096 }
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
const formatBytes = (bytes: number, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
const schema = z.object({
image: z
.instanceof(File, {
message: 'Please select an image file.'
})
.refine((file) => file.size <= MAX_FILE_SIZE, {
message: `The image is too large. Please choose an image smaller than ${formatBytes(MAX_FILE_SIZE)}.`
})
.refine((file) => ACCEPTED_IMAGE_TYPES.includes(file.type), {
message: 'Please upload a valid image file (JPEG, PNG, or WebP).'
})
.refine(
(file) =>
new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image()
img.onload = () => {
const meetsDimensions =
img.width >= MIN_DIMENSIONS.width &&
img.height >= MIN_DIMENSIONS.height &&
img.width <= MAX_DIMENSIONS.width &&
img.height <= MAX_DIMENSIONS.height
resolve(meetsDimensions)
}
img.src = e.target?.result as string
}
reader.readAsDataURL(file)
}),
{
message: `The image dimensions are invalid. Please upload an image between ${MIN_DIMENSIONS.width}x${MIN_DIMENSIONS.height} and ${MAX_DIMENSIONS.width}x${MAX_DIMENSIONS.height} pixels.`
}
)
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
image: undefined
})
async function onSubmit(event: FormSubmitEvent<Schema>) {
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4 w-96" @submit="onSubmit">
<UFormField name="image" label="Image" description="JPG, GIF or PNG. 2MB Max.">
<UFileUpload v-model="state.image" accept="image/*" class="min-h-48" />
</UFormField>
<UButton type="submit" label="Submit" color="neutral" />
</UForm>
</template>Met standaard slot
U kunt de standaardsleuf gebruiken om uw eigen FileUpload-component te maken.
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
const MIN_DIMENSIONS = { width: 200, height: 200 }
const MAX_DIMENSIONS = { width: 4096, height: 4096 }
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
const formatBytes = (bytes: number, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
const schema = z.object({
avatar: z
.instanceof(File, {
message: 'Please select an image file.'
})
.refine((file) => file.size <= MAX_FILE_SIZE, {
message: `The image is too large. Please choose an image smaller than ${formatBytes(MAX_FILE_SIZE)}.`
})
.refine((file) => ACCEPTED_IMAGE_TYPES.includes(file.type), {
message: 'Please upload a valid image file (JPEG, PNG, or WebP).'
})
.refine(
(file) =>
new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image()
img.onload = () => {
const meetsDimensions =
img.width >= MIN_DIMENSIONS.width &&
img.height >= MIN_DIMENSIONS.height &&
img.width <= MAX_DIMENSIONS.width &&
img.height <= MAX_DIMENSIONS.height
resolve(meetsDimensions)
}
img.src = e.target?.result as string
}
reader.readAsDataURL(file)
}),
{
message: `The image dimensions are invalid. Please upload an image between ${MIN_DIMENSIONS.width}x${MIN_DIMENSIONS.height} and ${MAX_DIMENSIONS.width}x${MAX_DIMENSIONS.height} pixels.`
}
)
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
avatar: undefined
})
function createObjectUrl(file: File): string {
return URL.createObjectURL(file)
}
async function onSubmit(event: FormSubmitEvent<Schema>) {
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4 w-64" @submit="onSubmit">
<UFormField name="avatar" label="Avatar" description="JPG, GIF or PNG. 1MB Max.">
<UFileUpload v-slot="{ open, removeFile }" v-model="state.avatar" accept="image/*">
<div class="flex flex-wrap items-center gap-3">
<UAvatar
size="lg"
:src="state.avatar ? createObjectUrl(state.avatar) : undefined"
icon="i-lucide-image"
/>
<UButton
:label="state.avatar ? 'Change image' : 'Upload image'"
color="neutral"
variant="outline"
@click="open()"
/>
</div>
<p v-if="state.avatar" class="text-xs text-muted mt-1.5">
{{ state.avatar.name }}
<UButton
label="Remove"
color="error"
variant="link"
size="xs"
class="p-0"
@click="removeFile()"
/>
</p>
</UFileUpload>
</UFormField>
<UButton type="submit" label="Submit" color="neutral" />
</UForm>
</template><script setup lang="ts">
import { reactive } from 'vue'
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
const MIN_DIMENSIONS = { width: 200, height: 200 }
const MAX_DIMENSIONS = { width: 4096, height: 4096 }
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
const formatBytes = (bytes: number, decimals = 2) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
const schema = z.object({
avatar: z
.instanceof(File, {
message: 'Please select an image file.'
})
.refine((file) => file.size <= MAX_FILE_SIZE, {
message: `The image is too large. Please choose an image smaller than ${formatBytes(MAX_FILE_SIZE)}.`
})
.refine((file) => ACCEPTED_IMAGE_TYPES.includes(file.type), {
message: 'Please upload a valid image file (JPEG, PNG, or WebP).'
})
.refine(
(file) =>
new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image()
img.onload = () => {
const meetsDimensions =
img.width >= MIN_DIMENSIONS.width &&
img.height >= MIN_DIMENSIONS.height &&
img.width <= MAX_DIMENSIONS.width &&
img.height <= MAX_DIMENSIONS.height
resolve(meetsDimensions)
}
img.src = e.target?.result as string
}
reader.readAsDataURL(file)
}),
{
message: `The image dimensions are invalid. Please upload an image between ${MIN_DIMENSIONS.width}x${MIN_DIMENSIONS.height} and ${MAX_DIMENSIONS.width}x${MAX_DIMENSIONS.height} pixels.`
}
)
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
avatar: undefined
})
function createObjectUrl(file: File): string {
return URL.createObjectURL(file)
}
async function onSubmit(event: FormSubmitEvent<Schema>) {
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4 w-64" @submit="onSubmit">
<UFormField name="avatar" label="Avatar" description="JPG, GIF or PNG. 1MB Max.">
<UFileUpload v-slot="{ open, removeFile }" v-model="state.avatar" accept="image/*">
<div class="flex flex-wrap items-center gap-3">
<UAvatar
size="lg"
:src="state.avatar ? createObjectUrl(state.avatar) : undefined"
icon="i-lucide-image"
/>
<UButton
:label="state.avatar ? 'Change image' : 'Upload image'"
color="neutral"
variant="outline"
@click="open()"
/>
</div>
<p v-if="state.avatar" class="text-xs text-muted mt-1.5">
{{ state.avatar.name }}
<UButton
label="Remove"
color="error"
variant="link"
size="xs"
class="p-0"
@click="removeFile()"
/>
</p>
</UFileUpload>
</UFormField>
<UButton type="submit" label="Submit" color="neutral" />
</UForm>
</template>Met bestanden-bodem slot
U kunt de files-bottom-sleuf gebruiken om een Button onder de bestandslijst toe te voegen om bijvoorbeeld alle bestanden te verwijderen.
<script setup lang="ts">
const value = ref<File[]>([])
</script>
<template>
<UFileUpload
v-model="value"
icon="i-lucide-image"
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
layout="list"
multiple
:interactive="false"
class="w-96 min-h-48"
>
<template #actions="{ open }">
<UButton
label="Select images"
icon="i-lucide-upload"
color="neutral"
variant="outline"
@click="open()"
/>
</template>
<template #files-bottom="{ removeFile, files }">
<UButton
v-if="files?.length"
label="Remove all files"
color="neutral"
@click="removeFile()"
/>
</template>
</UFileUpload>
</template><script setup lang="ts">
import { ref } from 'vue'
const value = ref<File[]>([])
</script>
<template>
<UFileUpload
v-model="value"
icon="i-lucide-image"
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
layout="list"
multiple
:interactive="false"
class="w-96 min-h-48"
>
<template #actions="{ open }">
<UButton
label="Select images"
icon="i-lucide-upload"
color="neutral"
variant="outline"
@click="open()"
/>
</template>
<template #files-bottom="{ removeFile, files }">
<UButton
v-if="files?.length"
label="Remove all files"
color="neutral"
@click="removeFile()"
/>
</template>
</UFileUpload>
</template>interactive prop is ingesteld op false in dit voorbeeld om het standaard klikbare gebied te voorkomen.Met bestanden-top slot
U kunt de files-top-sleuf gebruiken om een Button boven de bestandslijst toe te voegen om bijvoorbeeld nieuwe bestanden toe te voegen.
<script setup lang="ts">
const value = ref<File[]>([])
</script>
<template>
<UFileUpload
v-model="value"
icon="i-lucide-image"
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
layout="grid"
multiple
:interactive="false"
class="w-96 min-h-48"
>
<template #actions="{ open }">
<UButton
label="Select images"
icon="i-lucide-upload"
color="neutral"
variant="outline"
@click="open()"
/>
</template>
<template #files-top="{ open, files }">
<div v-if="files?.length" class="mb-2 flex items-center justify-between">
<p class="font-bold">Files ({{ files?.length }})</p>
<UButton
icon="i-lucide-plus"
label="Add more"
color="neutral"
variant="outline"
class="-my-2"
@click="open()"
/>
</div>
</template>
</UFileUpload>
</template><script setup lang="ts">
import { ref } from 'vue'
const value = ref<File[]>([])
</script>
<template>
<UFileUpload
v-model="value"
icon="i-lucide-image"
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
layout="grid"
multiple
:interactive="false"
class="w-96 min-h-48"
>
<template #actions="{ open }">
<UButton
label="Select images"
icon="i-lucide-upload"
color="neutral"
variant="outline"
@click="open()"
/>
</template>
<template #files-top="{ open, files }">
<div v-if="files?.length" class="mb-2 flex items-center justify-between">
<p class="font-bold">Files ({{ files?.length }})</p>
<UButton
icon="i-lucide-plus"
label="Add more"
color="neutral"
variant="outline"
class="-my-2"
@click="open()"
/>
</div>
</template>
</UFileUpload>
</template>API
Props
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
id | string | |
name | string | |
icon | appConfig.ui.icons.upload | anyThe icon to display. Set to |
label | string | |
description | string | |
color | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" |
variant | 'area' | "button" | "area"The |
size | 'md' | "xs" | "sm" | "md" | "lg" | "xl" |
layout | 'grid' | "list" | "grid"The layout of how files are displayed.
Only works when |
position | 'outside' | "inside" | "outside"The position of the files.
Only works when |
highlight | boolean Highlight the ring color like a focus state. | |
accept | '*' | stringSpecifies the allowed file types for the input. Provide a comma-separated list of MIME types or file extensions (e.g., "image/png,application/pdf,.jpg"). |
multiple | false | M |
reset | false | boolean Reset the file input when the dialog is opened. |
dropzone | true | boolean Create a zone that allows the user to drop files onto it. |
interactive | true | boolean Make the dropzone interactive when the user is clicking on it. |
required | boolean | |
disabled | boolean | |
fileIcon | appConfig.ui.icons.file | anyThe icon to display for the file. |
fileImage | true | boolean Preview the file (currently only |
fileDelete | true | boolean | Omit<ButtonProps, LinkPropsKeys> Configure the delete button for the file.
When |
fileDeleteIcon | appConfig.ui.icons.close | anyThe icon displayed to delete a file. |
preview | true | boolean Show the file preview/list after upload. |
modelValue | null | M extends true ? File[] : File | |
ui | { root?: SlotClass; base?: SlotClass; wrapper?: SlotClass; icon?: SlotClass; avatar?: SlotClass; label?: SlotClass; description?: SlotClass; actions?: SlotClass; files?: SlotClass; file?: SlotClass; fileLeadingAvatar?: SlotClass; fileWrapper?: SlotClass; fileName?: SlotClass; fileSize?: SlotClass; fileTrailingButton?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
default | { open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; ui: object; } |
leading | { ui: object; } |
label | {} |
description | {} |
actions | { files: FileUploadFiles<M> | undefined; open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; } |
files | { files: FileUploadFiles<M>; removeFile: (index?: number | undefined) => void; } |
files-top | { files: FileUploadFiles<M>; open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; } |
files-bottom | { files: FileUploadFiles<M>; open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; } |
file | { file: File; index: number; removeFile: (index?: number | undefined) => void; } |
file-leading | { file: File; index: number; ui: object; } |
file-name | { file: File; index: number; } |
file-size | { file: File; index: number; } |
file-trailing | { file: File; index: number; ui: object; } |
Uitzendt
| Event | Type |
|---|---|
change | [event: Event] |
update:modelValue | [value: (M extends true ? File[] : File) | null | undefined] |
Expose
Wanneer u de component opent via een sjabloonref, kunt u het volgende gebruiken:
| Naam | Type |
|---|---|
inputRef | Ref<HTMLInputElement | null> |
dropzoneRef | Ref<HTMLDivElement | null> |
Thema
export default defineAppConfig({
ui: {
fileUpload: {
slots: {
root: 'relative flex flex-col',
base: [
'w-full flex-1 bg-default border border-default flex flex-col gap-2 items-stretch justify-center rounded-lg focus-visible:outline-3',
'transition-[background] ease-out'
],
wrapper: 'flex flex-col items-center justify-center text-center',
icon: 'shrink-0',
avatar: 'shrink-0',
label: 'font-medium text-default mt-2',
description: 'text-muted mt-1',
actions: 'flex flex-wrap gap-1.5 shrink-0 mt-4',
files: '',
file: 'relative',
fileLeadingAvatar: 'shrink-0',
fileWrapper: 'flex flex-col min-w-0',
fileName: 'text-default truncate',
fileSize: 'text-muted truncate',
fileTrailingButton: ''
},
variants: {
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
variant: {
area: {
wrapper: 'px-4 py-3',
base: 'p-4'
},
button: {}
},
size: {
xs: {
base: 'text-xs',
icon: 'size-4',
file: 'text-xs px-2 py-1 gap-1',
fileWrapper: 'flex-row gap-1'
},
sm: {
base: 'text-xs',
icon: 'size-4',
file: 'text-xs px-2.5 py-1.5 gap-1.5',
fileWrapper: 'flex-row gap-1'
},
md: {
base: 'text-sm',
icon: 'size-5',
file: 'text-xs px-2.5 py-1.5 gap-1.5'
},
lg: {
base: 'text-sm',
icon: 'size-5',
file: 'text-sm px-3 py-2 gap-2',
fileSize: 'text-xs'
},
xl: {
base: 'text-base',
icon: 'size-6',
file: 'text-sm px-3 py-2 gap-2'
}
},
layout: {
list: {
root: 'gap-2 items-start',
files: 'flex flex-col w-full gap-2',
file: 'min-w-0 flex items-center border border-default rounded-md w-full',
fileTrailingButton: 'ms-auto'
},
grid: {
fileWrapper: 'hidden',
fileLeadingAvatar: 'size-full rounded-lg',
fileTrailingButton: 'absolute -top-1.5 -end-1.5 p-0 rounded-full border-2 border-bg'
}
},
position: {
inside: '',
outside: ''
},
dropzone: {
true: 'border-dashed data-[dragging=true]:bg-elevated/25'
},
interactive: {
true: ''
},
highlight: {
true: ''
},
multiple: {
true: ''
},
disabled: {
true: 'cursor-not-allowed opacity-75'
}
},
compoundVariants: [
{
color: 'primary',
class: 'outline-primary/25 focus-visible:outline-3 focus-visible:border-primary'
},
{
color: 'primary',
highlight: true,
class: 'border-primary'
},
{
color: 'neutral',
class: 'outline-inverted/25 focus-visible:outline-3 focus-visible:border-inverted'
},
{
color: 'neutral',
highlight: true,
class: 'border-inverted'
},
{
size: 'xs',
layout: 'list',
class: {
fileTrailingButton: '-me-1'
}
},
{
size: 'sm',
layout: 'list',
class: {
fileTrailingButton: '-me-1.5'
}
},
{
size: 'md',
layout: 'list',
class: {
fileTrailingButton: '-me-1.5'
}
},
{
size: 'lg',
layout: 'list',
class: {
fileTrailingButton: '-me-2'
}
},
{
size: 'xl',
layout: 'list',
class: {
fileTrailingButton: '-me-2'
}
},
{
variant: 'button',
size: 'xs',
class: {
base: 'p-1'
}
},
{
variant: 'button',
size: 'sm',
class: {
base: 'p-1.5'
}
},
{
variant: 'button',
size: 'md',
class: {
base: 'p-1.5'
}
},
{
variant: 'button',
size: 'lg',
class: {
base: 'p-2'
}
},
{
variant: 'button',
size: 'xl',
class: {
base: 'p-2'
}
},
{
layout: 'grid',
multiple: true,
class: {
files: 'grid grid-cols-2 md:grid-cols-3 gap-4 w-full',
file: 'p-0 aspect-square'
}
},
{
layout: 'grid',
multiple: false,
class: {
file: 'absolute inset-0 p-0'
}
},
{
interactive: true,
disabled: false,
class: 'hover:bg-elevated/25'
}
],
defaultVariants: {
color: 'primary',
variant: 'area',
size: 'md'
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
fileUpload: {
slots: {
root: 'relative flex flex-col',
base: [
'w-full flex-1 bg-default border border-default flex flex-col gap-2 items-stretch justify-center rounded-lg focus-visible:outline-3',
'transition-[background] ease-out'
],
wrapper: 'flex flex-col items-center justify-center text-center',
icon: 'shrink-0',
avatar: 'shrink-0',
label: 'font-medium text-default mt-2',
description: 'text-muted mt-1',
actions: 'flex flex-wrap gap-1.5 shrink-0 mt-4',
files: '',
file: 'relative',
fileLeadingAvatar: 'shrink-0',
fileWrapper: 'flex flex-col min-w-0',
fileName: 'text-default truncate',
fileSize: 'text-muted truncate',
fileTrailingButton: ''
},
variants: {
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
variant: {
area: {
wrapper: 'px-4 py-3',
base: 'p-4'
},
button: {}
},
size: {
xs: {
base: 'text-xs',
icon: 'size-4',
file: 'text-xs px-2 py-1 gap-1',
fileWrapper: 'flex-row gap-1'
},
sm: {
base: 'text-xs',
icon: 'size-4',
file: 'text-xs px-2.5 py-1.5 gap-1.5',
fileWrapper: 'flex-row gap-1'
},
md: {
base: 'text-sm',
icon: 'size-5',
file: 'text-xs px-2.5 py-1.5 gap-1.5'
},
lg: {
base: 'text-sm',
icon: 'size-5',
file: 'text-sm px-3 py-2 gap-2',
fileSize: 'text-xs'
},
xl: {
base: 'text-base',
icon: 'size-6',
file: 'text-sm px-3 py-2 gap-2'
}
},
layout: {
list: {
root: 'gap-2 items-start',
files: 'flex flex-col w-full gap-2',
file: 'min-w-0 flex items-center border border-default rounded-md w-full',
fileTrailingButton: 'ms-auto'
},
grid: {
fileWrapper: 'hidden',
fileLeadingAvatar: 'size-full rounded-lg',
fileTrailingButton: 'absolute -top-1.5 -end-1.5 p-0 rounded-full border-2 border-bg'
}
},
position: {
inside: '',
outside: ''
},
dropzone: {
true: 'border-dashed data-[dragging=true]:bg-elevated/25'
},
interactive: {
true: ''
},
highlight: {
true: ''
},
multiple: {
true: ''
},
disabled: {
true: 'cursor-not-allowed opacity-75'
}
},
compoundVariants: [
{
color: 'primary',
class: 'outline-primary/25 focus-visible:outline-3 focus-visible:border-primary'
},
{
color: 'primary',
highlight: true,
class: 'border-primary'
},
{
color: 'neutral',
class: 'outline-inverted/25 focus-visible:outline-3 focus-visible:border-inverted'
},
{
color: 'neutral',
highlight: true,
class: 'border-inverted'
},
{
size: 'xs',
layout: 'list',
class: {
fileTrailingButton: '-me-1'
}
},
{
size: 'sm',
layout: 'list',
class: {
fileTrailingButton: '-me-1.5'
}
},
{
size: 'md',
layout: 'list',
class: {
fileTrailingButton: '-me-1.5'
}
},
{
size: 'lg',
layout: 'list',
class: {
fileTrailingButton: '-me-2'
}
},
{
size: 'xl',
layout: 'list',
class: {
fileTrailingButton: '-me-2'
}
},
{
variant: 'button',
size: 'xs',
class: {
base: 'p-1'
}
},
{
variant: 'button',
size: 'sm',
class: {
base: 'p-1.5'
}
},
{
variant: 'button',
size: 'md',
class: {
base: 'p-1.5'
}
},
{
variant: 'button',
size: 'lg',
class: {
base: 'p-2'
}
},
{
variant: 'button',
size: 'xl',
class: {
base: 'p-2'
}
},
{
layout: 'grid',
multiple: true,
class: {
files: 'grid grid-cols-2 md:grid-cols-3 gap-4 w-full',
file: 'p-0 aspect-square'
}
},
{
layout: 'grid',
multiple: false,
class: {
file: 'absolute inset-0 p-0'
}
},
{
interactive: true,
disabled: false,
class: 'hover:bg-elevated/25'
}
],
defaultVariants: {
color: 'primary',
variant: 'area',
size: 'md'
}
}
}
})
]
})