Dropzone

Dropzone

드래그 앤 드롭으로 사용자에게서 파일을 받아요. @mantine/dropzone 패키지로 제공되며 MIT 라이선스로 배포돼요.

출처: 문서

본문

설치

yarn add @mantine/dropzone

설치 후 애플리케이션의 루트에서 패키지 스타일을 import 해요:

import '@mantine/core/styles.css';
// ‼️ dropzone 스타일은 core 패키지 스타일 다음에 import 하세요
import '@mantine/dropzone/styles.css';

사용법

Dropzone은 사용자에게서 하나 이상의 파일을 받을 수 있게 해줘요. 이 컴포넌트는 react-dropzone을 기반으로 하며 모든 핵심 기능을 지원해요:

  • 제공된 mime types에 기반해 파일을 수락/거부해요

  • 개별 파일 크기를 제한해요

  • 주어진 children을 렌더링하고 현재 상태에 따라 요소를 표시하는 컨텍스트 기반 컴포넌트를 제공해요

import { Group, Text } from '@mantine/core';
import { UploadSimpleIcon, ImageIcon, XIcon } from '@phosphor-icons/react';
import { Dropzone, DropzoneProps, IMAGE_MIME_TYPE } from '@mantine/dropzone';

export function BaseDemo(props: Partial<DropzoneProps>) {
  return (
    <Dropzone
      onDrop={(files) => console.log('accepted files', files)}
      onReject={(files) => console.log('rejected files', files)}
      maxSize={5 * 1024 ** 2}
      accept={IMAGE_MIME_TYPE}
      {...props}
    >
      <Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
        <Dropzone.Accept>
          <UploadSimpleIcon size="3.2rem" />
        </Dropzone.Accept>
        <Dropzone.Reject>
          <XIcon size="3.2rem" />
        </Dropzone.Reject>
        <Dropzone.Idle>
          <ImageIcon size="3.2rem" />
        </Dropzone.Idle>

        <div>
          <Text size="xl" inline>
            Drag images here or click to select files
          </Text>
          <Text size="sm" c="dimmed" inline mt={7}>
            Attach as many files as you like, each file should not exceed 5mb
          </Text>
        </div>
      </Group>
    </Dropzone>
  );
}

Dropzone.Accept, Dropzone.Reject, Dropzone.Idle

Dropzone.Accept, Dropzone.Reject, Dropzone.Idle 컴포넌트는 사용자가 특정 동작을 수행할 때만 보여요:

  • Dropzone.Accept는 사용자가 수락할 수 있는 파일을 dropzone 위로 끌어올 때만 보여요

  • Dropzone.Reject는 사용자가 수락할 수 없는 파일을 dropzone 위로 끌어올 때만 보여요

  • Dropzone.Idle는 사용자가 dropzone 위로 아무것도 끌어올리지 않을 때 보여요

로딩 상태

LoadingOverlay 컴포넌트로 로딩 상태를 나타내려면 loading prop을 설정해요. loading prop이 true일 때 사용자는 새 파일을 드롭하거나 선택할 수 없어요(Dropzone이 비활성화됨):

import { Dropzone } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone loading onDrop={() => {}}>
      {/* children */}
    </Dropzone>
  );
}

비활성 상태

자신만의 로딩 상태를 구현하고 싶다면 LoadingOverlay 없이 Dropzone을 비활성화할 수 있어요. loading과 마찬가지로 Dropzone이 비활성화되면 사용자는 새 파일을 드롭하거나 선택할 수 없어요:

import { Dropzone } from '@mantine/dropzone';
import classes from './Demo.module.css';

function Demo() {
  return (
    <Dropzone disabled onDrop={() => {}}>
      {/* children... */}
    </Dropzone>
  );
}

파일 브라우저 수동 열기

컴포넌트 밖에서 파일 브라우저를 열려면 openRef prop으로 파일 브라우저를 트리거할 함수를 얻어요:

import { useRef } from 'react';
import { Button, Group } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';

function Demo() {
  const openRef = useRef<() => void>(null);

  return (
    <>
      <Dropzone openRef={openRef} onDrop={() => {}}>
        {/* children */}
      </Dropzone>
      <Group justify="center" mt="md">
        <Button onClick={() => openRef.current?.()}>Select files</Button>
      </Group>
    </>
  );
}

자식 포인터 이벤트 활성화

기본적으로 Dropzone은 드래그 이벤트가 동작하도록 자식의 포인터 이벤트를 비활성화해요. activateOnClick={false}일 때 Dropzone 안의 어떤 자식을 클릭해도 아무 일도 일어나지 않아요. 하지만 pointerEvents: 'all' 스타일을 설정해 자식을 클릭 가능하게 만들 수 있어요. 이 스타일은 버튼이나 링크 같은 인터랙티브 요소에만 설정해야 한다는 점에 주의하세요.

import { useRef } from 'react';
import { Button, Group } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';

function Demo() {
  const openRef = useRef<() => void>(null);

  return (
    <Dropzone onDrop={() => {}} activateOnClick={false}>
      <Button
        onClick={() => openRef.current?.()} style={{ pointerEvents: 'all' }}
      >
        Select files
      </Button>
    </Dropzone>
  );
}

Mime types

파일 타입을 지정하려면 키가 mime type이고 값이 파일 확장자 배열인 객체를 제공해요. 특정 파일 타입을 수락하는 더 많은 예시는 react-dropzone 문서에서 찾을 수 있어요.

