`useDropzone` is a headless hook that wires drag-and-drop and the native file picker to the
upload store. It ships **no markup and no styling** — you get prop getters for a drop target and
a hidden `<input type="file">`, plus a live `isDragging` flag. Everything visual is yours.

```tsx
import { useDropzone } from '@gentleduck/upload/react'

function Drop() {
  const { getRootProps, getInputProps, isDragging, open } = useDropzone({
    purpose: 'attachment',
    accept: 'image/*,.pdf',
  })

  return (
    <div {...getRootProps()} data-active={isDragging}>
      <input {...getInputProps()} />
      <p>Drop files here or click to browse</p>
      <button type="button" onClick={open}>Choose files</button>
    </div>
  )
}
```

Dropped or selected files are dispatched to the store as `addFiles` (or handed to your own
`onFiles`). The hook reads the store from `<UploadProvider>` context — or pass an explicit
`store`.

## Options

```ts
useDropzone<P>({
  purpose: P                          // forwarded to the addFiles command (required)
  meta?: Record<string, unknown>      // caller metadata forwarded to backend API calls
  accept?: string                     // "image/*,.pdf" — filters files and sets input accept
  multiple?: boolean                  // default true; false keeps only the first file
  disabled?: boolean                  // default false; no drop, no click-to-open
  noClick?: boolean                   // default false; don't open the picker on root click
  onFiles?: (files: File[]) => void   // override the default addFiles dispatch
  store?: UploadStore                 // dispatch target; falls back to context
})
```

### `accept` matching

`accept` is a comma-separated list of tokens, matched like the native attribute:

| Token | Matches |
| --- | --- |
| `image/png` | exact MIME type |
| `image/*` | any type in the group |
| `.pdf` | filename extension (case-insensitive) |

A file passes if it matches **any** token. An absent or empty `accept` allows everything.

## Return value

| Field | Type | Purpose |
| --- | --- | --- |
| `isDragging` | `boolean` | True while files are dragged over the root |
| `open` | `() => void` | Programmatically open the file picker |
| `inputRef` | `RefObject` | Ref to the hidden input (already attached by `getInputProps`) |
| `getRootProps` | `(extra?) => props` | Spread onto the drop target |
| `getInputProps` | `(extra?) => props` | Spread onto the hidden `<input type="file">` |

Both getters **merge** any handlers/styles you pass, calling yours first — so
`getRootProps({ onClick })` still runs your `onClick` before opening the picker.

## Behavior notes

* **Nested children don't flicker** — an internal depth counter tracks `dragenter`/`dragleave`,
  so `isDragging` stays true while hovering child elements.
* **`drop` is enabled** — the root's `dragover`/`drop` call `preventDefault`, so the browser
  won't navigate away when a file is dropped.
* **Re-selecting the same file works** — the input's value is reset after each change, so
  picking the same file twice still fires.
* **Filtering happens before dispatch** — files rejected by `accept` never reach the store; with
  `multiple: false` only the first accepted file is dispatched.

## Custom handling

Skip the store entirely and receive the raw file list:

```tsx
const { getRootProps, getInputProps } = useDropzone({
  purpose: 'attachment',
  onFiles: (files) => console.log('picked', files),
})
```

## Pure helpers

The matching logic is exported for reuse and testing:

```ts
import { fileMatchesAccept, matchesAcceptToken, selectFiles } from '@gentleduck/upload/react'

selectFiles(files, { accept: 'image/*', multiple: false }) // filtered, first-only
fileMatchesAccept(file, 'image/*,.pdf')                     // boolean
```

## Next

* [useUploader](/duck-upload/react/use-uploader) — render the items the dropzone adds.
* [UploadProvider](/duck-upload/react/upload-provider) — the store context the hook reads.