Files
Crussell/frontend/src/lib/components/ui/file-drop-zone.svelte
T
2025-10-13 22:35:37 +01:00

72 lines
1.6 KiB
Svelte

<script lang="ts">
const { onfiles = () => {}, accept = '*', multiple = false, children } = $props();
let dragging = $state(false);
let fileInputEl: HTMLInputElement; // Reference to the hidden file input
function handleDrop(e: DragEvent) {
e.preventDefault();
dragging = false;
const files = Array.from(e.dataTransfer?.files ?? []);
if (files.length) {
onfiles(files);
}
}
function handleDragOver(e: DragEvent) {
e.preventDefault();
dragging = true;
}
function handleDragLeave(e: DragEvent) {
e.preventDefault();
dragging = false;
}
/** Handles file selection from the native dialog */
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
const files = Array.from(input.files ?? []);
if (files.length) {
onfiles(files);
}
// IMPORTANT: Reset the input value so the same file can be selected again
input.value = '';
}
/** Programmatically clicks the hidden file input */
function openFileSelect() {
fileInputEl.click();
}
</script>
<div
role="button"
aria-label="Drop files or click to upload"
tabindex="0"
ondragover={handleDragOver}
ondragleave={handleDragLeave}
ondrop={handleDrop}
onclick={openFileSelect}
onkeydown={(e) => {
// Allow keyboard users to trigger the click with Enter or Space
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
openFileSelect();
}
}}
class="rounded-lg border-2 border-dashed p-6 text-center transition-colors
{dragging ? 'border-primary bg-primary/5' : 'border-gray-300'}"
>
<input
type="file"
bind:this={fileInputEl}
{accept}
{multiple}
onchange={handleFileSelect}
class="sr-only"
/>
{@render children?.()}
</div>