@linktr.ee/messaging-react 3.5.0-rc-1782793261 → 3.5.0-rc-1782802201

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 (33) hide show
  1. package/dist/{Card-49AvU29H.js → Card-B4Pl1YDS.js} +2 -2
  2. package/dist/{Card-49AvU29H.js.map → Card-B4Pl1YDS.js.map} +1 -1
  3. package/dist/{Card-Cse19FAO.cjs → Card-BN8zZir8.cjs} +2 -2
  4. package/dist/{Card-Cse19FAO.cjs.map → Card-BN8zZir8.cjs.map} +1 -1
  5. package/dist/{Card-q0zqEbga.js → Card-BP5tTT6R.js} +3 -3
  6. package/dist/{Card-q0zqEbga.js.map → Card-BP5tTT6R.js.map} +1 -1
  7. package/dist/{Card-DS8zNUkL.js → Card-BS-w4E4s.js} +2 -2
  8. package/dist/{Card-DS8zNUkL.js.map → Card-BS-w4E4s.js.map} +1 -1
  9. package/dist/{Card-DBIVUdM5.cjs → Card-Dc-vSe7n.cjs} +2 -2
  10. package/dist/{Card-DBIVUdM5.cjs.map → Card-Dc-vSe7n.cjs.map} +1 -1
  11. package/dist/{Card-DgPx-6aq.cjs → Card-UOf2uGf-.cjs} +2 -2
  12. package/dist/{Card-DgPx-6aq.cjs.map → Card-UOf2uGf-.cjs.map} +1 -1
  13. package/dist/{LockedThumbnail-BFgw9F0g.cjs → LockedThumbnail-CUC76ycd.cjs} +2 -2
  14. package/dist/{LockedThumbnail-BFgw9F0g.cjs.map → LockedThumbnail-CUC76ycd.cjs.map} +1 -1
  15. package/dist/{LockedThumbnail-CRBDhB49.js → LockedThumbnail-nUEb_HRw.js} +2 -2
  16. package/dist/{LockedThumbnail-CRBDhB49.js.map → LockedThumbnail-nUEb_HRw.js.map} +1 -1
  17. package/dist/{index-DxzkXLSW.js → index-CghCKfXq.js} +945 -930
  18. package/dist/{index-DxzkXLSW.js.map → index-CghCKfXq.js.map} +1 -1
  19. package/dist/index-DPP8cBmP.cjs +2 -0
  20. package/dist/{index-BcmwckcI.cjs.map → index-DPP8cBmP.cjs.map} +1 -1
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.ts +9 -2
  23. package/dist/index.js +1 -1
  24. package/package.json +2 -2
  25. package/src/components/ChannelList/ChannelList.stories.tsx +13 -9
  26. package/src/components/ChannelList/ChannelListContext.tsx +4 -0
  27. package/src/components/ChannelList/CustomChannelPreview.test.tsx +46 -1
  28. package/src/components/ChannelList/CustomChannelPreview.tsx +17 -2
  29. package/src/components/ChannelList/index.test.tsx +20 -3
  30. package/src/components/ChannelList/index.tsx +3 -1
  31. package/src/index.ts +1 -0
  32. package/src/types.ts +9 -2
  33. package/dist/index-BcmwckcI.cjs +0 -2
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BcmwckcI.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-DPP8cBmP.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -135,9 +135,10 @@ export declare interface ChannelListProps {
135
135
  customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>;
136
136
  /**
137
137
  * Custom component rendered for each channel row in the conversation list.
138
- * Defaults to the package's built-in CustomChannelPreview.
138
+ * Receives Stream's channel preview props plus this package's selection
139
+ * state and `onChannelSelect` handler.
139
140
  */
140
- channelPreview?: ComponentType<ChannelPreviewUIComponentProps>;
141
+ channelPreview?: ComponentType<ChannelPreviewProps>;
141
142
  renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
142
143
  /**
143
144
  * Language code used to pick translated message text from Stream Chat i18n.
@@ -146,6 +147,12 @@ export declare interface ChannelListProps {
146
147
  viewerLanguage?: string;
147
148
  }
148
149
 
150
+ export declare type ChannelPreviewProps = ChannelPreviewUIComponentProps & {
151
+ isSelected: boolean;
152
+ selectedChannel?: Channel | null;
153
+ onChannelSelect: (channel: Channel) => void;
154
+ };
155
+
149
156
  /**
150
157
  * Channel view component with message list and input
151
158
  */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as g, f as r, L as l, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as C, o as v, p as A, q as k, s as F, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B, E as D } from "./index-DxzkXLSW.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as g, f as r, L as l, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as C, o as v, p as A, q as k, s as F, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B, E as D } from "./index-CghCKfXq.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.5.0-rc-1782793261",
