@linktr.ee/messaging-react 1.40.1 → 2.0.0

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/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.
@@ -283,6 +293,13 @@ export declare interface FaqListProps {
283
293
  */
284
294
  export declare const formatRelativeTime: (date: Date) => string;
285
295
 
296
+ export declare function getMessageDisplayText({ message, viewerLanguage, }: {
297
+ message?: MessageWithI18n | null;
298
+ viewerLanguage?: string;
299
+ }): string | undefined;
300
+
301
+ export declare function isLinkAttachment(a: Attachment): boolean;
302
+
286
303
  export declare const LockedAttachment: {
287
304
  Creator: (props: CreatorCardProps) => JSX_2.Element;
288
305
  Visitor: (props: VisitorCardProps) => JSX_2.Element;
@@ -356,6 +373,10 @@ declare interface MessageVoteButtonsProps {
356
373
  onVoteDown: () => void;
357
374
  }
358
375
 
376
+ declare type MessageWithI18n = Pick<LocalMessage, 'text'> & {
377
+ i18n?: Record<string, string> | null;
378
+ };
379
+
359
380
  /**
360
381
  * Messaging capabilities configuration
361
382
  */
@@ -460,13 +481,14 @@ export declare interface MessagingShellProps extends ChannelViewPassthroughProps
460
481
  renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
461
482
  }
462
483
 
484
+ export declare function normalizeLanguageCode(language?: string): string | undefined;
485
+
463
486
  /**
464
487
  * Generic participant interface for different host environments
465
488
  */
466
489
  export declare interface Participant {
467
490
  id: string;
468
491
  name: string;
469
- email?: string;
470
492
  image?: string;
471
493
  username?: string;
472
494
  phone?: string;
package/dist/index.js CHANGED
@@ -1,23 +1,26 @@
1
- import { b as a, c as t, C as i, d as n, e as o, f as g, F as r, g as M, L as m, M as l, h as u, i as h, j as d, k as v, r as C, l as L, u as c, m as A, n as F } from "./index-8ZuHtwFb.js";
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 as u, i as h, j as L, k as d, l as C, m as c, n as v, r as A, o as k, u as p, p as F, q as f } from "./index-Brz9orsI.js";
2
2
  export {
3
- a as ActionButton,
3
+ e as ActionButton,
4
4
  t as Avatar,
5
5
  i as ChannelEmptyState,
6
6
  n as ChannelList,
7
7
  o as ChannelView,
8
8
  g as CustomMessageProvider,
9
- r as FaqList,
10
- M as FaqListItem,
11
- m as LockedAttachment,
9
+ m as FaqList,
10
+ r as FaqListItem,
11
+ M as LockedAttachment,
12
12
  l as MediaMessage,
13
13
  u as MessageVoteButtons,
14
14
  h as MessagingProvider,
15
- d as MessagingShell,
16
- v as formatRelativeTime,
17
- C as resolveLinkAttachment,
18
- L as resolveMediaFromMessage,
19
- c as useCustomMessage,
20
- A as useMessageVote,
21
- F as useMessaging
15
+ L as MessagingShell,
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
22
25
  };
23
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.1",
3
+ "version": "2.0.0",
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 (
@@ -257,6 +257,7 @@ const ChannelViewInner: React.FC<{
257
257
  message: NonNullable<MessageUIComponentProps['message']>
258
258
  ) => React.ReactNode
259
259
  dmAgentEnabled?: boolean
260
+ viewerLanguage?: string
260
261
  }> = ({
261
262
  onBack,
262
263
  showBackButton,
@@ -275,6 +276,7 @@ const ChannelViewInner: React.FC<{
275
276
  customChannelActions,
276
277
  renderMessage,
277
278
  dmAgentEnabled = false,
279
+ viewerLanguage,
278
280
  }) => {
279
281
  const { channel } = useChannelStateContext()
280
282
  const infoDialogRef = useRef<HTMLDialogElement>(null)
@@ -339,7 +341,11 @@ const ChannelViewInner: React.FC<{
339
341
  // eslint-disable-next-line react-hooks/rules-of-hooks
340
342
  const { message } = useMessageContext('ChannelView')
341
343
  const messageNode = (
342
- <CustomMessage {...props} chatbotVotingEnabled={chatbotVotingEnabled} />
344
+ <CustomMessage
345
+ {...props}
346
+ chatbotVotingEnabled={chatbotVotingEnabled}
347
+ viewerLanguage={viewerLanguage}
348
+ />
343
349
  )
344
350
 
345
351
  if (!renderMessage || !message) {
@@ -348,7 +354,7 @@ const ChannelViewInner: React.FC<{
348
354
 
349
355
  return renderMessage(messageNode, message)
350
356
  },
351
- [chatbotVotingEnabled, renderMessage]
357
+ [chatbotVotingEnabled, renderMessage, viewerLanguage]
352
358
  )
353
359
 
354
360
  return (
@@ -436,6 +442,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
436
442
  customChannelActions,
437
443
  renderMessage,
438
444
  sendButton,
445
+ viewerLanguage,
439
446
  }) => {
440
447
  // Custom send message handler that:
441
448
  // 1. Applies messageMetadata if provided
@@ -517,6 +524,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
517
524
  customProfileContent={customProfileContent}
518
525
  customChannelActions={customChannelActions}
519
526
  renderMessage={renderMessage}
527
+ viewerLanguage={viewerLanguage}
520
528
  />
521
529
  </Channel>
522
530
  </div>
@@ -386,6 +386,34 @@ ChatbotVariants.args = {
386
386
  ],
387
387
  }
388
388
 
389
+ export const ChatbotLinkPreviews: StoryFn<TemplateProps> = Template.bind({})
390
+ ChatbotLinkPreviews.args = {
391
+ messages: [
392
+ {
393
+ id: 'msg-1',
394
+ text: 'Check the FAQ pages: pizzeriamozza.com/faq or osteriamozza.com/faq.',
395
+ user: storyUsers.creator,
396
+ metadata: { custom_type: 'MESSAGE_CHATBOT' },
397
+ attachments: [
398
+ {
399
+ type: 'link',
400
+ og_scrape_url: 'https://www.pizzeriamozza.com/faq',
401
+ title: 'Pizzeria Mozza | Pizza Restaurant in CA',
402
+ text: 'FAQ | Pizzeria Mozza',
403
+ image_url: 'https://picsum.photos/seed/mozza1/560/315',
404
+ },
405
+ {
406
+ type: 'link',
407
+ og_scrape_url: 'https://www.osteriamozza.com/faq',
408
+ title: 'Osteria Mozza | Italian Restaurant in LA',
409
+ text: 'FAQ | Osteria Mozza',
410
+ image_url: 'https://picsum.photos/seed/mozza2/560/315',
411
+ },
412
+ ],
413
+ },
414
+ ],
415
+ }
416
+
389
417
  export const WithTypingIndicatorComparison: StoryFn<TemplateProps> =
390
418
  Template.bind({})
391
419
  WithTypingIndicatorComparison.args = {
@@ -30,8 +30,10 @@ import {
30
30
  } from 'stream-chat-react'
31
31
 
32
32
  import { useMessageVote } from '../../hooks/useMessageVote'
33
+ import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
33
34
  import { Avatar } from '../Avatar'
34
35
  import LockedAttachment from '../LockedAttachment'
36
+ import { isLinkAttachment } from '../MediaMessage'
35
37
 
36
38
  import { useCustomMessage } from './context'
37
39
  import {
@@ -44,10 +46,12 @@ import { MessageVoteButtons } from './MessageVoteButtons'
44
46
 
45
47
  type CustomMessageUIComponentProps = MessageUIComponentProps & {
46
48
  chatbotVotingEnabled?: boolean
49
+ viewerLanguage?: string
47
50
  }
48
51
 
49
52
  type CustomMessageWithContextProps = MessageContextValue & {
50
53
  chatbotVotingEnabled?: boolean
54
+ viewerLanguage?: string
51
55
  }
52
56
 
53
57
  const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
@@ -67,11 +71,13 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
67
71
  message,
68
72
  renderText,
69
73
  threadList,
74
+ viewerLanguage,
70
75
  } = props
71
76
 
72
77
  const { client } = useChatContext('CustomMessage')
73
78
  const { channel } = useChannelStateContext('CustomMessage')
74
- const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } = useCustomMessage('LockedAttachment')
79
+ const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } =
80
+ useCustomMessage('LockedAttachment')
75
81
  const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
76
82
  const reminder = useMessageReminder(message.id)
77
83
  const { selected: voteState, voteUp, voteDown } = useMessageVote(message)
@@ -95,15 +101,23 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
95
101
  () => isMessageAIGenerated?.(message),
96
102
  [isMessageAIGenerated, message]
97
103
  )
98
- const finalAttachments = useMemo(
99
- () =>
100
- !message.shared_location && !message.attachments
101
- ? []
102
- : !message.shared_location
103
- ? message.attachments
104
- : [message.shared_location, ...(message.attachments ?? [])],
105
- [message]
106
- )
104
+ const finalAttachments = useMemo(() => {
105
+ const attachments = message.attachments ?? []
106
+ const raw = message.shared_location
107
+ ? [message.shared_location, ...attachments]
108
+ : attachments
109
+
110
+ if (!isChatbotMessage(message)) return raw
111
+
112
+ const filtered = raw.filter((a) => !('type' in a) || !isLinkAttachment(a))
113
+ return filtered.length === raw.length ? raw : filtered
114
+ }, [message])
115
+ const displayMessage = useMemo(() => {
116
+ const displayText = getMessageDisplayText({ message, viewerLanguage })
117
+ return displayText === message.text
118
+ ? message
119
+ : { ...message, text: displayText }
120
+ }, [message, viewerLanguage])
107
121
 
108
122
  if (isDateSeparatorMessage(message)) {
109
123
  return null
@@ -244,7 +258,10 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
244
258
  )}
