nexus-5-frontend-1/src/lib/components/media/MediaUploadZone.svelte
2026-01-26 11:25:38 -05:00

101 lines
2.5 KiB
Svelte

<script lang="ts">
import { CloudArrowUpOutline } from 'flowbite-svelte-icons';
let {
mediaType = 'photo',
multiple = true,
onFilesSelected
}: {
mediaType?: 'photo' | 'video';
multiple?: boolean;
onFilesSelected: (files: FileList) => void;
} = $props();
let isDragging = $state(false);
let fileInput: HTMLInputElement;
const acceptTypes = mediaType === 'photo' ? 'image/*' : 'video/*';
const maxSize = mediaType === 'photo' ? '10MB' : '100MB';
const formats = mediaType === 'photo' ? 'JPG, PNG, GIF, WEBP' : 'MP4, MOV, WEBM';
function handleDrop(e: DragEvent) {
e.preventDefault();
isDragging = false;
const files = e.dataTransfer?.files;
if (files && files.length > 0) {
validateAndEmit(files);
}
}
function handleDragOver(e: DragEvent) {
e.preventDefault();
isDragging = true;
}
function handleDragLeave(e: DragEvent) {
e.preventDefault();
isDragging = false;
}
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
validateAndEmit(input.files);
}
}
function validateAndEmit(files: FileList) {
const validFiles: File[] = [];
const typePrefix = mediaType === 'photo' ? 'image/' : 'video/';
for (const file of files) {
if (file.type.startsWith(typePrefix)) {
validFiles.push(file);
}
}
if (validFiles.length > 0) {
const dt = new DataTransfer();
validFiles.forEach((f) => dt.items.add(f));
onFilesSelected(dt.files);
}
}
function openFileDialog() {
fileInput?.click();
}
</script>
<div
class="relative rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center transition-colors dark:border-gray-600 dark:bg-gray-800 {isDragging
? 'border-primary-500 bg-primary-50 dark:border-primary-400 dark:bg-primary-900/20'
: 'hover:border-gray-400 dark:hover:border-gray-500'}"
ondrop={handleDrop}
ondragover={handleDragOver}
ondragleave={handleDragLeave}
role="button"
tabindex="0"
onclick={openFileDialog}
onkeydown={(e) => e.key === 'Enter' && openFileDialog()}
>
<input
bind:this={fileInput}
type="file"
accept={acceptTypes}
{multiple}
onchange={handleFileSelect}
class="hidden"
/>
<CloudArrowUpOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-500" />
<p class="mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
{isDragging ? `Drop ${mediaType}s here` : `Drag & drop ${mediaType}s or click to browse`}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
Max size: {maxSize} • Formats: {formats}
</p>
</div>