3
+ "version": "3.5.0-rc-1782802201",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@linktr.ee/component-library": "11.8.6",
41
- "@linktr.ee/messaging-core": "2.3.0-rc-1782793261",
41
+ "@linktr.ee/messaging-core": "2.3.0-rc-1782802201",
42
42
  "@phosphor-icons/react": "^2.1.10"
43
43
  },
44
44
  "devDependencies": {
@@ -1,12 +1,11 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
2
  import React from 'react'
3
- import type { ChannelPreviewUIComponentProps } from 'stream-chat-react'
4
3
 
5
4
  import { MockChatProviderWithChannels } from '../../stories/mocks'
5
+ import type { ChannelPreviewProps } from '../../types'
6
6
 
7
7
  import { ChannelList } from '.'
8
8
 
9
-
10
9
  type ComponentProps = React.ComponentProps<typeof ChannelList>
11
10
 
12
11
  // ChannelList renders a list of mocked channels driven by MockChatProvider —
@@ -38,8 +37,10 @@ const Template: StoryFn<ComponentProps> = (args) => {
38
37
  )
39
38
  }
40
39
 
41
- const HostChannelPreview: React.FC<ChannelPreviewUIComponentProps> = ({
40
+ const HostChannelPreview: React.FC<ChannelPreviewProps> = ({
42
41
  channel,
42
+ isSelected,
43
+ onChannelSelect,
43
44
  unread,
44
45
  }) => {
45
46
  const participant = Object.values(channel?.state?.members || {}).find(
@@ -47,7 +48,11 @@ const HostChannelPreview: React.FC<ChannelPreviewUIComponentProps> = ({
47
48
  )
48
49
 
49
50
  return (
50
- <div className="mx-3 mt-3 rounded-[20px] border border-[#e0dfdb] bg-white p-4 shadow-[0_4px_16px_rgba(0,0,0,0.06)]">
51
+ <button
52
+ type="button"
53
+ onClick={() => onChannelSelect(channel)}
54
+ className="mx-3 mt-3 block w-[calc(100%-1.5rem)] rounded-[20px] border border-[#e0dfdb] bg-white p-4 text-left shadow-[0_4px_16px_rgba(0,0,0,0.06)]"
55
+ >
51
56
  <div className="flex items-center justify-between">
52
57
  <div className="min-w-0">
53
58
  <p className="truncate text-sm font-semibold text-[#191918]">
@@ -55,6 +60,7 @@ const HostChannelPreview: React.FC<ChannelPreviewUIComponentProps> = ({
55
60
  </p>
56
61
  <p className="mt-1 line-clamp-1 text-xs text-[#717070]">
57
62
  Host-owned channel preview
63
+ {isSelected ? ' (selected)' : ''}
58
64
  </p>
59
65
  </div>
60
66
  {Boolean(unread) && (
@@ -63,7 +69,7 @@ const HostChannelPreview: React.FC<ChannelPreviewUIComponentProps> = ({
63
69
  </span>
64
70
  )}
65
71
  </div>
66
- </div>
72
+ </button>
67
73
  )
68
74
  }
69
75
 
@@ -79,11 +85,9 @@ WithSelection.args = {
79
85
  onChannelSelect: (channel) => console.log('Channel selected:', channel.id),
80
86
  }
81
87
 
82
- export const Custom: StoryFn<ComponentProps> = Template.bind({})
83
- Custom.args = {
88
+ export const CustomChannelPreview: StoryFn<ComponentProps> = Template.bind({})
89
+ CustomChannelPreview.args = {
84
90
  filters: { type: 'messaging' },
85
91
  onChannelSelect: (channel) => console.log('Channel selected:', channel.id),
86
92
  channelPreview: HostChannelPreview,
87
93
  }
88
-
89
-
@@ -1,6 +1,8 @@
1
1
  import React from 'react'
2
2
  import type { Channel, LocalMessage } from 'stream-chat'
3
3
 
4
+ import type { ChannelListProps } from '../../types'
5
+
4
6
  type ChannelListContextValue = {
5
7
  selectedChannel?: Channel | null
6
8
  onChannelSelect: (channel: Channel) => void
@@ -9,6 +11,7 @@ type ChannelListContextValue = {
9
11
  message: LocalMessage | undefined,
10
12
  defaultPreview?: string
11
13
  ) => React.ReactNode
14
+ channelPreview?: ChannelListProps['channelPreview']
12
15
  viewerLanguage?: string
13
16
  }
14
17
 
@@ -17,6 +20,7 @@ const ChannelListContext = React.createContext<ChannelListContextValue>({
17
20
  onChannelSelect: () => {},
18
21
  debug: false,
19
22
  renderMessagePreview: undefined,
23
+ channelPreview: undefined,
20
24
  viewerLanguage: undefined,
21
25
  })
22
26
 
@@ -1,9 +1,11 @@
1
- import { act } from '@testing-library/react'
1
+ import { act, fireEvent } from '@testing-library/react'
2
2
  import { Channel, LocalMessage, StreamChat } from 'stream-chat'
3
3
  import { describe, expect, it, vi } from 'vitest'
4
4
 
5
5
  import { renderWithProviders, screen } from '../../test/utils'
6
+ import type { ChannelPreviewProps } from '../../types'
6
7
 
8
+ import { ChannelListProvider } from './ChannelListContext'
7
9
  import CustomChannelPreview from './CustomChannelPreview'
8
10
 
9
11
  const mockUser = {
@@ -272,4 +274,47 @@ describe('CustomChannelPreview', () => {
272
274
  screen.getByRole('heading', { name: /starred conversation/i })
273
275
  ).toBeInTheDocument()
274
276
  })
277
+
278
+ it('renders a custom channel preview with selection props from context', () => {
279
+ const channel = createMockChannel([])
280
+ const onChannelSelect = vi.fn()
281
+ const ChannelPreview = vi.fn(
282
+ ({
283
+ channel,
284
+ isSelected,
285
+ onChannelSelect,
286
+ selectedChannel,
287
+ }: ChannelPreviewProps) => (
288
+ <button type="button" onClick={() => onChannelSelect(channel)}>
289
+ {isSelected ? 'Selected' : 'Not selected'} {selectedChannel?.id}
290
+ </button>
291
+ )
292
+ )
293
+
294
+ renderWithProviders(
295
+ <ChannelListProvider
296
+ value={{
297
+ selectedChannel: channel,
298
+ onChannelSelect,
299
+ debug: false,
300
+ channelPreview: ChannelPreview,
301
+ }}
302
+ >
303
+ <CustomChannelPreview channel={channel} />
304
+ </ChannelListProvider>
305
+ )
306
+
307
+ fireEvent.click(screen.getByRole('button', { name: 'Selected channel-1' }))
308
+
309
+ expect(ChannelPreview).toHaveBeenCalledWith(
310
+ expect.objectContaining({
311
+ channel,
312
+ isSelected: true,
313
+ selectedChannel: channel,
314
+ onChannelSelect,
315
+ }),
316
+ expect.anything()
317
+ )
318
+ expect(onChannelSelect).toHaveBeenCalledWith(channel)
319
+ })
275
320
  })
@@ -15,16 +15,32 @@ import { useChannelListContext } from './ChannelListContext'
15
15
  * Custom channel preview that handles selection
16
16
  */
17
17
  const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
18
- ({ channel, unread }) => {
18
+ (props) => {
19
+ const { channel, unread } = props
19
20
  const {
20
21
  selectedChannel,
21
22
  onChannelSelect,
22
23
  debug,
24
+ channelPreview,
23
25
  renderMessagePreview,
24
26
  viewerLanguage,
25
27
  } = useChannelListContext()
26
28
 
27
29
  const isSelected = selectedChannel?.id === channel?.id
30
+ const isChannelStarred = useChannelStar(channel)
31
+
32
+ if (channelPreview) {
33
+ const ChannelPreview = channelPreview
34
+
35
+ return (
36
+ <ChannelPreview
37
+ {...props}
38
+ isSelected={isSelected}
39
+ selectedChannel={selectedChannel}
40
+ onChannelSelect={onChannelSelect}
41
+ />
42
+ )
43
+ }
28
44
 
29
45
  const handleClick = () => {
30
46
  if (channel) {
@@ -105,7 +121,6 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
105
121
  const messagePreview = renderMessagePreview
106
122
  ? renderMessagePreview(lastMessage, lastMessageText)
107
123
  : `${isLastMessageFromChatbot ? '✨ ' : ''}${lastMessageText}`
108
- const isChannelStarred = useChannelStar(channel)
109
124
 
110
125
  // Use the unread prop passed by Stream Chat (reactive and updates automatically)
111
126
  const unreadCount = unread ?? 0
@@ -7,7 +7,14 @@ import { renderWithProviders } from '../../test/utils'
7
7
  import { ChannelList } from '.'
8
8
 
9
9
  const streamChannelListMock = vi.fn()
10
- const { CustomChannelPreviewMock, InfiniteScrollMock } = vi.hoisted(() => ({
10
+ const {
11
+ ChannelListProviderMock,
12
+ CustomChannelPreviewMock,
13
+ InfiniteScrollMock,
14
+ } = vi.hoisted(() => ({
15
+ ChannelListProviderMock: vi.fn(
16
+ ({ children }: { children: React.ReactNode; value: unknown }) => children
17
+ ),
11
18
  CustomChannelPreviewMock: () => null,
12
19
  InfiniteScrollMock: () => null,
13
20
  }))
@@ -24,6 +31,10 @@ vi.mock('../../providers/MessagingProvider', () => ({
24
31
  useMessagingContext: () => ({ debug: false }),
25
32
  }))
26
33
 
34
+ vi.mock('./ChannelListContext', () => ({
35
+ ChannelListProvider: ChannelListProviderMock,
36
+ }))
37
+
27
38
  vi.mock('./CustomChannelPreview', () => ({
28
39
  default: CustomChannelPreviewMock,
29
40
  }))
@@ -35,6 +46,7 @@ describe('ChannelList', () => {
35
46
  }
36
47
 
37
48
  beforeEach(() => {
49
+ ChannelListProviderMock.mockClear()
38
50
  streamChannelListMock.mockClear()
39
51
  })
40
52
 
@@ -65,7 +77,7 @@ describe('ChannelList', () => {
65
77
  })
66
78
  })
67
79
 
68
- it('passes a custom channel preview through to Stream ChannelList', () => {
80
+ it('keeps the built-in channel preview bridge when a custom preview is provided', () => {
69
81
  const ChannelPreview = () => null
70
82
 
71
83
  renderWithProviders(
@@ -73,9 +85,14 @@ describe('ChannelList', () => {
73
85
  )
74
86
 
75
87
  expect(streamChannelListMock).toHaveBeenCalledOnce()
88
+ const contextValue = ChannelListProviderMock.mock.calls[0][0].value as {
89
+ channelPreview?: unknown
90
+ }
91
+
76
92
  expect(streamChannelListMock.mock.calls[0][0]).toMatchObject({
77
- Preview: ChannelPreview,
93
+ Preview: CustomChannelPreviewMock,
78
94
  })
95
+ expect(contextValue.channelPreview).toBe(ChannelPreview)
79
96
  })
80
97
 
81
98
  it('passes allowNewMessagesFromUnfilteredChannels through to Stream ChannelList', () => {
@@ -66,6 +66,7 @@ export const ChannelList = React.memo<ChannelListProps>(
66
66
  selectedChannel,
67
67
  onChannelSelect,
68
68
  debug,
69
+ channelPreview,
69
70
  renderMessagePreview,
70
71
  viewerLanguage,
71
72
  }),
@@ -73,6 +74,7 @@ export const ChannelList = React.memo<ChannelListProps>(
73
74
  selectedChannel,
74
75
  onChannelSelect,
75
76
  debug,
77
+ channelPreview,
76
78
  renderMessagePreview,
77
79
  viewerLanguage,
78
80
  ]
@@ -98,7 +100,7 @@ export const ChannelList = React.memo<ChannelListProps>(
98
100
  }
99
101
  channelRenderFilterFn={wrappedChannelRenderFilterFn}
100
102
  Paginator={InfiniteScroll}
101
- Preview={channelPreview ?? CustomChannelPreview}
103
+ Preview={CustomChannelPreview}
102
104
  EmptyStateIndicator={customEmptyStateIndicator}
103
105
  />
104
106
  </ChannelListProvider>
package/src/index.ts CHANGED
@@ -51,6 +51,7 @@ export type { ParticipantDisplayUser } from './utils/resolveParticipantDisplayNa
51
51
  export type {
52
52
  MessagingShellProps,
53
53
  ChannelListProps,
54
+ ChannelPreviewProps,
54
55
  ChannelViewProps,
55
56
  MessagingProviderProps,
56
57
  MessagingCapabilities,
package/src/types.ts CHANGED
@@ -33,6 +33,12 @@ export type MessageLinkClickHandler = (
33
33
  message: LocalMessage | undefined
34
34
  ) => void
35
35
 
36
+ export type ChannelPreviewProps = ChannelPreviewUIComponentProps & {
37
+ isSelected: boolean
38
+ selectedChannel?: Channel | null
39
+ onChannelSelect: (channel: Channel) => void
40
+ }
41
+
36
42
  /**
37
43
  * Generic participant interface for different host environments
38
44
  */
@@ -94,9 +100,10 @@ export interface ChannelListProps {
94
100
  customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>
95
101
  /**
96
102
  * Custom component rendered for each channel row in the conversation list.
97
- * Defaults to the package's built-in CustomChannelPreview.
103
+ * Receives Stream's channel preview props plus this package's selection
104
+ * state and `onChannelSelect` handler.
98
105
  */
99
- channelPreview?: ComponentType<ChannelPreviewUIComponentProps>
106
+ channelPreview?: ComponentType<ChannelPreviewProps>
100
107
  renderMessagePreview?: (
101
108
  message: LocalMessage | undefined,
102
109
  defaultPreview?: string