@linktr.ee/messaging-react 3.4.2-rc-1782395074 → 3.4.2-rc-1782453401

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 (41) hide show
  1. package/dist/{Card-mpVcQ8-W.js → Card-52LnCirC.js} +3 -3
  2. package/dist/{Card-mpVcQ8-W.js.map → Card-52LnCirC.js.map} +1 -1
  3. package/dist/{Card-V-bNUxcw.cjs → Card-BIa-47Ne.cjs} +2 -2
  4. package/dist/{Card-V-bNUxcw.cjs.map → Card-BIa-47Ne.cjs.map} +1 -1
  5. package/dist/{Card-CphU_jG-.js → Card-BaXLsSK2.js} +2 -2
  6. package/dist/{Card-CphU_jG-.js.map → Card-BaXLsSK2.js.map} +1 -1
  7. package/dist/{Card-DFy_ZvFH.cjs → Card-BkVj6kGN.cjs} +2 -2
  8. package/dist/{Card-DFy_ZvFH.cjs.map → Card-BkVj6kGN.cjs.map} +1 -1
  9. package/dist/{Card-DxroYNrS.cjs → Card-CcTyYY9N.cjs} +2 -2
  10. package/dist/{Card-DxroYNrS.cjs.map → Card-CcTyYY9N.cjs.map} +1 -1
  11. package/dist/{Card-BKalDyyu.js → Card-ql9pImRa.js} +2 -2
  12. package/dist/{Card-BKalDyyu.js.map → Card-ql9pImRa.js.map} +1 -1
  13. package/dist/{LockedThumbnail-BD7qtcso.js → LockedThumbnail-C3YOnS-o.js} +2 -2
  14. package/dist/{LockedThumbnail-BD7qtcso.js.map → LockedThumbnail-C3YOnS-o.js.map} +1 -1
  15. package/dist/{LockedThumbnail-B3W7upHs.cjs → LockedThumbnail-D0ASm7LP.cjs} +2 -2
  16. package/dist/{LockedThumbnail-B3W7upHs.cjs.map → LockedThumbnail-D0ASm7LP.cjs.map} +1 -1
  17. package/dist/{index-CjwQ2Hj3.js → index-CbWEp3Zs.js} +1136 -1093
  18. package/dist/index-CbWEp3Zs.js.map +1 -0
  19. package/dist/index-DkqM2z_j.cjs +2 -0
  20. package/dist/index-DkqM2z_j.cjs.map +1 -0
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.ts +13 -25
  23. package/dist/index.js +13 -14
  24. package/package.json +2 -2
  25. package/src/components/ChannelList/CustomChannelPreview.stories.tsx +63 -0
  26. package/src/components/ChannelList/CustomChannelPreview.test.tsx +56 -6
  27. package/src/components/ChannelList/CustomChannelPreview.tsx +7 -10
  28. package/src/components/ChannelView.stories.tsx +38 -7
  29. package/src/components/ChannelView.test.tsx +29 -15
  30. package/src/components/ChannelView.tsx +19 -9
  31. package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +0 -34
  32. package/src/components/CustomMessageInput/index.tsx +1 -23
  33. package/src/components/MessagingShell/index.tsx +0 -2
  34. package/src/components/ParticipantBadges.tsx +39 -0
  35. package/src/hooks/useChannelStar.ts +9 -6
  36. package/src/index.ts +0 -1
  37. package/src/stream-custom-data.ts +16 -1
  38. package/src/types.ts +0 -18
  39. package/dist/index-CjwQ2Hj3.js.map +0 -1
  40. package/dist/index-tI-4cxV1.cjs +0 -2
  41. package/dist/index-tI-4cxV1.cjs.map +0 -1
