@linktr.ee/messaging-react 1.40.2 → 2.0.1-rc-1778656305

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.
Files changed (61) hide show
  1. package/dist/Card-CAC3fPjy.js +107 -0
  2. package/dist/Card-CAC3fPjy.js.map +1 -0
  3. package/dist/Card-DLUBUg_w.js +132 -0
  4. package/dist/Card-DLUBUg_w.js.map +1 -0
  5. package/dist/Card-_StSlnYh.js +163 -0
  6. package/dist/Card-_StSlnYh.js.map +1 -0
  7. package/dist/LockedThumbnail-p5RsFOug.js +220 -0
  8. package/dist/LockedThumbnail-p5RsFOug.js.map +1 -0
  9. package/dist/assets/index.css +1 -1
  10. package/dist/index-B1h46F9x.js +3092 -0
  11. package/dist/index-B1h46F9x.js.map +1 -0
  12. package/dist/index.d.ts +109 -30
  13. package/dist/index.js +14 -12
  14. package/package.json +2 -2
  15. package/src/components/ChannelInfoDialog/ChannelInfoDialog.test.tsx +2 -14
  16. package/src/components/ChannelInfoDialog/index.tsx +4 -8
  17. package/src/components/ChannelList/ChannelListContext.tsx +2 -0
  18. package/src/components/ChannelList/CustomChannelPreview.tsx +14 -3
  19. package/src/components/ChannelList/index.tsx +9 -1
  20. package/src/components/ChannelView.test.tsx +11 -0
  21. package/src/components/ChannelView.tsx +44 -33
  22. package/src/components/CustomMessage/index.tsx +24 -7
  23. package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +57 -17
  24. package/src/components/CustomTypingIndicator/CustomTypingIndicator.test.tsx +187 -0
  25. package/src/components/CustomTypingIndicator/DmAgentContext.ts +3 -0
  26. package/src/components/CustomTypingIndicator/index.tsx +101 -37
  27. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +230 -89
  28. package/src/components/LockedAttachment/components/Composer/Card.tsx +221 -0
  29. package/src/components/LockedAttachment/components/Composer/index.ts +2 -0
  30. package/src/components/LockedAttachment/components/Received/Card.tsx +191 -0
  31. package/src/components/LockedAttachment/components/Received/CardActions.tsx +91 -0
  32. package/src/components/LockedAttachment/components/Received/index.ts +2 -0
  33. package/src/components/LockedAttachment/components/Sent/Card.tsx +177 -0
  34. package/src/components/LockedAttachment/components/Sent/index.ts +2 -0
  35. package/src/components/LockedAttachment/components/_shared/CardBody.tsx +94 -0
  36. package/src/components/LockedAttachment/components/_shared/GalleryThumbnail.tsx +178 -0
  37. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +39 -0
  38. package/src/components/LockedAttachment/components/_shared/LockedCardShell.tsx +36 -0
  39. package/src/components/LockedAttachment/components/_shared/LockedThumbnail.tsx +128 -0
  40. package/src/components/LockedAttachment/index.tsx +43 -12
  41. package/src/components/LockedAttachment/types.ts +17 -0
  42. package/src/components/MediaMessage/index.tsx +2 -2
  43. package/src/components/MessagingShell/index.tsx +4 -4
  44. package/src/index.ts +18 -2
  45. package/src/stories/mocks.tsx +2 -9
  46. package/src/styles.css +7 -0
  47. package/src/types.ts +11 -1
  48. package/src/utils/getMessageDisplayText.test.ts +44 -0
  49. package/src/utils/getMessageDisplayText.ts +27 -0
  50. package/dist/Card-A0lkei-S.js +0 -138
  51. package/dist/Card-A0lkei-S.js.map +0 -1
  52. package/dist/Card-DXoAKkv0.js +0 -127
  53. package/dist/Card-DXoAKkv0.js.map +0 -1
  54. package/dist/index-B_PLgcDi.js +0 -2994
  55. package/dist/index-B_PLgcDi.js.map +0 -1
  56. package/src/components/LockedAttachment/components/Creator/Card.tsx +0 -210
  57. package/src/components/LockedAttachment/components/Creator/index.tsx +0 -2
  58. package/src/components/LockedAttachment/components/Visitor/Card.tsx +0 -155
  59. package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +0 -62
  60. package/src/components/LockedAttachment/components/Visitor/LockBadge.tsx +0 -12
  61. package/src/components/LockedAttachment/components/Visitor/index.ts +0 -2
