npm package: @lg-chat/message
Description: lg-chat Message
Latest version: 12.0.0

# Message

## Installation

### PNPM

```shell
pnpm add @lg-chat/message
```

### Yarn

```shell
yarn add @lg-chat/message
```

### NPM

```shell
npm install @lg-chat/message
```

## Example

```tsx
import { Message } from '@lg-chat/message';

return (
  <div>
    <Message messageBody="Question" />
    <Message isSender={false} messageBody="Answer" />
  </div>
);
```

### Compound Components

The `Message` component uses a compound component pattern, allowing you to compose different parts of a message using subcomponents like `Message.Actions`, `Message.Links`, `Message.Promotion`, `Message.ActionCard`, and `Message.VerifiedBanner`.

**Note:** The layout and order of compound components are enforced by the `Message` component itself. Even if you change the order of subcomponents in your JSX, they will be rendered in the correct, intended order within the message bubble. This ensures consistent UI and accessibility regardless of how you compose your message children.

#### Message.Actions

```tsx
import React from 'react';
import { Message } from '@lg-chat/message';
import { MessageRatingValue } from '@lg-chat/message-rating';

const MessageWithActions = () => {
  const handleCopy = () => console.log('Message copied');
  const handleRetry = () => console.log('Message retried');

  const handleRatingChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    options: { rating: MessageRatingValue },
  ) => {
    console.log('Rating changed:', options.rating);
  };

  const handleSubmitFeedback = async (
    e: React.FormEvent<HTMLFormElement>,
    options: { rating: MessageRatingValue; feedback?: string },
  ) => {
    console.log('Feedback submitted:', options);
  };

  return (
    <Message messageBody="Test message">
      <Message.Actions
        onClickCopy={handleCopy}
        onClickRetry={handleRetry}
        onRatingChange={handleRatingChange}
        onSubmitFeedback={handleSubmitFeedback}
      />
    </Message>
  );
};
```

### Message.Links

```tsx
import React from 'react';
import { Message } from '@lg-chat/message';

const MessageWithLinks = () => {
  const links = [
    {
      href: 'https://mongodb.design',
      children: 'LeafyGreen UI',
      variant: 'Website',
    },
    {
      href: 'https://mongodb.github.io/leafygreen-ui/?path=/docs/overview-introduction--docs',
      children: 'LeafyGreen UI Docs',
      variant: 'Docs',
    },
    {
      href: 'https://learn.mongodb.com/',
      children: 'MongoDB University',
      variant: 'Learn',
    },
  ];

  const handleLinkClick = () => console.log('Link clicked');

  return (
    <Message isSender={false} messageBody="Test message">
      <Message.Links
        links={links}
        onLinkClick={handleLinkClick}
        headingText="Related Resources"
      />
    </Message>
  );
};
```

### Message.Promotion

```tsx
import React from 'react';
import { Message } from '@lg-chat/message';

const MessageWithPromotion = () => {
  const handlePromotionClick = () => console.log('Promotion clicked');

  return (
    <Message isSender={false} messageBody="Test message">
      <Message.Promotion
        promotionText="Go learn more about this skill!"
        promotionUrl="https://learn.mongodb.com/skills"
        onPromotionLinkClick={handlePromotionClick}
      />
    </Message>
  );
};
```

### Message.ActionCard

```tsx
import React from 'react';
import { Message, ActionCardState } from '@lg-chat/message';
import DatabaseIcon from '@leafygreen-ui/icon/dist/Database';

const MessageWithActionCard = () => {
  const handleCancel = () => console.log('Cancel clicked');
  const handleRun = () => console.log('Run clicked');
  const handleToggleExpanded = (isOpen: boolean) => {
    console.log('Expanded state:', isOpen);
  };

  return (
    <Message isSender={false} messageBody="Test message">
      <Message.ActionCard
        chips={[{ label: 'MongoDB', glyph: <DatabaseIcon /> }]}
        initialIsExpanded={false}
        onToggleExpanded={handleToggleExpanded}
        showExpandButton={true}
        state={ActionCardState.Idle}
        title="Run list-databases?"
      >
        <Message.ActionCard.ExpandableContent>
          {`# Tool Execution Result

This is a markdown content example showing tool execution results.

