

<Preview name="DropzoneVariantsExample" />

## Overview [#overview]

The **Dropzone** component provides an accessible drag-and-drop area for file uploads. Built on React Aria, it includes keyboard navigation, screen reader support, file picker integration, type/size/count validation, and visual feedback for hover, drag target, disabled, and rejected states.

## Usage [#usage]

Dropzone provides the upload interaction. Compose visual content inside it with [Empty State](/components/empty-state) sub-components.

```tsx
import { Button } from "@tilt-legal/cubitt-components/button";
import {
  Dropzone,
  DropzoneAction,
  DropzoneFolderAnimation,
} from "@tilt-legal/cubitt-components/dropzone";
import {
  EmptyStateDescription,
  EmptyStateHeading,
  EmptyStateMedia,
  EmptyStateTitle,
} from "@tilt-legal/cubitt-components/empty-state";
import { CloudUpload } from "@tilt-legal/cubitt-icons/ui/outline";
```

```tsx
<Dropzone onFileDrop={(files) => console.log(files)}>
  <EmptyStateMedia>
    <CloudUpload />
  </EmptyStateMedia>
  <EmptyStateHeading>
    <EmptyStateTitle>Drop files here</EmptyStateTitle>
    <EmptyStateDescription>or click to browse</EmptyStateDescription>
  </EmptyStateHeading>
</Dropzone>
```

## Examples [#examples]

### Variants [#variants]

Switch between the default, ghost, and minimal dropzone variants.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="DropzoneVariantsExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    const variants = ["default", "ghost", "minimal"] as const;
    const [variant, setVariant] = useState<"default" | "ghost" | "minimal">(
      "default"
    );

    <>
      <Dropzone variant={variant}>
        <EmptyStateMedia>
          <CloudUpload />
        </EmptyStateMedia>
        <EmptyStateHeading>
          <EmptyStateTitle>Drop files here</EmptyStateTitle>
          <EmptyStateDescription>
            Switch variants to compare surface and drag states.
          </EmptyStateDescription>
        </EmptyStateHeading>
      </Dropzone>
      <PreviewFooter>
        <ToggleGroup
          aria-label="Dropzone variant"
          groupVariant="segmented"
          multiple={false}
          onValueChange={(value) => {
            if (variants.includes(value as (typeof variants)[number])) {
              setVariant(value as (typeof variants)[number]);
            }
          }}
          value={variant}
        >
          <ToggleGroupItem value="default">Default</ToggleGroupItem>
          <ToggleGroupItem value="ghost">Ghost</ToggleGroupItem>
          <ToggleGroupItem value="minimal">Minimal</ToggleGroupItem>
        </ToggleGroup>
      </PreviewFooter>
    </>;
    ```
  </Tab>
</Tabs>

### Folder Animation [#folder-animation]

Use the animated folder for drag-over feedback. File type icons are based on the parent `accept` prop.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="DropzoneWithFolderExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dropzone
      accept={[".pdf", ".docx", ".xlsx"]}
      onFileDrop={(files) => console.log(files)}
    >
      <DropzoneFolderAnimation />
      <EmptyStateHeading>
        <EmptyStateTitle>Upload documents</EmptyStateTitle>
        <EmptyStateDescription>
          PDF, Word, and Excel files accepted
        </EmptyStateDescription>
      </EmptyStateHeading>
    </Dropzone>;
    ```
  </Tab>
</Tabs>

### Action Button [#action-button]

Add an explicit action inside the dropzone while preserving the full drag-and-drop target.

<Tabs className="bg-transparent border-none rounded-none" items="['Preview', 'Code']">
  <Tab value="Preview" className="p-0">
    <Preview name="DropzoneWithActionExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Dropzone onFileDrop={(files) => console.log(files)}>
      <EmptyStateMedia>
        <CloudUpload />
      </EmptyStateMedia>
      <EmptyStateHeading>
        <EmptyStateTitle>Drag and drop files</EmptyStateTitle>
        <EmptyStateDescription>PDF, DOCX, or images up to 10MB</EmptyStateDescription>
      </EmptyStateHeading>
      <DropzoneAction>
        <Button>Browse files</Button>
      </DropzoneAction>
    </Dropzone>;
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

### Dropzone [#dropzone]

Root component that handles drag-and-drop and file selection.

| Prop               | Type                                    | Default     | Description                                                          |
| ------------------ | --------------------------------------- | ----------- | -------------------------------------------------------------------- |
| `onFileDrop`       | `(files: File[]) => void`               | -           | Callback when valid files are dropped or selected                    |
| `onFilesRejected`  | `(rejections: FileRejection[]) => void` | -           | Callback when files are rejected by type, size, or count validation  |
| `accept`           | `string \| readonly string[]`           | -           | MIME types or file extensions for the picker and runtime validation  |
| `multiple`         | `boolean`                               | `true`      | Allow multiple file selection                                        |
| `maxSize`          | `number`                                | -           | Maximum file size in bytes                                           |
| `maxFiles`         | `number`                                | -           | Maximum number of accepted files                                     |
| `isDisabled`       | `boolean`                               | `false`     | Disable the dropzone                                                 |
| `disableClick`     | `boolean`                               | `false`     | Disable click-to-browse while preserving drag-and-drop               |
| `variant`          | `"default" \| "minimal" \| "ghost"`     | `"default"` | Visual variant                                                       |
| `getDropOperation` | `(types) => DropOperation`              | -           | Custom React Aria drop operation resolver                            |
| `onDrop`           | `(e: DropEvent) => void`                | -           | Raw React Aria drop event callback, called after Cubitt file parsing |
| `children`         | `ReactNode`                             | -           | Composed visual content                                              |
| `className`        | `string`                                | -           | Additional CSS classes                                               |

Dropzone also accepts the remaining React Aria `DropZone` props, except `children` is owned by Cubitt's composed content API.

#### FileRejection [#filerejection]

```tsx
type FileRejection = {
  file: File;
  reason: "type" | "size" | "count";
  message: string;
};
```

### DropzoneFolderAnimation [#dropzonefolderanimation]

Animated folder component with documents that fan out when opened. It responds to drag events from the parent `Dropzone`.

| Prop                | Type      | Default | Description                                                     |
| ------------------- | --------- | ------- | --------------------------------------------------------------- |
| `showFileTypeIcons` | `boolean` | `true`  | Show file type icons on documents based on parent `accept` prop |
| `hideFrontPanel`    | `boolean` | `false` | Hide the front folder panel for visual debugging                |
| `className`         | `string`  | -       | Additional CSS classes                                          |

### DropzoneAction [#dropzoneaction]

Wrapper for action buttons. Handles click propagation so the nested button opens the file picker without double-triggering the root.

| Prop        | Type        | Default | Description                   |
| ----------- | ----------- | ------- | ----------------------------- |
| `children`  | `ReactNode` | -       | Button or interactive element |
| `className` | `string`    | -       | Additional CSS classes        |
