@better-zap/react 0.2.2 → 25.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -8
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/link-preview-card.cjs +76 -0
- package/dist/link-preview-card.d.cts +26 -0
- package/dist/link-preview-card.d.mts +26 -0
- package/dist/link-preview-card.mjs +75 -0
- package/dist/message-bubble.cjs +7 -1
- package/dist/message-bubble.d.cts +7 -1
- package/dist/message-bubble.d.mts +7 -1
- package/dist/message-bubble.mjs +7 -1
- package/dist/message-view.cjs +19 -4
- package/dist/message-view.d.cts +11 -1
- package/dist/message-view.d.mts +11 -1
- package/dist/message-view.mjs +19 -4
- package/package.json +98 -35
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ dependency, and Better Zap **adapters** that map domain data onto those primitiv
|
|
|
105
105
|
| --- | --- | --- |
|
|
106
106
|
| Data mapping (domain → presentation) | None — you pass `align`/`variant` directly | Own it — map `Conversation`/`UIMessage` fields (`sender`/`status`/`direction`) to presentational props |
|
|
107
107
|
| Row layout | `Message`, `MessageAvatar`, `MessageContent`, `MessageHeader`, `MessageFooter` | Composed internally by `MessageBubble` / `MessageList`'s default renderer |
|
|
108
|
-
| Bubble surface | `Bubble`, `BubbleContent`, `BubbleReactions` | Composed internally; still swappable via `renderMessage` (see [MessageList](#messagelist)) |
|
|
108
|
+
| Bubble surface | `Bubble`, `BubbleContent`, `BubbleReactions`, `LinkPreviewCard` | Composed internally; still swappable via `renderMessage` (see [MessageList](#messagelist)) |
|
|
109
109
|
| Metadata / timestamps | You render them (`MessageFooter`, etc.) | Own default formatting (`formatDate`/`formatTime`), overridable via props |
|
|
110
110
|
| Actions | `ComposerButton`, `ComposerSend`, etc. — you wire callbacks | `MessageInput` gates action buttons on callbacks being provided (see [Actions and failures](#actions-and-failures)) |
|
|
111
111
|
| Responsive orchestration | None | `WhatsappDashboard` owns mobile list/chat navigation; `ConversationList`/`MessageView` read it optionally |
|
|
@@ -123,7 +123,8 @@ The root entry (`@better-zap/react`) re-exports the full public surface and is a
|
|
|
123
123
|
| `@better-zap/react` | client | Full barrel |
|
|
124
124
|
| `@better-zap/react/bubble` | server-safe | Presentational bubble primitives |
|
|
125
125
|
| `@better-zap/react/message` | server-safe | Row layout primitives |
|
|
126
|
-
| `@better-zap/react/message-bubble` | server-safe | Domain `MessageBubble` adapter |
|
|
126
|
+
| `@better-zap/react/message-bubble` | server-safe | Domain `MessageBubble` adapter (renders the client `LinkPreviewCard` when given a preview) |
|
|
127
|
+
| `@better-zap/react/link-preview-card` | client | Open Graph card (hides its image if it fails to load) |
|
|
127
128
|
| `@better-zap/react/utils` | server-safe | `cn`, `getDisplayDate`, `renderSlot` |
|
|
128
129
|
| `@better-zap/react/composer` | client | Draft/send orchestration |
|
|
129
130
|
| `@better-zap/react/message-input` | client | Domain input + freeform window |
|
|
@@ -424,6 +425,85 @@ prepends *duplicate-label* groups (accepted).
|
|
|
424
425
|
Also available: `formatTime(iso)` for the **default** bubble timestamp only
|
|
425
426
|
(custom `renderMessage` owns its own time formatting).
|
|
426
427
|
|
|
428
|
+
## Link previews
|
|
429
|
+
|
|
430
|
+
WhatsApp shows an Open Graph card (image, title, description, domain) at the
|
|
431
|
+
top of a bubble that contains a link. You supply the preview data; the
|
|
432
|
+
components only render it.
|
|
433
|
+
|
|
434
|
+
- `MessageList` `resolveLinkPreview(message)` returns a `LinkPreview` (or
|
|
435
|
+
`null`/`undefined`) for each message. The default renderer shows the card,
|
|
436
|
+
and custom `renderMessage` functions receive it as `linkPreview`.
|
|
437
|
+
- `MessageBubble` `linkPreview` renders the card inside the bubble, above the
|
|
438
|
+
text. The bubble takes WhatsApp's fixed 330px preview width (still capped at
|
|
439
|
+
65% of the chat). Without a preview the bubble renders exactly as before.
|
|
440
|
+
- `LinkPreviewCard` is the card on its own, for `Bubble` compositions. Pass the
|
|
441
|
+
bubble's `variant` so the card gets the matching tint.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
interface LinkPreview {
|
|
445
|
+
url: string;
|
|
446
|
+
title: string;
|
|
447
|
+
description?: string;
|
|
448
|
+
image?: string; // absolute http(s) URL
|
|
449
|
+
siteName?: string;
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`resolveLinkPreview` is synchronous. Fetch previews elsewhere, keep them in
|
|
454
|
+
state, and pass a new resolver when they change: rows re-render when the
|
|
455
|
+
function's identity changes, so wrap it in `useCallback` keyed on that state.
|
|
456
|
+
`firstLinkIn` from `better-zap` picks the same link `FormattedMessage` renders
|
|
457
|
+
as an anchor:
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
import { useCallback } from "react";
|
|
461
|
+
import { MessageList } from "@better-zap/react";
|
|
462
|
+
import type { LinkPreview, UIMessage } from "@better-zap/react";
|
|
463
|
+
import { firstLinkIn } from "better-zap";
|
|
464
|
+
|
|
465
|
+
function ChatWithPreviews({
|
|
466
|
+
messages,
|
|
467
|
+
previews,
|
|
468
|
+
}: {
|
|
469
|
+
messages: UIMessage[];
|
|
470
|
+
previews: Record<string, LinkPreview>;
|
|
471
|
+
}) {
|
|
472
|
+
const resolveLinkPreview = useCallback(
|
|
473
|
+
(message: UIMessage) => {
|
|
474
|
+
const url = message.templateName ? null : firstLinkIn(message.content);
|
|
475
|
+
return url ? previews[url] : undefined;
|
|
476
|
+
},
|
|
477
|
+
[previews],
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
return (
|
|
481
|
+
<MessageList messages={messages} resolveLinkPreview={resolveLinkPreview} />
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
In your own `Bubble` composition:
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
import { Bubble, BubbleContent, LinkPreviewCard } from "@better-zap/react";
|
|
490
|
+
|
|
491
|
+
<Bubble variant="primary" align="end" className="w-[330px]">
|
|
492
|
+
<BubbleContent>
|
|
493
|
+
<LinkPreviewCard preview={preview} variant="primary" />
|
|
494
|
+
{text}
|
|
495
|
+
</BubbleContent>
|
|
496
|
+
</Bubble>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Fetching stays in your app because it needs SSRF guards, timeouts, size limits
|
|
500
|
+
and caching. Do it on the server, never from the browser. `better-zap` ships
|
|
501
|
+
`parseOpenGraph(html, pageUrl)` to turn the fetched page into a `LinkPreview`.
|
|
502
|
+
Some sites (Amazon, for example) return full Open Graph tags only to known
|
|
503
|
+
unfurler user agents such as `WhatsApp/2.23.20.0`. The
|
|
504
|
+
[link previews guide](https://github.com/Dosbodoke/better-zap/blob/main/landing/content/docs/guides/link-previews.mdx) has a
|
|
505
|
+
complete server fetcher and client hook.
|
|
506
|
+
|
|
427
507
|
## MessageView
|
|
428
508
|
|
|
429
509
|
**MessageView** is the chat pane shell (background, empty state, header, content).
|
|
@@ -523,9 +603,5 @@ remain public — nothing described below removes an export.
|
|
|
523
603
|
|
|
524
604
|
### Versioning
|
|
525
605
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
export: `MessageBubble`, `MessageInput`, `MessageList`, `ConversationList`,
|
|
529
|
-
`MessageView`, and `WhatsappDashboard` all remain public. The composable
|
|
530
|
-
primitives (`Bubble*`, `Message*` layout parts, `Composer*`, `DateDivider`)
|
|
531
|
-
are additive — they sit alongside the adapters, not in place of them.
|
|
606
|
+
See the [package release policy](../../README.md#releases) and the
|
|
607
|
+
[maintainer release runbook](../../.changeset/README.md).
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_utils = require("./utils-Bp9IahGG.cjs");
|
|
4
4
|
const require_whatsapp_dashboard = require("./whatsapp-dashboard.cjs");
|
|
5
5
|
const require_bubble = require("./bubble.cjs");
|
|
6
|
+
const require_link_preview_card = require("./link-preview-card.cjs");
|
|
6
7
|
const require_message = require("./message.cjs");
|
|
7
8
|
const require_message_bubble = require("./message-bubble.cjs");
|
|
8
9
|
const require_message_view = require("./message-view.cjs");
|
|
@@ -24,6 +25,7 @@ exports.ConversationList = require_conversation_list.ConversationList;
|
|
|
24
25
|
exports.DateDivider = require_message_view.DateDivider;
|
|
25
26
|
exports.FormattedMessage = require_message_bubble.FormattedMessage;
|
|
26
27
|
exports.FreeformWindowClosedError = require_message_input.FreeformWindowClosedError;
|
|
28
|
+
exports.LinkPreviewCard = require_link_preview_card.LinkPreviewCard;
|
|
27
29
|
exports.Message = require_message.Message;
|
|
28
30
|
exports.MessageAvatar = require_message.MessageAvatar;
|
|
29
31
|
exports.MessageBubble = require_message_bubble.MessageBubble;
|
package/dist/index.d.cts
CHANGED
|
@@ -4,10 +4,11 @@ import { a as ConversationListProps, c as ConversationFilterValue, i as Conversa
|
|
|
4
4
|
import { MobileView, WhatsappDashboard, WhatsappDashboardContextValue, WhatsappDashboardProps, useOptionalWhatsappDashboard, useWhatsappDashboard } from "./whatsapp-dashboard.cjs";
|
|
5
5
|
import { DateDivider, DateDividerRenderContext, MessageGroupPosition, MessageList, MessageListProps, MessageRenderContext, MessageView, MessageViewContent, MessageViewContentProps, MessageViewEmpty, MessageViewHeader, MessageViewHeaderLabels, MessageViewHeaderProps, MessageViewProps } from "./message-view.cjs";
|
|
6
6
|
import { FormattedMessage, MessageBubble, MessageBubbleGroupPosition, MessageBubbleProps } from "./message-bubble.cjs";
|
|
7
|
+
import { LinkPreviewCard, LinkPreviewCardProps } from "./link-preview-card.cjs";
|
|
7
8
|
import { Message, MessageAlign, MessageAvatar, MessageAvatarProps, MessageContent, MessageContentProps, MessageFooter, MessageFooterProps, MessageGroup, MessageGroupProps, MessageHeader, MessageHeaderProps, MessageProps } from "./message.cjs";
|
|
8
9
|
import { cn, getDisplayDate, renderSlot } from "./utils.cjs";
|
|
9
10
|
import { FreeformWindowClosedError, MessageInput, MessageInputLabels, MessageInputProps } from "./message-input.cjs";
|
|
10
|
-
import { Conversation, Conversation as Conversation$1, ConversationRecord, FreeformMessageWindow, FreeformMessageWindow as FreeformMessageWindow$1, UIMessage, UIMessage as UIMessage$1, UIMessageStatus } from "better-zap";
|
|
11
|
+
import { Conversation, Conversation as Conversation$1, ConversationRecord, FreeformMessageWindow, FreeformMessageWindow as FreeformMessageWindow$1, LinkPreview, UIMessage, UIMessage as UIMessage$1, UIMessageStatus } from "better-zap";
|
|
11
12
|
|
|
12
13
|
//#region src/use-freeform-message-window.d.ts
|
|
13
14
|
interface UseFreeformMessageWindowResult extends FreeformMessageWindow$1 {
|
|
@@ -16,4 +17,4 @@ interface UseFreeformMessageWindowResult extends FreeformMessageWindow$1 {
|
|
|
16
17
|
}
|
|
17
18
|
declare function useFreeformMessageWindow(conversation?: Pick<Conversation$1, "freeformMessageWindow" | "lastIncomingMessageAt"> | null, messages?: UIMessage$1[]): UseFreeformMessageWindowResult;
|
|
18
19
|
//#endregion
|
|
19
|
-
export { Bubble, BubbleAlign, BubbleContent, BubbleContentProps, BubbleGroup, BubbleGroupProps, BubbleProps, BubbleReactions, BubbleReactionsProps, BubbleVariant, Composer, ComposerButton, ComposerButtonProps, ComposerContextValue, ComposerError, ComposerErrorProps, ComposerProps, ComposerSend, ComposerSendProps, ComposerStateContextValue, ComposerTextarea, ComposerTextareaProps, type Conversation, ConversationFilterChips, ConversationFilterChipsProps, ConversationFilterValue, ConversationItem, ConversationItemProps, ConversationList, ConversationListLabels, ConversationListProps, type ConversationRecord, DateDivider, DateDividerRenderContext, FormattedMessage, type FreeformMessageWindow, FreeformWindowClosedError, Message, MessageAlign, MessageAvatar, MessageAvatarProps, MessageBubble, MessageBubbleGroupPosition, MessageBubbleProps, MessageContent, MessageContentProps, MessageFooter, MessageFooterProps, MessageGroup, MessageGroupPosition, MessageGroupProps, MessageHeader, MessageHeaderProps, MessageInput, MessageInputLabels, MessageInputProps, MessageList, MessageListProps, MessageProps, MessageRenderContext, MessageView, MessageViewContent, MessageViewContentProps, MessageViewEmpty, MessageViewHeader, MessageViewHeaderLabels, MessageViewHeaderProps, MessageViewProps, MobileView, type UIMessage, type UIMessageStatus, UseFreeformMessageWindowResult, WhatsappDashboard, WhatsappDashboardContextValue, WhatsappDashboardProps, cn, getDisplayDate, renderSlot, useComposer, useComposerState, useComposerValue, useFreeformMessageWindow, useOptionalWhatsappDashboard, useWhatsappDashboard };
|
|
20
|
+
export { Bubble, BubbleAlign, BubbleContent, BubbleContentProps, BubbleGroup, BubbleGroupProps, BubbleProps, BubbleReactions, BubbleReactionsProps, BubbleVariant, Composer, ComposerButton, ComposerButtonProps, ComposerContextValue, ComposerError, ComposerErrorProps, ComposerProps, ComposerSend, ComposerSendProps, ComposerStateContextValue, ComposerTextarea, ComposerTextareaProps, type Conversation, ConversationFilterChips, ConversationFilterChipsProps, ConversationFilterValue, ConversationItem, ConversationItemProps, ConversationList, ConversationListLabels, ConversationListProps, type ConversationRecord, DateDivider, DateDividerRenderContext, FormattedMessage, type FreeformMessageWindow, FreeformWindowClosedError, type LinkPreview, LinkPreviewCard, LinkPreviewCardProps, Message, MessageAlign, MessageAvatar, MessageAvatarProps, MessageBubble, MessageBubbleGroupPosition, MessageBubbleProps, MessageContent, MessageContentProps, MessageFooter, MessageFooterProps, MessageGroup, MessageGroupPosition, MessageGroupProps, MessageHeader, MessageHeaderProps, MessageInput, MessageInputLabels, MessageInputProps, MessageList, MessageListProps, MessageProps, MessageRenderContext, MessageView, MessageViewContent, MessageViewContentProps, MessageViewEmpty, MessageViewHeader, MessageViewHeaderLabels, MessageViewHeaderProps, MessageViewProps, MobileView, type UIMessage, type UIMessageStatus, UseFreeformMessageWindowResult, WhatsappDashboard, WhatsappDashboardContextValue, WhatsappDashboardProps, cn, getDisplayDate, renderSlot, useComposer, useComposerState, useComposerValue, useFreeformMessageWindow, useOptionalWhatsappDashboard, useWhatsappDashboard };
|
package/dist/index.d.mts
CHANGED
|
@@ -4,10 +4,11 @@ import { a as ConversationListProps, c as ConversationFilterValue, i as Conversa
|
|
|
4
4
|
import { MobileView, WhatsappDashboard, WhatsappDashboardContextValue, WhatsappDashboardProps, useOptionalWhatsappDashboard, useWhatsappDashboard } from "./whatsapp-dashboard.mjs";
|
|
5
5
|
import { DateDivider, DateDividerRenderContext, MessageGroupPosition, MessageList, MessageListProps, MessageRenderContext, MessageView, MessageViewContent, MessageViewContentProps, MessageViewEmpty, MessageViewHeader, MessageViewHeaderLabels, MessageViewHeaderProps, MessageViewProps } from "./message-view.mjs";
|
|
6
6
|
import { FormattedMessage, MessageBubble, MessageBubbleGroupPosition, MessageBubbleProps } from "./message-bubble.mjs";
|
|
7
|
+
import { LinkPreviewCard, LinkPreviewCardProps } from "./link-preview-card.mjs";
|
|
7
8
|
import { Message, MessageAlign, MessageAvatar, MessageAvatarProps, MessageContent, MessageContentProps, MessageFooter, MessageFooterProps, MessageGroup, MessageGroupProps, MessageHeader, MessageHeaderProps, MessageProps } from "./message.mjs";
|
|
8
9
|
import { cn, getDisplayDate, renderSlot } from "./utils.mjs";
|
|
9
10
|
import { FreeformWindowClosedError, MessageInput, MessageInputLabels, MessageInputProps } from "./message-input.mjs";
|
|
10
|
-
import { Conversation, Conversation as Conversation$1, ConversationRecord, FreeformMessageWindow, FreeformMessageWindow as FreeformMessageWindow$1, UIMessage, UIMessage as UIMessage$1, UIMessageStatus } from "better-zap";
|
|
11
|
+
import { Conversation, Conversation as Conversation$1, ConversationRecord, FreeformMessageWindow, FreeformMessageWindow as FreeformMessageWindow$1, LinkPreview, UIMessage, UIMessage as UIMessage$1, UIMessageStatus } from "better-zap";
|
|
11
12
|
|
|
12
13
|
//#region src/use-freeform-message-window.d.ts
|
|
13
14
|
interface UseFreeformMessageWindowResult extends FreeformMessageWindow$1 {
|
|
@@ -16,4 +17,4 @@ interface UseFreeformMessageWindowResult extends FreeformMessageWindow$1 {
|
|
|
16
17
|
}
|
|
17
18
|
declare function useFreeformMessageWindow(conversation?: Pick<Conversation$1, "freeformMessageWindow" | "lastIncomingMessageAt"> | null, messages?: UIMessage$1[]): UseFreeformMessageWindowResult;
|
|
18
19
|
//#endregion
|
|
19
|
-
export { Bubble, BubbleAlign, BubbleContent, BubbleContentProps, BubbleGroup, BubbleGroupProps, BubbleProps, BubbleReactions, BubbleReactionsProps, BubbleVariant, Composer, ComposerButton, ComposerButtonProps, ComposerContextValue, ComposerError, ComposerErrorProps, ComposerProps, ComposerSend, ComposerSendProps, ComposerStateContextValue, ComposerTextarea, ComposerTextareaProps, type Conversation, ConversationFilterChips, ConversationFilterChipsProps, ConversationFilterValue, ConversationItem, ConversationItemProps, ConversationList, ConversationListLabels, ConversationListProps, type ConversationRecord, DateDivider, DateDividerRenderContext, FormattedMessage, type FreeformMessageWindow, FreeformWindowClosedError, Message, MessageAlign, MessageAvatar, MessageAvatarProps, MessageBubble, MessageBubbleGroupPosition, MessageBubbleProps, MessageContent, MessageContentProps, MessageFooter, MessageFooterProps, MessageGroup, MessageGroupPosition, MessageGroupProps, MessageHeader, MessageHeaderProps, MessageInput, MessageInputLabels, MessageInputProps, MessageList, MessageListProps, MessageProps, MessageRenderContext, MessageView, MessageViewContent, MessageViewContentProps, MessageViewEmpty, MessageViewHeader, MessageViewHeaderLabels, MessageViewHeaderProps, MessageViewProps, MobileView, type UIMessage, type UIMessageStatus, UseFreeformMessageWindowResult, WhatsappDashboard, WhatsappDashboardContextValue, WhatsappDashboardProps, cn, getDisplayDate, renderSlot, useComposer, useComposerState, useComposerValue, useFreeformMessageWindow, useOptionalWhatsappDashboard, useWhatsappDashboard };
|
|
20
|
+
export { Bubble, BubbleAlign, BubbleContent, BubbleContentProps, BubbleGroup, BubbleGroupProps, BubbleProps, BubbleReactions, BubbleReactionsProps, BubbleVariant, Composer, ComposerButton, ComposerButtonProps, ComposerContextValue, ComposerError, ComposerErrorProps, ComposerProps, ComposerSend, ComposerSendProps, ComposerStateContextValue, ComposerTextarea, ComposerTextareaProps, type Conversation, ConversationFilterChips, ConversationFilterChipsProps, ConversationFilterValue, ConversationItem, ConversationItemProps, ConversationList, ConversationListLabels, ConversationListProps, type ConversationRecord, DateDivider, DateDividerRenderContext, FormattedMessage, type FreeformMessageWindow, FreeformWindowClosedError, type LinkPreview, LinkPreviewCard, LinkPreviewCardProps, Message, MessageAlign, MessageAvatar, MessageAvatarProps, MessageBubble, MessageBubbleGroupPosition, MessageBubbleProps, MessageContent, MessageContentProps, MessageFooter, MessageFooterProps, MessageGroup, MessageGroupPosition, MessageGroupProps, MessageHeader, MessageHeaderProps, MessageInput, MessageInputLabels, MessageInputProps, MessageList, MessageListProps, MessageProps, MessageRenderContext, MessageView, MessageViewContent, MessageViewContentProps, MessageViewEmpty, MessageViewHeader, MessageViewHeaderLabels, MessageViewHeaderProps, MessageViewProps, MobileView, type UIMessage, type UIMessageStatus, UseFreeformMessageWindowResult, WhatsappDashboard, WhatsappDashboardContextValue, WhatsappDashboardProps, cn, getDisplayDate, renderSlot, useComposer, useComposerState, useComposerValue, useFreeformMessageWindow, useOptionalWhatsappDashboard, useWhatsappDashboard };
|
package/dist/index.mjs
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { cn, getDisplayDate, renderSlot } from "./utils.mjs";
|
|
3
3
|
import { WhatsappDashboard, useOptionalWhatsappDashboard, useWhatsappDashboard } from "./whatsapp-dashboard.mjs";
|
|
4
4
|
import { Bubble, BubbleContent, BubbleGroup, BubbleReactions } from "./bubble.mjs";
|
|
5
|
+
import { LinkPreviewCard } from "./link-preview-card.mjs";
|
|
5
6
|
import { Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader } from "./message.mjs";
|
|
6
7
|
import { FormattedMessage, MessageBubble } from "./message-bubble.mjs";
|
|
7
8
|
import { DateDivider, MessageList, MessageView, MessageViewContent, MessageViewEmpty, MessageViewHeader } from "./message-view.mjs";
|
|
8
9
|
import { n as ConversationList, r as ConversationFilterChips, t as ConversationItem } from "./conversation-list-C0EmReS-.mjs";
|
|
9
10
|
import { Composer, ComposerButton, ComposerError, ComposerSend, ComposerTextarea, useComposer, useComposerState, useComposerValue } from "./composer.mjs";
|
|
10
11
|
import { n as MessageInput, r as useFreeformMessageWindow, t as FreeformWindowClosedError } from "./message-input-CkaL6fb4.mjs";
|
|
11
|
-
export { Bubble, BubbleContent, BubbleGroup, BubbleReactions, Composer, ComposerButton, ComposerError, ComposerSend, ComposerTextarea, ConversationFilterChips, ConversationItem, ConversationList, DateDivider, FormattedMessage, FreeformWindowClosedError, Message, MessageAvatar, MessageBubble, MessageContent, MessageFooter, MessageGroup, MessageHeader, MessageInput, MessageList, MessageView, MessageViewContent, MessageViewEmpty, MessageViewHeader, WhatsappDashboard, cn, getDisplayDate, renderSlot, useComposer, useComposerState, useComposerValue, useFreeformMessageWindow, useOptionalWhatsappDashboard, useWhatsappDashboard };
|
|
12
|
+
export { Bubble, BubbleContent, BubbleGroup, BubbleReactions, Composer, ComposerButton, ComposerError, ComposerSend, ComposerTextarea, ConversationFilterChips, ConversationItem, ConversationList, DateDivider, FormattedMessage, FreeformWindowClosedError, LinkPreviewCard, Message, MessageAvatar, MessageBubble, MessageContent, MessageFooter, MessageGroup, MessageHeader, MessageInput, MessageList, MessageView, MessageViewContent, MessageViewEmpty, MessageViewHeader, WhatsappDashboard, cn, getDisplayDate, renderSlot, useComposer, useComposerState, useComposerValue, useFreeformMessageWindow, useOptionalWhatsappDashboard, useWhatsappDashboard };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_utils = require("./utils-Bp9IahGG.cjs");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
//#region src/link-preview-card.tsx
|
|
7
|
+
const tintByVariant = {
|
|
8
|
+
default: "bg-[#f0f2f5]",
|
|
9
|
+
primary: "bg-[#d1f4cc]",
|
|
10
|
+
destructive: "bg-[#f8cdc9]",
|
|
11
|
+
outline: "bg-black/5",
|
|
12
|
+
muted: "bg-black/5"
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* WhatsApp-style Open Graph card: image, two-line title, two-line description
|
|
16
|
+
* and the link's domain. Render it inside `BubbleContent`, above the text.
|
|
17
|
+
* The image is hidden if it fails to load.
|
|
18
|
+
*/
|
|
19
|
+
function LinkPreviewCard({ preview, variant = "default", className, ...props }) {
|
|
20
|
+
const [failedImage, setFailedImage] = (0, react.useState)(null);
|
|
21
|
+
const image = preview.image && preview.image !== failedImage ? preview.image : null;
|
|
22
|
+
const domain = displayDomain(preview);
|
|
23
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
24
|
+
href: httpUrlOrUndefined(preview.url),
|
|
25
|
+
target: "_blank",
|
|
26
|
+
rel: "noopener noreferrer",
|
|
27
|
+
"data-slot": "link-preview",
|
|
28
|
+
className: require_utils.cn("-mx-[5px] -mt-[2px] mb-1.5 block overflow-hidden rounded-[6px] whitespace-normal no-underline focus-visible:outline-2 focus-visible:outline-offset-2", tintByVariant[variant], className),
|
|
29
|
+
...props,
|
|
30
|
+
children: [image && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
31
|
+
src: image,
|
|
32
|
+
alt: "",
|
|
33
|
+
loading: "lazy",
|
|
34
|
+
decoding: "async",
|
|
35
|
+
referrerPolicy: "no-referrer",
|
|
36
|
+
className: "block max-h-[180px] w-full bg-white object-cover",
|
|
37
|
+
onError: () => setFailedImage(image),
|
|
38
|
+
ref: (element) => {
|
|
39
|
+
if (element?.complete && element.naturalWidth === 0) setFailedImage(image);
|
|
40
|
+
}
|
|
41
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
42
|
+
className: "block px-2.5 py-2",
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
45
|
+
className: "line-clamp-2 text-[13.5px] font-medium leading-[18px] text-[#111b21]",
|
|
46
|
+
children: preview.title
|
|
47
|
+
}),
|
|
48
|
+
preview.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49
|
+
className: "mt-0.5 line-clamp-2 text-[12.5px] leading-[17px] text-[#667781]",
|
|
50
|
+
children: preview.description
|
|
51
|
+
}),
|
|
52
|
+
domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53
|
+
className: "mt-1 block truncate text-[12px] leading-4 text-[#8696a0]",
|
|
54
|
+
children: domain
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
})]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function parseUrl(value) {
|
|
61
|
+
try {
|
|
62
|
+
return new URL(value);
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function httpUrlOrUndefined(value) {
|
|
68
|
+
const url = parseUrl(value);
|
|
69
|
+
return url && (url.protocol === "http:" || url.protocol === "https:") ? value : void 0;
|
|
70
|
+
}
|
|
71
|
+
function displayDomain(preview) {
|
|
72
|
+
const hostname = parseUrl(preview.url)?.hostname;
|
|
73
|
+
return hostname ? hostname.replace(/^www\./i, "") : preview.siteName;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
exports.LinkPreviewCard = LinkPreviewCard;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BubbleVariant } from "./bubble.cjs";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { LinkPreview, LinkPreview as LinkPreview$1 } from "better-zap";
|
|
4
|
+
|
|
5
|
+
//#region src/link-preview-card.d.ts
|
|
6
|
+
interface LinkPreviewCardProps extends Omit<React.ComponentProps<"a">, "children"> {
|
|
7
|
+
preview: LinkPreview$1;
|
|
8
|
+
/**
|
|
9
|
+
* Variant of the surrounding `Bubble`, so the card gets the matching tint.
|
|
10
|
+
* @default "default"
|
|
11
|
+
*/
|
|
12
|
+
variant?: BubbleVariant;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* WhatsApp-style Open Graph card: image, two-line title, two-line description
|
|
16
|
+
* and the link's domain. Render it inside `BubbleContent`, above the text.
|
|
17
|
+
* The image is hidden if it fails to load.
|
|
18
|
+
*/
|
|
19
|
+
declare function LinkPreviewCard({
|
|
20
|
+
preview,
|
|
21
|
+
variant,
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: LinkPreviewCardProps): React.JSX.Element;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { type LinkPreview, LinkPreviewCard, LinkPreviewCardProps };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BubbleVariant } from "./bubble.mjs";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { LinkPreview, LinkPreview as LinkPreview$1 } from "better-zap";
|
|
4
|
+
|
|
5
|
+
//#region src/link-preview-card.d.ts
|
|
6
|
+
interface LinkPreviewCardProps extends Omit<React.ComponentProps<"a">, "children"> {
|
|
7
|
+
preview: LinkPreview$1;
|
|
8
|
+
/**
|
|
9
|
+
* Variant of the surrounding `Bubble`, so the card gets the matching tint.
|
|
10
|
+
* @default "default"
|
|
11
|
+
*/
|
|
12
|
+
variant?: BubbleVariant;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* WhatsApp-style Open Graph card: image, two-line title, two-line description
|
|
16
|
+
* and the link's domain. Render it inside `BubbleContent`, above the text.
|
|
17
|
+
* The image is hidden if it fails to load.
|
|
18
|
+
*/
|
|
19
|
+
declare function LinkPreviewCard({
|
|
20
|
+
preview,
|
|
21
|
+
variant,
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: LinkPreviewCardProps): React.JSX.Element;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { type LinkPreview, LinkPreviewCard, LinkPreviewCardProps };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "./utils.mjs";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/link-preview-card.tsx
|
|
6
|
+
const tintByVariant = {
|
|
7
|
+
default: "bg-[#f0f2f5]",
|
|
8
|
+
primary: "bg-[#d1f4cc]",
|
|
9
|
+
destructive: "bg-[#f8cdc9]",
|
|
10
|
+
outline: "bg-black/5",
|
|
11
|
+
muted: "bg-black/5"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* WhatsApp-style Open Graph card: image, two-line title, two-line description
|
|
15
|
+
* and the link's domain. Render it inside `BubbleContent`, above the text.
|
|
16
|
+
* The image is hidden if it fails to load.
|
|
17
|
+
*/
|
|
18
|
+
function LinkPreviewCard({ preview, variant = "default", className, ...props }) {
|
|
19
|
+
const [failedImage, setFailedImage] = useState(null);
|
|
20
|
+
const image = preview.image && preview.image !== failedImage ? preview.image : null;
|
|
21
|
+
const domain = displayDomain(preview);
|
|
22
|
+
return /* @__PURE__ */ jsxs("a", {
|
|
23
|
+
href: httpUrlOrUndefined(preview.url),
|
|
24
|
+
target: "_blank",
|
|
25
|
+
rel: "noopener noreferrer",
|
|
26
|
+
"data-slot": "link-preview",
|
|
27
|
+
className: cn("-mx-[5px] -mt-[2px] mb-1.5 block overflow-hidden rounded-[6px] whitespace-normal no-underline focus-visible:outline-2 focus-visible:outline-offset-2", tintByVariant[variant], className),
|
|
28
|
+
...props,
|
|
29
|
+
children: [image && /* @__PURE__ */ jsx("img", {
|
|
30
|
+
src: image,
|
|
31
|
+
alt: "",
|
|
32
|
+
loading: "lazy",
|
|
33
|
+
decoding: "async",
|
|
34
|
+
referrerPolicy: "no-referrer",
|
|
35
|
+
className: "block max-h-[180px] w-full bg-white object-cover",
|
|
36
|
+
onError: () => setFailedImage(image),
|
|
37
|
+
ref: (element) => {
|
|
38
|
+
if (element?.complete && element.naturalWidth === 0) setFailedImage(image);
|
|
39
|
+
}
|
|
40
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
41
|
+
className: "block px-2.5 py-2",
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", {
|
|
44
|
+
className: "line-clamp-2 text-[13.5px] font-medium leading-[18px] text-[#111b21]",
|
|
45
|
+
children: preview.title
|
|
46
|
+
}),
|
|
47
|
+
preview.description && /* @__PURE__ */ jsx("span", {
|
|
48
|
+
className: "mt-0.5 line-clamp-2 text-[12.5px] leading-[17px] text-[#667781]",
|
|
49
|
+
children: preview.description
|
|
50
|
+
}),
|
|
51
|
+
domain && /* @__PURE__ */ jsx("span", {
|
|
52
|
+
className: "mt-1 block truncate text-[12px] leading-4 text-[#8696a0]",
|
|
53
|
+
children: domain
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
})]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function parseUrl(value) {
|
|
60
|
+
try {
|
|
61
|
+
return new URL(value);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function httpUrlOrUndefined(value) {
|
|
67
|
+
const url = parseUrl(value);
|
|
68
|
+
return url && (url.protocol === "http:" || url.protocol === "https:") ? value : void 0;
|
|
69
|
+
}
|
|
70
|
+
function displayDomain(preview) {
|
|
71
|
+
const hostname = parseUrl(preview.url)?.hostname;
|
|
72
|
+
return hostname ? hostname.replace(/^www\./i, "") : preview.siteName;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { LinkPreviewCard };
|
package/dist/message-bubble.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_utils = require("./utils-Bp9IahGG.cjs");
|
|
3
3
|
const require_bubble = require("./bubble.cjs");
|
|
4
|
+
const require_link_preview_card = require("./link-preview-card.cjs");
|
|
4
5
|
const require_message = require("./message.cjs");
|
|
5
6
|
let react = require("react");
|
|
6
7
|
react = require_utils.__toESM(react);
|
|
@@ -16,7 +17,7 @@ const statusVariants = (0, require("class-variance-authority").cva)("flex items-
|
|
|
16
17
|
} },
|
|
17
18
|
defaultVariants: { variant: "default" }
|
|
18
19
|
});
|
|
19
|
-
function MessageBubble({ content, sender, timestamp, status, templateName, label, groupPosition = "single", className, ...props }) {
|
|
20
|
+
function MessageBubble({ content, sender, timestamp, status, templateName, label, groupPosition = "single", linkPreview, className, ...props }) {
|
|
20
21
|
const isIncoming = sender === "user";
|
|
21
22
|
const isFailed = status === "failed";
|
|
22
23
|
const align = sender === "user" ? "start" : "end";
|
|
@@ -34,6 +35,7 @@ function MessageBubble({ content, sender, timestamp, status, templateName, label
|
|
|
34
35
|
variant,
|
|
35
36
|
align,
|
|
36
37
|
tail: hasTail,
|
|
38
|
+
className: linkPreview ? "w-[330px]" : void 0,
|
|
37
39
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_bubble.BubbleContent, { children: [
|
|
38
40
|
label && !isIncoming && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
39
41
|
className: "mb-1 block text-xs font-medium opacity-70",
|
|
@@ -49,6 +51,10 @@ function MessageBubble({ content, sender, timestamp, status, templateName, label
|
|
|
49
51
|
}), templateName]
|
|
50
52
|
})
|
|
51
53
|
}),
|
|
54
|
+
linkPreview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_link_preview_card.LinkPreviewCard, {
|
|
55
|
+
preview: linkPreview,
|
|
56
|
+
variant
|
|
57
|
+
}),
|
|
52
58
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormattedMessage, { text: displayContent }),
|
|
53
59
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
54
60
|
className: "float-right -mb-1 ml-2 mt-1.5 flex items-center justify-end gap-1 shrink-0 h-[15px] select-none",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
-
import { UIMessageStatus } from "better-zap";
|
|
3
|
+
import { LinkPreview, UIMessageStatus } from "better-zap";
|
|
4
4
|
|
|
5
5
|
//#region src/message-bubble.d.ts
|
|
6
6
|
type MessageBubbleGroupPosition = "single" | "first" | "middle" | "last";
|
|
@@ -17,6 +17,11 @@ interface MessageBubbleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
17
17
|
* WhatsApp's grouping. @default "single"
|
|
18
18
|
*/
|
|
19
19
|
groupPosition?: MessageBubbleGroupPosition;
|
|
20
|
+
/**
|
|
21
|
+
* Open Graph card shown inside the bubble, above the text. The bubble takes
|
|
22
|
+
* WhatsApp's fixed preview width while a card is shown.
|
|
23
|
+
*/
|
|
24
|
+
linkPreview?: LinkPreview | null;
|
|
20
25
|
}
|
|
21
26
|
declare function MessageBubble({
|
|
22
27
|
content,
|
|
@@ -26,6 +31,7 @@ declare function MessageBubble({
|
|
|
26
31
|
templateName,
|
|
27
32
|
label,
|
|
28
33
|
groupPosition,
|
|
34
|
+
linkPreview,
|
|
29
35
|
className,
|
|
30
36
|
...props
|
|
31
37
|
}: MessageBubbleProps): react_jsx_runtime0.JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
-
import { UIMessageStatus } from "better-zap";
|
|
3
|
+
import { LinkPreview, UIMessageStatus } from "better-zap";
|
|
4
4
|
|
|
5
5
|
//#region src/message-bubble.d.ts
|
|
6
6
|
type MessageBubbleGroupPosition = "single" | "first" | "middle" | "last";
|
|
@@ -17,6 +17,11 @@ interface MessageBubbleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
17
17
|
* WhatsApp's grouping. @default "single"
|
|
18
18
|
*/
|
|
19
19
|
groupPosition?: MessageBubbleGroupPosition;
|
|
20
|
+
/**
|
|
21
|
+
* Open Graph card shown inside the bubble, above the text. The bubble takes
|
|
22
|
+
* WhatsApp's fixed preview width while a card is shown.
|
|
23
|
+
*/
|
|
24
|
+
linkPreview?: LinkPreview | null;
|
|
20
25
|
}
|
|
21
26
|
declare function MessageBubble({
|
|
22
27
|
content,
|
|
@@ -26,6 +31,7 @@ declare function MessageBubble({
|
|
|
26
31
|
templateName,
|
|
27
32
|
label,
|
|
28
33
|
groupPosition,
|
|
34
|
+
linkPreview,
|
|
29
35
|
className,
|
|
30
36
|
...props
|
|
31
37
|
}: MessageBubbleProps): react_jsx_runtime0.JSX.Element;
|
package/dist/message-bubble.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cn } from "./utils.mjs";
|
|
2
2
|
import { Bubble, BubbleContent } from "./bubble.mjs";
|
|
3
|
+
import { LinkPreviewCard } from "./link-preview-card.mjs";
|
|
3
4
|
import { Message, MessageContent } from "./message.mjs";
|
|
4
5
|
import React from "react";
|
|
5
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -15,7 +16,7 @@ const statusVariants = cva("flex items-center leading-none", {
|
|
|
15
16
|
} },
|
|
16
17
|
defaultVariants: { variant: "default" }
|
|
17
18
|
});
|
|
18
|
-
function MessageBubble({ content, sender, timestamp, status, templateName, label, groupPosition = "single", className, ...props }) {
|
|
19
|
+
function MessageBubble({ content, sender, timestamp, status, templateName, label, groupPosition = "single", linkPreview, className, ...props }) {
|
|
19
20
|
const isIncoming = sender === "user";
|
|
20
21
|
const isFailed = status === "failed";
|
|
21
22
|
const align = sender === "user" ? "start" : "end";
|
|
@@ -33,6 +34,7 @@ function MessageBubble({ content, sender, timestamp, status, templateName, label
|
|
|
33
34
|
variant,
|
|
34
35
|
align,
|
|
35
36
|
tail: hasTail,
|
|
37
|
+
className: linkPreview ? "w-[330px]" : void 0,
|
|
36
38
|
children: /* @__PURE__ */ jsxs(BubbleContent, { children: [
|
|
37
39
|
label && !isIncoming && /* @__PURE__ */ jsx("span", {
|
|
38
40
|
className: "mb-1 block text-xs font-medium opacity-70",
|
|
@@ -48,6 +50,10 @@ function MessageBubble({ content, sender, timestamp, status, templateName, label
|
|
|
48
50
|
}), templateName]
|
|
49
51
|
})
|
|
50
52
|
}),
|
|
53
|
+
linkPreview && /* @__PURE__ */ jsx(LinkPreviewCard, {
|
|
54
|
+
preview: linkPreview,
|
|
55
|
+
variant
|
|
56
|
+
}),
|
|
51
57
|
/* @__PURE__ */ jsx(FormattedMessage, { text: displayContent }),
|
|
52
58
|
/* @__PURE__ */ jsxs("div", {
|
|
53
59
|
className: "float-right -mb-1 ml-2 mt-1.5 flex items-center justify-end gap-1 shrink-0 h-[15px] select-none",
|
package/dist/message-view.cjs
CHANGED
|
@@ -116,8 +116,19 @@ function MessageViewContent({ children, autoScroll = true, onScrollTop, classNam
|
|
|
116
116
|
})
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
const MemoizedMessageBubble = react.default.memo(require_message_bubble.MessageBubble);
|
|
119
|
+
const MemoizedMessageBubble = react.default.memo(require_message_bubble.MessageBubble, areMessageBubblePropsEqual);
|
|
120
120
|
const MemoizedDateDivider = react.default.memo(DateDivider);
|
|
121
|
+
function areMessageBubblePropsEqual(prev, next) {
|
|
122
|
+
const { linkPreview: prevPreview, ...prevRest } = prev;
|
|
123
|
+
const { linkPreview: nextPreview, ...nextRest } = next;
|
|
124
|
+
return (prevPreview && nextPreview ? shallowEqual(prevPreview, nextPreview) : !prevPreview && !nextPreview) && shallowEqual(prevRest, nextRest);
|
|
125
|
+
}
|
|
126
|
+
function shallowEqual(a, b) {
|
|
127
|
+
const left = a;
|
|
128
|
+
const right = b;
|
|
129
|
+
const keys = Object.keys(left);
|
|
130
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.is(left[key], right[key]));
|
|
131
|
+
}
|
|
121
132
|
function defaultFormatTime(isoDate) {
|
|
122
133
|
return new Date(isoDate).toLocaleTimeString("pt-BR", {
|
|
123
134
|
hour: "2-digit",
|
|
@@ -136,7 +147,7 @@ function getGroupPosition(messages, index, resolveFormatDate) {
|
|
|
136
147
|
if (prevSame && nextSame) return "middle";
|
|
137
148
|
return "last";
|
|
138
149
|
}
|
|
139
|
-
function MessageList({ messages, renderMessage, renderDateDivider, formatDate, formatTime, renderMessageLabel, autoScroll: autoScrollProp, onScrollTop: onScrollTopProp, className }) {
|
|
150
|
+
function MessageList({ messages, renderMessage, renderDateDivider, formatDate, formatTime, renderMessageLabel, resolveLinkPreview, autoScroll: autoScrollProp, onScrollTop: onScrollTopProp, className }) {
|
|
140
151
|
const scrollContext = (0, react.useContext)(MessageViewScrollContext);
|
|
141
152
|
const resolvedAutoScroll = autoScrollProp ?? scrollContext?.autoScroll ?? true;
|
|
142
153
|
const resolvedOnScrollTop = onScrollTopProp ?? scrollContext?.onScrollTop;
|
|
@@ -180,6 +191,7 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
180
191
|
contentContainerStyle: { paddingBottom: 16 },
|
|
181
192
|
data: items,
|
|
182
193
|
estimatedItemSize: 72,
|
|
194
|
+
extraData: resolveLinkPreview,
|
|
183
195
|
getItemType: (item) => item.type,
|
|
184
196
|
initialScrollAtEnd: resolvedAutoScroll,
|
|
185
197
|
keyExtractor: (item) => item.id,
|
|
@@ -195,6 +207,7 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
195
207
|
}) ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedDateDivider, { children: item.label });
|
|
196
208
|
const { message, groupPosition } = item;
|
|
197
209
|
const label = renderMessageLabel?.(message);
|
|
210
|
+
const linkPreview = resolveLinkPreview?.(message) ?? void 0;
|
|
198
211
|
const direction = message.direction;
|
|
199
212
|
const align = direction === "incoming" ? "start" : "end";
|
|
200
213
|
const ctx = {
|
|
@@ -203,7 +216,8 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
203
216
|
direction,
|
|
204
217
|
align,
|
|
205
218
|
groupPosition,
|
|
206
|
-
label
|
|
219
|
+
label,
|
|
220
|
+
linkPreview
|
|
207
221
|
};
|
|
208
222
|
return renderMessage?.(ctx) ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedMessageBubble, {
|
|
209
223
|
content: message.content || "",
|
|
@@ -212,7 +226,8 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
212
226
|
status: message.status,
|
|
213
227
|
templateName: message.templateName || void 0,
|
|
214
228
|
label,
|
|
215
|
-
groupPosition
|
|
229
|
+
groupPosition,
|
|
230
|
+
linkPreview
|
|
216
231
|
});
|
|
217
232
|
},
|
|
218
233
|
stickyHeaderIndices,
|
package/dist/message-view.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
-
import { Conversation, UIMessage } from "better-zap";
|
|
3
|
+
import { Conversation, LinkPreview, UIMessage } from "better-zap";
|
|
4
4
|
|
|
5
5
|
//#region src/message-view.d.ts
|
|
6
6
|
interface MessageViewProps extends React.ComponentProps<"div"> {
|
|
@@ -63,6 +63,8 @@ interface MessageRenderContext {
|
|
|
63
63
|
groupPosition: MessageGroupPosition;
|
|
64
64
|
/** Result of renderMessageLabel, when provided. */
|
|
65
65
|
label?: string;
|
|
66
|
+
/** Result of resolveLinkPreview, when it returned a preview. */
|
|
67
|
+
linkPreview?: LinkPreview;
|
|
66
68
|
}
|
|
67
69
|
interface DateDividerRenderContext {
|
|
68
70
|
/** Formatted label (output of formatDate / getDisplayDate). */
|
|
@@ -79,6 +81,13 @@ interface MessageListProps {
|
|
|
79
81
|
/** Formats the in-bubble timestamp used by the DEFAULT renderer. Default: pt-BR HH:mm. */
|
|
80
82
|
formatTime?: (isoDate: string) => string;
|
|
81
83
|
renderMessageLabel?: (message: UIMessage) => string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Returns the Open Graph card for a message, shown by the default renderer
|
|
86
|
+
* and passed to `renderMessage` as `linkPreview`. Must be synchronous: keep
|
|
87
|
+
* fetched previews in state and pass a new function (e.g. from
|
|
88
|
+
* `useCallback`) when they change; rows re-render when its identity changes.
|
|
89
|
+
*/
|
|
90
|
+
resolveLinkPreview?: (message: UIMessage) => LinkPreview | null | undefined;
|
|
82
91
|
/** Direct props — context from MessageViewContent still works; these win over it. */
|
|
83
92
|
autoScroll?: boolean;
|
|
84
93
|
onScrollTop?: () => void;
|
|
@@ -91,6 +100,7 @@ declare function MessageList({
|
|
|
91
100
|
formatDate,
|
|
92
101
|
formatTime,
|
|
93
102
|
renderMessageLabel,
|
|
103
|
+
resolveLinkPreview,
|
|
94
104
|
autoScroll: autoScrollProp,
|
|
95
105
|
onScrollTop: onScrollTopProp,
|
|
96
106
|
className
|
package/dist/message-view.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
-
import { Conversation, UIMessage } from "better-zap";
|
|
3
|
+
import { Conversation, LinkPreview, UIMessage } from "better-zap";
|
|
4
4
|
|
|
5
5
|
//#region src/message-view.d.ts
|
|
6
6
|
interface MessageViewProps extends React.ComponentProps<"div"> {
|
|
@@ -63,6 +63,8 @@ interface MessageRenderContext {
|
|
|
63
63
|
groupPosition: MessageGroupPosition;
|
|
64
64
|
/** Result of renderMessageLabel, when provided. */
|
|
65
65
|
label?: string;
|
|
66
|
+
/** Result of resolveLinkPreview, when it returned a preview. */
|
|
67
|
+
linkPreview?: LinkPreview;
|
|
66
68
|
}
|
|
67
69
|
interface DateDividerRenderContext {
|
|
68
70
|
/** Formatted label (output of formatDate / getDisplayDate). */
|
|
@@ -79,6 +81,13 @@ interface MessageListProps {
|
|
|
79
81
|
/** Formats the in-bubble timestamp used by the DEFAULT renderer. Default: pt-BR HH:mm. */
|
|
80
82
|
formatTime?: (isoDate: string) => string;
|
|
81
83
|
renderMessageLabel?: (message: UIMessage) => string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Returns the Open Graph card for a message, shown by the default renderer
|
|
86
|
+
* and passed to `renderMessage` as `linkPreview`. Must be synchronous: keep
|
|
87
|
+
* fetched previews in state and pass a new function (e.g. from
|
|
88
|
+
* `useCallback`) when they change; rows re-render when its identity changes.
|
|
89
|
+
*/
|
|
90
|
+
resolveLinkPreview?: (message: UIMessage) => LinkPreview | null | undefined;
|
|
82
91
|
/** Direct props — context from MessageViewContent still works; these win over it. */
|
|
83
92
|
autoScroll?: boolean;
|
|
84
93
|
onScrollTop?: () => void;
|
|
@@ -91,6 +100,7 @@ declare function MessageList({
|
|
|
91
100
|
formatDate,
|
|
92
101
|
formatTime,
|
|
93
102
|
renderMessageLabel,
|
|
103
|
+
resolveLinkPreview,
|
|
94
104
|
autoScroll: autoScrollProp,
|
|
95
105
|
onScrollTop: onScrollTopProp,
|
|
96
106
|
className
|
package/dist/message-view.mjs
CHANGED
|
@@ -114,8 +114,19 @@ function MessageViewContent({ children, autoScroll = true, onScrollTop, classNam
|
|
|
114
114
|
})
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
-
const MemoizedMessageBubble = React.memo(MessageBubble);
|
|
117
|
+
const MemoizedMessageBubble = React.memo(MessageBubble, areMessageBubblePropsEqual);
|
|
118
118
|
const MemoizedDateDivider = React.memo(DateDivider);
|
|
119
|
+
function areMessageBubblePropsEqual(prev, next) {
|
|
120
|
+
const { linkPreview: prevPreview, ...prevRest } = prev;
|
|
121
|
+
const { linkPreview: nextPreview, ...nextRest } = next;
|
|
122
|
+
return (prevPreview && nextPreview ? shallowEqual(prevPreview, nextPreview) : !prevPreview && !nextPreview) && shallowEqual(prevRest, nextRest);
|
|
123
|
+
}
|
|
124
|
+
function shallowEqual(a, b) {
|
|
125
|
+
const left = a;
|
|
126
|
+
const right = b;
|
|
127
|
+
const keys = Object.keys(left);
|
|
128
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.is(left[key], right[key]));
|
|
129
|
+
}
|
|
119
130
|
function defaultFormatTime(isoDate) {
|
|
120
131
|
return new Date(isoDate).toLocaleTimeString("pt-BR", {
|
|
121
132
|
hour: "2-digit",
|
|
@@ -134,7 +145,7 @@ function getGroupPosition(messages, index, resolveFormatDate) {
|
|
|
134
145
|
if (prevSame && nextSame) return "middle";
|
|
135
146
|
return "last";
|
|
136
147
|
}
|
|
137
|
-
function MessageList({ messages, renderMessage, renderDateDivider, formatDate, formatTime, renderMessageLabel, autoScroll: autoScrollProp, onScrollTop: onScrollTopProp, className }) {
|
|
148
|
+
function MessageList({ messages, renderMessage, renderDateDivider, formatDate, formatTime, renderMessageLabel, resolveLinkPreview, autoScroll: autoScrollProp, onScrollTop: onScrollTopProp, className }) {
|
|
138
149
|
const scrollContext = useContext(MessageViewScrollContext);
|
|
139
150
|
const resolvedAutoScroll = autoScrollProp ?? scrollContext?.autoScroll ?? true;
|
|
140
151
|
const resolvedOnScrollTop = onScrollTopProp ?? scrollContext?.onScrollTop;
|
|
@@ -178,6 +189,7 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
178
189
|
contentContainerStyle: { paddingBottom: 16 },
|
|
179
190
|
data: items,
|
|
180
191
|
estimatedItemSize: 72,
|
|
192
|
+
extraData: resolveLinkPreview,
|
|
181
193
|
getItemType: (item) => item.type,
|
|
182
194
|
initialScrollAtEnd: resolvedAutoScroll,
|
|
183
195
|
keyExtractor: (item) => item.id,
|
|
@@ -193,6 +205,7 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
193
205
|
}) ?? /* @__PURE__ */ jsx(MemoizedDateDivider, { children: item.label });
|
|
194
206
|
const { message, groupPosition } = item;
|
|
195
207
|
const label = renderMessageLabel?.(message);
|
|
208
|
+
const linkPreview = resolveLinkPreview?.(message) ?? void 0;
|
|
196
209
|
const direction = message.direction;
|
|
197
210
|
const align = direction === "incoming" ? "start" : "end";
|
|
198
211
|
const ctx = {
|
|
@@ -201,7 +214,8 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
201
214
|
direction,
|
|
202
215
|
align,
|
|
203
216
|
groupPosition,
|
|
204
|
-
label
|
|
217
|
+
label,
|
|
218
|
+
linkPreview
|
|
205
219
|
};
|
|
206
220
|
return renderMessage?.(ctx) ?? /* @__PURE__ */ jsx(MemoizedMessageBubble, {
|
|
207
221
|
content: message.content || "",
|
|
@@ -210,7 +224,8 @@ function MessageList({ messages, renderMessage, renderDateDivider, formatDate, f
|
|
|
210
224
|
status: message.status,
|
|
211
225
|
templateName: message.templateName || void 0,
|
|
212
226
|
label,
|
|
213
|
-
groupPosition
|
|
227
|
+
groupPosition,
|
|
228
|
+
linkPreview
|
|
214
229
|
});
|
|
215
230
|
},
|
|
216
231
|
stickyHeaderIndices,
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-zap/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "25.0.1",
|
|
4
4
|
"description": "React components for Better Zap.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/Dosbodoke/better-zap",
|
|
8
|
+
"url": "git+https://github.com/Dosbodoke/better-zap.git",
|
|
9
9
|
"directory": "packages/react"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
@@ -19,54 +19,114 @@
|
|
|
19
19
|
],
|
|
20
20
|
"exports": {
|
|
21
21
|
".": {
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/index.d.mts",
|
|
24
|
+
"default": "./dist/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/index.d.cts",
|
|
28
|
+
"default": "./dist/index.cjs"
|
|
29
|
+
}
|
|
25
30
|
},
|
|
26
31
|
"./bubble": {
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/bubble.d.mts",
|
|
34
|
+
"default": "./dist/bubble.mjs"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/bubble.d.cts",
|
|
38
|
+
"default": "./dist/bubble.cjs"
|
|
39
|
+
}
|
|
30
40
|
},
|
|
31
41
|
"./message": {
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
"import": {
|
|
43
|
+
"types": "./dist/message.d.mts",
|
|
44
|
+
"default": "./dist/message.mjs"
|
|
45
|
+
},
|
|
46
|
+
"require": {
|
|
47
|
+
"types": "./dist/message.d.cts",
|
|
48
|
+
"default": "./dist/message.cjs"
|
|
49
|
+
}
|
|
35
50
|
},
|
|
36
51
|
"./message-bubble": {
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
"import": {
|
|
53
|
+
"types": "./dist/message-bubble.d.mts",
|
|
54
|
+
"default": "./dist/message-bubble.mjs"
|
|
55
|
+
},
|
|
56
|
+
"require": {
|
|
57
|
+
"types": "./dist/message-bubble.d.cts",
|
|
58
|
+
"default": "./dist/message-bubble.cjs"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"./link-preview-card": {
|
|
62
|
+
"import": {
|
|
63
|
+
"types": "./dist/link-preview-card.d.mts",
|
|
64
|
+
"default": "./dist/link-preview-card.mjs"
|
|
65
|
+
},
|
|
66
|
+
"require": {
|
|
67
|
+
"types": "./dist/link-preview-card.d.cts",
|
|
68
|
+
"default": "./dist/link-preview-card.cjs"
|
|
69
|
+
}
|
|
40
70
|
},
|
|
41
71
|
"./composer": {
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
"import": {
|
|
73
|
+
"types": "./dist/composer.d.mts",
|
|
74
|
+
"default": "./dist/composer.mjs"
|
|
75
|
+
},
|
|
76
|
+
"require": {
|
|
77
|
+
"types": "./dist/composer.d.cts",
|
|
78
|
+
"default": "./dist/composer.cjs"
|
|
79
|
+
}
|
|
45
80
|
},
|
|
46
81
|
"./message-input": {
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
82
|
+
"import": {
|
|
83
|
+
"types": "./dist/message-input.d.mts",
|
|
84
|
+
"default": "./dist/message-input.mjs"
|
|
85
|
+
},
|
|
86
|
+
"require": {
|
|
87
|
+
"types": "./dist/message-input.d.cts",
|
|
88
|
+
"default": "./dist/message-input.cjs"
|
|
89
|
+
}
|
|
50
90
|
},
|
|
51
91
|
"./message-view": {
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
92
|
+
"import": {
|
|
93
|
+
"types": "./dist/message-view.d.mts",
|
|
94
|
+
"default": "./dist/message-view.mjs"
|
|
95
|
+
},
|
|
96
|
+
"require": {
|
|
97
|
+
"types": "./dist/message-view.d.cts",
|
|
98
|
+
"default": "./dist/message-view.cjs"
|
|
99
|
+
}
|
|
55
100
|
},
|
|
56
101
|
"./conversation-list": {
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
"import": {
|
|
103
|
+
"types": "./dist/conversation-list.d.mts",
|
|
104
|
+
"default": "./dist/conversation-list.mjs"
|
|
105
|
+
},
|
|
106
|
+
"require": {
|
|
107
|
+
"types": "./dist/conversation-list.d.cts",
|
|
108
|
+
"default": "./dist/conversation-list.cjs"
|
|
109
|
+
}
|
|
60
110
|
},
|
|
61
111
|
"./whatsapp-dashboard": {
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
112
|
+
"import": {
|
|
113
|
+
"types": "./dist/whatsapp-dashboard.d.mts",
|
|
114
|
+
"default": "./dist/whatsapp-dashboard.mjs"
|
|
115
|
+
},
|
|
116
|
+
"require": {
|
|
117
|
+
"types": "./dist/whatsapp-dashboard.d.cts",
|
|
118
|
+
"default": "./dist/whatsapp-dashboard.cjs"
|
|
119
|
+
}
|
|
65
120
|
},
|
|
66
121
|
"./utils": {
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
122
|
+
"import": {
|
|
123
|
+
"types": "./dist/utils.d.mts",
|
|
124
|
+
"default": "./dist/utils.mjs"
|
|
125
|
+
},
|
|
126
|
+
"require": {
|
|
127
|
+
"types": "./dist/utils.d.cts",
|
|
128
|
+
"default": "./dist/utils.cjs"
|
|
129
|
+
}
|
|
70
130
|
},
|
|
71
131
|
"./tailwind.css": "./dist/tailwind.css",
|
|
72
132
|
"./package.json": "./package.json"
|
|
@@ -78,7 +138,9 @@
|
|
|
78
138
|
"node": ">=20"
|
|
79
139
|
},
|
|
80
140
|
"publishConfig": {
|
|
81
|
-
"access": "public"
|
|
141
|
+
"access": "public",
|
|
142
|
+
"registry": "https://registry.npmjs.org/",
|
|
143
|
+
"provenance": true
|
|
82
144
|
},
|
|
83
145
|
"dependencies": {
|
|
84
146
|
"@hugeicons/core-free-icons": "^3.1.1",
|
|
@@ -87,7 +149,7 @@
|
|
|
87
149
|
"class-variance-authority": "^0.7.1",
|
|
88
150
|
"clsx": "^2.1.1",
|
|
89
151
|
"tailwind-merge": "^3.0.0",
|
|
90
|
-
"better-zap": "0.
|
|
152
|
+
"better-zap": "25.0.1"
|
|
91
153
|
},
|
|
92
154
|
"peerDependencies": {
|
|
93
155
|
"react": "^19.0.0",
|
|
@@ -106,6 +168,7 @@
|
|
|
106
168
|
"scripts": {
|
|
107
169
|
"build": "tsdown",
|
|
108
170
|
"typecheck": "tsc --noEmit",
|
|
109
|
-
"test": "pnpm run build && vitest run"
|
|
171
|
+
"test": "pnpm run build && vitest run",
|
|
172
|
+
"package:check": "publint --strict && attw --pack --profile node16 --exclude-entrypoints @better-zap/react/tailwind.css"
|
|
110
173
|
}
|
|
111
174
|
}
|