\`\`\`javascript
const result = await tool.execute();
console.log(result);
\`\`\``}
        </Message.ActionCard.ExpandableContent>
        <Message.ActionCard.Button onClick={handleCancel}>
          Cancel
        </Message.ActionCard.Button>
        <Message.ActionCard.Button onClick={handleRun} variant="primary">
          Run
        </Message.ActionCard.Button>
      </Message.ActionCard>
    </Message>
  );
};
```

### Message.VerifiedBanner

```tsx
import React from 'react';
import { Message } from '@lg-chat/message';

const MessageWithVerifiedBanner = () => {
  return (
    <Message isSender={false} messageBody="Test message">
      <Message.VerifiedBanner
        verifier="MongoDB Staff"
        verifiedAt={new Date('2024-03-24T16:20:00Z')}
        learnMoreUrl="https://mongodb.com/docs"
      />
    </Message>
  );
};
```

### Complete Example with All Subcomponents

```tsx
import React from 'react';
import { Message, ActionCardState } from '@lg-chat/message';
import { MessageRatingValue } from '@lg-chat/message-rating';

const Example = () => {
  const handleCopy = () => console.log('Message copied');
  const handleRetry = () => console.log('Message retried');

  const handleRatingChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    options: { rating: MessageRatingValue },
  ) => {
    console.log('Rating changed:', options.rating);
  };

  const handleSubmitFeedback = async (
    e: React.FormEvent<HTMLFormElement>,
    options: { rating: MessageRatingValue; feedback?: string },
  ) => {
    console.log('Feedback submitted:', options);
  };

  const links = [
    {
      href: 'https://mongodb.design',
      children: 'LeafyGreen UI',
      variant: 'Website',
    },
    {
      href: 'https://mongodb.com/docs',
      children: 'MongoDB Docs',
      variant: 'Docs',
    },
  ];

  const handleLinkClick = () => console.log('Link clicked');
  const handlePromotionClick = () => console.log('Promotion clicked');

  const handleCancel = () => console.log('Cancel clicked');
  const handleRun = () => console.log('Run clicked');

  return (
    <Message isSender={false} messageBody="Test message">
      <Message.ActionCard
        title="Run list-databases?"
        state={ActionCardState.Idle}
      >
        <Message.ActionCard.ExpandableContent>
          {`# Tool Execution Result

This is a markdown content example showing tool execution results.`}
        </Message.ActionCard.ExpandableContent>
        <Message.ActionCard.Button onClick={handleCancel}>
          Cancel
        </Message.ActionCard.Button>
        <Message.ActionCard.Button onClick={handleRun} variant="primary">
          Run
        </Message.ActionCard.Button>
      </Message.ActionCard>
      <Message.Promotion
        promotionText="Go learn more about this skill!"
        promotionUrl="https://learn.mongodb.com/skills"
        onPromotionLinkClick={handlePromotionClick}
      />
      <Message.Actions
        onClickCopy={handleCopy}
        onClickRetry={handleRetry}
        onRatingChange={handleRatingChange}
        onSubmitFeedback={handleSubmitFeedback}
      />
      <Message.VerifiedBanner
        verifier="MongoDB Staff"
        verifiedAt={new Date('2024-03-24T16:20:00Z')}
        learnMoreUrl="https://mongodb.com/docs"
      />
      <Message.Links links={links} onLinkClick={handleLinkClick} />
    </Message>
  );
};
```

## Properties

### Message

| Prop            | Type                                                                                                | Description                                         | Default |
| --------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ------- |
| `isSender`      | `boolean`                                                                                           | Indicates if the message is from the current user   | `true`  |
| `markdownProps` | [`LGMarkdownProps`](https://github.com/mongodb/leafygreen-ui/tree/main/chat/lg-markdown#properties) | Props passed to the internal ReactMarkdown instance |         |
| `messageBody`   | `string`                                                                                            | Message body text passed to LGMarkdown              |         |
| `sourceType`    | `'markdown' \| 'text'`                                                                              | Determines the rendering method of the message      |         |
| `...`           | `HTMLElementProps<'div'>`                                                                           | Props spread on the root element                    |         |

### Message.Actions

| Prop                            | Type                                                                                                  | Description                                                                                                                                                                                        | Default                       |
| ------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `errorMessage` _(optional)_     | `ReactNode`                                                                                           | Optional error message to display when feedback submission fails.                                                                                                                                  | `'Oops, please try again.'`   |
| `onClickCopy` _(optional)_      | `MouseEventHandler<HTMLButtonElement>`                                                                | Callback fired when the copy button is clicked.                                                                                                                                                    |                               |
| `onClickRetry` _(optional)_     | `MouseEventHandler<HTMLButtonElement>`                                                                | Callback fired when the retry button is clicked. If not provided, the retry button will not be rendered                                                                                            |                               |
| `onCloseFeedback` _(optional)_  | `MouseEventHandler<HTMLButtonElement>`                                                                | Callback fired when the feedback form is closed by clicking the close button                                                                                                                       |                               |
| `onRatingChange` _(optional)_   | `(e: ChangeEvent<HTMLInputElement>, options: { rating: MessageRatingValue }) => void`                 | Callback fired when the user clicks the like or dislike button. Receives the original change event and an options object with the rating. If not provided, the rating buttons will not be rendered |                               |
| `onSubmitFeedback` _(optional)_ | `(e: FormEvent<HTMLFormElement>, options: { rating: MessageRatingValue; feedback?: string }) => void` | Callback when the user submits the feedback form. Receives the original form event, plus an options object with rating and feedback. If not provided, the feedback form will not be rendered       |                               |
| `submitButtonText` _(optional)_ | `string`                                                                                              | Optional text for the feedback form's submit button                                                                                                                                                | `'Submit'`                    |
| `submittedMessage` _(optional)_ | `ReactNode`                                                                                           | Optional success message to display after feedback is submitted.                                                                                                                                   | `'Thanks for your feedback!'` |

### Message.Links

| Prop                       | Type                             | Description                                                  | Default               |
| -------------------------- | -------------------------------- | ------------------------------------------------------------ | --------------------- |
| `headingText` _(optional)_ | `string`                         | The text to display as the heading of the links section.     | `'Related Resources'` |
| `links`                    | `Array<RichLinkProps>`           | An array of link data to render in the links section.        |                       |
| `onLinkClick` _(optional)_ | `({ children: string }) => void` | A callback function that is called when any link is clicked. |                       |
| `...`                      | `HTMLElementProps<'div'>`        | Props spread on the root element                             |                       |

### Message.Promotion

| Prop                                | Type                      | Description                              | Default |
| ----------------------------------- | ------------------------- | ---------------------------------------- | ------- |
| `promotionText`                     | `string`                  | Promotion text content.                  |         |
| `promotionUrl`                      | `string`                  | Promotion URL for the "Learn More" link. |         |
| `onPromotionLinkClick` _(optional)_ | `() => void`              | Promotion onClick callback handler.      |         |
| `...`                               | `HTMLElementProps<'div'>` | Props spread on the root element         |         |

### Message.ActionCard

| Prop                             | Type                                                                                              | Description                                                                                                                                                                                                                | Default |
| -------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `chips` _(optional)_             | `Array<{ label: ReactNode; glyph?: ReactNode; formatTooltip?: (label: ReactNode) => ReactNode }>` | Metadata chips displayed in the header. Each chip supports `label`, optional `glyph` icon, and optional `formatTooltip` function to customize tooltip content. Tooltips are always shown on hover for chips in ActionCard. | `[]`    |
| `darkMode` _(optional)_          | `boolean`                                                                                         | Determines if the component will render in dark mode                                                                                                                                                                       | `false` |
| `description` _(optional)_       | `ReactNode`                                                                                       | Optional description text displayed below the title row in the Header.                                                                                                                                                     |         |
| `initialIsExpanded` _(optional)_ | `boolean`                                                                                         | Initial state of the expandable section. Ignored when `showExpandButton` is `false`.                                                                                                                                       | `false` |
| `onToggleExpanded` _(optional)_  | `(isOpen: boolean) => void`                                                                       | Callback fired when the expansion toggle is clicked. Receives the new open state as a parameter.                                                                                                                           |         |
| `showExpandButton` _(optional)_  | `boolean`                                                                                         | Whether the toggle button is visible.                                                                                                                                                                                      | `true`  |
| `state`                          | `'idle' \| 'running' \| 'success' \| 'error' \| 'canceled'`                                       | The current lifecycle state of the interaction. Can use string literals or import `ActionCardState` enum from `@lg-chat/message`.                                                                                          |         |
| `title`                          | `ReactNode`                                                                                       | Primary label displayed in the Header.                                                                                                                                                                                     |         |
| `...`                            | `HTMLElementProps<'div'>`                                                                         | Props spread on the container div element                                                                                                                                                                                  |         |

#### Message.ActionCard.Button

`Message.ActionCard.Button` accepts all props from [`@leafygreen-ui/button`](../../packages/button/README.md#properties), except for the `size` prop which cannot be configured and is always set to `'small'`.

#### Message.ActionCard.ExpandableContent

| Prop       | Type                      | Description                                                                  | Default |
| ---------- | ------------------------- | ---------------------------------------------------------------------------- | ------- |
| `children` | `string`                  | Markdown content to render in the expandable content area. Must be a string. |         |
| `...`      | `HTMLElementProps<'div'>` | Props spread on the container div element                                    |         |

### Message.VerifiedBanner

| Prop                        | Type     | Description                                       | Default |
| --------------------------- | -------- | ------------------------------------------------- | ------- |
| `learnMoreUrl` _(optional)_ | `string` | URL to learn more about the verification.         |         |
| `verifiedAt` _(optional)_   | `Date`   | The time the message was last verified.           |         |
| `verifier` _(optional)_     | `string` | The name of the entity that verified the message. |         |

## Behavior

### Message.Actions

The `Message.Actions` component provides a comprehensive set of actions for chat messages.

#### Rendering Behavior

- If only some optional props are provided, only those corresponding buttons/functionality are rendered:
  - Copy Button: Always renders
  - Retry Button: Renders when `onClickRetry` is provided
  - Rating Buttons: Render when `onRatingChange` is provided
  - Feedback Form: Shows when a rating is selected and `onSubmitFeedback` is provided

#### Context Integration

The component relies on `MessageContext` to access the message body for copy functionality.

#### Rating and Feedback Flow

1. Rating Only: User clicks thumbs up or thumbs down (only visible when `onRatingChange` is provided)

   - Calls `onRatingChange` with the selected rating
   - No feedback form appears

2. Rating + Feedback: User clicks thumbs up or thumbs down, then feedback form appears
   - Calls `onRatingChange` with the selected rating
   - Feedback form appears with a textarea for additional comments
   - User can optionally provide feedback text
   - User clicks submit to send feedback
   - Form shows success message and calls `onSubmitFeedback` with rating and feedback

#### State Management

The component manages its own internal state for:

- Message rating: `'unselected'` | `'disliked'` | `'liked'`
- Feedback text input
- Form state: `'unset'` | `'submitting'` | `'submitted'` | `'error'`

Form state is reset when the feedback form is closed or when a new rating is selected.

### Message.Links

The `Message.Links` component provides an expandable/collapsible section for displaying related links.

#### Rendering Behavior

- If the `links` array is empty, the component returns `null` and does not render anything
- Links are displayed in an expandable section with a chevron toggle button
- The section starts collapsed by default and can be expanded to show all links
- The link objects provided to the `Message.Links` component's `links` prop can leverage the following `variant` values which map to pre-defined badge glyphs, labels, and colors.
  - `"Blog"`
  - `"Book"`
  - `"Code"`
  - `"Docs"`
  - `"Learn"`
  - `"Video"`
  - `"Website"`

#### State Management

The component manages its own internal state for:

- Expansion state: Controls whether the links section is expanded or collapsed

### Message.Promotion

The `Message.Promotion` component displays promotional content with an award icon and "Learn More" link.

### Message.ActionCard

The `Message.ActionCard` component displays actionable, stateful cards with expandable content and buttons.

#### Rendering Behavior

- Buttons only render when `state="idle"`
- Multiple `Message.ActionCard.Button` components can be used to render buttons

#### State Management

The component manages its own internal state for:

- Expansion state: Controls whether the expandable content section is expanded or collapsed
- The expansion state can be configured via `initialIsExpanded` and `onToggleExpanded` props

### Message.VerifiedBanner

The `Message.VerifiedBanner` component displays verification information for messages.

#### Rendering Behavior

- If no verification props (`verifier`, `verifiedAt`, `learnMoreUrl`) are provided, basic "Verified" text is displayed
- The banner automatically formats the verification date using `toLocaleDateString()`
- "Learn More" link is displayed when optional `learnMoreUrl` is provided