1. Components
  2. drop-zone

Drop Zone

Component for drag and drop file uploads.

Demo

Drop or paste text or images here
"use client"

import {Text} from 'react-aria-components';
import {useState} from 'react';
import React from 'react';
import { DropZone } from '@/components/jk/drop-zone';

export const DemoDropZone = ()=> {
  const [content, setContent] = useState<string | React.ReactElement | null>(null);
  return (
    <DropZone
      // Determine whether dragged content should be accepted.
      getDropOperation={types => (
        ['text/plain', 'image/jpeg', 'image/png', 'image/gif'].some(t => types.has(t)) ? 'copy' : 'cancel'
      )}
      onDrop={async (event) => {
        // Find the first accepted item.
        const item = event.items.find(item => (
          (item.kind === 'text' && item.types.has('text/plain')) ||
          (item.kind === 'file' && item.type.startsWith('image/'))
        ));

        if (item?.kind === 'text') {
          const text = await item.getText('text/plain');
          setContent(text);
        } else if (item?.kind === 'file') {
          const file = await item.getFile();
          const url = URL.createObjectURL(file);
          setContent(<img src={url} alt={item.name} style={{maxHeight: 100, maxWidth: '100%'}} />)
        }
      }}>
      <Text slot="label">
        {content || "Drop or paste text or images here"}
      </Text>
    </DropZone>
  );
}

Installation

terminal
npx shadcn@latest add @jk-ui/drop-zone

Examples

Or drag and drop PNG, JPG, GIF up to 10MB
"use client"

import type { DropEvent } from "@react-types/shared"
import { useState } from "react"
import { isFileDropItem } from "react-aria-components"
import { DropZone } from "@/components/jk/drop-zone"
import { Description } from "@/components/jk/input"
import { FileTrigger } from "@/components/jk/file-trigger"

export const DropZoneWithFileTrigger = ()=> {
  const [droppedImage, setDroppedImage] = useState<string | undefined>(undefined)

  const onDropHandler = async (e: DropEvent) => {
    const item = e.items
      .filter(isFileDropItem)
      .find((item) => item.type === "image/jpeg" || item.type === "image/png")
    if (item) {
      const file = await item.getFile()
      setDroppedImage(URL.createObjectURL(file))
    }
  }

  const onSelectHandler = async (e: FileList | null) => {
    if (e) {
      const files = Array.from([...e])
      const item = files[0]

      if (item) {
        setDroppedImage(URL.createObjectURL(item))
      }
    }
  }
  return (
    <DropZone
      getDropOperation={(types) =>
        types.has("image/jpeg") || types.has("image/png") ? "copy" : "cancel"
      }
      onDrop={onDropHandler}
      className={"w-full max-w-sm"}
    >
      {droppedImage ? (
        <img alt="" src={droppedImage} className="aspect-square size-full object-contain" />
      ) : (
        <div className="grid space-y-3">
          <div className="mx-auto grid size-12 place-content-center rounded-full border bg-bg-muted/70 border-border group-data-drop-target:border-primary/70 group-data-drop-target:bg-primary/20">
            <span aria-hidden className="size-5 iconify ph--image" />
          </div>
          <div className="flex justify-center">
            <FileTrigger
            size="sm"
              acceptedFileTypes={["image/png", "image/jpeg"]}
              allowsMultiple={false}
              onSelect={onSelectHandler}
            >
              Upload a file
            </FileTrigger>
          </div>
          <Description>Or drag and drop PNG, JPG, GIF up to 10MB</Description>
        </div>
      )}
      <input type="hidden" name="image" value={droppedImage} />
    </DropZone>
  )
}