@@ -32,6 +32,7 @@ import CustomTypingIndicator from './CustomTypingIndicator'
32
32
  import { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'
33
33
  import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
34
34
  import { LoadingState } from './MessagingShell/LoadingState'
35
+ import { ParticipantBadges } from './ParticipantBadges'
35
36
 
36
37
  const ICON_BTN_CLASS =
37
38
  'size-10 rounded-full hover:bg-[#E5E4E1] flex items-center justify-center transition-colors duration-150 focus-ring'
@@ -136,7 +137,6 @@ const CustomChannelHeader: React.FC<{
136
137
  id={participant?.user?.id || channel.id || 'unknown'}
137
138
  name={participantName}
138
139
  image={participantImage}
139
- starred={showStarButton && isStarred}
140
140
  dmAgentEnabled={dmAgentEnabled}
141
141
  size={48}
142
142
  />
@@ -148,6 +148,10 @@ const CustomChannelHeader: React.FC<{
148
148
  className="flex max-w-full items-center gap-0.5 rounded text-center text-xs font-medium text-black/90 transition-opacity hover:opacity-70"
149
149
  >
150
150
  <span className="min-w-0 truncate">{participantName}</span>
151
+ <ParticipantBadges
152
+ participant={participant}
153
+ isStarred={isStarred}
154
+ />
151
155
  {headerTitleBadges && (
152
156
  <span className="shrink-0">{headerTitleBadges}</span>
153
157
  )}
@@ -156,6 +160,10 @@ const CustomChannelHeader: React.FC<{
156
160
  ) : (
157
161
  <p className="flex max-w-full items-center justify-center gap-1 text-center text-xs font-medium text-black/90">
158
162
  <span className="min-w-0 truncate">{participantName}</span>
163
+ <ParticipantBadges
164
+ participant={participant}
165
+ isStarred={isStarred}
166
+ />
159
167
  {headerTitleBadges && (
160
168
  <span className="shrink-0">{headerTitleBadges}</span>
161
169
  )}
@@ -228,7 +236,6 @@ const CustomChannelHeader: React.FC<{
228
236
  id={participant?.user?.id || channel.id || 'unknown'}
229
237
  name={participantName}
230
238
  image={participantImage}
231
- starred={showStarButton && isStarred}
232
239
  dmAgentEnabled={dmAgentEnabled}
233
240
  size={48}
234
241
  />
@@ -241,6 +248,10 @@ const CustomChannelHeader: React.FC<{
241
248
  className="flex min-w-0 max-w-full items-center gap-1 rounded font-medium text-black/90 transition-opacity hover:opacity-70"
242
249
  >
243
250
  <span className="min-w-0 truncate">{participantName}</span>
251
+ <ParticipantBadges
252
+ participant={participant}
253
+ isStarred={isStarred}
254
+ />
244
255
  {headerTitleBadges && (
245
256
  <span className="shrink-0">{headerTitleBadges}</span>
246
257
  )}
@@ -249,6 +260,10 @@ const CustomChannelHeader: React.FC<{
249
260
  ) : (
250
261
  <h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
251
262
  <span className="min-w-0 truncate">{participantName}</span>
263
+ <ParticipantBadges
264
+ participant={participant}
265
+ isStarred={isStarred}
266
+ />
252
267
  {headerTitleBadges && (
253
268
  <span className="shrink-0">{headerTitleBadges}</span>
254
269
  )}
@@ -340,7 +355,6 @@ const ChannelViewInner: React.FC<{
340
355
  viewerLanguage?: string
341
356
  showChannelInfo?: boolean
342
357
  onParticipantNameClick?: () => void
343
- composerInput?: ChannelViewProps['composerInput']
344
358
  }> = ({
345
359
  onBack,
346
360
  showBackButton,
@@ -368,7 +382,6 @@ const ChannelViewInner: React.FC<{
368
382
  viewerLanguage,
369
383
  showChannelInfo = true,
370
384
  onParticipantNameClick,
371
- composerInput,
372
385
  }) => {
373
386
  const { channel } = useChannelStateContext()
374
387
 
@@ -390,10 +403,10 @@ const ChannelViewInner: React.FC<{
390
403
  }, [channel._client?.userID, channel.state?.members])
391
404
 
392
405
  const currentUserIsAccount =
393
- (currentMember?.user as { is_account?: boolean } | undefined)?.is_account ??
406
+ currentMember?.user?.is_account ??
394
407
  (currentMember as { is_account?: boolean } | undefined)?.is_account
395
408
  const participantIsAccount =
396
- (participant?.user as { is_account?: boolean } | undefined)?.is_account ??
409
+ participant?.user?.is_account ??
397
410
  (participant as { is_account?: boolean } | undefined)?.is_account
398
411
 
399
412
  const showDmAgentHeader =
@@ -485,7 +498,6 @@ const ChannelViewInner: React.FC<{
485
498
  renderFooter={() => renderMessageInputFooter?.(channel)}
486
499
  disabled={composerDisabled}
487
500
  disabledReason={composerDisabledReason}
488
- composerInput={composerInput}
489
501
  />
490
502
  </Window>
491
503
  </WithComponents>
@@ -532,7 +544,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
532
544
  viewerLanguage,
533
545
  showChannelInfo = true,
534
546
  onParticipantNameClick,
535
- composerInput,
536
547
  }) => {
537
548
  // Custom send message handler that:
538
549
  // 1. Applies messageMetadata if provided
@@ -647,7 +658,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
647
658
  viewerLanguage={viewerLanguage}
648
659
  showChannelInfo={showChannelInfo}
649
660
  onParticipantNameClick={onParticipantNameClick}
650
- composerInput={composerInput}
651
661
  />
652
662
  </Channel>
653
663
  </DmAgentEnabledContext.Provider>
@@ -204,40 +204,6 @@ describe('CustomMessageInput', () => {
204
204
  ).toBeInTheDocument()
205
205
  })
206
206
 
207
- it('renders a custom composerInput in place of the default composer', () => {
208
- mockChannelData = {}
209
-
210
- renderWithProviders(
211
- <CustomMessageInput
212
- composerInput={() => (
213
- <div data-testid="custom-composer">custom composer</div>
214
- )}
215
- />
216
- )
217
-
218
- // The host-owned composer renders instead of the built-in textarea/send.
219
- expect(screen.getByTestId('custom-composer')).toBeInTheDocument()
220
- expect(screen.queryByTestId('textarea-composer')).not.toBeInTheDocument()
221
- // It is still mounted inside Stream's <MessageInput> so it keeps the
222
- // message-input context (send pipeline, attachments, previews).
223
- expect(screen.getByTestId('stream-message-input')).toBeInTheDocument()
224
- })
225
-
226
- it('keeps the frozen wrapper around a custom composerInput', () => {
227
- mockChannelData = { frozen: true }
228
-
229
- const { container } = renderWithProviders(
230
- <CustomMessageInput
231
- composerInput={() => <div data-testid="custom-composer" />}
232
- />
233
- )
234
-
235
- const messageInput = container.querySelector('.message-input')
236
- expect(messageInput).toHaveAttribute('aria-disabled', 'true')
237
- expect(messageInput).toHaveAttribute('inert')
238
- expect(screen.getByTestId('custom-composer')).toBeInTheDocument()
239
- })
240
-
241
207
  it('does not render the locked panel when the composer is not disabled', () => {
242
208
  mockChannelData = {}
243
209
 
@@ -23,15 +23,6 @@ import { CustomLinkPreviewList } from '../CustomLinkPreviewList'
23
23
  */
24
24
  const ComposerLockedContext = React.createContext(false)
25
25
 
26
- /**
27
- * Read the composer's locked (frozen-channel) state from inside a custom
28
- * composer supplied via `composerInput`. Returns true when the channel is
29
- * frozen; mirror the default composer by rendering the textarea read-only and
30
- * disabling the send control.
31
- */
32
- export const useComposerLocked = (): boolean =>
33
- useContext(ComposerLockedContext)
34
-
35
26
  const DefaultSendButton: React.FC<{
36
27
  sendMessage: () => void
37
28
  disabled?: boolean
@@ -110,18 +101,6 @@ export interface CustomMessageInputProps {
110
101
  * `disabled` is true.
111
102
  */
112
103
  disabledReason?: string
113
- /**
114
- * Replace the inner composer — the textarea + send arrangement rendered
115
- * inside Stream's `<MessageInput>` — with a host-owned component. When
116
- * provided it is passed to Stream as the `Input` component, so it runs inside
117
- * the message-input context and can drive sending, attachments, and previews
118
- * via the Stream hooks (`useMessageInputContext`,
119
- * `useMessageComposerHasSendableData`, `TextareaComposer`, …). The outer
120
- * frozen/`renderActions`/`renderFooter` wrapper is unchanged; read
121
- * `useComposerLocked()` to honour the frozen state. Defaults to the built-in
122
- * composer.
123
- */
124
- composerInput?: React.ComponentType
125
104
  }
126
105
 
127
106
  export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
@@ -129,7 +108,6 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
129
108
  renderFooter,
130
109
  disabled = false,
131
110
  disabledReason,
132
- composerInput,
133
111
  }) => {
134
112
  const { channel } = useChannelStateContext()
135
113
  const isFrozen = channel?.data?.frozen === true
@@ -165,7 +143,7 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
165
143
  </div>
166
144
  )}
167
145
  <ComposerLockedContext.Provider value={isFrozen}>
168
- <MessageInput Input={composerInput ?? CustomMessageInputInner} />
146
+ <MessageInput Input={CustomMessageInputInner} />
169
147
  </ComposerLockedContext.Provider>
170
148
  </div>
171
149
  {renderFooter?.()}
@@ -39,7 +39,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
39
39
  renderMessage,
40
40
  onMessageLinkClick,
41
41
  showChannelInfo,
42
- composerInput,
43
42
  }) => {
44
43
  const {
45
44
  client,
@@ -265,7 +264,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
265
264
  renderMessage={renderMessage}
266
265
  onMessageLinkClick={onMessageLinkClick}
267
266
  showChannelInfo={showChannelInfo}
268
- composerInput={composerInput}
269
267
  />
270
268
  </div>
271
269
  </div>
@@ -0,0 +1,39 @@
1
+ import { CurrencyDollarIcon, StarIcon } from '@phosphor-icons/react'
2
+ import React from 'react'
3
+ import type { ChannelMemberResponse } from 'stream-chat'
4
+
5
+ export const ParticipantBadges: React.FC<{
6
+ participant?: ChannelMemberResponse
7
+ isStarred?: boolean
8
+ }> = ({ participant, isStarred = false }) => {
9
+ const isCustomer = participant?.customer === true
10
+
11
+ if (!isCustomer && !isStarred) return null
12
+
13
+ return (
14
+ <span className="inline-flex shrink-0 items-center gap-1">
15
+ {isCustomer && (
16
+ <span
17
+ role="img"
18
+ aria-label="Customer"
19
+ className="inline-flex size-4 items-center justify-center rounded-full bg-[#22C55E] text-white"
20
+ >
21
+ <CurrencyDollarIcon
22
+ aria-hidden="true"
23
+ className="size-3"
24
+ weight="bold"
25
+ />
26
+ </span>
27
+ )}
28
+ {isStarred && (
29
+ <span
30
+ role="img"
31
+ aria-label="Starred conversation"
32
+ className="inline-flex size-4 items-center justify-center rounded-full bg-[#F6C343] text-white"
33
+ >
34
+ <StarIcon aria-hidden="true" className="size-3" weight="fill" />
35
+ </span>
36
+ )}
37
+ </span>
38
+ )
39
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react'
2
- import { Channel, Event } from 'stream-chat'
2
+ import type { Channel, Event } from 'stream-chat'
3
3
 
4
4
  export const useChannelStar = (channel?: Channel) => {
5
5
  const [isChannelStarred, setIsChannelStarred] = useState(
@@ -15,15 +15,18 @@ export const useChannelStar = (channel?: Channel) => {
15
15
  setIsChannelStarred(!!channel.state.membership?.pinned_at)
16
16
 
17
17
  const handleMemberUpdate = (event: Event) => {
18
- setIsChannelStarred(
19
- event?.member ? !!event.member.pinned_at : !!channel.state.membership?.pinned_at
20
- )
18
+ const currentUserId = channel._client?.userID
19
+ const memberUserId = event.member?.user_id ?? event.member?.user?.id
20
+
21
+ if (!currentUserId || memberUserId !== currentUserId) return
22
+
23
+ setIsChannelStarred(!!event.member?.pinned_at)
21
24
  }
22
25
 
23
- channel.on('member.updated', handleMemberUpdate)
26
+ const subscription = channel.on('member.updated', handleMemberUpdate)
24
27
 
25
28
  return () => {
26
- channel.off('member.updated', handleMemberUpdate)
29
+ subscription.unsubscribe()
27
30
  }
28
31
  }, [channel])
29
32
 
package/src/index.ts CHANGED
@@ -30,7 +30,6 @@ export { MessagingProvider } from './providers/MessagingProvider'
30
30
  export { CustomMessageProvider } from './components/CustomMessage/context'
31
31
 
32
32
  // Hooks
33
- export { useComposerLocked } from './components/CustomMessageInput'
34
33
  export { useMessaging } from './hooks/useMessaging'
35
34
  export { useMessageVote } from './hooks/useMessageVote'
36
35
  export { useCustomMessage } from './components/CustomMessage/context'
@@ -2,7 +2,8 @@
2
2
  * Stream Chat custom data type augmentation.
3
3
  *
4
4
  * This file extends Stream Chat's type system by augmenting their empty
5
- * CustomMessageData and CustomChannelData interfaces with our custom properties.
5
+ * CustomMessageData, CustomChannelData, CustomMemberData, and CustomUserData
6
+ * interfaces with our custom properties.
6
7
  *
7
8
  * @see https://getstream.io/chat/docs/sdk/react/guides/typescript_and_custom_data_types/
8
9
  *
@@ -98,6 +99,14 @@ declare module 'stream-chat' {
98
99
  chatbot_paused?: boolean
99
100
  }
100
101
 
102
+ interface CustomMemberData {
103
+ /**
104
+ * Whether the member should be identified as a customer in participant UI.
105
+ * Stored on the channel member so it is scoped to this conversation.
106
+ */
107
+ customer?: boolean
108
+ }
109
+
101
110
  interface CustomMessageData {
102
111
  /**
103
112
  * When true, hides the date timestamp in system messages.
@@ -115,4 +124,10 @@ declare module 'stream-chat' {
115
124
  */
116
125
  dm_agent_system_type?: DmAgentSystemType
117
126
  }
127
+
128
+ interface CustomUserData {
129
+ is_account?: boolean
130
+ is_follower?: boolean
131
+ username?: string
132
+ }
118
133
  }
package/src/types.ts CHANGED
@@ -324,23 +324,6 @@ export interface ChannelViewProps {
324
324
  */
325
325
  attachmentPreviewList?: ComponentType
326
326
 
327
- /**
328
- * Replace the entire inner composer — the textarea + send arrangement — with
329
- * a host-owned component. Passed to Stream's `<MessageInput>` as the `Input`
330
- * component, so it runs inside the message-input context and reuses the
331
- * library's send pipeline (`doSendMessageRequest`, message metadata, DM-agent
332
- * suppression), attachments, and previews via the Stream hooks
333
- * (`useMessageInputContext`, `useMessageComposerHasSendableData`,
334
- * `TextareaComposer`, …). Use it when a surface needs a different composer
335
- * layout (e.g. a full-width textarea above a toolbar row) than the default.
336
- *
337
- * The outer wrapper — frozen handling and the `renderMessageInputActions` /
338
- * `renderMessageInputFooter` slots — is unchanged. Read the exported
339
- * `useComposerLocked()` hook to honour the frozen state. Defaults to the
340
- * built-in composer when omitted.
341
- */
342
- composerInput?: ComponentType
343
-
344
327
  /**
345
328
  * Fired when the participant's name in the channel header is clicked. When
346
329
  * provided, the name renders as a button with a trailing caret (an affordance
@@ -376,7 +359,6 @@ export type ChannelViewPassthroughProps = Pick<
376
359
  | 'renderMessage'
377
360
  | 'onMessageLinkClick'
378
361
  | 'showChannelInfo'
379
- | 'composerInput'
380
362
  >
381
363
 
382
364
  /**