package/dist/index.d.ts CHANGED
@@ -101,6 +101,11 @@ export declare interface ChannelListProps {
101
101
  sort?: ChannelSort;
102
102
  customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>;
103
103
  renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
104
+ /**
105
+ * Language code used to pick translated message text from Stream Chat i18n.
106
+ * Falls back to message.text when no matching translation exists.
107
+ */
108
+ viewerLanguage?: string;
104
109
  }
105
110
 
106
111
  /**
@@ -112,7 +117,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
112
117
  * Props that MessagingShell passes through to ChannelView.
113
118
  * ChannelViewProps is the source of truth for these props.
114
119
  */
115
- declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'sendButton'>;
120
+ declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'sendButton'>;
116
121
 
117
122
  /**
118
123
  * ChannelView component props
@@ -185,6 +190,11 @@ export declare interface ChannelViewProps {
185
190
  * Defaults to false.
186
191
  */
187
192
  chatbotVotingEnabled?: boolean;
193
+ /**
194
+ * Language code used to pick translated message text from Stream Chat i18n.
195
+ * Falls back to message.text when no matching translation exists.
196
+ */
197
+ viewerLanguage?: string;
188
198
  /**
189
199
  * Custom render function for a banner/card component that renders
190
200
  * between the channel header and message list.
@@ -231,15 +241,38 @@ export declare interface ChannelViewProps {
231
241
  sendButton?: ComponentType<any>;
232
242
  }
233
243
 
234
- export declare interface CreatorCardProps extends LockedAttachmentBaseProps {
244
+ export declare interface ComposerCardProps extends LockedAttachmentBaseProps {
245
+ /** Placeholder shown in the title slot before the composer types one. */
235
246
  placeholderTitle?: string;
247
+ /** Placeholder shown in the amount slot before one is configured. */
236
248
  placeholderAmountText?: string;
237
- isUnlocking?: boolean;
249
+ /**
250
+ * When provided, renders a dismiss X in the thumbnail corner. Called when
251
+ * the composer clicks it to remove the attachment.
252
+ */
238
253
  onDismiss?: () => void;
254
+ /** Fired the first time the composer taps the thumbnail to preview. */
239
255
  onPreviewClick?: () => void;
256
+ /**
257
+ * Lazily loads the underlying source so the composer can preview the
258
+ * attachment they're about to send. Called the first time the thumbnail is
259
+ * tapped; the returned source is cached and reused on subsequent toggles.
260
+ */
240
261
  onFetchSource?: () => Promise<LockedAttachmentSource | void>;
262
+ /**
263
+ * When provided, renders a pencil button in the body bottom-right that the
264
+ * composer can use to edit the attachment metadata (e.g. open the price /
265
+ * gallery editor). Matches the Composer "Button" instance in Figma.
266
+ */
267
+ onEditClick?: () => void;
241
268
  }
242
269
 
270
+ /**
271
+ * @deprecated Renamed to `SentCardProps`. Drafting usages (with `onDismiss`)
272
+ * should migrate to `ComposerCardProps`.
273
+ */
274
+ export declare type CreatorCardProps = SentCardProps;
275
+
243
276
  export declare const CustomMessageProvider: Provider<Partial<CustomMessageRegistry>>;
244
277
 