import { Dropzone } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone
      onDrop={() => {}}
      accept={{ 'image/png': ['.png'], 'image/gif': ['.gif'] }}
    >
      {/* children */}
    </Dropzone>
  );
}

accept prop에 mime types 배열을 제공해 파일 타입을 지정할 수도 있어요:

import { Dropzone } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone onDrop={() => {}} accept={['image/png', 'image/gif']}>
      {/* children */}
    </Dropzone>
  );
}

조사 시간을 아끼려면 @mantine/dropzone에서 내보내는 MIME_TYPES 변수를 사용할 수 있어요:

import { Dropzone, MIME_TYPES } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone
      onDrop={() => {}}
      accept={{
        'image/png': ['.png'],
        'image/gif': ['.gif'],
        [MIME_TYPES.jpeg]: ['.jpg', '.jpeg'],
      }}
    >
      {/* children */}
    </Dropzone>
  );
}

MIME_TYPES는 다음 데이터를 포함해요:

Key Mime type
png image/png
gif image/gif
jpeg image/jpeg
svg image/svg+xml
webp image/webp
avif image/avif
heic image/heic
heif image/heif
mp4 video/mp4
zip application/zip
rar application/x-rar
7z application/x-7z-compressed
csv text/csv
pdf application/pdf
doc application/msword
docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
xls application/vnd.ms-excel
xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
ppt application/vnd.ms-powerpoint
pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
exe application/vnd.microsoft.portable-executable

추가로 그룹화된 mime types를 사용할 수 있어요:

변수 Mime types
IMAGE_MIME_TYPE image/png, image/gif, image/jpeg, image/svg+xml, image/webp, image/avif, image/heic, image/heif
PDF_MIME_TYPE application/pdf
MS_WORD_MIME_TYPE application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document
MS_EXCEL_MIME_TYPE application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
MS_POWERPOINT_MIME_TYPE application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';

function Demo() {
  return (
    <Dropzone onDrop={() => {}} accept={IMAGE_MIME_TYPE}>
      {/* children */}
    </Dropzone>
  );
}

Styles API

Dropzone 루트 요소는 현재 상태에 따라 스타일을 바꾸는 다음 data 속성을 가져요:

  • data-loading – loading prop이 true일 때

  • data-accept – 사용자가 수락할 수 있는 파일을 dropzone 위로 끌어올 때

  • data-reject – 사용자가 수락할 수 없는 파일을 dropzone 위로 끌어올 때

  • data-idle – 기본 상태 – 사용자가 dropzone 위로 어떤 파일도 끌어올리지 않을 때

import { Text } from '@mantine/core';
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';
import classes from './Demo.module.css';

function Demo() {
  return (
    <Dropzone onDrop={() => {}} accept={IMAGE_MIME_TYPE} className={classes.root}>
      Drop images here
    </Dropzone>
  );
}
.root {
  &[data-accept] {
    color: var(--mantine-color-text);
  }

  &[data-reject] {
    color: var(--mantine-color-red-6);
  }
}

이미지 미리보기

import { useState } from 'react';
import { Text, Image, SimpleGrid } from '@mantine/core';
import { Dropzone, IMAGE_MIME_TYPE, FileWithPath } from '@mantine/dropzone';

function Demo() {
  const [files, setFiles] = useState<FileWithPath[]>([]);

  const previews = files.map((file, index) => {
    const imageUrl = URL.createObjectURL(file);
    return <Image key={index} src={imageUrl} onLoad={() => URL.revokeObjectURL(imageUrl)} />;
  });

  return (
    <div>
      <Dropzone accept={IMAGE_MIME_TYPE} onDrop={setFiles}>
        <Text ta="center">Drop images here</Text>
      </Dropzone>

      <SimpleGrid cols={{ base: 1, sm: 4 }} mt={files.length > 0 ? 'xl' : 0}>
        {previews}
      </SimpleGrid>
    </div>
  );
}

ref 가져오기

import { useEffect, useRef } from 'react';
import { Dropzone } from '@mantine/dropzone';

function Demo() {
  const dropzoneRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    dropzoneRef.current?.focus();
  }, []);

  return (
    <Dropzone onDrop={() => {}} ref={dropzoneRef}>
      {/* children */}
    </Dropzone>
  );
}

Dropzone.FullScreen 컴포넌트

Dropzone.FullScreen은 특정 영역 대신 브라우저 창에 드롭된 파일을 받을 수 있게 해줘요. Dropzone 컴포넌트와 같은 props를 지원해요.

import { useState } from 'react';
import { Group, Text, Button } from '@mantine/core';
import { UploadSimpleIcon, ImageIcon, XIcon } from '@phosphor-icons/react';
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';

function Demo() {
  const [active, setActive] = useState(false);

  return (
    <>
      <Group justify="center">
        <Button onClick={() => setActive((d) => !d)}>
          {active ? 'Deactivate' : 'Activate'} full screen dropzone
        </Button>
      </Group>

      <Dropzone.FullScreen
        active={active}
        accept={IMAGE_MIME_TYPE}
        onDrop={(files) => {
          console.log(files);
          setActive(false);
        }}
      >
        <Text size="xl" inline>
          Drag images here or click to select files
        </Text>
        <Text size="sm" c="dimmed" inline mt={7}>
          Attach as many files as you like, each file should not exceed 5mb
        </Text>
      </Dropzone.FullScreen>
    </>
  );
}

더 알아보기 (Learn more)