

import {
  SimpleUploadDemo,
  SimpleUploadWithS3Demo,
  BasicDropzoneDemo,
  AvatarUploadDemo,
  FormResetDemo,
} from "../../../src/components/file-upload-demos";

## Overview

The File Upload system gives you two ways to build file upload experiences: use `FileUploadField` for a ready-to-use component, or combine `useFileUpload` with the `FileUpload.*` primitives for fully custom UIs.

## Import

```tsx
// Recommended: ready-to-use component
import { FileUploadField } from "@px-ui/forms";

// Advanced: hook + composable primitives
import { FileUpload, useFileUpload } from "@px-ui/core";

// File type icons
import { getFileTypeIcon } from "@px-ui/core";
```

## Usage

This example uses `FileUploadField`, which includes state management, validation, and optional upload support.

<Preview>
  <SimpleUploadDemo />
</Preview>

<CodeBlock>
  ```tsx
  import { FileUploadField } from "@px-ui/forms";

  export function Uploader() {
    return (
      <FileUploadField
        accept="image/*,.pdf,.doc,.docx"
        maxSize={10 * 1024 * 1024}
        onFilesChange={(files) => console.log(files)}
      />
    );
  }
  ```
</CodeBlock>

## Examples

### With S3/presigned uploads

Configure uploads by providing `upload.getPresignedUrl` and `upload.uploadFile`. Progress is handled for you when `uploadFile` calls `onProgress`.

<Preview>
  <SimpleUploadWithS3Demo />
</Preview>

<CodeBlock>
  ```tsx
  import { FileUploadField } from "@px-ui/forms";

  export function UploaderWithUpload() {
    return (
      <FileUploadField
        accept="image/*,.pdf"
        multiple
        maxFiles={5}
        maxSize={5 * 1024 * 1024}
        upload={{
          getPresignedUrl: async ({ filename, contentType, size }) => {
            const res = await fetch("/api/get-upload-url", {
              method: "POST",
              body: JSON.stringify({ filename, contentType, size }),
            });
            const data = await res.json();

            return {
              result: {
                url: data.url,
                fullPath: data.fullPath,
              },
            };
          },
          uploadFile: async (url, file, presignedData, onProgress) => {
            // Implement upload to `url` and call `onProgress(0..100)`.
            // Return the final URL (or rely on `presignedData.fullPath`).
            await fetch(url, { method: "PUT", body: file });

            onProgress?.(100);
            return { result: { url: presignedData.fullPath } };
          },
        }}
      />
    );
  }
  ```
</CodeBlock>

### Fully custom UI (hook + primitives)

Use `useFileUpload` for state/actions and pass the entire hook return directly into `FileUpload.Root`.

<Preview>
  <BasicDropzoneDemo />
</Preview>

<CodeBlock>
  ```tsx
  import { FileUpload, useFileUpload } from "@px-ui/core";

  export function CustomDropzone() {
    const upload = useFileUpload({
      onFilesChange: (files) => console.log(files),
    });

    return (
      <FileUpload.Root upload={upload}>
        <FileUpload.Dropzone />
      </FileUpload.Root>
    );
  }
  ```
</CodeBlock>

### Avatar upload (render prop)

Use the `render` prop for complete control over the dropzone element. The render function receives `props` (event handlers to spread) and `state` (current upload state).

<Preview>
  <AvatarUploadDemo />
</Preview>

<CodeBlock>
  ```tsx
  import { FileUploadField } from "@px-ui/forms";

  export function AvatarUpload() {
    return (
      <FileUploadField
        accept="image/*"
        maxSize={2 * 1024 * 1024}
        showFileList={false}
        render={(props, state) => (
          <div
            {...props}
            className={`flex size-32 cursor-pointer items-center justify-center rounded-full bg-neutral-100 transition-colors ${
              state.isDragging ? "border-2 border-dashed border-blue-500" : ""
            }`}
          >
            {state.files.length > 0 ? (
              <img
                src={state.files[0].preview}
                alt="Avatar"
                className="size-full rounded-full object-cover"
              />
            ) : (
              <div className="flex flex-col items-center justify-center text-neutral-500">
                <UserIcon className="size-10" />
                <span className="mt-1 text-xs">Upload</span>
              </div>
            )}
          </div>
        )}
      />
    );
  }
  ```
</CodeBlock>

### Form reset integration

Use `reset()` and `instanceKey` for proper form reset handling.

<Preview>
  <FormResetDemo />
</Preview>

