diff --git a/docs.json b/docs.json index daec3b436..7525187b6 100644 --- a/docs.json +++ b/docs.json @@ -710,6 +710,7 @@ "ui-kit/react/v6/call-logs", "ui-kit/react/v6/search", "ui-kit/react/v6/ai-assistant-chat", + "ui-kit/react/v6/card-bubble", "ui-kit/react/v6/notification-feed" ] }, @@ -731,6 +732,7 @@ "ui-kit/react/v6/guide-search-messages", "ui-kit/react/v6/guide-call-log-details", "ui-kit/react/v6/guide-group-chat", + "ui-kit/react/v6/card-messages", "ui-kit/react/v6/custom-text-formatter-guide", "ui-kit/react/v6/mentions-formatter-guide", "ui-kit/react/v6/url-formatter-guide", diff --git a/ui-kit/react/v6/card-bubble.mdx b/ui-kit/react/v6/card-bubble.mdx new file mode 100644 index 000000000..501aac081 --- /dev/null +++ b/ui-kit/react/v6/card-bubble.mdx @@ -0,0 +1,198 @@ +--- +title: "Card Bubble" +description: "React component that renders a developer Card Message inside a message bubble using the prebuilt CometChat Cards renderer, with pure-forwarded card actions." +--- + + +```json +{ + "component": "CometChatCardBubble", + "kind": "content-view-component", + "package": "@cometchat/chat-uikit-react", + "import": "import { CometChatCardBubble } from \"@cometchat/chat-uikit-react\";", + "renderer": "@cometchat/cards-react (CometChatCardView)", + "description": "Render-only bubble for developer Card Messages (category: \"card\"). Serializes the raw card payload verbatim and hands it to the prebuilt Cards renderer. Forwards card actions untouched — implements no action behavior.", + "usage": "Rendered automatically by CometChatMessageList for any message with category \"card\". Instantiate directly only in a fully custom message renderer.", + "props": { + "message": { "type": "CometChat.BaseMessage", "required": false, "description": "The card message. Raw payload is read from getCard() → data.card." }, + "cardJson": { "type": "string", "default": "derived from message", "description": "Raw card JSON to render directly. Used for nested agent-card blocks." }, + "isSentByMe": { "type": "boolean", "default": "true", "description": "Toggles outgoing/incoming styling." }, + "onCardAction": { "type": "(message, action) => void", "default": "undefined", "description": "Optional per-instance action callback. The bubble also always emits ccCardActionClicked on the UI event bus." } + }, + "eventBus": "CometChatUIEvents.ccCardActionClicked", + "relatedComponents": ["CometChatMessageList", "CometChatMessageTemplate", "CometChatAIAssistantChat"], + "cssRootClass": "cometchat-card-bubble" +} +``` + + +The `CometChatCardBubble` component renders a **developer Card Message** (`message.getCategory() === "card"`) as a card bubble inside a conversation. It is the card equivalent of the text bubble: the surrounding [`CometChatMessageList`](/ui-kit/react/v6/message-list) bubble supplies the container, receipts, reactions, options, reply and thread view — this component only replaces the **content view** with the rendered card. + +## Overview + +The UI Kit is a **render-only** consumer of cards: it draws cards delivered by the SDK and forwards card actions to your app. It never parses or mutates the card body, and never sends or creates cards. When a card message arrives, the UI Kit routes it to this bubble automatically. + +The component follows a strict **render-only contract**: + +- **Render-only** — the raw card payload from `message.getCard()` (or the raw `data.card`) is serialized verbatim and handed to the prebuilt `CometChatCardView` renderer (from [`@cometchat/cards-react`](https://www.npmjs.com/package/@cometchat/cards-react)) as a `cardJson` string. The UI Kit performs **zero transformation** of the payload. +- **No behavior** — the bubble runs no action logic of its own. When a user taps an interactive element, the renderer's raw action is **pure-forwarded** on two channels (see [Card Actions](#card-actions)); your app owns all behavior. +- **Graceful fallback** — when the payload is empty or invalid, a single-line fallback text is shown instead of an empty bubble (see [Fallback Behavior](#fallback-behavior)). + + + Card rendering (layout, theming, interactive elements) is owned by the prebuilt `@cometchat/cards-react` renderer library, **not** by the UI Kit. The UI Kit is responsible only for delivering the payload to the renderer and forwarding actions back out. + + +## Automatic Rendering + +In the standard chat flow you do **not** instantiate this component yourself. [`CometChatMessageList`](/ui-kit/react/v6/message-list) routes any message with category `"card"` to `CometChatCardBubble` automatically, keyed by **category** (the developer `type` is arbitrary). To handle card actions in this flow, subscribe to the [`ccCardActionClicked`](#card-actions) event bus — no component wiring is required. + +Use the component directly only when you are building a fully custom message renderer. + +## Basic Usage + +```tsx +import { CometChatCardBubble } from "@cometchat/chat-uikit-react"; + +function CardMessage({ cardMessage }: { cardMessage: CometChat.BaseMessage }) { + return ; +} +``` + +### Incoming vs Outgoing Messages + +`isSentByMe` toggles the outgoing (sender) vs incoming (receiver) styling, mirroring the text bubble. + +```tsx +import { CometChatCardBubble } from "@cometchat/chat-uikit-react"; + +function CardList({ + incomingCard, + outgoingCard, +}: { + incomingCard: CometChat.BaseMessage; + outgoingCard: CometChat.BaseMessage; +}) { + return ( + <> + {/* Incoming card (left-aligned) */} + + + {/* Outgoing card (right-aligned) */} + + + ); +} +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `message` | `CometChat.BaseMessage` | — | The card message to render. The raw payload is read from `message.getCard()`, falling back to `message.getData().card`. Used both to draw the card and to tag forwarded actions. | +| `cardJson` | `string` | derived from `message` | Raw card JSON to render directly. When omitted, it is derived from `message`. Used for nested agent-card blocks where the payload is already extracted. | +| `isSentByMe` | `boolean` | `true` | Toggles outgoing/incoming styling. `false` for incoming/receiver messages, `true` for outgoing/sender messages. | +| `onCardAction` | `(message: CometChat.BaseMessage \| undefined, action: any) => void` | `undefined` | Optional per-instance action callback for apps that render this bubble directly. Carries the message and the renderer's raw action. | + + + The renderer theme is resolved automatically from `CometChatUIKit.themeMode` — there is no `themeMode` prop. To restyle the rendered card, theme the `@cometchat/cards-react` renderer directly. + + +## Card Actions + +The Cards renderer is a **pure renderer** — it emits actions through a callback without executing them. The bubble forwards each action on **both** channels and runs no behavior of its own: + +1. **`onCardAction` prop** — for apps that render this bubble directly. +2. **`CometChatUIEvents.ccCardActionClicked` event bus** — for the standard, internally rendered flow where the bubble is created by the UI Kit. This is the recommended channel for the default message list. + + + Act on **one** channel only to avoid double-handling the same tap. In the standard `CometChatMessageList` flow, use the `ccCardActionClicked` bus. + + +### Handling actions via the prop + +```tsx +import { CometChatCardBubble } from "@cometchat/chat-uikit-react"; + +function CardMessage({ cardMessage }: { cardMessage: CometChat.BaseMessage }) { + const onCardAction = ( + message: CometChat.BaseMessage | undefined, + action: any + ) => { + // `action` is the raw renderer action. + // Your app implements the behavior — the UI Kit performs none. + console.log("Card action on message", message?.getId(), action); + }; + + return ; +} +``` + +### Handling actions via the event bus + +```tsx +import { useEffect } from "react"; +import { CometChatUIEvents } from "@cometchat/chat-uikit-react"; + +function useCardActions() { + useEffect(() => { + const sub = CometChatUIEvents.ccCardActionClicked.subscribe(({ message, action }) => { + switch (action?.type) { + case "openUrl": + window.open(action.url, "_blank", "noopener,noreferrer"); + break; + case "copyToClipboard": + navigator.clipboard?.writeText(action.value); + break; + // ...handle the remaining action types + } + }); + return () => sub.unsubscribe(); + }, []); +} +``` + +The full set of action types and a complete reference handler are covered in the [Card Messages guide](/ui-kit/react/v6/card-messages#handling-card-actions). + +## Fallback Behavior + +When the payload is empty or invalid (`null`, a blank string, or an empty object), the bubble renders a single line of fallback text instead of an empty card. The fallback is resolved in this order: + +1. `message.getFallbackText()` +2. `message.getText()` +3. Localized `"Card Message"` (the `card_message` localization key) + +This keeps the conversation readable even if a card payload is malformed or a client cannot render it. + +## Customization + +The card's internal layout, colors, and typography are controlled by the `@cometchat/cards-react` renderer. The bubble wrapper and fallback line are styled with CSS variables: + +```css +.cometchat-card-bubble { + /* Bubble surface (incoming) */ + --cometchat-background-color-02: #f5f5f5; + --cometchat-radius-3: 12px; +} + +.cometchat-card-bubble__fallback { + /* Fallback text */ + --cometchat-font-body-regular: 400 14px "Inter"; + --cometchat-text-color-secondary: #666666; +} +``` + +To restyle the rendered card itself (button colors, header, body), theme the renderer rather than overriding CSS — the card DOM is owned by `@cometchat/cards-react`. + +## Technical Details + +- **Content-view component** — rendered as the `contentView` of the developer card message template, so it inherits the full bubble (status info, reply view, reactions, options). +- **Renderer dependency** — `CometChatCardView` from `@cometchat/cards-react`. +- **Callback before schema** — the renderer's `onAction` callback is bound before `cardJson` is rendered, so actions are captured from the first tap. +- **Theme** — the renderer `themeMode` is taken from `CometChatUIKit.themeMode`. + +## Related + +- **[Card Messages guide](/ui-kit/react/v6/card-messages)** — the full card rendering implementation: developer cards, agent cards, streaming cards, and the complete action vocabulary. +- **[CometChatMessageList](/ui-kit/react/v6/message-list)** — routes card messages to this bubble. +- **[CometChatMessageTemplate](/ui-kit/react/v6/message-template)** — the template that maps `category: "card"` to this bubble. +- **[Events](/ui-kit/react/v6/events)** — the `ccCardActionClicked` event reference. diff --git a/ui-kit/react/v6/card-messages.mdx b/ui-kit/react/v6/card-messages.mdx new file mode 100644 index 000000000..aaf22a00f --- /dev/null +++ b/ui-kit/react/v6/card-messages.mdx @@ -0,0 +1,199 @@ +--- +title: "Card Messages" +description: "How the React UI Kit renders Card Messages — developer cards, persisted agent cards, and streaming agent cards — and how to handle card actions." +--- + + + +| Field | Value | +| --- | --- | +| Package | `@cometchat/chat-uikit-react` | +| Renderer | `@cometchat/cards-react` (`CometChatCardView`) | +| Components | `CometChatCardBubble`, `CometChatAIAssistantMessageBubble`, `CometChatStreamMessageBubble` | +| Key event | `CometChatUIEvents.ccCardActionClicked` | +| Required setup | `CometChatUIKit.init(uiKitSettings)` then `CometChatUIKit.login("UID")` | +| Purpose | Render structured, interactive card payloads inside conversations and forward card actions to your app | +| Related | [Card Bubble](/ui-kit/react/v6/card-bubble) \| [AI Assistant Chat](/ui-kit/react/v6/ai-assistant-chat) \| [Events](/ui-kit/react/v6/events) | + + + +Card Messages are structured, interactive cards delivered inside conversations. The React UI Kit renders them through the prebuilt [`@cometchat/cards-react`](https://www.npmjs.com/package/@cometchat/cards-react) renderer and forwards every card action back to your application. + + + The UI Kit is a **render-only** consumer of cards: it draws cards delivered by the SDK and forwards their actions to your app. It never parses or mutates the card body, and never sends or creates cards. + + +## The render-only contract + +The UI Kit is a **render-only** consumer of cards. For every card surface, the rules are identical: + +1. **Pass the payload through unchanged.** The raw card payload is read from the SDK, serialized verbatim (`JSON.stringify`), and handed to `CometChatCardView` as a `cardJson` string. The UI Kit performs **no transformation**. +2. **Forward actions, run no behavior.** The renderer emits actions through a callback. The UI Kit forwards them on the `ccCardActionClicked` event bus. Your app implements **all** behavior (open a URL, start a chat, call an API, …). +3. **Fall back gracefully.** When a payload is empty or invalid, a single fallback line is shown instead of a broken card. + +The developer bubble, the agent bubble, and the streaming bubble all treat payloads identically. + +## Three delivery paths + +A card can reach the conversation in three ways. The UI Kit routes each one to the correct renderer automatically. + +| Path | Source | Component | Routed by | +| --- | --- | --- | --- | +| **Developer card** | `CardMessage` with `category: "card"` | [`CometChatCardBubble`](/ui-kit/react/v6/card-bubble) | message **category** | +| **Persisted agent card** | `AIAssistantMessage` content block | `CometChatAIAssistantMessageBubble` | element `type: "card"` | +| **Streaming agent card** | Live `card_start` / `card` / `card_end` events | `CometChatStreamMessageBubble` | stream event type | + +### Developer cards + +A message with category `"card"` is routed to `CometChatCardBubble` by [`CometChatMessageList`](/ui-kit/react/v6/message-list)'s message template, keyed on **category** (the developer `type` is arbitrary). The bubble renders `message.getCard()` and forwards taps on the `ccCardActionClicked` bus. + +In the conversation list, a card message's preview is its `getText()` if present, otherwise the localized `"Card Message"` label. + +To react to incoming developer cards in real time, listen on the SDK message bus: + +```tsx +import { useEffect } from "react"; +import { CometChatMessageEvents } from "@cometchat/chat-uikit-react"; + +function useCardListener() { + useEffect(() => { + const sub = CometChatMessageEvents.onCardMessageReceived.subscribe( + (card: CometChat.InteractiveMessage) => { + console.log("Card received:", card.getId(), (card as any).getCard?.()); + } + ); + return () => sub.unsubscribe(); + }, []); +} +``` + +See the [Card Bubble](/ui-kit/react/v6/card-bubble) component reference for props, actions, and theming. + +### Persisted agent cards + +After an AI agent run completes, the persisted `AIAssistantMessage` exposes its content as an **ordered list of blocks** via `getElements()`. `CometChatAIAssistantMessageBubble` renders them in order: + +- a `text` block renders as Markdown (via `react-markdown`); +- a `card` block renders through `CometChatCardView`, using the same render-only path as developer cards. + +When a message has no elements (older messages), the bubble falls back to `getText()`. No additional wiring is required — the [AI Assistant Chat](/ui-kit/react/v6/ai-assistant-chat) flow instantiates this bubble for you. Taps on a nested agent card are forwarded on the same `ccCardActionClicked` bus. + +### Streaming agent cards + +While an agent run is streaming, cards arrive progressively and are rendered by `CometChatStreamMessageBubble`, which subscribes to the streaming service's `messageStream`: + +| Stream event | Behavior | +| --- | --- | +| `card_start` | Shows an in-place loader labeled with the event's `executionText`, keyed by `cardId`. | +| `card` | Replaces the loader (correlated by `cardId`) with the rendered card. | +| `card_end` | No-op — the run-complete persisted `AIAssistantMessage` replaces the streamed bubble. | + +Because no persisted message exists yet during streaming, a tap on a streaming card is forwarded without an owning message on the `ccCardActionClicked` bus; the persisted bubble that follows is the source of truth. + +## Handling card actions + +The Cards renderer emits actions but never executes them. The UI Kit forwards each action **untouched** on `CometChatUIEvents.ccCardActionClicked`. Subscribe **once** at app startup and dispatch by action type. + +### The event payload + +```typescript +export interface ICardActionClicked { + /** The owning message — CardMessage (developer) or AIAssistantMessage (persisted agent card). */ + message: CometChat.BaseMessage; + /** The renderer's raw action object. The kit never interprets it. */ + action: any; +} +``` + +### Action types + +`action` is the raw action object emitted by `@cometchat/cards-react`, narrowed by its `type`. The UI Kit forwards all nine of them; your app decides what each one does: + +| `type` | Intended behavior | +| --- | --- | +| `openUrl` | Open a URL (in a new tab or a webview). | +| `copyToClipboard` | Copy a value to the clipboard. | +| `downloadFile` | Download a file. | +| `sendMessage` | Send a text message to a user/group (or the current conversation). | +| `apiCall` | Make an HTTP request. | +| `chatWithUser` | Open a one-to-one chat with a user. | +| `chatWithGroup` | Open a group chat. | +| `initiateCall` | Start an audio/video call. | +| `customCallback` | Invoke an app-defined handler, keyed by `callbackId`. | + +### Reference handler + +Subscribe once — a top-level hook mounted at your app shell is the natural home — and dispatch by `type`: + +```tsx +import { useEffect } from "react"; +import { CometChat } from "@cometchat/chat-sdk-javascript"; +import { CometChatUIEvents } from "@cometchat/chat-uikit-react"; + +export function useCardActions() { + useEffect(() => { + const sub = CometChatUIEvents.ccCardActionClicked.subscribe(({ message, action }) => { + if (!action || typeof action !== "object") return; + + switch (action.type) { + case "openUrl": { + if (!action.url) return; + const target = action.openIn === "webview" ? "_self" : "_blank"; + window.open(action.url, target, "noopener,noreferrer"); + break; + } + case "copyToClipboard": { + if (action.value != null) navigator.clipboard?.writeText(action.value); + break; + } + case "chatWithUser": { + if (!action.uid) return; + CometChat.getUser(action.uid).then((user) => { + // Navigate your app to the conversation with `user`. + }); + break; + } + // ...handle downloadFile, sendMessage, apiCall, chatWithGroup, + // initiateCall, and customCallback the same way. + default: + break; // Unknown action — the renderer owns the action vocabulary. + } + }); + + return () => sub.unsubscribe(); + }, []); +} +``` + +Call the hook once from your app shell so it subscribes exactly once: + +```tsx +function App() { + useCardActions(); + return ; +} +``` + + + Each tap is forwarded on **one** bus to all subscribers. Subscribe in a single place so an action runs exactly once. If you also bind the [`onCardAction` prop](/ui-kit/react/v6/card-bubble#card-actions) on a directly-rendered card bubble, handle the action on only one of the two channels. + + +### `customCallback` actions + +A `customCallback` action carries a `callbackId` (and optional `payload`). Map each `callbackId` to an app-defined handler, and use the action's element context and `message?.getId()` for context. No server message is sent by the UI Kit for a custom callback — it is entirely app-defined. + +## Fallback behavior + +Every card surface resolves a single-line fallback when the payload is empty or invalid: + +1. `getFallbackText()` (when available on the message/element) +2. `getText()` +3. Localized `"Card Message"` + +This guarantees a readable conversation even when a card cannot be rendered. + +## Related + +- **[Card Bubble](/ui-kit/react/v6/card-bubble)** — the developer card component reference. +- **[AI Assistant Chat](/ui-kit/react/v6/ai-assistant-chat)** — the agent chat experience that renders persisted and streaming agent cards. +- **[Events](/ui-kit/react/v6/events)** — `ccCardActionClicked` and `onCardMessageReceived` reference. diff --git a/ui-kit/react/v6/events.mdx b/ui-kit/react/v6/events.mdx index b00bb4989..c310c4c77 100644 --- a/ui-kit/react/v6/events.mdx +++ b/ui-kit/react/v6/events.mdx @@ -77,6 +77,7 @@ Events provide decoupled communication between UI Kit components. Components emi | **onMessageEdited** | This event is emitted when the CometChat SDK listener indicates that a message has been edited. | | **onMessageDeleted** | This event is emitted when the CometChat SDK listener indicates that a message has been deleted. | | **onTransientMessageReceived** | This event is emitted when the CometChat SDK listener emits a transient message. | +| **onCardMessageReceived** | This event is emitted when the CometChat SDK listener emits a developer card message (`category: "card"`). See [Card Messages](/ui-kit/react/v6/card-messages#developer-cards). | ### CometChatCallEvents @@ -96,3 +97,4 @@ UI events are triggered when a user interacts with UI Kit elements such as butto | Name | Description | | ------------------- | ---------------------------------------------------------------------------- | | **ccActiveChatChanged** | This event is triggered when the user navigates to a particular chat window. | +| **ccCardActionClicked** | This event is triggered when a user taps an interactive element on a rendered card. The UI Kit forwards the raw renderer action untouched; your app implements the behavior. See [Card Messages](/ui-kit/react/v6/card-messages#handling-card-actions). |