

<Preview name="ConversationShowcaseExample" />

## Overview [#overview]

`Conversation` wraps the message feed area. It handles scroll positioning through `use-stick-to-bottom`, but it does not own messages or rendering.

## Usage [#usage]

```tsx
import { Card, CardMeta } from "@tilt-legal/cubitt-components/card";
import {
  Conversation,
  ConversationContent,
  ConversationDownload,
  ConversationEmptyState,
  ConversationScrollButton,
  ConversationTimeline,
  Message,
  MessageContent,
  MessageResponse,
  messagesToMarkdown,
} from "@tilt-legal/cubitt-components/chat-elements";
```

## Examples [#examples]

### Default [#default]

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

  <Tab value="Code">
    ```tsx
    <Card className="h-80">
      <Conversation className="min-h-0">
        <ConversationContent>
          {messages.map((message) => (
            <Message key={message.id} from={message.role}>
              <MessageContent>
                {message.role === "assistant" ? (
                  <MessageResponse>{message.text}</MessageResponse>
                ) : (
                  message.text
                )}
              </MessageContent>
            </Message>
          ))}
        </ConversationContent>
        <ConversationScrollButton />
      </Conversation>
    </Card>
    ```
  </Tab>
</Tabs>

### Timeline [#timeline]

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

  <Tab value="Code">
    ```tsx
    const timelineItems = turns.map((turn) => ({
      id: turn.id,
      targetId: `turn-${turn.id}`,
      message: turn.userText,
      response: turn.assistantText,
      files: turn.files,
    }));

    <Conversation className="h-96 rounded-xl border border-border-3">
      <ConversationTimeline items={timelineItems} />
      <ConversationContent className="pl-16">
        {turns.map((turn) => (
          <div id={`turn-${turn.id}`} key={turn.id} className="scroll-mt-4">
            <Message from="user">
              <MessageContent>{turn.userText}</MessageContent>
            </Message>
            <Message from="assistant">
              <MessageContent>
                <MessageResponse>{turn.assistantText}</MessageResponse>
              </MessageContent>
            </Message>
          </div>
        ))}
      </ConversationContent>
    </Conversation>
    ```
  </Tab>
</Tabs>

### Download [#download]

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

  <Tab value="Code">
    ```tsx
    <Card className="h-80">
      <CardMeta className="min-h-12 justify-between px-4 py-0">
        <p>Lease review conversation</p>
        <ConversationDownload
          className="static"
          filename="conversation.md"
          messages={messages}
        />
      </CardMeta>
      <Conversation className="min-h-0">
        <ConversationContent>{messages.map(...)}</ConversationContent>
        <ConversationScrollButton />
      </Conversation>
    </Card>

    const markdown = messagesToMarkdown(messages);
    ```
  </Tab>
</Tabs>

