@linktr.ee/messaging-react 3.12.1 → 3.13.0-rc-1783924547

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.
@@ -1,9 +1,3 @@
1
- import {
2
- ArrowLeftIcon,
3
- CaretRightIcon,
4
- SparkleIcon,
5
- StarIcon,
6
- } from '@phosphor-icons/react'
7
1
  import classNames from 'classnames'
8
2
  import React, { useCallback, useEffect, useRef } from 'react'
9
3
  import { Channel as ChannelType } from 'stream-chat'
@@ -18,13 +12,9 @@ import {
18
12
  MessageUIComponentProps,
19
13
  } from 'stream-chat-react'
20
14
 
21
- import { useChannelStar } from '../hooks/useChannelStar'
22
15
  import type { ChannelViewProps } from '../types'
23
16
  import { resolveConversationParticipant } from '../utils/resolveConversationParticipant'
24
- import { resolveParticipantDisplayName } from '../utils/resolveParticipantDisplayName'
25
17
 
26
- import { Avatar } from './Avatar'
27
- import { ChannelActionsMenu } from './ChannelActionsMenu'
28
18
  import { ChannelHeaderRedesign } from './ChannelHeaderRedesign'
29
19
  import { CustomDateSeparator } from './CustomDateSeparator'
30
20
  import { CustomMessage } from './CustomMessage'