<CodeBlock>
  ```tsx
  import { FileUpload, useFileUpload, Button } from "@px-ui/core";

  export function FormResetDemo() {
    const [name, setName] = useState("");
    const upload = useFileUpload({
      multiple: true,
      upload: { getPresignedUrl, uploadFile },
      onFilesChange: (files) => console.log("Files changed:", files),
    });

    const handleSubmit = (e) => {
      e.preventDefault();
      console.log("Submitted:", { name, files: upload.files });
    };

    const handleReset = () => {
      setName("");
      upload.reset(); // Clears files, cancels uploads, increments instanceKey
    };

    return (
      <form onSubmit={handleSubmit}>
        <input value={name} onChange={(e) => setName(e.target.value)} />

        {/* Use instanceKey to force remount on reset */}
        <FileUpload.Root key={upload.instanceKey} upload={upload} multiple>
          <FileUpload.Dropzone size="sm" />
          <FileUpload.List>
            {(files) => files.map((file) => (
              <FileUpload.ListItem key={file.id} file={file} />
            ))}
          </FileUpload.List>
        </FileUpload.Root>

        <Button type="submit">Submit</Button>
        <Button type="button" onClick={handleReset}>Reset</Button>
      </form>
    );
  }
  ```
</CodeBlock>

## File Type Icons

The library includes file type icons that automatically match based on file extension or MIME type.

<CodeBlock>
  ```tsx
  import { getFileTypeIcon } from "@px-ui/core";

  // Get the icon component for a file
  const Icon = getFileTypeIcon({ name: "document.pdf", type: "application/pdf" });

  // Use it like any React component
  <Icon className="size-5" />
  ```
</CodeBlock>

Available icons: `PdfIcon`, `WordIcon`, `ExcelIcon`, `ImageIcon`, `VideoIcon`, `AudioIcon`, `ArchiveIcon`, `CodeIcon`, `TextIcon`, `GenericFileIcon`.

## Anatomy

The file upload primitives are designed to be composed together:

* **`FileUpload.Root`**: Provides context for all child components (accepts `upload` prop)
* **`FileUpload.Dropzone`**: Drag-and-drop area (also supports paste and keyboard)
* **`FileUpload.List`**: Container for file items (render prop receives files)
* **`FileUpload.ListItem`**: Consolidated file row with icon, name, size, progress, and actions
* **`FileUpload.HiddenInput`**: Hidden file input (if you need it separately from Dropzone)
* **`FileUpload.ClearAll`**: Clear all selected files button

## API Reference

### FileUploadField

A ready-to-use upload component that internally uses `useFileUpload` and the `FileUpload.*` primitives.

| Prop             | Type                                                                           | Default                             | Description                              |
| ---------------- | ------------------------------------------------------------------------------ | ----------------------------------- | ---------------------------------------- |
| `size`           | `"sm" \| "default" \| "lg"`                                                    | `"default"`                         | Dropzone size                            |
| `dropzoneText`   | `string`                                                                       | `"Paste Or Drag & Drop Files Here"` | Dropzone label text                      |
| `buttonText`     | `string`                                                                       | `"Browse for files"`                | Trigger button label                     |
| `showFileList`   | `boolean`                                                                      | `true`                              | Show file list below the control         |
| `initialFiles`   | `Array<{ id: string; name: string; size: number; type: string; url: string }>` | `[]`                                | Pre-populate with already-uploaded files |
| `disabled`       | `boolean`                                                                      | `false`                             | Disable all interactions                 |
| `renderFileItem` | `(file, actions) => React.ReactNode`                                           | -                                   | Custom renderer for list items           |
| `onError`        | `(error: { type: string; message: string; files?: File[] }) => void`           | -                                   | Validation errors callback               |

**Also accepts hook options:** `accept`, `multiple`, `maxSize`, `maxFiles`, `onFilesChange`, `onFileAdd`, `onFileRemove`, and `upload`.

***

### useFileUpload

A hook that manages file selection, validation, previews, and (optionally) uploads.

**Options**:

| Option          | Type              | Default    | Description                                 |
| --------------- | ----------------- | ---------- | ------------------------------------------- |
| `accept`        | `string`          | `"*"`      | Accepted file types (e.g. `"image/*,.pdf"`) |
| `multiple`      | `boolean`         | `false`    | Allow multiple files                        |
| `maxSize`       | `number`          | `Infinity` | Max file size in bytes                      |
| `maxFiles`      | `number`          | `Infinity` | Max file count (only when `multiple`)       |
| `initialFiles`  | `FileMetadata[]`  | `[]`       | Pre-populate with uploaded files            |
| `onFilesChange` | `(files) => void` | -          | Called when files change                    |
| `onFileAdd`     | `(file) => void`  | -          | Called when a file is added                 |
| `onFileRemove`  | `(file) => void`  | -          | Called when a file is removed               |
| `upload`        | `UploadConfig`    | -          | Enables uploads and progress tracking       |

