Back to React Spectrum

DropZone

packages/react-aria-components/docs/DropZone.mdx

2022-12-1610.7 KB
Original Source

{/* Copyright 2023 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs'; export default Layout;

import docs from 'docs:react-aria-components'; import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import Anatomy from '/packages/react-aria/docs/datepicker/daterangepicker-anatomy.svg'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {Divider} from '@react-spectrum/divider'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import {Keyboard} from '@react-spectrum/text'; import FileTrigger from '@react-spectrum/docs/pages/assets/component-illustrations/FileTrigger.svg'


category: Drag and drop keywords: [dropzone, drop, dnd, drag and drop, aria, accessibility] type: component

DropZone

<PageDescription>{docs.exports.DropZone.description}</PageDescription>

<HeaderInfo packageData={packageData} componentNames={['DropZone']} />

Example

tsx
import {DropZone, Text} from 'react-aria-components';

function Example() {
  let [dropped, setDropped] = React.useState(false);

  return (
    <DropZone
      onDrop={() => {
        setDropped(true);
      }}>
      <Text slot="label">
        {dropped ? "You dropped something" : "Drop object here"}
      </Text>
    </DropZone>
  );
}
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary> ```css hidden @import './Button.mdx' layer(dropzone); ```
css
@import "@react-aria/example-theme";

.react-aria-DropZone {
  color: var(--text-color);
  background: var(--overlay-background);
  border: 1px solid var(--border-color);
  forced-color-adjust: none;
  border-radius: 4px;
  appearance: none;
  vertical-align: middle;
  font-size: 1.2rem;
  text-align: center;
  margin: 0;
  outline: none;
  padding: 24px 12px;
  width: 25%;
  display: inline-block;

  &[data-focus-visible],
  &[data-drop-target] {
    outline: 2px solid var(--focus-ring-color);
    outline-offset: -1px;
  }

  &[data-drop-target] {
    background: var(--highlight-overlay);
  }
}
</details>

Features

There is no native element to implement a drop zone in HTML. DropZone helps achieve accessible dropzone components that can be styled as needed.

  • Styleable – Hover, keyboard focus, and the drop target states are provided for easy styling. These styles only apply when interacting with an appropriate input device, unlike CSS pseudo classes.
  • Accessible – Support for native drag and drop via mouse and touch, as well as keyboard and screen reader interactions. Copy and paste is also supported as a keyboard accessible alternative.
  • Flexible – Files, directories, and custom data types can be dropped, and the contents of the drop zone can be fully customized.

Anatomy

A drop zone consists of a target element for the dropped objects. Users may drop objects via mouse, keyboard, or touch. DropZone accepts any content as its children, which may change when the user drops content. A FileTrigger is commonly paired with a DropZone to allow a user to choose files from their device.

A visual label should be provided to DropZone using a Text element with a label slot. If it is not provided, then an aria-label or aria-labelledby prop must be passed to identify the visually hidden button to assistive technology.

tsx
import {DropZone, Text} from 'react-aria-components';

<DropZone>
  <Text slot="label" />
</DropZone>

Composed Components

A drop zone can include a FileTrigger as a child, which may also be used standalone or reused in other components.

<section className={styles.cardGroup} data-size="small">

<ExampleCard url="FileTrigger.html" title="FileTrigger" description="A file trigger allows a user to access the file system with any pressable component such as a Button."> <FileTrigger /> </ExampleCard>

</section>

Events

DropZone supports user interactions via mouse, keyboard, and touch. You can handle all of these via the onDrop prop. In addition, the onDropEnter, onDropMove, and onDropExit events are fired as the user interacts with the dropzone.

tsx
import type {TextDropItem} from '@react-aria/dnd';

function Example() {
  let [dropped, setDropped] = React.useState(null);

  return (
    <>
      <Draggable />
      <DropZone
        onDrop={async(e) => {
          let items = await Promise.all(
            e.items
              .filter(item => item.kind === 'text' && item.types.has('text/plain'))
              .map((item: TextDropItem) => item.getText('text/plain'))
          );
          setDropped(items.join('\n'));
        }}>
        <Text slot="label">
          {dropped || 'Drop here'}
        </Text>
      </ DropZone>
    </>
  );
}

<Example />

The Draggable component used above is defined below. See useDrag for more details and documentation.

<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show code</summary>
tsx
import {useDrag} from 'react-aria-components';

function Draggable() {
  let {dragProps, isDragging} = useDrag({
    getItems() {
      return [{
        'text/plain': 'hello world',
        'my-app-custom-type': JSON.stringify({message: 'hello world'})
      }];
    }
  });

  return (
    <div {...dragProps} role="button" tabIndex={0} className={`draggable ${isDragging ? 'dragging' : ''}`}>
      Drag me
    </div>
  );
}
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
css
.draggable {
  display: inline-block;
  vertical-align: top;
  border: 1px solid gray;
  padding: 10px;
  margin-right: 20px;
  border-radius: 4px;
}

.draggable.dragging {
  opacity: 0.5;
}
</details> </details>

Labeling

The label slot enables the user to reference the text content to define the dropzone's accessible name.

tsx
import {Text} from 'react-aria-components';

function Example() {
  let [dropped, setDropped] = React.useState(false);

  return (
    <DropZone
      onDrop={() => setDropped(true)}>
      <Text slot="label">
        {dropped ? 'Successful drop!' : 'Drop files here'}
      </Text>
    </DropZone>
  );
}

FileTrigger

To allow the selection of files from the user's device, pass FileTrigger as a child of DropZone.

tsx
import {FileTrigger, Button} from 'react-aria-components';
import type {FileDropItem} from 'react-aria'

function Example() {
  let [files, setFiles] = React.useState(null);

  return(
    <DropZone
      onDrop={(e) => {
        let files = e.items.filter((file) => file.kind === 'file') as FileDropItem[];
        let filenames = files.map((file) => file.name);
        setFiles(filenames.join(', '));
      }}>
      <FileTrigger
        allowsMultiple
        onSelect={(e) => {
          let files = Array.from(e);
          let filenames = files.map((file) => file.name);
          setFiles(filenames.join(', '));
        }}>
        <Button>Select files</Button>
      </FileTrigger>
      <Text slot="label" style={{display: 'block'}}>
        {files || 'Drop files here'}
      </Text>
    </DropZone>
  );
}

Visual feedback

A dropzone displays visual feedback to the user when a drag hovers over the drop target by passing the getDropOperation function. If a drop target only supports data of specific types (e.g. images, videos, text, etc.), then it should implement the getDropOperation prop and return cancel for types that aren't supported. This will prevent visual feedback indicating that the drop target accepts the dragged data when this is not true. Read more about getDropOperation.

tsx

function Example() {
  let [dropped, setDropped] = React.useState(false);

  return (
    <DropZone
      getDropOperation={(types) => types.has('image/png') ? 'copy' : 'cancel'}
      onDrop={() => setDropped(true)}>
      {dropped ? 'Successful drop!' : 'Drop files here'}
    </DropZone>
  );
}

Props

<PropTable component={docs.exports.DropZone} links={docs.links} />

Styling

React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

css
.react-aria-DropZone {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

jsx
<DropZone className="my-dropzone">
</DropZone>

In addition, some components support multiple UI states (e.g. focused, placeholder, readonly, etc.). React Aria components expose states using data attributes, which you can target in CSS selectors. For example:

css
.react-aria-DropZone[data-drop-target] {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

jsx
<DropZone className={({isDropTarget}) => isDropTarget ? 'bg-gray-700' : 'bg-gray-600'} />

Render props may also be used as children to alter what elements are rendered based on the current state. For example, you could render an extra element when the drop target is in an active state.

jsx
<DropZone>
  {({isDropTarget}) => (
    <>
      {isDropTarget && <DropHighlight/>}
      Drop item here
    </>
  )}
</DropZone>

The states, selectors, and render props for DropZone are documented below.

<StateTable properties={docs.exports.DropZoneRenderProps.properties} />

Advanced customization

Hooks

If you need to customize things further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See useDrop for more details.