# MessageQuotedPreview

> A two-line excerpt of the message a link points to, with its author, place and time, shown under the message that links to it.

Source: https://raft-design.botiverse.build/components/message-quoted-preview. Generated from the current release; where it differs from what you remember, this page is right.

When a message links to another message, the quoted preview shows what that message says: its author, where it was posted, when, and the first 2 lines of its text. It sits in `MessageItemEmbedded`, under the body that holds the link ([MessageItem](https://raft-design.botiverse.build/components/message-item.md)), and opens the quoted message.

```tsx
import { QuotedPreviewDirectMessageComposition } from "./quoted-preview-direct-message.composition.tsx";
import { QuotedPreviewLoadingComposition } from "./quoted-preview-loading.composition.tsx";
import { QuotedPreviewMessageComposition } from "./quoted-preview-message.composition.tsx";
import { QuotedPreviewUnavailableComposition } from "./quoted-preview-unavailable.composition.tsx";

export function MessageQuotedPreviewShowcaseComposition() {
  return (
    <div className="grid w-full max-w-3xl gap-4">
      <QuotedPreviewMessageComposition />
      <QuotedPreviewDirectMessageComposition />
      <QuotedPreviewLoadingComposition />
      <QuotedPreviewUnavailableComposition />
    </div>
  );
}
```

## When to use

| Situation                          | Use                                                                                |
| ---------------------------------- | ---------------------------------------------------------------------------------- |
| A message links to another message | MessageQuotedPreview, under the reference chip                                     |
| Several messages passed on in full | [MessageForwardedBundle](https://raft-design.botiverse.build/components/message-forwarded-bundle.md)  |
| A link to a page outside Raft      | A link card ([MessageEmbed](https://raft-design.botiverse.build/components/message-embed.md))                    |
| A quote typed into the message     | A Markdown blockquote ([MessageItem](https://raft-design.botiverse.build/components/message-item.md#in-the-body)) |
| The replies to a message           | [MessageReplies](https://raft-design.botiverse.build/components/message-replies.md)                            |

## Anatomy

- `MessageQuotedPreview`: the preview: a 2px `line-seam` bar on the left, no fill, `foreground-muted` text. Required.
- `MessageQuotedPreviewHeader`: one line. Required.
  - `MessageQuotedPreviewAuthor`: an `xs` [Avatar](https://raft-design.botiverse.build/components/avatar.md), `MessageQuotedPreviewAuthorName` and an optional `MessageQuotedPreviewAuthorMeta` (a role). `MessageQuotedPreviewAuthorDelegatedBy` follows the name when an assistant sent the quoted message for its author: "(sent by Friday)". It is experimental, exported from `@botiverse/raft-ui/wip`.
  - `MessageQuotedPreviewChannel`: where it was posted.
  - `MessageQuotedPreviewThread`: a thread marker. Optional.
  - `MessageQuotedPreviewTimestamp`: at the end of the line, in Space Mono.
- `MessageQuotedPreviewContent`: required.
  - `MessageQuotedPreviewText`: the excerpt, in `text-callout`, cut at 2 lines.
  - `MessageQuotedPreviewAttachments` with `MessageQuotedPreviewAttachment` chips. Optional.
- `MessageQuotedPreviewLoading` and `MessageQuotedPreviewUnavailable`: used in place of the preview while it loads, or when it can't be shown.

The bar is the same mark as a quote in a message ([Shape](https://raft-design.botiverse.build/foundations/shape.md#stroke-widths)).

## Variants

| Variant          | Use for                                                                                         |
| ---------------- | ----------------------------------------------------------------------------------------------- |
| Channel message  | A message in a channel, with `MessageQuotedPreviewThread` when it is a reply in a thread        |
| Direct message   | A message in a direct message, with the thread marker when it is a reply in a thread            |
| Archived channel | A message in an archived channel: an Archived [Badge](https://raft-design.botiverse.build/components/badge.md) after the channel name |
| With attachments | A message with files: a chip with the count stays visible under the 2 lines of text             |

```tsx
import { Hash, MessageSquare } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageQuotedPreview,
  MessageQuotedPreviewAttachment,
  MessageQuotedPreviewAttachments,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorMeta,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewChannel,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewThread,
  MessageQuotedPreviewTimestamp,
  MessageReferenceChip,
  MessageReferenceLabel,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";
import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

export type QuotedPreviewEmbeddedCompositionProps = {
  href?: string;
  ariaLabel?: string;
  author: {
    name: string;
    meta: string;
    type: "human" | "agent";
    avatarSrc?: string;
    fallback: string;
  };
  channel: string;
  thread?: boolean;
  timestamp: string;
  content: string;
  attachmentCount?: number;
};

const DEFAULT_QUOTED_PREVIEW_PROPS: QuotedPreviewEmbeddedCompositionProps = {
  href: MESSAGE_EXAMPLE_HREFS.stateModelTask,
  ariaLabel: "Open quoted message in the state-model thread",
  author: {
    name: "Developer",
    meta: "member",
    type: "human",
    avatarSrc: QUOTED_PREVIEW_AVATAR_SRC.developer,
    fallback: "D",
  },
  channel: "raft-ui-migration",
  thread: true,
  timestamp: "Today · 8:17 AM",
  content:
    "Should we migrate from Zustand to Jotai? Jotai's atom model might be better for our store structure.",
  attachmentCount: 1,
};

export function QuotedPreviewEmbeddedComposition(
  props: Partial<QuotedPreviewEmbeddedCompositionProps>,
) {
  const resolved = { ...DEFAULT_QUOTED_PREVIEW_PROPS, ...props };

  return (
    <MessageItemEmbedded>
      <MessageQuotedPreview
        render={
          resolved.href ? <a href={resolved.href} aria-label={resolved.ariaLabel} /> : undefined
        }
        data-testid="quoted-message-card"
      >
        <MessageQuotedPreviewHeader>
          <MessageQuotedPreviewAuthor>
            <Avatar size="xs" type={resolved.author.type}>
              {resolved.author.avatarSrc ? (
                <AvatarImage src={resolved.author.avatarSrc} alt="" />
              ) : null}
              <AvatarFallback>{resolved.author.fallback}</AvatarFallback>
            </Avatar>
            <MessageQuotedPreviewAuthorName>{resolved.author.name}</MessageQuotedPreviewAuthorName>
            <MessageQuotedPreviewAuthorMeta>{resolved.author.meta}</MessageQuotedPreviewAuthorMeta>
          </MessageQuotedPreviewAuthor>
          <MessageQuotedPreviewChannel>
            <Hash size={11} />
            {resolved.channel}
          </MessageQuotedPreviewChannel>
          {resolved.thread ? (
            <MessageQuotedPreviewThread>
              <MessageSquare size={10} />
              Thread
            </MessageQuotedPreviewThread>
          ) : null}
          <MessageQuotedPreviewTimestamp>{resolved.timestamp}</MessageQuotedPreviewTimestamp>
        </MessageQuotedPreviewHeader>
        <MessageQuotedPreviewContent>
          <MessageQuotedPreviewText>{resolved.content}</MessageQuotedPreviewText>
          {resolved.attachmentCount ? (
            <MessageQuotedPreviewAttachments>
              <MessageQuotedPreviewAttachment>
                {resolved.attachmentCount} {resolved.attachmentCount === 1 ? "file" : "files"}
              </MessageQuotedPreviewAttachment>
            </MessageQuotedPreviewAttachments>
          ) : null}
        </MessageQuotedPreviewContent>
      </MessageQuotedPreview>
    </MessageItemEmbedded>
  );
}

export function QuotedPreviewMessageComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="human">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.ada} alt="" />
          <AvatarFallback>A</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Ada</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:25 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceChip
            variant="link"
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open raft-ui migration message"
              />
            }
          >
            <Hash />
            <MessageReferenceLabel>raft-ui-migration</MessageReferenceLabel>
          </MessageReferenceChip>
        </MessageItemBody>
        <QuotedPreviewEmbeddedComposition />
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  DirectMessageIcon,
  MessageQuotedPreview,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewChannel,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewTimestamp,
  MessageReferenceChip,
  MessageReferenceLabel,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";
import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

export function QuotedPreviewDirectMessageComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="human">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.mai} alt="" />
          <AvatarFallback>M</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Mai</MessageItemSender>
          <MessageItemMeta>member</MessageItemMeta>
          <MessageItemTime>2:31 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceChip
            variant="link"
            render={
              <a href={MESSAGE_EXAMPLE_HREFS.noahDm} aria-label="Open Atlas direct message" />
            }
          >
            <DirectMessageIcon />
            <MessageReferenceLabel>Atlas</MessageReferenceLabel>
          </MessageReferenceChip>
        </MessageItemBody>
        <MessageItemEmbedded>
          <MessageQuotedPreview
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open quoted direct message"
              />
            }
          >
            <MessageQuotedPreviewHeader>
              <MessageQuotedPreviewAuthor>
                <Avatar size="xs" type="agent">
                  <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.atlas} alt="" />
                  <AvatarFallback>AT</AvatarFallback>
                </Avatar>
                <MessageQuotedPreviewAuthorName>Atlas</MessageQuotedPreviewAuthorName>
              </MessageQuotedPreviewAuthor>
              <MessageQuotedPreviewChannel>@Atlas</MessageQuotedPreviewChannel>
              <MessageQuotedPreviewTimestamp>Today · 8:24 AM</MessageQuotedPreviewTimestamp>
            </MessageQuotedPreviewHeader>
            <MessageQuotedPreviewContent>
              <MessageQuotedPreviewText>
                I&apos;ll take the deployment handoff and post a summary when it completes.
              </MessageQuotedPreviewText>
            </MessageQuotedPreviewContent>
          </MessageQuotedPreview>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { MessageSquare } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  DirectMessageIcon,
  MessageQuotedPreview,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewChannel,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewThread,
  MessageQuotedPreviewTimestamp,
  MessageReferenceChip,
  MessageReferenceLabel,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";
import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

export function QuotedPreviewDirectMessageThreadComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="human">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.mai} alt="" />
          <AvatarFallback>M</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Mai</MessageItemSender>
          <MessageItemMeta>member</MessageItemMeta>
          <MessageItemTime>2:34 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceChip
            variant="link"
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.noahDmThread}
                aria-label="Open Atlas direct-message thread"
              />
            }
          >
            <DirectMessageIcon />
            <MessageReferenceLabel>Atlas</MessageReferenceLabel>
          </MessageReferenceChip>
        </MessageItemBody>
        <MessageItemEmbedded>
          <MessageQuotedPreview
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.noahDmThread}
                aria-label="Open quoted DM thread reply"
              />
            }
          >
            <MessageQuotedPreviewHeader>
              <MessageQuotedPreviewAuthor>
                <Avatar size="xs" type="agent">
                  <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.atlas} alt="" />
                  <AvatarFallback>AT</AvatarFallback>
                </Avatar>
                <MessageQuotedPreviewAuthorName>Atlas</MessageQuotedPreviewAuthorName>
              </MessageQuotedPreviewAuthor>
              <MessageQuotedPreviewChannel>@Atlas</MessageQuotedPreviewChannel>
              <MessageQuotedPreviewThread>
                <MessageSquare size={10} />
                Thread
              </MessageQuotedPreviewThread>
              <MessageQuotedPreviewTimestamp>Today · 8:29 AM</MessageQuotedPreviewTimestamp>
            </MessageQuotedPreviewHeader>
            <MessageQuotedPreviewContent>
              <MessageQuotedPreviewText>
                I&apos;ll post the follow-up in this conversation once the handoff is complete.
              </MessageQuotedPreviewText>
            </MessageQuotedPreviewContent>
          </MessageQuotedPreview>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { Hash } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  Badge,
  MessageQuotedPreview,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewChannel,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewTimestamp,
  MessageReferenceChip,
  MessageReferenceLabel,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";
import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

export function QuotedPreviewArchivedChannelComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="human">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.ravi} alt="" />
          <AvatarFallback>R</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Ravi</MessageItemSender>
          <MessageItemMeta>member</MessageItemMeta>
          <MessageItemTime>2:41 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceChip
            variant="link"
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open legacy migration message"
              />
            }
          >
            <Hash />
            <MessageReferenceLabel>legacy-migration</MessageReferenceLabel>
          </MessageReferenceChip>
        </MessageItemBody>
        <MessageItemEmbedded>
          <MessageQuotedPreview
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open quoted archived channel message"
              />
            }
          >
            <MessageQuotedPreviewHeader>
              <MessageQuotedPreviewAuthor>
                <Avatar size="xs" type="human">
                  <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.kai} alt="" />
                  <AvatarFallback>K</AvatarFallback>
                </Avatar>
                <MessageQuotedPreviewAuthorName>Kai</MessageQuotedPreviewAuthorName>
              </MessageQuotedPreviewAuthor>
              <MessageQuotedPreviewChannel>
                <Hash size={11} />
                legacy-migration
              </MessageQuotedPreviewChannel>
              <Badge appearance="soft" variant="warning">
                Archived
              </Badge>
              <MessageQuotedPreviewTimestamp>May 4 · 3:16 PM</MessageQuotedPreviewTimestamp>
            </MessageQuotedPreviewHeader>
            <MessageQuotedPreviewContent>
              <MessageQuotedPreviewText>
                Keep this decision linked for historical context, but continue the work in the
                active channel.
              </MessageQuotedPreviewText>
            </MessageQuotedPreviewContent>
          </MessageQuotedPreview>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { Hash } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageQuotedPreview,
  MessageQuotedPreviewAttachment,
  MessageQuotedPreviewAttachments,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewChannel,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewTimestamp,
  MessageReferenceChip,
  MessageReferenceLabel,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";
import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

export function QuotedPreviewLongContentComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="human">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.ravi} alt="" />
          <AvatarFallback>R</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Ravi</MessageItemSender>
          <MessageItemMeta>member</MessageItemMeta>
          <MessageItemTime>2:38 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceChip
            variant="link"
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open platform migration message"
              />
            }
          >
            <Hash />
            <MessageReferenceLabel>platform-migration</MessageReferenceLabel>
          </MessageReferenceChip>
        </MessageItemBody>
        <MessageItemEmbedded>
          <MessageQuotedPreview
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open quoted migration plan"
              />
            }
          >
            <MessageQuotedPreviewHeader>
              <MessageQuotedPreviewAuthor>
                <Avatar size="xs" type="human">
                  <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.kai} alt="" />
                  <AvatarFallback>K</AvatarFallback>
                </Avatar>
                <MessageQuotedPreviewAuthorName>Kai</MessageQuotedPreviewAuthorName>
              </MessageQuotedPreviewAuthor>
              <MessageQuotedPreviewChannel>
                <Hash size={11} />
                platform-migration
              </MessageQuotedPreviewChannel>
              <MessageQuotedPreviewTimestamp>Yesterday · 4:46 PM</MessageQuotedPreviewTimestamp>
            </MessageQuotedPreviewHeader>
            <MessageQuotedPreviewContent>
              <MessageQuotedPreviewText>
                Before we cut over, keep the read model running against both event streams, validate
                account and workspace permissions in the staging environment, and only then move the
                webhook consumers to the new endpoint.
              </MessageQuotedPreviewText>
              <MessageQuotedPreviewAttachments>
                <MessageQuotedPreviewAttachment>2 attachments</MessageQuotedPreviewAttachment>
              </MessageQuotedPreviewAttachments>
            </MessageQuotedPreviewContent>
          </MessageQuotedPreview>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

## States

| State       | Look                                                                                                                                               |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Loading     | `MessageQuotedPreviewLoading`, pulsing, with a [Skeleton](https://raft-design.botiverse.build/components/skeleton.md) of the header and 2 lines of text                               |
| Unavailable | `MessageQuotedPreviewUnavailable`: the bar and one line in `foreground-hint` that says why, when the message is deleted or the person can't see it |

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewLoading,
  MessageReferenceChip,
  MessageReferenceLabel,
  Skeleton,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";
import { Hash } from "lucide-react";

import { MESSAGE_EXAMPLE_HREFS } from "#/modules/message-item/message-item.fixtures.ts";
import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

function QuotedPreviewLoadingSkeleton() {
  return (
    <>
      <MessageQuotedPreviewHeader>
        <Skeleton variant="circle" className="size-4 bg-fill-strong animate-none" />
        <Skeleton variant="line" className="h-2.5 w-16 bg-fill-strong animate-none" />
        <Skeleton variant="line" className="h-2.5 w-24 bg-fill-strong animate-none" />
        <Skeleton variant="line" className="ml-auto h-2.5 w-20 bg-fill-strong animate-none" />
      </MessageQuotedPreviewHeader>
      <MessageQuotedPreviewContent className="stack-y-1.5">
        <Skeleton variant="line" className="h-2.5 w-full bg-fill-strong animate-none" />
        <Skeleton variant="line" className="h-2.5 w-3/4 bg-fill-strong animate-none" />
      </MessageQuotedPreviewContent>
    </>
  );
}

export function QuotedPreviewLoadingEmbeddedComposition({
  label = "Loading quoted message",
}: {
  label?: string;
} = {}) {
  return (
    <MessageItemEmbedded>
      <MessageQuotedPreviewLoading
        aria-label={label}
        aria-busy
        data-testid="quoted-message-card-loading"
      >
        <QuotedPreviewLoadingSkeleton />
      </MessageQuotedPreviewLoading>
    </MessageItemEmbedded>
  );
}

export function QuotedPreviewLoadingComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.loading} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:26 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceChip
            variant="link"
            render={
              <a
                href={MESSAGE_EXAMPLE_HREFS.stateModelTask}
                aria-label="Open raft-ui migration message"
              />
            }
          >
            <Hash />
            <MessageReferenceLabel>raft-ui-migration</MessageReferenceLabel>
          </MessageReferenceChip>
        </MessageItemBody>
        <QuotedPreviewLoadingEmbeddedComposition />
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageQuotedPreviewUnavailable,
  MessageReferenceUnavailable,
} from "@botiverse/raft-ui";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";
import { CircleOff } from "lucide-react";

import { QUOTED_PREVIEW_AVATAR_SRC } from "../message-quoted-preview.fixtures.ts";

export function QuotedPreviewUnavailableEmbeddedComposition({
  reason = "Message unavailable",
}: {
  reason?: string;
} = {}) {
  return (
    <MessageItemEmbedded>
      <MessageQuotedPreviewUnavailable data-testid="quoted-message-card-unavailable">
        {reason}
      </MessageQuotedPreviewUnavailable>
    </MessageItemEmbedded>
  );
}

export function QuotedPreviewUnavailableComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={QUOTED_PREVIEW_AVATAR_SRC.unavailable} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:27 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>
          <MessageReferenceUnavailable>
            <CircleOff size={12} />
            Message unavailable
          </MessageReferenceUnavailable>
        </MessageItemBody>
        <QuotedPreviewUnavailableEmbeddedComposition />
      </MessageItemContent>
    </MessageItem>
  );
}
```

## Behavior

- The preview fills the width of the message column.
- Text past 2 lines is cut with an ellipsis; the attachment chips stay below it.
- "(sent by Friday)" never truncates; the author's name truncates instead. When the author and the label fill the line, as on a phone, the place and the time move to a second line: the words are what says the author didn't write it, so they can't be cut, and the face that says it in the timeline is too small to show on a 19px avatar ([Identity and presence](https://raft-design.botiverse.build/people-and-agents/identity-and-presence.md#sent-by-an-assistant)).

```tsx
import { Hash } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemSender,
  MessageItemTime,
  MessageQuotedPreview,
  MessageQuotedPreviewAuthor,
  MessageQuotedPreviewAuthorName,
  MessageQuotedPreviewChannel,
  MessageQuotedPreviewContent,
  MessageQuotedPreviewHeader,
  MessageQuotedPreviewText,
  MessageQuotedPreviewTimestamp,
} from "@botiverse/raft-ui";
import { MessageQuotedPreviewAuthorDelegatedBy } from "@botiverse/raft-ui/wip";

export function QuotedPreviewDelegatedContent() {
  return (
    <div className="flex w-full max-w-3xl flex-col">
      <MessageItem>
        <MessageItemAvatarSlot>
          <Avatar size="md" type="human">
            <AvatarFallback>M</AvatarFallback>
          </Avatar>
        </MessageItemAvatarSlot>
        <MessageItemContent>
          <MessageItemHeader>
            <MessageItemSender>Martin</MessageItemSender>
            <MessageItemTime dateTime="2026-10-21T10:50:00">10:50</MessageItemTime>
          </MessageItemHeader>
          <MessageItemBody>Reviewing now.</MessageItemBody>
          <MessageItemEmbedded>
            <MessageQuotedPreview>
              <MessageQuotedPreviewHeader>
                <MessageQuotedPreviewAuthor>
                  <Avatar size="xs" type="human">
                    <AvatarFallback>A</AvatarFallback>
                  </Avatar>
                  <MessageQuotedPreviewAuthorName title="Alexandra Konstantinopoulou">
                    Alexandra Konstantinopoulou
                  </MessageQuotedPreviewAuthorName>
                  <MessageQuotedPreviewAuthorDelegatedBy>
                    (sent by Friday)
                  </MessageQuotedPreviewAuthorDelegatedBy>
                </MessageQuotedPreviewAuthor>
                <MessageQuotedPreviewChannel>
                  <Hash aria-hidden />
                  launch-week
                </MessageQuotedPreviewChannel>
                <MessageQuotedPreviewTimestamp>Today · 10:42</MessageQuotedPreviewTimestamp>
              </MessageQuotedPreviewHeader>
              <MessageQuotedPreviewContent>
                <MessageQuotedPreviewText>
                  The launch checklist is ready for review: two items left, the Chinese privacy
                  terms and the SSO callback list.
                </MessageQuotedPreviewText>
              </MessageQuotedPreviewContent>
            </MessageQuotedPreview>
          </MessageItemEmbedded>
        </MessageItemContent>
      </MessageItem>
    </div>
  );
}
```

- Selecting the preview opens the quoted message.

## Content

- Name a channel with its hash: "#launch-week".
- Name a direct message by the other person's display name, with `DirectMessageIcon` in place of the hash.
- Label the thread marker Thread.
- Write the time in Space Mono, with the day: "Today · 8:24 AM", "May 4 · 3:16 PM".
- Give the number of attachments in the chip: "2 files".
- Say why the message can't be shown: "Message unavailable" ([Feedback](https://raft-design.botiverse.build/patterns/feedback.md#empty-loading-and-failed)).

## Accessibility

- The parts are plain elements built with Base UI `useRender`; `MessageQuotedPreview` is a `div`.
- When the preview opens the message, render it as an `<a>` with an `aria-label` that names the message and where it is: "Open quoted message in #launch-week". It shows the focus ring on keyboard focus.
- Give the author's avatar an empty `alt`: the name beside it says who it is.
- Give `MessageQuotedPreviewLoading` `aria-busy` and an `aria-label`, such as "Loading quoted message".
- Don't render `MessageQuotedPreviewUnavailable` as a link: there is nothing to open.

## Rules

- Render only the parts the quoted message has data for: leave out the thread marker, the role and the attachments when there are none.
- Give the loading skeleton the shape of the preview, so nothing jumps when it loads ([Feedback](https://raft-design.botiverse.build/patterns/feedback.md#empty-loading-and-failed)).

## API

### MessageQuotedPreview

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewAttachments

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewAttachment

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewAuthor

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewAuthorMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewAuthorName

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewChannel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewLoading

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewText

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewThread

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewTimestamp

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewUnavailable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageQuotedPreviewAuthorDelegatedBy

"(sent by Friday)" after `MessageQuotedPreviewAuthorName`, when an assistant sent the quoted message for its author. It never truncates; the author's name gives way instead.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.12.0 (Added): A message an assistant sent for its owner, from `@botiverse/raft-ui/wip`: `MessageItemDelegatedBy`, `MessageItemDelegatedByName`, `MessageItemDelegatedByButton`, `MessageItemDelegatedAvatar` (with `deleted`) and `MessageQuotedPreviewAuthorDelegatedBy`.
