# Attachment

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/attachment
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=569-26294

```tsx
import { Attachment } from '@commenda/pathfinder/attachment';

<Attachment
  item={{ id: 'pdf', filename: 'GST F5 return.pdf' }}
  onPreview={openPreview}
  onRemove={removeAttachment}
/>;
```


## Usage [#usage]

```tsx
import { Attachment } from '@commenda/pathfinder/attachment';

<Attachment
  item={{
    id: 'filing-pdf',
    filename: 'GST F5 return.pdf',
    mediaType: 'application/pdf',
  }}
  onPreview={openPreview}
  onRemove={removeAttachment}
  onRetry={retryUpload}
/>;
```

The application owns uploads, cancellation, retry, storage URLs, and viewer content. Callbacks receive the original item with its stable ID. Updating an item's `upload` value changes the pill without replacing its identity.

## Upload lifecycle [#upload-lifecycle]

Use `upload: { status: 'uploading', progress: 42 }` for measured progress, `progress: null` for indeterminate progress, and `{ status: 'failed', error: 'Connection lost' }` for failure. Omit `upload`, or set its status to `ready`, after success. Failed uploads remain visible; clicking retries only when `onRetry` is supplied. Set the uploading state promptly when retry begins.

## Sent attachments [#sent-attachments]

Omit `onRemove` after sending. Hover previews and the main preview action remain available. Images use the image file-type icon in the pill; the actual picture belongs in the preview.


```tsx
<Attachment item={attachment} onPreview={openPreview} />;
```


## Preview composition [#preview-composition]

`previewConfig={{ content: <FileSummary />, isLoading }}` supplies the hover/focus preview. `onPreview` can open Pathfinder's Sheet or your existing viewer. No PDF viewer, fetcher, or file URL resolver is bundled. The example uses a Sheet with app-supplied content.

## Validate before upload [#validate-before-upload]

```tsx
import { AttachmentValidation } from '@commenda/pathfinder/attachment';

const result = AttachmentValidation.validate(
  selectedFiles,
  { accept: ['.pdf', 'image/*'], maxBytes: 10_000_000, maxCount: 10 },
  existingAttachments.length
);
// Start uploads for result.accepted.
// Summarize result.rejected in one application-owned Toast.
```

Validation preserves file objects and selection order. Rejections contain `file` and `reasons` (`type`, `size`, `count`). Extensions are case-insensitive, size limits are inclusive, and count includes existing attachments plus accepted candidates. File extensions can be checked when browsers omit MIME metadata. This is pre-upload feedback; the server must enforce its own upload constraints.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `item` | `AttachmentItem` | required | Required stable `id` and original `filename`; optional `mediaType`, `url`, and byte `size` |
| `onPreview` | `((item: AttachmentItem) => void)` | optional | Ready pill activation; host opens its viewer |
| `onRetry` | `((item: AttachmentItem) => void)` | optional | Failed pill activation; host performs retry |
| `onRemove` | `((item: AttachmentItem) => void)` | optional | Separate remove control, including during uploads |
| `previewConfig` | `{ content: ReactNode; isLoading?: boolean; }` | optional | Optional host-owned preview content |
| `className` | `string` | optional | Outer pill styling |

### previewConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `content` | `ReactNode` | Yes |  |
| `isLoading` | `boolean` | No |  |

## Design and accessibility rules [#design-and-accessibility-rules]

- **ATTACH-01:** Figma 569:26294. Height 28px, radius 6px, 0.5px pearl-300 border, Label/12 Strong. Keep the original filename and extension; truncate at 240px with full name available to assistive technology and native hover text.
- **ATTACH-02:** Uploading replaces the file icon with ProgressRing. Failure uses StatusFailed, a 1px red-300 border and red-800 text. Keep failures in the list; a Toast must not replace them.
- **ATTACH-03:** Removal is a separate named button. The host cancels outstanding upload work and ignores stale completion results after removal.
- **ATTACH-04:** These are user-added inputs. Agent-returned files are artifacts and belong to the output components in a later phase.
- Preview and retry use native buttons with keyboard activation. Removal never triggers preview. Existing Popover and Sheet primitives supply focus and reduced-motion behavior.

## Migration notes [#migration-notes]

Map existing `key` to `id`, `fileName` to `filename`, and `mimeType` to `mediaType`. Keep opaque `fileRef` resolution inside your `onPreview` or preview-content adapter; do not treat it as a public URL. Existing per-item callbacks can be delegated by ID. Actual consumer migration is out of scope.
