

<Preview name="AttachmentsStatesExample" />

## Overview [#overview]

`Attachments` renders file and source-document references without owning upload, persistence, or message state. Use it anywhere chat UI needs a consistent attachment preview.

## Usage [#usage]

```tsx
import {
  Attachment,
  AttachmentInfo,
  AttachmentPreview,
  AttachmentRemove,
  Attachments,
} from "@tilt-legal/cubitt-components/chat-elements";
```

## Examples [#examples]

### States [#states]

Use file lifecycle fields to render default, uploading, failed, and loading attachment states.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="AttachmentsStatesExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { useEffect, useState } from "react";

    const states = ["default", "uploading", "error", "loading"] as const;
    const uploadSteps = [
      { progress: 0, duration: 2000 },
      { progress: 30, duration: 1000 },
      { progress: 60, duration: 1000 },
      { progress: 80, duration: 1000 },
      { progress: 100, duration: 1000 },
    ] as const;

    const [state, setState] = useState<(typeof states)[number]>("default");
    const [stepIndex, setStepIndex] = useState(0);

    useEffect(() => {
      if (state !== "uploading") {
        setStepIndex(0);
        return;
      }

      const timeout = window.setTimeout(() => {
        setStepIndex((index) => (index + 1) % uploadSteps.length);
      }, uploadSteps[stepIndex]?.duration);

      return () => window.clearTimeout(timeout);
    }, [state, stepIndex]);

    const progress = uploadSteps[stepIndex]?.progress ?? 0;

    const lifecycle =
      state === "default"
        ? { state: "default" as const }
        : state === "uploading"
          ? { state: "uploading" as const, progress }
          : { state };

    const image = {
      id: "site-photo",
      type: "file" as const,
      mediaType: "image/png",
      filename: "site-photo.png",
      url: state === "default" ? "/site-photo.png" : "",
      ...lifecycle,
    };

    const document = {
      id: "witness-statement",
      type: "file" as const,
      mediaType:
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      filename: "witness-statement.docx",
      url: state === "default" ? "#witness-statement" : "",
      ...lifecycle,
    };

    <>
      <div className="flex flex-wrap items-center justify-center gap-4">
        <Attachments>
          <Attachment data={image} onCancel={cancel} onRemove={remove}>
            <AttachmentPreview />
            <AttachmentRemove />
          </Attachment>
        </Attachments>
        <Attachments variant="list">
          <Attachment
            className="w-[225px]"
            data={document}
            onCancel={cancel}
            onRemove={remove}
          >
            <AttachmentPreview />
            <AttachmentInfo showMediaType />
            <AttachmentRemove />
          </Attachment>
        </Attachments>
      </div>
      <PreviewFooter>
        <ToggleGroup
          aria-label="Attachment state"
          groupVariant="segmented"
          multiple={false}
          onValueChange={(value) => {
            if (states.includes(value as (typeof states)[number])) {
              setState(value as (typeof states)[number]);
            }
          }}
          value={state}
        >
          <ToggleGroupItem value="default">Default</ToggleGroupItem>
          <ToggleGroupItem value="uploading">Uploading</ToggleGroupItem>
          <ToggleGroupItem value="error">Failed</ToggleGroupItem>
          <ToggleGroupItem value="loading">Loading</ToggleGroupItem>
        </ToggleGroup>
      </PreviewFooter>
    </>;
    ```
  </Tab>
</Tabs>

### Inline [#inline]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="AttachmentsInlineExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Attachments variant="inline">
      {files.map((file) => (
        <Attachment data={file} key={file.id} onRemove={() => remove(file.id)}>
          <AttachmentPreview />
          <AttachmentInfo />
          <AttachmentRemove />
        </Attachment>
      ))}
    </Attachments>
    ```
  </Tab>
</Tabs>

### List [#list]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="AttachmentsListExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Attachments variant="list">
      {files.map((file) => (
        <Attachment data={file} key={file.id} onRemove={() => remove(file.id)}>
          <AttachmentPreview />
          <AttachmentInfo showMediaType />
          <AttachmentRemove />
        </Attachment>
      ))}
    </Attachments>
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

### Attachments [#attachments]

Container that provides the attachment display variant to child attachments. Extends all HTML `div` props.

| Prop        | Type                           | Default  | Description                                             |
| ----------- | ------------------------------ | -------- | ------------------------------------------------------- |
| `variant`   | `"grid" \| "inline" \| "list"` | `"grid"` | Layout style shared with child `Attachment` components. |
| `children`  | `React.ReactNode`              | -        | Attachment items.                                       |
| `className` | `string`                       | -        | Additional CSS classes for the container.               |

### Attachment [#attachment]

Single attachment item. Extends all HTML `div` props.

| Prop        | Type              | Default | Description                                                     |
| ----------- | ----------------- | ------- | --------------------------------------------------------------- |
| `data`      | `AttachmentData`  | -       | File or source-document display data. Required.                 |
| `onCancel`  | `() => void`      | -       | Enables upload cancellation when `data.state` is `"uploading"`. |
| `onRemove`  | `() => void`      | -       | Enables `AttachmentRemove` and is called after removal.         |
| `children`  | `React.ReactNode` | -       | Attachment subcomponents.                                       |
| `className` | `string`          | -       | Additional CSS classes for the item.                            |

