<!-- source-hash: cf353b09cad830f836030fb8046e62bc -->
A flexible React dropzone component supporting both simple file selection and async managed upload workflows, with drag-and-drop, validation, and file list rendering.

## Key Components

### Interfaces
- **`ManagedFileEntry`** — Represents an async-uploaded file with `id`, `fileName`, `fileSize`, `status` (`uploading` | `uploaded` | `error`), and optional `error` message
- **`FileUploadProps`** — Full prop interface supporting both simple (`value`/`onChange`) and managed (`managedFiles`/`onRemoveManagedFile`) modes

### Helpers
- **`formatFileSize(bytes)`** — Converts bytes to human-readable string (B, KB, MB, GB)
- **`matchesAccept(file, accept)`** — Validates a file against an accept string (MIME types, extensions, wildcards)
- **`dragHasFiles(e)`** — Detects if a native drag event carries file data

### Component
- **`FileUpload`** — Main export; renders a dropzone button (when empty) or file list (when files exist), with optional window-level drop capture via `acceptWindowDrops`

## Usage Example

```typescript
// Simple single-file mode
<FileUpload
  value={file}
  onChange={(f) => setFile(f as File)}
  accept="image/*"
  maxSize={5 * 1024 * 1024}
  fieldLabel="Profile Photo"
/>

// Async managed upload mode
<FileUpload
  onChange={(files) => uploadToS3(files as File[])}
  managedFiles={managedFiles}
  onRemoveManagedFile={(id) => removeManagedFile(id)}
  multiple
  maxFiles={5}
  acceptWindowDrops
  label="Drop attachments here"
/>
```

> **Tip:** Use `managedFiles` + `onRemoveManagedFile` for presigned URL or background upload flows where upload status (`uploading` | `uploaded` | `error`) needs to be reflected in the UI. Use plain `value`/`onChange` for synchronous or form-controlled scenarios.