# File Upload

## Overview

A drag-and-drop file input component that accepts one or multiple files. Renders a dropzone area with visual feedback on drag, displays selected file names, and validates against allowed MIME types and file size limits.

The package also exports a **headless `useFileUpload` hook** for building fully custom drop-zone UIs while reusing all the validation and drag-state logic.

---

## Exports

| Export                | Description                                     |
| --------------------- | ----------------------------------------------- |
| `FileUpload`          | Ready-to-use drag-and-drop file input component |
| `useFileUpload`       | Headless hook — all logic, no UI                |
| `UseFileUploadProps`  | TypeScript props type for the hook              |
| `UseFileUploadReturn` | TypeScript return type for the hook             |

---

## When to Use

- Document upload forms
- Avatar/profile image replacement
- Data import (CSV, JSON uploads)
- Any file selection where UX is important

---

## `FileUpload` Component Props

| Prop            | Type                      | Default | Required | Description                                  |
| --------------- | ------------------------- | ------- | -------- | -------------------------------------------- |
| `accept`        | `string`                  | `'*'`   | No       | Accepted MIME types (e.g., `'image/*,.pdf'`) |
| `multiple`      | `boolean`                 | `false` | No       | Allow multiple file selection                |
| `maxSize`       | `number`                  | —       | No       | Maximum file size in bytes                   |
| `onFilesChange` | `(files: File[]) => void` | —       | No       | Called when selection changes                |
| `disabled`      | `boolean`                 | `false` | No       | Disables the dropzone                        |
| `className`     | `string`                  | —       | No       | Additional CSS classes                       |

---

## Examples

### Image Upload

```tsx
import { FileUpload } from 'xertica-ui/ui';

<FileUpload
  accept="image/*"
  maxSize={5 * 1024 * 1024} // 5MB
  onFilesChange={files => handleAvatarUpload(files[0])}
/>;
```

### Multiple Document Upload

```tsx
<FileUpload
  accept=".pdf,.doc,.docx"
  multiple
  onFilesChange={files => setSelectedDocuments(files)}
/>
```

### Inside react-hook-form

```tsx
<FormField
  control={form.control}
  name="attachment"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Attachment</FormLabel>
      <FormControl>
        <FileUpload accept=".pdf" onFilesChange={files => field.onChange(files[0])} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>
```

---

## `useFileUpload` Hook

A headless hook that encapsulates all file upload logic — drag state, file validation, error messaging, and the hidden input ref — without rendering any UI. Use it when the `<FileUpload>` component's visual design doesn't fit your needs.

### Props

| Prop            | Type                                                    | Default         | Description                                            |
| --------------- | ------------------------------------------------------- | --------------- | ------------------------------------------------------ |
| `maxFiles`      | `number`                                                | `1`             | Maximum number of files allowed                        |
| `maxSize`       | `number`                                                | `5242880` (5MB) | Maximum file size in bytes                             |
| `onFilesChange` | `(files: File[]) => void`                               | —               | Called with the accepted file list whenever it changes |
| `onError`       | `(rejected: File[], reason: 'size' \| 'count') => void` | —               | Called when files are rejected                         |
| `disabled`      | `boolean`                                               | `false`         | Whether the upload area is disabled                    |

### Return Value

| Property         | Type                                         | Description                                          |
| ---------------- | -------------------------------------------- | ---------------------------------------------------- |
| `files`          | `File[]`                                     | Currently accepted files                             |
| `dragActive`     | `boolean`                                    | Whether a drag is active over the drop zone          |
| `errorMessage`   | `string \| null`                             | Inline error message, or `null`                      |
| `inputRef`       | `RefObject<HTMLInputElement>`                | Attach to the hidden `<input type="file">`           |
| `handleFiles`    | `(files: FileList \| null) => void`          | Process a `FileList` from any source                 |
| `handleDrag`     | `(e: DragEvent) => void`                     | Attach to `onDragEnter`, `onDragOver`, `onDragLeave` |
| `handleDrop`     | `(e: DragEvent) => void`                     | Attach to `onDrop`                                   |
| `handleChange`   | `(e: ChangeEvent<HTMLInputElement>) => void` | Attach to the hidden input's `onChange`              |
| `removeFile`     | `(index: number) => void`                    | Remove a file by its index                           |
| `openFileDialog` | `() => void`                                 | Programmatically open the native file picker         |

### Custom Drop Zone Example

```tsx
import { useFileUpload } from 'xertica-ui/ui';
import { UploadCloud } from 'lucide-react';

function CustomDropZone() {
  const {
    files,
    dragActive,
    errorMessage,
    inputRef,
    handleDrag,
    handleDrop,
    handleChange,
    removeFile,
    openFileDialog,
  } = useFileUpload({
    maxFiles: 3,
    maxSize: 10 * 1024 * 1024, // 10MB
    onFilesChange: files => console.log('Files:', files),
  });

  return (
    <div className="space-y-3">
      <div
        onDragEnter={handleDrag}
        onDragOver={handleDrag}
        onDragLeave={handleDrag}
        onDrop={handleDrop}
        onClick={openFileDialog}
        className={cn(
          'flex flex-col items-center justify-center rounded-xl border-2 border-dashed p-10 cursor-pointer transition-colors',
          dragActive ? 'border-primary bg-primary/5' : 'border-border hover:border-primary/50'
        )}
      >
        <UploadCloud className="size-8 text-muted-foreground mb-2" />
        <p className="text-sm text-muted-foreground">
          Drop files here or <span className="text-primary font-medium">browse</span>
        </p>
        <input ref={inputRef} type="file" multiple className="hidden" onChange={handleChange} />
      </div>

      {errorMessage && <p className="text-sm text-destructive">{errorMessage}</p>}

      {files.length > 0 && (
        <ul className="space-y-1">
          {files.map((file, i) => (
            <li key={i} className="flex items-center justify-between text-sm">
              <span>{file.name}</span>
              <Button size="sm" variant="ghost" onClick={() => removeFile(i)}>
                Remove
              </Button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
```

---

## AI Rules

- Do not use native `<input type="file">` — always use `<FileUpload>` or `useFileUpload` from `xertica-ui`.
- `maxSize` is in bytes — calculate using `MB * 1024 * 1024`.
- In forms, use `onFilesChange` to call `field.onChange` — not `{...field}`.
- For avatar uploads, use `accept="image/*"` and `multiple={false}`.
- When using `useFileUpload`, attach `handleDrag` to **all three** drag events: `onDragEnter`, `onDragOver`, and `onDragLeave`.
- Always attach `inputRef` to a hidden `<input type="file">` element — `openFileDialog()` requires it.
- `removeFile(index)` removes by array index, not by file name.