### AttachmentPreview [#attachmentpreview]

Media preview for an attachment. Must be rendered inside `Attachment`. Extends all HTML `div` props.

| Prop           | Type              | Default | Description                                                              |
| -------------- | ----------------- | ------- | ------------------------------------------------------------------------ |
| `fallbackIcon` | `React.ReactNode` | -       | Custom preview content. Replaces image/video/source/mime-type rendering. |
| `className`    | `string`          | -       | Additional CSS classes for the preview wrapper.                          |

### AttachmentInfo [#attachmentinfo]

Attachment label and optional media type. Must be rendered inside `Attachment`; renders nothing for the `grid` variant. Extends all HTML `div` props.

| Prop            | Type      | Default | Description                                        |
| --------------- | --------- | ------- | -------------------------------------------------- |
| `showMediaType` | `boolean` | `false` | Show a second line with the file/media type label. |
| `className`     | `string`  | -       | Additional CSS classes for the info wrapper.       |

### AttachmentRemove [#attachmentremove]

Remove or cancel button for an attachment. Must be rendered inside `Attachment`; renders nothing when the parent does not provide an available action. Extends Button props.

| Prop        | Type                                         | Default                         | Description                                                                       |
| ----------- | -------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------- |
| `label`     | `string`                                     | `"Remove"` or `"Cancel upload"` | Accessible label and screen-reader text for the button.                           |
| `onClick`   | `React.MouseEventHandler<HTMLButtonElement>` | -                               | Called before the parent action; prevent default to skip removal or cancellation. |
| `children`  | `React.ReactNode`                            | X icon                          | Custom remove button content.                                                     |
| `className` | `string`                                     | -                               | Additional CSS classes for the button.                                            |

### AttachmentHoverCard [#attachmenthovercard]

Hover-card wrapper for attachment previews. Extends PreviewCard props.

| Prop         | Type     | Default | Description                                                                              |
| ------------ | -------- | ------- | ---------------------------------------------------------------------------------------- |
| `openDelay`  | `number` | `0`     | Accepted for API parity with hover-card usage; currently rendered through `PreviewCard`. |
| `closeDelay` | `number` | `0`     | Accepted for API parity with hover-card usage; currently rendered through `PreviewCard`. |

### AttachmentHoverCardTrigger [#attachmenthovercardtrigger]

Trigger for `AttachmentHoverCard`. Extends PreviewCardTrigger props.

| Prop       | Type                 | Default | Description                       |
| ---------- | -------------------- | ------- | --------------------------------- |
| `render`   | `React.ReactElement` | -       | Custom trigger element to render. |
| `children` | `React.ReactNode`    | -       | Trigger content.                  |

### AttachmentHoverCardContent [#attachmenthovercardcontent]

Content for `AttachmentHoverCard`. Extends PreviewCardContent props.

| Prop        | Type     | Default   | Description                                  |
| ----------- | -------- | --------- | -------------------------------------------- |
| `align`     | `string` | `"start"` | Alignment passed to PreviewCardContent.      |
| `className` | `string` | -         | Additional CSS classes for the content pane. |

### AttachmentEmpty [#attachmentempty]

Empty attachment state. Extends all HTML `div` props.

| Prop        | Type              | Default            | Description                                     |
| ----------- | ----------------- | ------------------ | ----------------------------------------------- |
| `children`  | `React.ReactNode` | `"No attachments"` | Custom empty-state content.                     |
| `className` | `string`          | -                  | Additional CSS classes for the empty-state row. |

### Utilities [#utilities]

| Export                  | Type                                                                                                                                                                                                                          | Description                                                                  |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `getMediaCategory`      | `(data: AttachmentData) => AttachmentMediaCategory`                                                                                                                                                                           | Returns `image`, `video`, `audio`, `document`, `source`, or `unknown`.       |
| `getAttachmentLabel`    | `(data: AttachmentData) => string`                                                                                                                                                                                            | Returns the display label for file or source-document data.                  |
| `useAttachmentsContext` | `() => { variant: AttachmentVariant }`                                                                                                                                                                                        | Reads the current container variant, defaulting to `grid`.                   |
| `useAttachmentContext`  | `() => { data: AttachmentData; mediaCategory: AttachmentMediaCategory; progress: number; state: "default" \| "loading" \| "uploading" \| "error"; onCancel?: () => void; onRemove?: () => void; variant: AttachmentVariant }` | Reads the current attachment item context. Must be used inside `Attachment`. |

### Types [#types]

| Type                      | Value / Shape                                                          | Description                                                                                          |
| ------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `AttachmentVariant`       | `"grid" \| "inline" \| "list"`                                         | Supported attachment layouts.                                                                        |
| `AttachmentMediaCategory` | `"image" \| "video" \| "audio" \| "document" \| "source" \| "unknown"` | Normalized media categories.                                                                         |
| `AttachmentData`          | `ChatAttachmentFileData \| (SourceDocumentUIPart & { id?: string })`   | Data accepted by `Attachment`. File data supports inherited `state` and `progress` lifecycle fields. |