245
259
  {message.text && (
246
260
  <div className="str-chat__message-bubble">
247
- <MessageText message={message} renderText={renderText} />
261
+ <MessageText
262
+ message={displayMessage}
263
+ renderText={renderText}
264
+ />
248
265
  </div>
249
266
  )}
250
267
  </div>
@@ -270,11 +287,14 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
270
287
  ) : null}
271
288
  {isAIGenerated ? (
272
289
  <StreamedMessageText
273
- message={message}
290
+ message={displayMessage}
274
291
  renderText={renderText}
275
292
  />
276
293
  ) : (
277
- <MessageText message={message} renderText={renderText} />
294
+ <MessageText
295
+ message={displayMessage}
296
+ renderText={renderText}
297
+ />
278
298
  )}
279
299
  <MessageErrorIcon />
280
300
  </div>
@@ -314,6 +334,7 @@ const MemoizedCustomMessage = React.memo(
314
334
  CustomMessageWithContext,
315
335
  (prev, next) => {
316
336
  if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false
337
+ if (prev.viewerLanguage !== next.viewerLanguage) return false
317
338
  return areMessageUIPropsEqual(prev, next)
318
339
  }
319
340
  ) as typeof CustomMessageWithContext
@@ -95,12 +95,14 @@ const LinkCard: React.FC<{
95
95
  return <div className="block">{body}</div>
96
96
  }