@@ -36,339 +26,6 @@ import { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'
36
26
  import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
37
27
  import { LoadingState } from './MessagingShell/LoadingState'
38
28
 
39
- const ICON_BTN_CLASS =
40
- 'size-10 rounded-full hover:bg-[#E5E4E1] flex items-center justify-center transition-colors duration-150 focus-ring'
41
-
42
- const DM_AGENT_HEADER_HELPER_TEXT = 'Replies instantly with AI assistant'
43
-
44
- /**
45
- * Custom channel header component
46
- */
47
- const CustomChannelHeader: React.FC<{
48
- onBack?: () => void
49
- showBackButton: boolean
50
- showStarButton?: boolean
51
- dmAgentEnabled?: boolean
52
- onLeaveConversation?: (channel: ChannelType) => void
53
- onBlockParticipant?: (participantId?: string) => void
54
- showDeleteConversation?: boolean
55
- showBlockParticipant?: boolean
56
- showReportParticipant?: boolean
57
- onDeleteConversationClick?: () => void
58
- onBlockParticipantClick?: () => void
59
- onReportParticipantClick?: () => void
60
- customChannelActions?: React.ReactNode
61
- renderChannelActions?: React.ReactNode
62
- showChannelInfo?: boolean
63
- onParticipantNameClick?: () => void
64
- renderHeaderTitleBadges?: ChannelViewProps['renderHeaderTitleBadges']
65
- }> = ({
66
- onBack,
67
- showBackButton,
68
- showStarButton = false,
69
- dmAgentEnabled = false,
70
- onLeaveConversation,
71
- onBlockParticipant,
72
- showDeleteConversation = true,
73
- showBlockParticipant = true,
74
- showReportParticipant = true,
75
- onDeleteConversationClick,
76
- onBlockParticipantClick,
77
- onReportParticipantClick,
78
- customChannelActions,
79
- renderChannelActions,
80
- showChannelInfo = true,
81
- onParticipantNameClick,
82
- renderHeaderTitleBadges,
83
- }) => {
84
- const { channel } = useChannelStateContext()
85
- const { client } = useChatContext()
86
-
87
- // Get participant info (excluding current user)
88
- const participant = React.useMemo(
89
- () =>
90
- resolveConversationParticipant(
91
- channel.state?.members,
92
- client?.userID,
93
- channel.type
94
- ),
95
- [channel.state?.members, client?.userID, channel.type]
96
- )
97
-
98
- const participantName = resolveParticipantDisplayName(participant?.user)
99
- const participantImage = participant?.user?.image
100
- const isStarred = useChannelStar(channel)
101
- const headerTitleBadges = renderHeaderTitleBadges?.({
102
- channel,
103
- participant,
104
- })
105
- const mobileParticipantIdentityClassName =
106
- 'flex max-w-full flex-col items-center gap-1 rounded-[12px] px-1 py-0.5 text-center text-xs font-medium text-black/90'
107
- const desktopParticipantIdentityClassName =
108
- 'flex min-w-0 max-w-full items-center gap-4 rounded-[12px] px-1 py-0.5 text-left text-black/90'
109
- const participantTitle = (
110
- <>
111
- <span className="min-w-0 truncate">{participantName}</span>
112
- {headerTitleBadges && (
113
- <span className="shrink-0">{headerTitleBadges}</span>
114
- )}
115
- </>
116
- )
117
-
118
- const handleStarClick = async () => {
119
- try {
120
- if (isStarred) {
121
- await channel.unpin()
122
- } else {
123
- await channel.pin()
124
- }
125
- } catch (error) {
126
- console.error(
127
- '[CustomChannelHeader] Failed to update pinned status:',
128
- error
129
- )
130
- }
131
- }
132
-
133
- return (
134
- <div className="@container">
135
- <div className="grid grid-cols-[1fr_auto_1fr] w-full items-center @lg:hidden px-6 py-3">
136
- <div className="flex items-center gap-2">
137
- {showBackButton && (
138
- <button
139
- className={classNames(
140
- ICON_BTN_CLASS,
141
- 'messaging-channel-view-back-button-mobile bg-[#F1F0EE]'
142
- )}
143
- onClick={onBack || (() => {})}
144
- type="button"
145
- aria-label="Back to conversations"
146
- >
147
- <ArrowLeftIcon className="size-5 text-black/90" />
148
- </button>
149
- )}
150
- </div>
151
- <div
152
- className="flex flex-col gap-1 items-center"
153
- data-dd-privacy="mask"
154
- >
155
- {onParticipantNameClick ? (
156
- <button
157
- type="button"
158
- onClick={onParticipantNameClick}
159
- aria-label={`View details for ${participantName}`}
160
- className={classNames(
161
- mobileParticipantIdentityClassName,
162
- 'appearance-none border-0 bg-transparent transition-opacity hover:opacity-70 focus-ring focus-visible:outline-none'
163
- )}
164
- >
165
- <Avatar
166
- id={participant?.user?.id || channel.id || 'unknown'}
167
- name={participantName}
168
- image={participantImage}
169
- starred={showStarButton && isStarred}
170
- dmAgentEnabled={dmAgentEnabled}
171
- size={48}
172
- />
173
- <span className="flex max-w-full items-center gap-0.5">
174
- {participantTitle}
175
- <CaretRightIcon className="size-3 shrink-0" weight="bold" />
176
- </span>
177
- {dmAgentEnabled && (
178
- <div className="flex items-center gap-1 text-[10px] leading-3 text-black/55">
179
- <SparkleIcon className="size-3 shrink-0 text-black/55" />
180
- <span>{DM_AGENT_HEADER_HELPER_TEXT}</span>
181
- </div>
182
- )}
183
- </button>
184
- ) : (
185
- <div className={mobileParticipantIdentityClassName}>
186
- <Avatar
187
- id={participant?.user?.id || channel.id || 'unknown'}
188
- name={participantName}
189
- image={participantImage}
190
- starred={showStarButton && isStarred}
191
- dmAgentEnabled={dmAgentEnabled}
192
- size={48}
193
- />
194
- <p className="flex max-w-full items-center justify-center gap-0.5">
195
- {participantTitle}
196
- <CaretRightIcon
197
- className="invisible size-3 shrink-0"
198
- weight="bold"
199
- />
200
- </p>
201
- {dmAgentEnabled && (
202
- <div className="flex items-center gap-1 text-[10px] leading-3 text-black/55">
203
- <SparkleIcon className="size-3 shrink-0 text-black/55" />
204
- <span>{DM_AGENT_HEADER_HELPER_TEXT}</span>
205
- </div>
206
- )}
207
- </div>
208
- )}
209
- </div>
210
- <div className="flex justify-end items-center gap-2">
211
- {showStarButton && (
212
- <button
213
- className={ICON_BTN_CLASS}
214
- onClick={handleStarClick}
215
- type="button"
216
- aria-label={
217
- isStarred ? 'Unstar conversation' : 'Star conversation'
218
- }
219
- >
220
- <StarIcon
221
- className={classNames('size-5', {
222
- 'text-yellow-600': isStarred,
223
- 'text-black/90': !isStarred,
224
- })}
225
- weight={isStarred ? 'duotone' : 'regular'}
226
- />
227
- </button>
228
- )}
229
- {showChannelInfo &&
230
- (renderChannelActions !== undefined ? (
231
- renderChannelActions
232
- ) : (
233
- <ChannelActionsMenu
234
- channel={channel}
235
- participant={participant}
236
- showDeleteConversation={showDeleteConversation}
237
- showBlockParticipant={showBlockParticipant}
238
- showReportParticipant={showReportParticipant}
239
- onLeaveConversation={onLeaveConversation}
240
- onBlockParticipant={onBlockParticipant}
241
- onDeleteConversationClick={onDeleteConversationClick}
242
- onBlockParticipantClick={onBlockParticipantClick}
243
- onReportParticipantClick={onReportParticipantClick}
244
- customChannelActions={customChannelActions}
245
- triggerClassName={classNames(ICON_BTN_CLASS, 'bg-[#F1F0EE]')}
246
- />
247
- ))}
248
- </div>
249
- </div>
250
- <div className="px-6 py-3 hidden @lg:flex items-center justify-between gap-3 min-h-12 border-b border-b-black/[0.08]">
251
- <div className="flex items-center gap-4 min-w-0" data-dd-privacy="mask">
252
- {showBackButton && onBack && (
253
- <button
254
- className={classNames(
255
- ICON_BTN_CLASS,
256
- 'messaging-channel-view-back-button-desktop'
257
- )}
258
- type="button"
259
- onClick={onBack}
260
- aria-label="Back to conversations"
261
- >
262
- <ArrowLeftIcon className="size-5 text-black/90" />
263
- </button>
264
- )}
265
- {onParticipantNameClick ? (
266
- <button
267
- type="button"
268
- onClick={onParticipantNameClick}
269
- aria-label={`View details for ${participantName}`}
270
- className={classNames(
271
- desktopParticipantIdentityClassName,
272
- 'appearance-none border-0 bg-transparent transition-opacity hover:opacity-70 focus-ring focus-visible:outline-none'
273
- )}
274
- >
275
- <Avatar
276
- id={participant?.user?.id || channel.id || 'unknown'}
277
- name={participantName}
278
- image={participantImage}
279
- starred={showStarButton && isStarred}
280
- dmAgentEnabled={dmAgentEnabled}
281
- size={48}
282
- />
283
- <div className="min-w-0">
284
- <h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
285
- {participantTitle}
286
- <CaretRightIcon className="size-4 shrink-0" weight="bold" />
287
- </h1>
288
- {dmAgentEnabled && (
289
- <div className="mt-0.5 flex items-center gap-1 text-[10px] leading-3 text-black/55">
290
- <SparkleIcon className="size-3 shrink-0 text-black/55" />
291
- <span className="truncate">
292
- {DM_AGENT_HEADER_HELPER_TEXT}
293
- </span>
294
- </div>
295
- )}
296
- </div>
297
- </button>
298
- ) : (
299
- <div className={desktopParticipantIdentityClassName}>
300
- <Avatar
301
- id={participant?.user?.id || channel.id || 'unknown'}
302
- name={participantName}
303
- image={participantImage}
304
- starred={showStarButton && isStarred}
305
- dmAgentEnabled={dmAgentEnabled}
306
- size={48}
307
- />
308
- <div className="min-w-0">
309
- <h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
310
- {participantTitle}
311
- <CaretRightIcon
312
- className="invisible size-4 shrink-0"
313
- weight="bold"
314
- />
315
- </h1>
316
- {dmAgentEnabled && (
317
- <div className="mt-0.5 flex items-center gap-1 text-[10px] leading-3 text-black/55">
318
- <SparkleIcon className="size-3 shrink-0 text-black/55" />
319
- <span className="truncate">
320
- {DM_AGENT_HEADER_HELPER_TEXT}
321
- </span>
322
- </div>
323
- )}
324
- </div>
325
- </div>
326
- )}
327
- </div>
328
- <div className="flex items-center gap-2">
329
- {showStarButton && (
330
- <button
331
- className={ICON_BTN_CLASS}
332
- onClick={handleStarClick}
333
- type="button"
334
- aria-label={
335
- isStarred ? 'Unstar conversation' : 'Star conversation'
336
- }
337
- >
338
- <StarIcon
339
- className={classNames('size-6', {
340
- 'text-yellow-600': isStarred,
341
- 'text-black/90': !isStarred,
342
- })}
343
- weight={isStarred ? 'duotone' : 'regular'}
344
- />
345
- </button>
346
- )}
347
- {showChannelInfo &&
348
- (renderChannelActions !== undefined ? (
349
- renderChannelActions
350
- ) : (
351
- <ChannelActionsMenu
352
- channel={channel}
353
- participant={participant}
354
- showDeleteConversation={showDeleteConversation}
355
- showBlockParticipant={showBlockParticipant}
356
- showReportParticipant={showReportParticipant}
357
- onLeaveConversation={onLeaveConversation}
358
- onBlockParticipant={onBlockParticipant}
359
- onDeleteConversationClick={onDeleteConversationClick}
360
- onBlockParticipantClick={onBlockParticipantClick}
361
- onReportParticipantClick={onReportParticipantClick}
362
- customChannelActions={customChannelActions}
363
- triggerClassName={ICON_BTN_CLASS}
364
- />
365
- ))}
366
- </div>
367
- </div>
368
- </div>
369
- )
370
- }
371
-
372
29
  /**
373
30
  * Inner component that has access to channel context
374
31
  */
@@ -392,7 +49,6 @@ const ChannelViewInner: React.FC<{
392
49
  showStarButton?: boolean
393
50
  chatbotVotingEnabled?: boolean
394
51
  renderChannelBanner?: () => React.ReactNode
395
- renderHeaderTitleBadges?: ChannelViewProps['renderHeaderTitleBadges']
396
52
  customChannelActions?: React.ReactNode
397
53
  renderChannelActions?: React.ReactNode
398
54
  renderMessage?: (
@@ -423,7 +79,6 @@ const ChannelViewInner: React.FC<{
423
79
  showStarButton = false,
424
80
  chatbotVotingEnabled = false,
425
81
  renderChannelBanner,
426
- renderHeaderTitleBadges,
427
82
  customChannelActions,
428
83
  renderChannelActions,
429
84
  renderMessage,
@@ -473,7 +128,7 @@ const ChannelViewInner: React.FC<{
473
128
  {renderChannelBanner()}
474
129
  </React.Fragment>
475
130
  ) : null
476
- const redesignedHeader = onParticipantNameClick ? (
131
+ const channelHeader = (
477
132
  <ChannelHeaderRedesign
478
133
  className="-mx-[var(--str-chat\_\_spacing-2)] px-[var(--str-chat\_\_spacing-2)] md:-mx-[min(var(--str-chat\_\_spacing-10),4%)] md:px-[min(var(--str-chat\_\_spacing-10),4%)]"
479
134
  onBack={onBack}
@@ -493,7 +148,7 @@ const ChannelViewInner: React.FC<{
493
148
  renderChannelActions={renderChannelActions}
494
149
  onParticipantNameClick={onParticipantNameClick}
495
150
  />
496
- ) : null
151
+ )
497
152
 
498
153
  // Prevents all message instances from unmounting when ChannelViewInner re-renders
499
154
  const MessageOverride = useCallback(
@@ -523,48 +178,20 @@ const ChannelViewInner: React.FC<{
523
178
  overrides={{
524
179
  Message: MessageOverride,
525
180
  MessageActions: CustomMessageActions,
526
- UnreadMessagesSeparator: () => null,
527
181
  }}
528
182
  >
529
183
  <Window>
530
- {/* Custom Channel Header */}
531
- {!onParticipantNameClick && (
532
- <>
533
- <div key="lt-channel-header">
534
- <CustomChannelHeader
535
- onBack={onBack}
536
- showBackButton={showBackButton}
537
- showChannelInfo={showChannelInfo}
538
- showStarButton={showStarButton}
539
- dmAgentEnabled={showDmAgentHeader}
540
- onLeaveConversation={onLeaveConversation}
541
- onBlockParticipant={onBlockParticipant}
542
- showDeleteConversation={showDeleteConversation}
543
- showBlockParticipant={showBlockParticipant}
544
- showReportParticipant={showReportParticipant}
545
- onDeleteConversationClick={onDeleteConversationClick}
546
- onBlockParticipantClick={onBlockParticipantClick}
547
- onReportParticipantClick={onReportParticipantClick}
548
- customChannelActions={customChannelActions}
549
- renderChannelActions={renderChannelActions}
550
- renderHeaderTitleBadges={renderHeaderTitleBadges}
551
- />
552
- </div>
553
- {channelBanner}
554
- </>
555
- )}
556
-
557
184
  {/* Message List */}
558
185
  <div
559
186
  key="lt-channel-message-list"
560
187
  className="flex-1 overflow-hidden relative"
561
188
  >
562
189
  {/* Stream only renders MessageList.head when there are messages, so
563
- empty redesigned channels need the header and banner in normal flow. */}
564
- {onParticipantNameClick && !hasVisibleMessages && (
190
+ empty channels need the header and banner in normal flow. */}
191
+ {!hasVisibleMessages && (
565
192
  <div className="str-chat__list">
566
193
  <div className="str-chat__message-list-scroll">
567
- {redesignedHeader}
194
+ {channelHeader}
568
195
  {channelBanner}
569
196
  </div>
570
197
  </div>
@@ -572,9 +199,9 @@ const ChannelViewInner: React.FC<{
572
199
 
573
200
  <MessageList
574
201
  head={
575
- onParticipantNameClick && hasVisibleMessages ? (
202
+ hasVisibleMessages ? (
576
203
  <div className="sticky top-0 z-10">
577
- {redesignedHeader}
204
+ {channelHeader}
578
205
  {channelBanner}
579
206
  </div>
580
207
  ) : undefined
@@ -636,7 +263,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
636
263
  showStarButton = false,
637
264
  chatbotVotingEnabled = false,
638
265
  renderChannelBanner,
639
- renderHeaderTitleBadges,
640
266
  customChannelActions,
641
267
  renderChannelActions,
642
268
  renderMessage,
@@ -754,7 +380,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
754
380
  dmAgentEnabled={dmAgentEnabled}
755
381
  chatbotVotingEnabled={chatbotVotingEnabled}
756
382
  renderChannelBanner={renderChannelBanner}
757
- renderHeaderTitleBadges={renderHeaderTitleBadges}
758
383
  customChannelActions={customChannelActions}
759
384
  renderChannelActions={renderChannelActions}
760
385
  renderMessage={renderMessage}
@@ -31,7 +31,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
31
31
  onMessageSent,
32
32
  chatbotVotingEnabled = false,
33
33
  viewerLanguage,
34
- renderHeaderTitleBadges,
35
34
  renderChannelBanner,
36
35
  customChannelActions,
37
36
  renderChannelActions,
@@ -258,7 +257,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
258
257
  onMessageSent={onMessageSent}
259
258
  chatbotVotingEnabled={chatbotVotingEnabled}
260
259
  viewerLanguage={viewerLanguage}
261
- renderHeaderTitleBadges={renderHeaderTitleBadges}
262
260
  customChannelActions={customChannelActions}
263
261
  renderChannelActions={renderChannelActions}
264
262
  onParticipantNameClick={onParticipantNameClick}
package/src/types.ts CHANGED
@@ -268,8 +268,8 @@ export interface ChannelViewProps {
268
268
  showStarButton?: boolean
269
269
 
270
270
  /**
271
- * Custom badges rendered inline after the participant name in the channel
272
- * header title.
271
+ * @deprecated No longer rendered. The redesigned header shows built-in paid
272
+ * and starred badges instead. Kept temporarily so existing call sites type-check.
273
273
  */
274
274
  renderHeaderTitleBadges?: (context: {
275
275
  channel: Channel
@@ -399,10 +399,9 @@ export interface ChannelViewProps {
399
399
  composerInput?: ComponentType
400
400
 
401
401
  /**
402
- * Fired when the participant's name in the channel header is clicked. When
403
- * provided, the name renders as a button with a trailing caret (an affordance
404
- * for opening participant details / thread content); when omitted, the name
405
- * renders as plain text.
402
+ * Fired when the participant identity in the channel header is clicked. When
403
+ * provided, the avatar + name pill render as a button with a trailing caret;
404
+ * when omitted, they render as non-interactive chrome.
406
405
  *
407
406
  * @example
408
407
  * onParticipantNameClick={() => openThreadContentDialog()}
@@ -425,7 +424,6 @@ export type ChannelViewPassthroughProps = Pick<
425
424
  | 'onMessageSent'
426
425
  | 'chatbotVotingEnabled'
427
426
  | 'viewerLanguage'
428
- | 'renderHeaderTitleBadges'
429
427
  | 'renderChannelBanner'
430
428
  | 'customChannelActions'
431
429
  | 'renderChannelActions'