245
278
  export declare interface CustomMessageRegistry {
@@ -283,11 +316,19 @@ export declare interface FaqListProps {
283
316
  */
284
317
  export declare const formatRelativeTime: (date: Date) => string;
285
318
 
319
+ export declare function getMessageDisplayText({ message, viewerLanguage, }: {
320
+ message?: MessageWithI18n | null;
321
+ viewerLanguage?: string;
322
+ }): string | undefined;
323
+
286
324
  export declare function isLinkAttachment(a: Attachment): boolean;
287
325
 
288
326
  export declare const LockedAttachment: {
289
- Creator: (props: CreatorCardProps) => JSX_2.Element;
290
- Visitor: (props: VisitorCardProps) => JSX_2.Element;
327
+ Composer: (props: ComposerCardProps) => JSX_2.Element;
328
+ Sent: (props: SentCardProps) => JSX_2.Element;
329
+ Received: (props: ReceivedCardProps) => JSX_2.Element;
330
+ Creator: (props: SentCardProps) => JSX_2.Element;
331
+ Visitor: (props: ReceivedCardProps) => JSX_2.Element;
291
332
  };
292
333
 
293
334
  declare interface LockedAttachmentBaseProps {
@@ -297,6 +338,14 @@ declare interface LockedAttachmentBaseProps {
297
338
  detail?: string;
298
339
  amountText?: string;
299
340
  paymentStatus?: PaymentStatus;
341
+ /**
342
+ * When provided with 2+ items, the card renders as a mixed-media carousel
343
+ * (e.g. a couple of photos + a video) instead of a single thumbnail. Each
344
+ * item brings its own thumbnail and optional source so that
345
+ * `LockedAttachment.Composer` / `.Sent` / `.Received` can all share the
346
+ * same carousel chrome.
347
+ */
348
+ gallery?: LockedAttachmentGalleryItem[];
300
349
  }
301
350
 
302
351
  export declare interface LockedAttachmentContextValue {
@@ -306,6 +355,15 @@ export declare interface LockedAttachmentContextValue {
306
355
  onFetchSource?: (message: LocalMessage, channel: Channel) => Promise<LockedAttachmentSource | void>;
307
356
  }
308
357
 
358
+ export declare interface LockedAttachmentGalleryItem {
359
+ /** MIME type of this carousel item — drives the per-item play / lock affordance. */
360
+ mimeType: string;
361
+ /** Poster image used for both the locked (blurred) and unlocked preview state. */
362
+ thumbnailUrl?: string;
363
+ /** Underlying source (image or video URL) — shown only when unlocked. */
364
+ sourceUrl?: string;
365
+ }
366
+
309
367
  export declare interface LockedAttachmentSource {
310
368
  /** Proxied URL used by the media player for in-app playback. */
311
369
  sourceUrl: string;
@@ -358,6 +416,10 @@ declare interface MessageVoteButtonsProps {
358
416
  onVoteDown: () => void;
359
417
  }
360
418
 
419
+ declare type MessageWithI18n = Pick<LocalMessage, 'text'> & {
420
+ i18n?: Record<string, string> | null;
421
+ };
422
+
361
423
  /**
362
424
  * Messaging capabilities configuration
363
425
  */
@@ -462,13 +524,14 @@ export declare interface MessagingShellProps extends ChannelViewPassthroughProps
462
524
  renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
463
525
  }
464
526
 
527
+ export declare function normalizeLanguageCode(language?: string): string | undefined;
528
+
465
529
  /**
466
530
  * Generic participant interface for different host environments
467
531
  */
468
532
  export declare interface Participant {
469
533
  id: string;
470
534
  name: string;
471
- email?: string;
472
535
  image?: string;
473
536
  username?: string;
474
537
  phone?: string;
@@ -481,10 +544,48 @@ export declare interface Participant {
481
544
  */
482
545
  declare type PaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded';
483
546
 
547
+ export declare interface ReceivedCardProps extends LockedAttachmentBaseProps {
548
+ /**
549
+ * Called when the recipient clicks Unlock on an unpaid attachment.
550
+ * Use this to open a checkout flow. Omit to hide the Unlock button.
551
+ */
552
+ onUnlockClick?: () => void;
553
+ /**
554
+ * Called to fetch the attachment source — fired automatically when
555
+ * `paymentStatus` transitions to `'paid'`, or immediately on click when
556
+ * `paymentStatus` is already `'paid'`. Return a `LockedAttachmentSource`
557
+ * to unlock the card.
558
+ */
559
+ onFetchSource?: () => Promise<LockedAttachmentSource | void>;
560
+ /**
561
+ * Called when the recipient clicks Download on an unlocked card.
562
+ * Omit to hide the Download button.
563
+ */
564
+ onDownloadClick?: () => void;
565
+ /**
566
+ * When true, shows a loading spinner on the Unlock button.
567
+ * Driven by the LockedAttachmentContext (e.g. checkout in progress).
568
+ */
569
+ isUnlocking?: boolean;
570
+ }
571
+
484
572
  export declare function resolveLinkAttachment(message: LocalMessage): Attachment | undefined;
485
573
 
486
574
  export declare function resolveMediaFromMessage(message: LocalMessage): MediaMessageResolved | null;
487
575
 
576
+ export declare interface SentCardProps extends LockedAttachmentBaseProps {
577
+ /** Placeholder shown in the title slot when no title is set. */
578
+ placeholderTitle?: string;
579
+ /** Fired the first time the sender taps the thumbnail to preview their own attachment. */
580
+ onPreviewClick?: () => void;
581
+ /**
582
+ * Lazily loads the underlying source so the sender can preview the attachment.
583
+ * Called the first time the thumbnail is tapped; the returned source is cached
584
+ * and reused on subsequent toggles.
585
+ */
586
+ onFetchSource?: () => Promise<LockedAttachmentSource | void>;
587
+ }
588
+
488
589
  export declare function useCustomMessage<K extends keyof CustomMessageRegistry>(key: K): CustomMessageRegistry[K];
489
590
 
490
591
  /**
@@ -508,30 +609,8 @@ declare interface UseMessageVoteResult {
508
609
  */
509
610
  export declare const useMessaging: () => MessagingContextValue;
510
611
 
511
- export declare interface VisitorCardProps extends LockedAttachmentBaseProps {
512
- /**
513
- * Called when the visitor clicks Unlock on an unpaid attachment.
514
- * Use this to open a checkout flow. Omit to hide the Unlock button.
515
- */
516
- onUnlockClick?: () => void;
517
- /**
518
- * Called to fetch the attachment source — fired automatically when
519
- * paymentStatus transitions to 'paid', or immediately on click when
520
- * paymentStatus is already 'paid'. Return a LockedAttachmentSource to
521
- * unlock the card.
522
- */
523
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
524
- /**
525
- * Called when the visitor clicks Download on an unlocked card.
526
- * Omit to hide the Download button.
527
- */
528
- onDownloadClick?: () => void;
529
- /**
530
- * When true, shows loading dots on the Unlock button.
531
- * Driven by the LockedAttachmentContext (e.g. checkout in progress, payment processing).
532
- */
533
- isUnlocking?: boolean;
534
- }
612
+ /** @deprecated Renamed to `ReceivedCardProps`. */
613
+ export declare type VisitorCardProps = ReceivedCardProps;
535
614
 
536
615
  export declare type VoteSelection = 'up' | 'down' | null;
537
616
 
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { b as e, c as t, C as i, d as n, e as o, f as g, F as m, g as r, L as M, M as l, h, i as u, j as L, k as c, l as d, r as v, m as C, u as A, n as k, o as F } from "./index-B_PLgcDi.js";
1
+ import { a as e, b as t, C as i, c as n, d as o, e as g, F as m, f as M, L as r, M as l, h as u, i as h, j as L, k as d, l as C, m as c, n as v, o as A, p as k, u as p, q as F, s as f } from "./index-B1h46F9x.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
@@ -7,18 +7,20 @@ export {
7
7
  o as ChannelView,
8
8
  g as CustomMessageProvider,
9
9
  m as FaqList,
10
- r as FaqListItem,
11
- M as LockedAttachment,
10
+ M as FaqListItem,
11
+ r as LockedAttachment,
12
12
  l as MediaMessage,
13
- h as MessageVoteButtons,
14
- u as MessagingProvider,
13
+ u as MessageVoteButtons,
14
+ h as MessagingProvider,
15
15
  L as MessagingShell,
16
- c as formatRelativeTime,
17
- d as isLinkAttachment,
18
- v as resolveLinkAttachment,
19
- C as resolveMediaFromMessage,
20
- A as useCustomMessage,
21
- k as useMessageVote,
22
- F as useMessaging
16
+ d as formatRelativeTime,
17
+ C as getMessageDisplayText,
18
+ c as isLinkAttachment,
19
+ v as normalizeLanguageCode,
20
+ A as resolveLinkAttachment,
21
+ k as resolveMediaFromMessage,
22
+ p as useCustomMessage,
23
+ F as useMessageVote,
24
+ f as useMessaging
23
25
  };
24
26
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "1.40.2",
3
+ "version": "2.0.1-rc-1778656305",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@linktr.ee/component-library": "11.8.6",
37
- "@linktr.ee/messaging-core": "^1.8.0",
37
+ "@linktr.ee/messaging-core": "^2.0.0",
38
38
  "@phosphor-icons/react": "^2.1.10"
39
39
  },
40
40
  "devDependencies": {
@@ -65,14 +65,13 @@ const createChannel = () =>
65
65
  }) as unknown as Channel
66
66
 
67
67
  const createParticipant = (
68
- overrides: Partial<{ name: string; email: string; username: string }> = {}
68
+ overrides: Partial<{ name: string; username: string }> = {}
69
69
  ) =>
70
70
  ({
71
71
  user: {
72
72
  id: 'linker-1',
73
73
  name: overrides.name ?? 'Linker',
74
74
  image: undefined,
75
- email: overrides.email,
76
75
  username: overrides.username,
77
76
  },
78
77
  role: 'member',
@@ -99,18 +98,7 @@ describe('ChannelInfoDialog', () => {
99
98
  expect(nameEl).toBeInTheDocument()
100
99
  })
101
100
 
102
- it('renders participant email as secondary info', () => {
103
- renderWithProviders(
104
- <ChannelInfoDialog
105
- {...defaultProps()}
106
- participant={createParticipant({ email: 'linker@example.com' })}
107
- />
108
- )
109
-
110
- expect(screen.getByText('linker@example.com')).toBeInTheDocument()
111
- })
112
-
113
- it('renders participant username as secondary info when no email', () => {
101
+ it('renders participant username as secondary info', () => {
114
102
  renderWithProviders(
115
103
  <ChannelInfoDialog
116
104
  {...defaultProps()}
@@ -12,9 +12,8 @@ import ActionButton from '../ActionButton'
12
12
  import { Avatar } from '../Avatar'
13
13
  import { CloseButton } from '../CloseButton'
14
14
 
15
- // Custom user type with email and username
15
+ // Custom user type with username
16
16
  type CustomUser = {
17
- email?: string
18
17
  username?: string
19
18
  }
20
19
 
@@ -180,13 +179,10 @@ export const ChannelInfoDialog: React.FC<ChannelInfoDialogProps> = ({
180
179
  const participantName =
181
180
  participant.user?.name || participant.user?.id || 'Unknown member'
182
181
  const participantImage = participant.user?.image
183
- const participantEmail = (participant.user as CustomUser)?.email
184
182
  const participantUsername = (participant.user as CustomUser)?.username
185
- const participantSecondary = participantEmail
186
- ? participantEmail
187
- : participantUsername
188
- ? `linktr.ee/${participantUsername}`
189
- : undefined
183
+ const participantSecondary = participantUsername
184
+ ? `linktr.ee/${participantUsername}`
185
+ : undefined
190
186
  const participantId = participant.user?.id || 'unknown'
191
187
 
192
188
  return (
@@ -9,6 +9,7 @@ type ChannelListContextValue = {
9
9
  message: LocalMessage | undefined,
10
10
  defaultPreview?: string
11
11
  ) => React.ReactNode
12
+ viewerLanguage?: string
12
13
  }
13
14
 
14
15
  const ChannelListContext = React.createContext<ChannelListContextValue>({
@@ -16,6 +17,7 @@ const ChannelListContext = React.createContext<ChannelListContextValue>({
16
17
  onChannelSelect: () => {},
17
18
  debug: false,
18
19
  renderMessagePreview: undefined,
20
+ viewerLanguage: undefined,
19
21
  })
20
22
 
21
23
  export const ChannelListProvider = ChannelListContext.Provider
@@ -4,6 +4,7 @@ import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
4
4
 
5
5
  import { useChannelStar } from '../../hooks/useChannelStar'
6
6
  import { formatRelativeTime } from '../../utils/formatRelativeTime'
7
+ import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
7
8
  import { Avatar } from '../Avatar'
8
9
  import { isChatbotMessage } from '../CustomMessage/MessageTag'
9
10
 
@@ -14,8 +15,13 @@ import { useChannelListContext } from './ChannelListContext'
14
15
  */
15
16
  const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
16
17
  ({ channel, unread }) => {
17
- const { selectedChannel, onChannelSelect, debug, renderMessagePreview } =
18
- useChannelListContext()
18
+ const {
19
+ selectedChannel,
20
+ onChannelSelect,
21
+ debug,
22
+ renderMessagePreview,
23
+ viewerLanguage,
24
+ } = useChannelListContext()
19
25
 
20
26
  const isSelected = selectedChannel?.id === channel?.id
21
27
 
@@ -53,7 +59,12 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
53
59
  })()
54
60
 
55
61
  const getLastMessageText = () => {
56
- if (lastMessage?.text) return lastMessage.text
62
+ const displayText = getMessageDisplayText({
63
+ message: lastMessage,
64
+ viewerLanguage,
65
+ })
66
+ if (displayText) return displayText
67
+
57
68
  const isTip = lastMessage?.metadata?.custom_type === 'MESSAGE_TIP'
58
69
  if (isTip) return '💵 Sent a tip'
59
70
 
@@ -28,6 +28,7 @@ export const ChannelList = React.memo<ChannelListProps>(
28
28
  className,
29
29
  customEmptyStateIndicator,
30
30
  renderMessagePreview,
31
+ viewerLanguage,
31
32
  }) => {
32
33
  // Track renders
33
34
  const renderCountRef = React.useRef(0)
@@ -64,8 +65,15 @@ export const ChannelList = React.memo<ChannelListProps>(
64
65
  onChannelSelect,
65
66
  debug,
66
67
  renderMessagePreview,
68
+ viewerLanguage,
67
69
  }),
68
- [selectedChannel, onChannelSelect, debug, renderMessagePreview]
70
+ [
71
+ selectedChannel,
72
+ onChannelSelect,
73
+ debug,
74
+ renderMessagePreview,
75
+ viewerLanguage,
76
+ ]
69
77
  )
70
78
 
71
79
  return (
@@ -28,6 +28,17 @@ vi.mock('stream-chat-react', () => ({
28
28
  ),
29
29
  useMessageContext: () => ({ message: { id: 'message-1', text: 'hello' } }),
30
30
  useChannelStateContext: () => ({ channel: activeChannel }),
31
+ useChatContext: () => ({ client: { user: { id: 'visitor-1' } } }),
32
+ useTypingContext: () => ({ typing: {} }),
33
+ useAIState: () => ({ aiState: 'AI_STATE_IDLE' }),
34
+ AIStates: {
35
+ Error: 'AI_STATE_ERROR',
36
+ ExternalSources: 'AI_STATE_EXTERNAL_SOURCES',
37
+ Generating: 'AI_STATE_GENERATING',
38
+ Idle: 'AI_STATE_IDLE',
39
+ Stop: 'AI_STATE_STOP',
40
+ Thinking: 'AI_STATE_THINKING',
41
+ },
31
42
  }))
32
43
 
33
44
  vi.mock('../providers/MessagingProvider', () => ({
@@ -28,6 +28,7 @@ import { CustomMessage } from './CustomMessage'
28
28
  import { CustomMessageInput } from './CustomMessageInput'
29
29
  import { CustomSystemMessage } from './CustomSystemMessage'
30
30
  import CustomTypingIndicator from './CustomTypingIndicator'
31
+ import { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'
31
32
  import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
32
33
  import { LoadingState } from './MessagingShell/LoadingState'
33
34
 
@@ -257,6 +258,7 @@ const ChannelViewInner: React.FC<{
257
258
  message: NonNullable<MessageUIComponentProps['message']>
258
259
  ) => React.ReactNode
259
260
  dmAgentEnabled?: boolean
261
+ viewerLanguage?: string
260
262
  }> = ({
261
263
  onBack,
262
264
  showBackButton,
@@ -275,6 +277,7 @@ const ChannelViewInner: React.FC<{
275
277
  customChannelActions,
276
278
  renderMessage,
277
279
  dmAgentEnabled = false,
280
+ viewerLanguage,
278
281
  }) => {
279
282
  const { channel } = useChannelStateContext()
280
283
  const infoDialogRef = useRef<HTMLDialogElement>(null)
@@ -339,7 +342,11 @@ const ChannelViewInner: React.FC<{
339
342
  // eslint-disable-next-line react-hooks/rules-of-hooks
340
343
  const { message } = useMessageContext('ChannelView')
341
344
  const messageNode = (
342
- <CustomMessage {...props} chatbotVotingEnabled={chatbotVotingEnabled} />
345
+ <CustomMessage
346
+ {...props}
347
+ chatbotVotingEnabled={chatbotVotingEnabled}
348
+ viewerLanguage={viewerLanguage}
349
+ />
343
350
  )
344
351
 
345
352
  if (!renderMessage || !message) {
@@ -348,7 +355,7 @@ const ChannelViewInner: React.FC<{
348
355
 
349
356
  return renderMessage(messageNode, message)
350
357
  },
351
- [chatbotVotingEnabled, renderMessage]
358
+ [chatbotVotingEnabled, renderMessage, viewerLanguage]
352
359
  )
353
360
 
354
361
  return (
@@ -436,6 +443,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
436
443
  customChannelActions,
437
444
  renderMessage,
438
445
  sendButton,
446
+ viewerLanguage,
439
447
  }) => {
440
448
  // Custom send message handler that:
441
449
  // 1. Applies messageMetadata if provided
@@ -488,37 +496,40 @@ export const ChannelView = React.memo<ChannelViewProps>(
488
496
  className
489
497
  )}
490
498
  >
491
- <Channel
492
- channel={channel}
493
- MessageSystem={CustomSystemMessage}
494
- EmptyStateIndicator={CustomChannelEmptyState}
495
- LoadingIndicator={LoadingState}
496
- DateSeparator={CustomDateSeparator}
497
- TypingIndicator={CustomTypingIndicator}
498
- doSendMessageRequest={doSendMessageRequest}
499
- {...(sendButton ? { SendButton: sendButton } : {})}
500
- >
501
- <ChannelViewInner
502
- onBack={onBack}
503
- showBackButton={showBackButton}
504
- renderMessageInputActions={renderMessageInputActions}
505
- renderConversationFooter={renderConversationFooter}
506
- onLeaveConversation={onLeaveConversation}
507
- onBlockParticipant={onBlockParticipant}
508
- CustomChannelEmptyState={CustomChannelEmptyState}
509
- showDeleteConversation={showDeleteConversation}
510
- onDeleteConversationClick={onDeleteConversationClick}
511
- onBlockParticipantClick={onBlockParticipantClick}
512
- onReportParticipantClick={onReportParticipantClick}
513
- showStarButton={showStarButton}
514
- dmAgentEnabled={dmAgentEnabled}
515
- chatbotVotingEnabled={chatbotVotingEnabled}
516
- renderChannelBanner={renderChannelBanner}
517
- customProfileContent={customProfileContent}
518
- customChannelActions={customChannelActions}
519
- renderMessage={renderMessage}
520
- />
521
- </Channel>
499
+ <DmAgentEnabledContext.Provider value={dmAgentEnabled ?? false}>
500
+ <Channel
501
+ channel={channel}
502
+ MessageSystem={CustomSystemMessage}
503
+ EmptyStateIndicator={CustomChannelEmptyState}
504
+ LoadingIndicator={LoadingState}
505
+ DateSeparator={CustomDateSeparator}
506
+ TypingIndicator={CustomTypingIndicator}
507
+ doSendMessageRequest={doSendMessageRequest}
508
+ {...(sendButton ? { SendButton: sendButton } : {})}
509
+ >
510
+ <ChannelViewInner
511
+ onBack={onBack}
512
+ showBackButton={showBackButton}
513
+ renderMessageInputActions={renderMessageInputActions}
514
+ renderConversationFooter={renderConversationFooter}
515
+ onLeaveConversation={onLeaveConversation}
516
+ onBlockParticipant={onBlockParticipant}
517
+ CustomChannelEmptyState={CustomChannelEmptyState}
518
+ showDeleteConversation={showDeleteConversation}
519
+ onDeleteConversationClick={onDeleteConversationClick}
520
+ onBlockParticipantClick={onBlockParticipantClick}
521
+ onReportParticipantClick={onReportParticipantClick}
522
+ showStarButton={showStarButton}
523
+ dmAgentEnabled={dmAgentEnabled}
524
+ chatbotVotingEnabled={chatbotVotingEnabled}
525
+ renderChannelBanner={renderChannelBanner}
526
+ customProfileContent={customProfileContent}
527
+ customChannelActions={customChannelActions}
528
+ renderMessage={renderMessage}
529
+ viewerLanguage={viewerLanguage}
530
+ />
531
+ </Channel>
532
+ </DmAgentEnabledContext.Provider>
522
533
  </div>
523
534
  )
524
535
  }