### Empty [#empty]

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

  <Tab value="Code">
    ```tsx
    <Card className="h-80">
      <Conversation className="min-h-0">
        <ConversationContent className="h-full">
          <ConversationEmptyState
            title="No conversation yet"
            description="Messages will appear here as the assistant responds."
          />
        </ConversationContent>
      </Conversation>
    </Card>
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

### Conversation [#conversation]

Scroll-aware conversation root powered by `use-stick-to-bottom`. Extends `StickToBottom` props.

| Prop        | Type     | Default    | Description                                        |
| ----------- | -------- | ---------- | -------------------------------------------------- |
| `initial`   | `string` | `"smooth"` | Initial scroll behavior passed to `StickToBottom`. |
| `resize`    | `string` | `"smooth"` | Resize scroll behavior passed to `StickToBottom`.  |
| `role`      | `string` | `"log"`    | ARIA role for the conversation region.             |
| `className` | `string` | -          | Additional CSS classes for the scroll root.        |

### ConversationContent [#conversationcontent]

Message stack inside `Conversation`. Extends `StickToBottom.Content` props.

| Prop        | Type              | Default | Description                              |
| ----------- | ----------------- | ------- | ---------------------------------------- |
| `children`  | `React.ReactNode` | -       | Conversation messages or custom content. |
| `className` | `string`          | -       | Additional CSS classes for the content.  |

### ConversationTimeline [#conversationtimeline]

Left-side conversation turn index for jumping to user messages. Consumers own turn grouping and target ids; Cubitt owns the rail, hover preview, file indicator, active styling, and scroll affordance.

| Prop         | Type                                       | Default                   | Description                                                    |
| ------------ | ------------------------------------------ | ------------------------- | -------------------------------------------------------------- |
| `items`      | `ConversationTimelineItem[]`               | -                         | Prepared turn summaries and target ids. Required.              |
| `label`      | `string`                                   | `"Conversation timeline"` | Accessible label for the timeline navigation.                  |
| `maxFiles`   | `number`                                   | `2`                       | Maximum file entities shown before the compact `+N` summary.   |
| `onNavigate` | `(item: ConversationTimelineItem) => void` | -                         | Called after a timeline item is selected.                      |
| `className`  | `string`                                   | -                         | Additional CSS classes for the positioned timeline navigation. |

### ConversationTimelineItem [#conversationtimelineitem]

Prepared display data for one user turn in `ConversationTimeline`.

| Field      | Type                       | Description                                                      |
| ---------- | -------------------------- | ---------------------------------------------------------------- |
| `id`       | `string`                   | Stable item id.                                                  |
| `targetId` | `string`                   | DOM id of the message or turn wrapper to scroll to.              |
| `message`  | `string`                   | User message preview. Rendered as one small-size line.           |
| `response` | `string`                   | Optional agent response preview. Rendered as small clamped text. |
| `files`    | `ChatAttachmentFileData[]` | Optional files shown as compact entities and rail dots.          |
| `label`    | `string`                   | Optional accessible label for the timeline anchor.               |

### ConversationEmptyState [#conversationemptystate]

Centered empty state for conversations. Extends all HTML `div` props.

| Prop          | Type              | Default                                       | Description                                                |
| ------------- | ----------------- | --------------------------------------------- | ---------------------------------------------------------- |
| `title`       | `string`          | `"No messages yet"`                           | Heading shown when `children` is not provided.             |
| `description` | `string`          | `"Start a conversation to see messages here"` | Supporting text shown when `children` is not provided.     |
| `icon`        | `React.ReactNode` | -                                             | Optional icon shown above the default title/description.   |
| `children`    | `React.ReactNode` | -                                             | Custom empty-state content. Replaces title/description UI. |
| `className`   | `string`          | -                                             | Additional CSS classes for the empty-state wrapper.        |

### ConversationScrollButton [#conversationscrollbutton]

Scroll-to-bottom affordance for `Conversation`. Extends Button props and only renders when the conversation is not at the bottom.

| Prop         | Type                                         | Default              | Description                                           |
| ------------ | -------------------------------------------- | -------------------- | ----------------------------------------------------- |
| `aria-label` | `string`                                     | `"Scroll to bottom"` | Accessible label for the icon button.                 |
| `onClick`    | `React.MouseEventHandler<HTMLButtonElement>` | -                    | Called after the internal `scrollToBottom()` handler. |
| `className`  | `string`                                     | -                    | Additional CSS classes for the positioned button.     |

### ConversationDownload [#conversationdownload]

Download button that converts messages to markdown and saves a `.md` file. Extends Button props except `onClick`.

| Prop            | Type                                            | Default                   | Description                                         |
| --------------- | ----------------------------------------------- | ------------------------- | --------------------------------------------------- |
| `messages`      | `UIMessage[]`                                   | -                         | Messages to serialize. Required.                    |
| `filename`      | `string`                                        | `"conversation.md"`       | Download filename.                                  |
| `formatMessage` | `(message: UIMessage, index: number) => string` | default formatter         | Optional formatter for each message before joining. |
| `aria-label`    | `string`                                        | `"Download conversation"` | Accessible label for the icon button.               |
| `children`      | `React.ReactNode`                               | download icon             | Custom button content.                              |
| `className`     | `string`                                        | -                         | Additional CSS classes for the positioned button.   |

### messagesToMarkdown [#messagestomarkdown]

Utility used by `ConversationDownload`.

| Parameter       | Type                                            | Default           | Description                                     |
| --------------- | ----------------------------------------------- | ----------------- | ----------------------------------------------- |
| `messages`      | `UIMessage[]`                                   | -                 | Messages to serialize. Required.                |
| `formatMessage` | `(message: UIMessage, index: number) => string` | default formatter | Optional formatter for each serialized message. |
