npm.io
2.0.4 • Published 6 months ago

direct-drop-area

Licence
MIT
Version
2.0.4
Deps
0
Size
147 kB
Vulns
0
Weekly
0
Stars
6

Direct Drop Area

A lightweight, feature-rich drag-and-drop file upload component for React.

No dependencies. No bloat. Just works.

npm version bundle size license


React Direct Drop Area React Direct Drop Area


Features

  • Drag & Drop - Drop files anywhere on the zone
  • Click to Upload - Opens native file picker
  • Keyboard Accessible - Full keyboard navigation support
  • Paste Support - Paste images directly from clipboard
  • Image Previews - Auto-generates preview URLs for images
  • Validation - File type, size, and custom validation
  • Multiple Files - Support for single or multiple file selection
  • Directory Upload - Upload entire folders
  • Browser URL Drop - Drag images directly from other websites
  • Lightweight - Zero dependencies, ~4KB gzipped
  • TypeScript - Full type definitions included
  • SSR Safe - Works with Next.js, Gatsby, etc.

Installation

# npm
npm install direct-drop-area

# yarn
yarn add direct-drop-area

# pnpm
pnpm add direct-drop-area

Quick Start

Basic Usage
import DirectDropArea from "direct-drop-area";

function App() {
  return (
    <DirectDropArea
      onDrop={(file) => {
        console.log("File dropped:", file);
      }}
    >
      {({ isDraggedOver }) => (
        <div
          style={{
            padding: "40px",
            border: `2px dashed ${isDraggedOver ? "green" : "gray"}`,
            background: isDraggedOver ? "#e8f5e9" : "white",
            textAlign: "center",
          }}
        >
          {isDraggedOver
            ? "Drop it here!"
            : "Drag file here or click to upload"}
        </div>
      )}
    </DirectDropArea>
  );
}
Image Upload with Preview
import { useState } from "react";
import DirectDropArea from "direct-drop-area";

function ImageUploader() {
  const [preview, setPreview] = useState(null);

  return (
    <DirectDropArea
      accept="image/*"
      maxSize={5 * 1024 * 1024} // 5MB
      onDrop={(file) => {
        setPreview(file.preview);
        console.log("Uploaded:", file.name, file.sizeFormatted);
      }}
      onDropRejected={(rejected) => {
        alert(rejected[0].errors[0].message);
      }}
    >
      {({ isDraggedOver, isProcessing }) => (
        <div
          style={{
            width: "300px",
            height: "200px",
            border: `2px dashed ${isDraggedOver ? "#4caf50" : "#ccc"}`,
            borderRadius: "12px",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            cursor: "pointer",
            background: isDraggedOver ? "#f0fff0" : "#fafafa",
          }}
        >
          {isProcessing ? (
            <span>Processing...</span>
          ) : preview ? (
            <img
              src={preview}
              alt="Preview"
              style={{ maxWidth: "100%", maxHeight: "100%" }}
            />
          ) : (
            <span>📷 Drop image or click to upload</span>
          )}
        </div>
      )}
    </DirectDropArea>
  );
}
Multiple Files with Validation
import DirectDropArea from "direct-drop-area";

function MultiUploader() {
  return (
    <DirectDropArea
      multiple
      maxFiles={5}
      accept=".pdf,.doc,.docx"
      maxSize={10 * 1024 * 1024} // 10MB per file
      onDropAccepted={(files) => {
        files.forEach((f) => {
          console.log(`✅ ${f.name} (${f.sizeFormatted})`);
        });
      }}
      onDropRejected={(rejected) => {
        rejected.forEach(({ file, errors }) => {
          console.log(`❌ ${file.name}: ${errors[0].message}`);
        });
      }}
    >
      {({ isDraggedOver }) => (
        <div className={`dropzone ${isDraggedOver ? "active" : ""}`}>
          Drop up to 5 documents here (PDF, DOC)
        </div>
      )}
    </DirectDropArea>
  );
}
Read File Contents
<DirectDropArea
  accept=".json,.txt"
  readAs="text"
  onDrop={(file, content) => {
    console.log("File name:", file.name);
    console.log("File content:", file.content);

    if (file.name.endsWith(".json")) {
      const data = JSON.parse(file.content);
      console.log("Parsed JSON:", data);
    }
  }}
>
  {({ isDraggedOver }) => <div>Drop a JSON or text file</div>}
</DirectDropArea>
Custom Styled Dropzone
import DirectDropArea from "direct-drop-area";
import "./styles.css";

function StyledDropzone() {
  return (
    <DirectDropArea accept="image/*" onDrop={(file) => console.log(file)}>
      {({ isDraggedOver, isDraggedOverDocument, isProcessing, open }) => (
        <div
          className={`
          dropzone 
          ${isDraggedOver ? "dropzone--active" : ""} 
          ${isDraggedOverDocument ? "dropzone--document-drag" : ""}
        `}
        >
          <div className="dropzone__icon">📁</div>
          <div className="dropzone__text">
            {isProcessing ? (
              "Processing..."
            ) : isDraggedOver ? (
              "Release to upload!"
            ) : (
              <>
                Drag & drop files here, or{" "}
                <button onClick={open} className="dropzone__button">
                  browse
                </button>
              </>
            )}
          </div>
          <div className="dropzone__hint">
            Supports: JPG, PNG, GIF up to 10MB
          </div>
        </div>
      )}
    </DirectDropArea>
  );
}
/* styles.css */
.dropzone {
  padding: 40px;
  border: 2px dashed #d0d0d0;
  border-radius: 16px;
  text-align: center;
  transition: all 0.2s ease;
  cursor: pointer;
}

