file-upload

Avatar upload

Avatar upload

Загрузка превью…

src/file-upload/c-file-upload-2.vue

<!-- Иконка по умолчанию (нет файла) — путь lucide "user". -->
<script setup lang="ts">
import { computed } from "vue"
import { Button } from "@/components/ui/button"
import { formatBytes, useFileUpload } from "@/composables/use-file-upload"
import ErrorAlert from "./_shared/ErrorAlert.vue"

const maxSize = 2 * 1024 * 1024

const [state, { removeFile, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, openFileDialog, handleFileChange }] = useFileUpload({
  maxFiles: 1,
  maxSize,
  accept: "image/*",
  multiple: false,
})

const currentFile = computed(() => state.files[0])
</script>

<template>
  <div class="flex flex-col items-center gap-4">
    <div class="relative">
      <div
        class="group/avatar relative h-24 w-24 cursor-pointer overflow-hidden rounded-full border border-dashed transition-colors"
        :class="[
          state.isDragging ? 'border-primary bg-primary/5' : 'border-muted-foreground/25 hover:border-muted-foreground/20',
          currentFile?.preview && 'border-solid',
        ]"
        @dragenter="handleDragEnter"
        @dragleave="handleDragLeave"
        @dragover="handleDragOver"
        @drop="handleDrop"
        @click="openFileDialog"
      >
        <input type="file" accept="image/*" class="sr-only" @change="handleFileChange" />

        <img v-if="currentFile?.preview" :src="currentFile.preview" alt="Avatar" class="h-full w-full object-cover" />
        <div v-else class="flex h-full w-full items-center justify-center">
          <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 size-6"
          ><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
        </div>
      </div>

      <Button
        v-if="currentFile"
        size="icon"
        variant="outline"
        class="absolute end-0.5 top-0.5 z-10 size-6 rounded-full dark:bg-zinc-800 hover:dark:bg-zinc-700"
        aria-label="Remove avatar"
        @click="removeFile(currentFile.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.5"
        ><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
      </Button>
    </div>

    <div class="space-y-0.5 text-center">
      <p class="text-sm font-medium">{{ currentFile ? "Avatar uploaded" : "Upload avatar" }}</p>
      <p class="text-muted-foreground text-xs">{{ `PNG, JPG up to ${formatBytes(maxSize)}` }}</p>
    </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-2.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)