97
97
 
98
+ export function isLinkAttachment(a: Attachment): boolean {
99
+ return a.type === 'link' || (!!a.og_scrape_url && !a.asset_url)
100
+ }
101
+
98
102
  export function resolveLinkAttachment(
99
103
  message: LocalMessage
100
104
  ): Attachment | undefined {
101
- return message.attachments?.find(
102
- (a) => a.type === 'link' || (!!a.og_scrape_url && !a.asset_url)
103
- )
105
+ return message.attachments?.find(isLinkAttachment)
104
106
  }
105
107
 
106
108
  async function triggerDownload(url: string, filename?: string): Promise<void> {
@@ -35,6 +35,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
35
35
  onMessageSent,
36
36
  showStarButton = false,
37
37
  chatbotVotingEnabled = false,
38
+ viewerLanguage,
38
39
  renderMessagePreview,
39
40
  renderChannelBanner,
40
41
  customProfileContent,
@@ -60,9 +61,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
60
61
  string | null
61
62
  >(null)
62
63
 
63
- const {
64
- showDeleteConversation = true,
65
- } = capabilities
64
+ const { showDeleteConversation = true } = capabilities
66
65
 
67
66
  // Create default filters and merge with provided filters
68
67
  const channelFilters = React.useMemo(() => {
@@ -194,7 +193,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
194
193
  const channel = await service.startChannelWithParticipant({
195
194
  id: initialParticipantData.id,
196
195
  name: initialParticipantData.name,
197
- email: initialParticipantData.email,
198
196
  phone: initialParticipantData.phone,
199
197
  })
200
198
 
@@ -375,6 +373,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
375
373
  channelRenderFilterFn={channelRenderFilterFn}
376
374
  customEmptyStateIndicator={channelListCustomEmptyStateIndicator}
377
375
  renderMessagePreview={renderMessagePreview}
376
+ viewerLanguage={viewerLanguage}
378
377
  />
379
378
  </div>
380
379
 
@@ -418,6 +417,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
418
417
  onMessageSent={onMessageSent}
419
418
  showStarButton={showStarButton}
420
419
  chatbotVotingEnabled={chatbotVotingEnabled}
420
+ viewerLanguage={viewerLanguage}
421
421
  customProfileContent={customProfileContent}
422
422
  customChannelActions={customChannelActions}
423
423
  renderMessage={renderMessage}
package/src/index.ts CHANGED
@@ -14,10 +14,14 @@ export { ChannelEmptyState } from './components/MessagingShell/ChannelEmptyState
14
14
  export { MessageVoteButtons } from './components/CustomMessage/MessageVoteButtons'
15
15
  export {
16
16
  MediaMessage,
17
+ isLinkAttachment,
17
18
  resolveLinkAttachment,
18
19
  resolveMediaFromMessage,
19
20
  } from './components/MediaMessage'
20
- export type { MediaMessageProps, MediaMessageResolved } from './components/MediaMessage'
21
+ export type {
22
+ MediaMessageProps,
23
+ MediaMessageResolved,
24
+ } from './components/MediaMessage'
21
25
 
22
26
  // Providers
23
27
  export { MessagingProvider } from './providers/MessagingProvider'
@@ -30,6 +34,10 @@ export { useCustomMessage } from './components/CustomMessage/context'
30
34
 
31
35
  // Utils
32
36
  export { formatRelativeTime } from './utils/formatRelativeTime'
37
+ export {
38
+ getMessageDisplayText,
39
+ normalizeLanguageCode,
40
+ } from './utils/getMessageDisplayText'
33
41
 
34
42
  // Types
35
43
  export type {
@@ -44,7 +52,11 @@ export type {
44
52
  export type { MessageMetadata } from './stream-custom-data'
45
53
  export type { AvatarProps } from './components/Avatar'
46
54
  export type { ActionButtonProps } from './components/ActionButton'
47
- export type { CreatorCardProps, VisitorCardProps, LockedAttachmentContextValue } from './components/LockedAttachment'
55
+ export type {
56
+ CreatorCardProps,
57
+ VisitorCardProps,
58
+ LockedAttachmentContextValue,
59
+ } from './components/LockedAttachment'
48
60
  export type { CustomMessageRegistry } from './components/CustomMessage/context'
49
61
  export type { AttachmentSourceType } from './components/AttachmentCard/utils/mimeType'
50
62
  export type { Faq, FaqListProps } from './components/FaqList'
@@ -110,31 +110,26 @@ export const mockParticipants = [
110
110
  {
111
111
  id: 'participant-1',
112
112
  name: 'Alice Johnson',
113
- email: 'alice@example.com',
114
113
  image: 'https://i.pravatar.cc/150?img=2',
115
114
  },
116
115
  {
117
116
  id: 'participant-2',
118
117
  name: 'Bob Smith',
119
- email: 'bob@example.com',
120
118
  image: 'https://i.pravatar.cc/150?img=3',
121
119
  },
122
120
  {
123
121
  id: 'participant-3',
124
122
  name: 'Carol Williams',
125
- email: 'carol@example.com',
126
123
  image: 'https://i.pravatar.cc/150?img=4',
127
124
  },
128
125
  {
129
126
  id: 'participant-4',
130
127
  name: 'David Brown',
131
- email: 'david@example.com',
132
128
  image: 'https://i.pravatar.cc/150?img=5',
133
129
  },
134
130
  {
135
131
  id: 'participant-5',
136
132
  name: 'Emma Davis',
137
- email: 'emma@example.com',
138
133
  image: 'https://i.pravatar.cc/150?img=6',
139
134
  },
140
135
  ]
@@ -143,10 +138,8 @@ export const mockParticipants = [
143
138
  export const mockParticipantSource = {
144
139
  loadParticipants: async (options?: { search?: string; limit?: number }) => {
145
140
  const searchTerm = options?.search || ''
146
- const filtered = mockParticipants.filter(
147
- (p) =>
148
- p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
149
- p.email.toLowerCase().includes(searchTerm.toLowerCase())
141
+ const filtered = mockParticipants.filter((p) =>
142
+ p.name.toLowerCase().includes(searchTerm.toLowerCase())
150
143
  )
151
144
  return {
152
145
  participants: filtered,
package/src/styles.css CHANGED
@@ -137,10 +137,6 @@
137
137
  .str-chat__attachment-list .str-chat__message-attachment--card {
138
138
  color: white;
139
139
  }
140
-
141
- a {
142
- color: color-mix(in srgb, white 60%, #005fff);
143
- }
144
140
  }
145
141
 
146
142
  .str-chat__unread-messages-separator,
@@ -369,6 +365,7 @@
369
365
  }
370
366
 
371
367
  .str-chat__message-text-inner a {
368
+ color: inherit;
372
369
  text-decoration: underline;
373
370
  text-underline-offset: 0.15em;
374
371
  word-break: break-word;
package/src/types.ts CHANGED
@@ -25,7 +25,6 @@ export type { LockedAttachmentSource } from './components/LockedAttachment'
25
25
  export interface Participant {
26
26
  id: string
27
27
  name: string
28
- email?: string
29
28
  image?: string
30
29
  username?: string
31
30
  phone?: string
@@ -95,6 +94,11 @@ export interface ChannelListProps {
95
94
  message: LocalMessage | undefined,
96
95
  defaultPreview?: string
97
96
  ) => React.ReactNode
97
+ /**
98
+ * Language code used to pick translated message text from Stream Chat i18n.
99
+ * Falls back to message.text when no matching translation exists.
100
+ */
101
+ viewerLanguage?: string
98
102
  }
99
103
 
100
104
  /**
@@ -173,6 +177,11 @@ export interface ChannelViewProps {
173
177
  * Defaults to false.
174
178
  */
175
179
  chatbotVotingEnabled?: boolean
180
+ /**
181
+ * Language code used to pick translated message text from Stream Chat i18n.
182
+ * Falls back to message.text when no matching translation exists.
183
+ */
184
+ viewerLanguage?: string
176
185
 
177
186
  /**
178
187
  * Custom render function for a banner/card component that renders
@@ -244,6 +253,7 @@ export type ChannelViewPassthroughProps = Pick<
244
253
  | 'onMessageSent'
245
254
  | 'showStarButton'
246
255
  | 'chatbotVotingEnabled'
256
+ | 'viewerLanguage'
247
257
  | 'renderChannelBanner'
248
258
  | 'customProfileContent'
249
259
  | 'customChannelActions'