.dropzone:hover {
  border-color: #2196f3;
  background: #f3f9ff;
}

.dropzone--active {
  border-color: #4caf50;
  background: #f0fff0;
  transform: scale(1.02);
}

.dropzone--document-drag {
  border-color: #ff9800;
}

.dropzone__icon {
  font-size: 48px;
  margin-bottom: 16px;
}

.dropzone__text {
  font-size: 18px;
  color: #333;
}

.dropzone__hint {
  font-size: 14px;
  color: #888;
  margin-top: 8px;
}

.dropzone__button {
  color: #2196f3;
  background: none;
  border: none;
  cursor: pointer;
  text-decoration: underline;
  font-size: inherit;
}

API Reference

Props
Prop Type Default Description
children function | ReactNode required Render prop function receiving state, or static children
onDrop function - Called with processed file(s) and content(s)
onDropAccepted function - Called only with valid files
onDropRejected function - Called with rejected files and errors
onError function - Called on processing errors
accept string null Accepted file types (e.g., "image/*", ".pdf")
multiple boolean false Allow multiple files
directory boolean false Allow folder upload
maxFiles number null Max files (with multiple: true)
maxSize number null Max file size in bytes
minSize number null Min file size in bytes
validate function null Custom validation function
disabled boolean false Disable the component
noClick boolean false Disable click to open dialog
noDrag boolean false Disable drag and drop
noKeyboard boolean false Disable keyboard interaction
noPaste boolean false Disable paste support
readAs string null Read files as 'text', 'dataURL', or 'arrayBuffer'
encoding string 'UTF-8' Encoding for text reading
Render Prop State

The render function receives an object with:

{
  isDraggedOver: boolean      // File is dragged over the drop zone
  isDraggedOverDocument: boolean  // File is dragged anywhere on the page
  isProcessing: boolean       // Files are being processed
  isDisabled: boolean         // Component is disabled
  open: () => void            // Programmatically open file dialog
}
File Object

Each processed file includes:

{
  file: File                  // Original File object
  id: string                  // Unique identifier
  name: string                // File name
  size: number                // Size in bytes
  type: string                // MIME type
  sizeFormatted: string       // Human readable size (e.g., "1.5 MB")
  content?: string | ArrayBuffer  // File content (if readAs specified)
  preview?: string            // Data URL for images
}
Rejected File Object
{
  file: File
  errors: Array<{
    code: 'FILE_TYPE_INVALID' | 'FILE_TOO_LARGE' | 'FILE_TOO_SMALL' | 'TOO_MANY_FILES' | 'CUSTOM_VALIDATION'
    message: string
  }>
}

Utility Functions

The package exports useful utility functions:

import {
  formatFileSize,
  readAsText,
  readAsDataURL,
  openFileDialog,
  isFileTypeValid,
} from "direct-drop-area";

// Format bytes to human readable
formatFileSize(1536000); // "1.5 MB"

// Read file contents
const text = await readAsText(file);
const dataURL = await readAsDataURL(file);

// Open file dialog programmatically
const file = await openFileDialog({ accept: "image/*" });
const files = await openFileDialog({ multiple: true });

// Validate file type
isFileTypeValid(file, "image/*"); // true/false

Styling Tips

Using isDraggedOverDocument

Show a full-page overlay when files are dragged anywhere on the page:

function App() {
  return (
    <>
      <DirectDropArea onDrop={handleDrop}>
        {({ isDraggedOver, isDraggedOverDocument }) => (
          <>
            {isDraggedOverDocument && (
              <div className="full-page-overlay">Drop anywhere to upload</div>
            )}
            <div className={`dropzone ${isDraggedOver ? "active" : ""}`}>
              Your drop zone content
            </div>
          </>
        )}
      </DirectDropArea>
    </>
  );
}
Disable During Upload
const [isUploading, setIsUploading] = useState(false)

<DirectDropArea
  disabled={isUploading}
  onDrop={async (file) => {
    setIsUploading(true)
    await uploadToServer(file)
    setIsUploading(false)
  }}
>
  {({ isDisabled }) => (
    <div style={{ opacity: isDisabled ? 0.5 : 1 }}>
      {isDisabled ? 'Uploading...' : 'Drop files here'}
    </div>
  )}
</DirectDropArea>

FAQ

How do I upload to a server?
<DirectDropArea
  onDrop={async (file) => {
    const formData = new FormData()
    formData.append('file', file.file)

    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData
    })

    const result = await response.json()
    console.log('Uploaded:', result)
  }}
>
Can I use it without the render prop?

Yes! Just pass children directly:

<DirectDropArea onDrop={handleDrop}>
  <div className="my-dropzone">Drop files here</div>
</DirectDropArea>

But you won't get access to isDraggedOver state this way.

Does it work with Next.js / SSR?

Yes! The component checks for browser environment and won't break during server-side rendering.

How do I clear the preview?

The component is stateless - manage previews in your own state:

const [preview, setPreview] = useState(null)

<DirectDropArea onDrop={(f) => setPreview(f.preview)}>
  {() => (
    <>
      {preview && (
        <>
          <img src={preview} />
          <button onClick={() => setPreview(null)}>Remove</button>
        </>
      )}
    </>
  )}
</DirectDropArea>

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT Shreenath Chakinala


Support

If this package helped you, consider giving it a on GitHub!

Found a bug? Open an issue