useFileDrop

hook hookupload

Drag&Drop-Hook für Datei-Uploads: liefert ein dragOver-Flag und Event-Handler zum Spread auf einen Container. MIME-Filter via accept; verlässt-Container-Erkennung gegen Flackern bei Kind-Elementen.

Keine interaktive Live-Demo — siehe Code & Beispiele unten.

Barrierefreiheit

  • Reines Verhalten — die a11y des Drop-Bereichs bestimmt der Konsument.

Beispiele

Eigener Drop-Bereich
const { dragOver, dropHandlers } = useFileDrop({ accept: ".sng", onFiles });
<div {...dropHandlers} data-over={dragOver}>…</div>

Quelldateien

hooks/useFileDrop.ts ts
import { useCallback, useState, type DragEvent } from 'react';

interface Options {
  /** MIME-Filter wie bei `<input accept>` — z.B. `'image/*,video/*'`. Dateien,
   *  die nicht matchen, werden vor `onFiles` herausgefiltert. */
  accept?: string | undefined;
  /** Erhält die gefilterten Dateien sowie das Drop-Event (für Koordinaten o.ä.). */
  onFiles: (files: File[], e: DragEvent<HTMLElement>) => void;
  /** Wenn `true`, ignoriert der Drop-Handler komplett (z.B. während Uploads). */
  disabled?: boolean | undefined;
}

interface DropHandlers {
  onDragOver: (e: DragEvent<HTMLElement>) => void;
  onDragEnter: (e: DragEvent<HTMLElement>) => void;
  onDragLeave: (e: DragEvent<HTMLElement>) => void;
  onDrop: (e: DragEvent<HTMLElement>) => void;
}

/** Drag&Drop-Hook für Datei-Uploads. Liefert einen `dragOver`-Flag und
 *  Event-Handler, die per Spread an ein Container-Element gebunden werden. */
export function useFileDrop({ accept, onFiles, disabled }: Options): {
  dragOver: boolean;
  dropHandlers: DropHandlers;
} {
  const [dragOver, setDragOver] = useState(false);

  const matches = useCallback(
    (file: File) => {
      if (!accept) return true;
      const patterns = accept.split(',').map((s) => s.trim());
      return patterns.some((p) => {
        if (p.endsWith('/*')) return file.type.startsWith(p.slice(0, -1));
        return file.type === p;
      });
    },
    [accept],
  );

  const dropHandlers: DropHandlers = {
    onDragOver: (e) => {
      if (disabled) return;
      if (!Array.from(e.dataTransfer.types).includes('Files')) return;
      e.preventDefault();
      setDragOver(true);
    },
    onDragEnter: (e) => {
      if (disabled) return;
      if (!Array.from(e.dataTransfer.types).includes('Files')) return;
      e.preventDefault();
      setDragOver(true);
    },
    onDragLeave: (e) => {
      // Nur ausschalten wenn wir den Container wirklich verlassen
      // (nicht beim Übergang auf Kind-Elemente).
      if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;
      setDragOver(false);
    },
    onDrop: (e) => {
      if (disabled) return;
      setDragOver(false);
      if (!e.dataTransfer.files || e.dataTransfer.files.length === 0) return;
      e.preventDefault();
      const files = Array.from(e.dataTransfer.files).filter(matches);
      if (files.length > 0) onFiles(files, e);
    },
  };

  return { dragOver, dropHandlers };
}

Holen

# MCP (Claude Code / Cursor)
get_component({ name: "use-file-drop" })

# Registry direkt
https://www.ekklesi.tools/design/registry/components/use-file-drop.json