From 7bf0984698e43b9aa2dd879f65951318ed410390 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:20:04 +0100 Subject: codebase overhaul (#697) --- apps/web/src/hooks/use-file-upload.ts | 103 ++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 apps/web/src/hooks/use-file-upload.ts (limited to 'apps/web/src/hooks/use-file-upload.ts') diff --git a/apps/web/src/hooks/use-file-upload.ts b/apps/web/src/hooks/use-file-upload.ts new file mode 100644 index 00000000..19badd98 --- /dev/null +++ b/apps/web/src/hooks/use-file-upload.ts @@ -0,0 +1,103 @@ +import { useState, useRef } from "react"; +import { hasDragData } from "@/lib/drag-data"; + +interface UseFileUploadOptions { + accept?: string; + multiple?: boolean; + onFilesSelected?: (files: FileList) => void; +} + +function containsFiles(dataTransfer: DataTransfer): boolean { + return !hasDragData({ dataTransfer }) && dataTransfer.types.includes("Files"); +} + +export function useFileUpload({ + accept, + multiple, + onFilesSelected, +}: UseFileUploadOptions = {}) { + const [isDragOver, setIsDragOver] = useState(false); + const dragCounterRef = useRef(0); + const inputRef = useRef(null); + + function openFilePicker() { + if (!inputRef.current) return; + + inputRef.current.accept = accept || "*"; + inputRef.current.multiple = multiple || false; + inputRef.current.click(); + } + + function handleFileChange(event: React.ChangeEvent) { + const files = event.target.files; + if (files && files.length > 0 && onFilesSelected) { + onFilesSelected(files); + } + + if (event.target) { + event.target.value = ""; + } + } + + function handleDragEnter(e: React.DragEvent) { + e.preventDefault(); + + if (!containsFiles(e.dataTransfer)) return; + + dragCounterRef.current += 1; + setIsDragOver(true); + } + + function handleDragOver(e: React.DragEvent) { + e.preventDefault(); + + if (!containsFiles(e.dataTransfer)) return; + } + + function handleDragLeave(e: React.DragEvent) { + e.preventDefault(); + + if (!containsFiles(e.dataTransfer)) return; + + dragCounterRef.current -= 1; + if (dragCounterRef.current === 0) { + setIsDragOver(false); + } + } + + function handleDrop(e: React.DragEvent) { + e.preventDefault(); + setIsDragOver(false); + dragCounterRef.current = 0; + + if (onFilesSelected && containsFiles(e.dataTransfer)) { + const files = e.dataTransfer.files; + const shouldUseMultiple = multiple ?? false; + + if (shouldUseMultiple) { + onFilesSelected(files); + } else if (files.length > 0) { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(files[0]); + onFilesSelected(dataTransfer.files); + } + } + } + + return { + isDragOver, + openFilePicker, + fileInputProps: { + ref: inputRef, + type: "file", + style: { display: "none" }, + onChange: handleFileChange, + }, + dragProps: { + onDragEnter: handleDragEnter, + onDragOver: handleDragOver, + onDragLeave: handleDragLeave, + onDrop: handleDrop, + }, + }; +} -- cgit v1.3.1