file-upload
Basic drag and drop file upload
Basic drag and drop file upload
Загрузка превью…
src/file-upload/c-file-upload-3.vue
<!-- Иконки "plus"/"file"/"x" — путь lucide, идентичны react.tsx. -->
<script setup lang="ts">
import { Button } from "@/components/ui/button"
import { formatBytes, useFileUpload, type FileMetadata } from "@/composables/use-file-upload"
import ErrorAlert from "./_shared/ErrorAlert.vue"
const maxFiles = 3
const maxSize = 2 * 1024 * 1024
const [state, { removeFile, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, openFileDialog, handleFileChange }] = useFileUpload({
maxFiles,
maxSize,
accept: "image/*",
multiple: true,
})
const isImage = (file: File | FileMetadata) => file.type.startsWith("image/")
</script>
<template>
<div class="w-full max-w-lg">
<div
class="border-border rounded-lg flex items-center gap-3 border border-dashed p-4 transition-colors"
:class="state.isDragging ? 'border-primary bg-primary/5' : 'border-muted-foreground/25 hover:border-muted-foreground/50'"
@dragenter="handleDragEnter"
@dragleave="handleDragLeave"
@dragover="handleDragOver"
@drop="handleDrop"
>
<input type="file" accept="image/*" multiple class="sr-only" @change="handleFileChange" />
<Button size="sm" :class="state.isDragging && 'animate-bounce'" @click="openFileDialog">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
><path d="M5 12h14" /><path d="M12 5v14" /></svg>
Add files
</Button>
<div class="flex flex-1 items-center gap-2">
<p v-if="state.files.length === 0" class="text-muted-foreground text-sm">
{{ `Drop files here or click to browse (max ${maxFiles} files)` }}
</p>
<template v-else>
<div v-for="fileItem in state.files" :key="fileItem.id" class="group/item relative shrink-0">
<img
v-if="isImage(fileItem.file) && fileItem.preview"
:src="fileItem.preview"
:alt="fileItem.file.name"
class="h-12 w-12 rounded-lg border object-cover"
:title="`${fileItem.file.name} (${formatBytes(fileItem.file.size)})`"
/>
<div
v-else
class="bg-muted flex h-12 w-12 items-center justify-center rounded-lg border"
:title="`${fileItem.file.name} (${formatBytes(fileItem.file.size)})`"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-muted-foreground h-5 w-5"
><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /></svg>
</div>
<Button
variant="outline"
size="icon"
class="absolute -end-2 -top-2 size-5 rounded-full opacity-0 shadow-md transition-opacity group-hover/item:opacity-100"
@click="removeFile(fileItem.id)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-3"
><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
</Button>
</div>
</template>
</div>
<div v-if="state.files.length > 0" class="text-muted-foreground shrink-0 text-xs">{{ `${state.files.length}/${maxFiles}` }}</div>
</div>
<ErrorAlert :errors="state.errors" />
</div>
</template>
src/file-upload/_shared/ErrorAlert.vue
<!--
Общий блок "File upload error(s)" (`registry-reui/bases/radix/hooks/
use-file-upload.ts` пробрасывает `errors: string[]`), повторяется во всех
паттернах категории с одинаковой разметкой. Деталь реализации — не
экспортируется из index.ts (тот же приём, что `calendar/_shared/
CCalendar.vue`). В проверяемом (пустом/дефолтном) состоянии `errors`
всегда пуст, поэтому этот компонент никогда не монтируется на диф.
-->
<script setup lang="ts">
import { Alert, AlertDescription, AlertTitle } from "@/components/reui/alert"
defineProps<{ errors: string[] }>()
</script>
<template>
<Alert v-if="errors.length > 0" variant="destructive" class="mt-5">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
<AlertTitle>File upload error(s)</AlertTitle>
<AlertDescription>
<p v-for="(error, index) in errors" :key="index" class="last:mb-0">{{ error }}</p>
</AlertDescription>
</Alert>
</template>
Установка
npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-file-upload-3.jsonЗависимости реестра
Источник: порт из ReUI (Keenthemes, MIT)