**Returns**: `UseFileUploadReturn`

* **State**: `files`, `isDragging`, `errors`, `isUploading`
* **Core Actions**: `addFiles`, `removeFile`, `clearFiles`, `clearErrors`
* **Form Integration**: `reset(newFiles?)`, `setFiles(files | (prev) => files)`, `instanceKey`
* **Upload Actions**: `uploadFiles`, `retryUpload`, `cancelUpload`
* **Drag/Drop + Input**: `handleDragEnter`, `handleDragLeave`, `handleDragOver`, `handleDrop`, `handleFileChange`, `openFileDialog`, `getInputProps`

***

### FileUpload.Root

Context provider - accepts the entire hook return value.

| Prop        | Type                  | Required | Description                             |
| ----------- | --------------------- | -------- | --------------------------------------- |
| `upload`    | `UseFileUploadReturn` | Yes      | The return value from `useFileUpload()` |
| `accept`    | `string`              | No       | Accepted types (used by Dropzone/input) |
| `multiple`  | `boolean`             | No       | Allow multiple file selection           |
| `disabled`  | `boolean`             | No       | Disable all interactions                |
| `className` | `string`              | No       | Additional CSS classes                  |

***

### FileUpload.Dropzone

Drag-and-drop area (also supports paste, click, and keyboard Enter/Space).

| Prop           | Type                                                                 | Default                             | Description                            |
| -------------- | -------------------------------------------------------------------- | ----------------------------------- | -------------------------------------- |
| `size`         | `"sm" \| "default" \| "lg"`                                          | `"default"`                         | Dropzone size                          |
| `dropzoneText` | `string`                                                             | `"Paste Or Drag & Drop Files Here"` | Primary label                          |
| `browseText`   | `string`                                                             | `"Browse for files"`                | Button label                           |
| `render`       | `(props: DropzoneRenderProps, state: DropzoneState) => ReactElement` | -                                   | Render prop for complete customization |

**Inherited Props:** All native `div` props (when not using `render`).

#### DropzoneRenderProps

Props passed to your custom element (spread these on your element):

```ts
interface DropzoneRenderProps {
  onDragEnter: React.DragEventHandler;
  onDragLeave: React.DragEventHandler;
  onDragOver: React.DragEventHandler;
  onDrop: React.DragEventHandler;
  onPaste: React.ClipboardEventHandler;
  onKeyDown: React.KeyboardEventHandler;
  onClick: React.MouseEventHandler;
  tabIndex: number;
  role: string;
  "aria-disabled": boolean;
  "data-dragging": boolean;
}
```

#### DropzoneState

State passed to your render function for conditional styling:

```ts
interface DropzoneState {
  isDragging: boolean;
  isUploading: boolean;
  disabled: boolean;
  files: FileUploadItem[];
}
```

***

### FileUpload.List

Renders nothing when there are no files.

| Prop       | Type                                  | Default | Description                            |
| ---------- | ------------------------------------- | ------- | -------------------------------------- |
| `children` | `ReactNode \| ((files) => ReactNode)` | -       | Render prop receives the current files |

**Inherited Props:** All native `div` props.

***

### FileUpload.ListItem

Consolidated file row component that replaces all the individual `Item*` parts.

| Prop           | Type              | Default | Description                                |
| -------------- | ----------------- | ------- | ------------------------------------------ |
| `file`         | `FileUploadItem`  | -       | The file object (required)                 |
| `showIcon`     | `boolean`         | `true`  | Show file type icon                        |
| `showProgress` | `boolean`         | `true`  | Show progress bar when uploading           |
| `showError`    | `boolean`         | `true`  | Show error message when status is error    |
| `showRemove`   | `boolean`         | `true`  | Show remove button                         |
| `showRetry`    | `boolean`         | `true`  | Show retry button when status is error     |
| `icon`         | `React.ReactNode` | -       | Custom icon (overrides auto-detected icon) |

**Inherited Props:** All native `div` props.

***

### FileUpload.HiddenInput

Hidden file input, useful if you need the input separate from the Dropzone.

**Inherited Props:** All native `input` props (except `type`).

***

### FileUpload.ClearAll

Button to clear all selected files. Renders nothing when there are no files.

**Inherited Props:** All `Button` props.
