79 lines
2.4 KiB
Vue
79 lines
2.4 KiB
Vue
<script setup lang="ts">
|
|
import { ImagePlus, X } from '@lucide/vue';
|
|
import { computed, ref } from 'vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Field, FieldDescription, FieldError, FieldLabel } from '@/components/ui/field';
|
|
import { Input } from '@/components/ui/input';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
const props = defineProps<{
|
|
id: string;
|
|
label: string;
|
|
description?: string;
|
|
currentUrl?: string | null;
|
|
errors?: string[];
|
|
accept?: string;
|
|
required?: boolean;
|
|
}>();
|
|
|
|
const model = defineModel<File | null>();
|
|
|
|
const previewUrl = ref<string | null>(null);
|
|
|
|
const displayUrl = computed(() => previewUrl.value ?? props.currentUrl ?? null);
|
|
|
|
function onFileChange(event: Event) {
|
|
const input = event.target as HTMLInputElement;
|
|
const file = input.files?.[0] ?? null;
|
|
|
|
if (previewUrl.value) {
|
|
URL.revokeObjectURL(previewUrl.value);
|
|
previewUrl.value = null;
|
|
}
|
|
|
|
model.value = file;
|
|
previewUrl.value = file ? URL.createObjectURL(file) : null;
|
|
}
|
|
|
|
function clearFile() {
|
|
if (previewUrl.value) {
|
|
URL.revokeObjectURL(previewUrl.value);
|
|
previewUrl.value = null;
|
|
}
|
|
|
|
model.value = null;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Field>
|
|
<FieldLabel :for="id" :required="required">
|
|
{{ label }}
|
|
</FieldLabel>
|
|
<FieldDescription v-if="description">
|
|
{{ description }}
|
|
</FieldDescription>
|
|
|
|
<div class="space-y-3">
|
|
<div v-if="displayUrl" :class="cn(
|
|
'relative overflow-hidden rounded-lg border bg-muted/30',
|
|
id === 'favicon' ? 'size-20' : 'aspect-video max-w-md',
|
|
)">
|
|
<img :src="displayUrl" :alt="label" class="size-full object-cover">
|
|
<Button v-if="model" type="button" variant="secondary" size="icon" class="absolute top-2 right-2 size-7"
|
|
@click="clearFile">
|
|
<X class="size-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<Input :id="id" type="file" :accept="accept ?? 'image/*'" class="max-w-md cursor-pointer"
|
|
@change="onFileChange" />
|
|
<ImagePlus v-if="!displayUrl" class="text-muted-foreground size-5 shrink-0" />
|
|
</div>
|
|
</div>
|
|
|
|
<FieldError :errors="errors ?? []" />
|
|
</Field>
|
|
</template>
|