@linktr.ee/messaging-react 3.5.0-rc-1782408966 → 3.5.0-rc-1782792813
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/{Card-iekRe643.js → Card-BWWBaNKF.js} +2 -2
- package/dist/{Card-iekRe643.js.map → Card-BWWBaNKF.js.map} +1 -1
- package/dist/{Card-DJtDI03Y.cjs → Card-Bj4YaHav.cjs} +2 -2
- package/dist/{Card-DJtDI03Y.cjs.map → Card-Bj4YaHav.cjs.map} +1 -1
- package/dist/{Card-CUHEDe8O.js → Card-BnGeMt4f.js} +2 -2
- package/dist/{Card-CUHEDe8O.js.map → Card-BnGeMt4f.js.map} +1 -1
- package/dist/{Card-BNzC_bp8.js → Card-DkXzpFLA.js} +3 -3
- package/dist/{Card-BNzC_bp8.js.map → Card-DkXzpFLA.js.map} +1 -1
- package/dist/{Card-DuMr5KMP.cjs → Card-DpCwDviq.cjs} +2 -2
- package/dist/{Card-DuMr5KMP.cjs.map → Card-DpCwDviq.cjs.map} +1 -1
- package/dist/{Card-DEa50m9p.cjs → Card-qrDaFHtJ.cjs} +2 -2
- package/dist/{Card-DEa50m9p.cjs.map → Card-qrDaFHtJ.cjs.map} +1 -1
- package/dist/{LockedThumbnail-CvpRZFi-.js → LockedThumbnail-DJR--PAi.js} +2 -2
- package/dist/{LockedThumbnail-CvpRZFi-.js.map → LockedThumbnail-DJR--PAi.js.map} +1 -1
- package/dist/{LockedThumbnail-aME0f_ZF.cjs → LockedThumbnail-DhXEk-NX.cjs} +2 -2
- package/dist/{LockedThumbnail-aME0f_ZF.cjs.map → LockedThumbnail-DhXEk-NX.cjs.map} +1 -1
- package/dist/index-DzOA7Yu8.cjs +2 -0
- package/dist/index-DzOA7Yu8.cjs.map +1 -0
- package/dist/{index-BlpDX5fl.js → index-ubCRSgQk.js} +1423 -1328
- package/dist/index-ubCRSgQk.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +40 -16
- package/dist/index.js +19 -19
- package/package.json +2 -2
- package/src/components/ChannelList/ChannelList.stories.tsx +5 -3
- package/src/components/ChannelList/CustomChannelPreview.stories.tsx +111 -1
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +58 -46
- package/src/components/ChannelList/CustomChannelPreview.tsx +12 -13
- package/src/components/ChannelList/index.test.tsx +25 -2
- package/src/components/ChannelList/index.tsx +2 -1
- package/src/components/ChannelView.stories.tsx +45 -12
- package/src/components/ChannelView.test.tsx +64 -62
- package/src/components/ChannelView.tsx +123 -68
- package/src/components/CustomMessage/index.tsx +1 -0
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +34 -0
- package/src/components/CustomMessageInput/index.tsx +27 -2
- package/src/components/MediaMessage/index.tsx +13 -5
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/components/ParticipantBadges.tsx +42 -0
- package/src/hooks/useChannelStar.ts +9 -6
- package/src/index.ts +1 -2
- package/src/stories/mocks.tsx +30 -5
- package/src/stream-custom-data.ts +12 -5
- package/src/types.ts +27 -1
- package/dist/index-BIWS3Inh.cjs +0 -2
- package/dist/index-BIWS3Inh.cjs.map +0 -1
- package/dist/index-BlpDX5fl.js.map +0 -1
- package/src/components/VerifiedBadge/index.tsx +0 -25
|
@@ -7,7 +7,8 @@ import { renderWithProviders } from '../../test/utils'
|
|
|
7
7
|
import { ChannelList } from '.'
|
|
8
8
|
|
|
9
9
|
const streamChannelListMock = vi.fn()
|
|
10
|
-
const { InfiniteScrollMock } = vi.hoisted(() => ({
|
|
10
|
+
const { CustomChannelPreviewMock, InfiniteScrollMock } = vi.hoisted(() => ({
|
|
11
|
+
CustomChannelPreviewMock: () => null,
|
|
11
12
|
InfiniteScrollMock: () => null,
|
|
12
13
|
}))
|
|
13
14
|
|
|
@@ -24,7 +25,7 @@ vi.mock('../../providers/MessagingProvider', () => ({
|
|
|
24
25
|
}))
|
|
25
26
|
|
|
26
27
|
vi.mock('./CustomChannelPreview', () => ({
|
|
27
|
-
default:
|
|
28
|
+
default: CustomChannelPreviewMock,
|
|
28
29
|
}))
|
|
29
30
|
|
|
30
31
|
describe('ChannelList', () => {
|
|
@@ -55,6 +56,28 @@ describe('ChannelList', () => {
|
|
|
55
56
|
})
|
|
56
57
|
})
|
|
57
58
|
|
|
59
|
+
it('uses the built-in channel preview by default', () => {
|
|
60
|
+
renderWithProviders(<ChannelList {...defaultProps} />)
|
|
61
|
+
|
|
62
|
+
expect(streamChannelListMock).toHaveBeenCalledOnce()
|
|
63
|
+
expect(streamChannelListMock.mock.calls[0][0]).toMatchObject({
|
|
64
|
+
Preview: CustomChannelPreviewMock,
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('passes a custom channel preview through to Stream ChannelList', () => {
|
|
69
|
+
const ChannelPreview = () => null
|
|
70
|
+
|
|
71
|
+
renderWithProviders(
|
|
72
|
+
<ChannelList {...defaultProps} channelPreview={ChannelPreview} />
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(streamChannelListMock).toHaveBeenCalledOnce()
|
|
76
|
+
expect(streamChannelListMock.mock.calls[0][0]).toMatchObject({
|
|
77
|
+
Preview: ChannelPreview,
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
58
81
|
it('passes allowNewMessagesFromUnfilteredChannels through to Stream ChannelList', () => {
|
|
59
82
|
const props: React.ComponentProps<typeof ChannelList> = {
|
|
60
83
|
...defaultProps,
|
|
@@ -28,6 +28,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
28
28
|
sort = DEFAULT_SORT,
|
|
29
29
|
className,
|
|
30
30
|
customEmptyStateIndicator,
|
|
31
|
+
channelPreview,
|
|
31
32
|
renderMessagePreview,
|
|
32
33
|
viewerLanguage,
|
|
33
34
|
}) => {
|
|
@@ -97,7 +98,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
97
98
|
}
|
|
98
99
|
channelRenderFilterFn={wrappedChannelRenderFilterFn}
|
|
99
100
|
Paginator={InfiniteScroll}
|
|
100
|
-
Preview={CustomChannelPreview}
|
|
101
|
+
Preview={channelPreview ?? CustomChannelPreview}
|
|
101
102
|
EmptyStateIndicator={customEmptyStateIndicator}
|
|
102
103
|
/>
|
|
103
104
|
</ChannelListProvider>
|
|
@@ -41,7 +41,14 @@ const createMockChannel = async (
|
|
|
41
41
|
client: StreamChat,
|
|
42
42
|
hasMessages = true,
|
|
43
43
|
followerStatus?: string | boolean,
|
|
44
|
-
isFrozen = false
|
|
44
|
+
isFrozen = false,
|
|
45
|
+
{
|
|
46
|
+
isCustomer = false,
|
|
47
|
+
isStarred = false,
|
|
48
|
+
}: {
|
|
49
|
+
isCustomer?: boolean
|
|
50
|
+
isStarred?: boolean
|
|
51
|
+
} = {}
|
|
45
52
|
) => {
|
|
46
53
|
const participant = mockParticipants[0]
|
|
47
54
|
|
|
@@ -142,8 +149,12 @@ const createMockChannel = async (
|
|
|
142
149
|
user_id: participant.id,
|
|
143
150
|
role: 'member',
|
|
144
151
|
is_account: true,
|
|
152
|
+
...(isCustomer ? { customer: true } : {}),
|
|
145
153
|
},
|
|
146
154
|
}
|
|
155
|
+
channel.state.membership = {
|
|
156
|
+
pinned_at: isStarred ? now().toISOString() : null,
|
|
157
|
+
} as ChannelMemberResponse
|
|
147
158
|
return {
|
|
148
159
|
channel: channelData,
|
|
149
160
|
members: [],
|
|
@@ -171,6 +182,8 @@ const createMockChannel = async (
|
|
|
171
182
|
type TemplateProps = ComponentProps & {
|
|
172
183
|
followerStatus?: string | boolean
|
|
173
184
|
isFrozen?: boolean
|
|
185
|
+
isCustomer?: boolean
|
|
186
|
+
isStarred?: boolean
|
|
174
187
|
typingUser?: {
|
|
175
188
|
id: string
|
|
176
189
|
name?: string
|
|
@@ -182,6 +195,8 @@ const Template: StoryFn<TemplateProps> = (args) => {
|
|
|
182
195
|
const {
|
|
183
196
|
followerStatus,
|
|
184
197
|
isFrozen = false,
|
|
198
|
+
isCustomer = false,
|
|
199
|
+
isStarred = false,
|
|
185
200
|
typingUser,
|
|
186
201
|
...channelViewProps
|
|
187
202
|
} = args
|
|
@@ -197,12 +212,13 @@ const Template: StoryFn<TemplateProps> = (args) => {
|
|
|
197
212
|
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
198
213
|
|
|
199
214
|
useEffect(() => {
|
|
200
|
-
createMockChannel(client, true, followerStatus, isFrozen
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
createMockChannel(client, true, followerStatus, isFrozen, {
|
|
216
|
+
isCustomer,
|
|
217
|
+
isStarred,
|
|
218
|
+
}).then((mockChannel) => {
|
|
219
|
+
setChannel(mockChannel)
|
|
220
|
+
})
|
|
221
|
+
}, [client, followerStatus, isCustomer, isFrozen, isStarred])
|
|
206
222
|
|
|
207
223
|
useEffect(() => {
|
|
208
224
|
if (!channel || !typingUser) {
|
|
@@ -251,18 +267,20 @@ Default.parameters = {
|
|
|
251
267
|
},
|
|
252
268
|
}
|
|
253
269
|
|
|
254
|
-
export const
|
|
255
|
-
|
|
270
|
+
export const ParticipantIdentityTrigger: StoryFn<TemplateProps> = Template.bind(
|
|
271
|
+
{}
|
|
272
|
+
)
|
|
273
|
+
ParticipantIdentityTrigger.args = {
|
|
256
274
|
showBackButton: false,
|
|
257
275
|
onParticipantNameClick: () =>
|
|
258
276
|
// eslint-disable-next-line no-alert
|
|
259
|
-
window.alert('Participant
|
|
277
|
+
window.alert('Participant details clicked — open Thread content'),
|
|
260
278
|
}
|
|
261
|
-
|
|
279
|
+
ParticipantIdentityTrigger.parameters = {
|
|
262
280
|
docs: {
|
|
263
281
|
description: {
|
|
264
282
|
story:
|
|
265
|
-
'Header with `onParticipantNameClick` set: the participant name
|
|
283
|
+
'Header with `onParticipantNameClick` set: the participant avatar and name render as one button with a trailing caret in both the mobile and desktop header layouts. Click the identity block to fire the callback (shows an alert here). With the prop omitted (see Default) the header remains non-interactive.',
|
|
266
284
|
},
|
|
267
285
|
},
|
|
268
286
|
}
|
|
@@ -307,6 +325,21 @@ WithHeaderTitleBadges.parameters = {
|
|
|
307
325
|
},
|
|
308
326
|
}
|
|
309
327
|
|
|
328
|
+
export const ParticipantBadges: StoryFn<TemplateProps> = Template.bind({})
|
|
329
|
+
ParticipantBadges.args = {
|
|
330
|
+
showBackButton: false,
|
|
331
|
+
isCustomer: true,
|
|
332
|
+
isStarred: true,
|
|
333
|
+
}
|
|
334
|
+
ParticipantBadges.parameters = {
|
|
335
|
+
docs: {
|
|
336
|
+
description: {
|
|
337
|
+
story:
|
|
338
|
+
'Shows the built-in customer badge from participant.customer and the starred badge from the current user membership pinned_at value in the channel header.',
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
|
|
310
343
|
export const RestrictedOfficialChannel: StoryFn<TemplateProps> = Template.bind(
|
|
311
344
|
{}
|
|
312
345
|
)
|
|
@@ -84,13 +84,9 @@ vi.mock('./Avatar', () => ({
|
|
|
84
84
|
},
|
|
85
85
|
}))
|
|
86
86
|
|
|
87
|
-
let mockIsStarred = false
|
|
88
|
-
vi.mock('../hooks/useChannelStar', () => ({
|
|
89
|
-
useChannelStar: () => mockIsStarred,
|
|
90
|
-
}))
|
|
91
|
-
|
|
92
87
|
const createChannel = ({
|
|
93
88
|
currentUserId = 'visitor-1',
|
|
89
|
+
currentUserPinnedAt,
|
|
94
90
|
currentUserRole = 'owner',
|
|
95
91
|
otherUser = {
|
|
96
92
|
id: 'linker-1' as const,
|
|
@@ -98,15 +94,17 @@ const createChannel = ({
|
|
|
98
94
|
username: undefined as string | undefined,
|
|
99
95
|
is_account: true,
|
|
100
96
|
},
|
|
97
|
+
otherMemberCustomer = false,
|
|
101
98
|
}: {
|
|
102
99
|
currentUserId?: 'visitor-1' | 'linker-1'
|
|
100
|
+
currentUserPinnedAt?: string
|
|
103
101
|
currentUserRole?: 'owner' | 'member'
|
|
102
|
+
otherMemberCustomer?: boolean
|
|
104
103
|
otherUser?: {
|
|
105
104
|
id: string
|
|
106
105
|
name?: string
|
|
107
106
|
username?: string
|
|
108
107
|
is_account?: boolean
|
|
109
|
-
is_verified?: boolean
|
|
110
108
|
}
|
|
111
109
|
} = {}) =>
|
|
112
110
|
({
|
|
@@ -134,12 +132,15 @@ const createChannel = ({
|
|
|
134
132
|
otherUser.is_account ?? currentUserId === 'visitor-1',
|
|
135
133
|
},
|
|
136
134
|
role: currentUserRole === 'owner' ? 'member' : 'owner',
|
|
135
|
+
customer: otherMemberCustomer,
|
|
137
136
|
},
|
|
138
137
|
},
|
|
139
|
-
membership: {
|
|
138
|
+
membership: {
|
|
139
|
+
pinned_at: currentUserPinnedAt,
|
|
140
|
+
},
|
|
140
141
|
messages: [],
|
|
141
142
|
},
|
|
142
|
-
on: vi.fn(),
|
|
143
|
+
on: vi.fn(() => ({ unsubscribe: vi.fn() })),
|
|
143
144
|
off: vi.fn(),
|
|
144
145
|
sendMessage: vi.fn(),
|
|
145
146
|
hide: vi.fn(),
|
|
@@ -154,7 +155,6 @@ describe('ChannelView', () => {
|
|
|
154
155
|
avatarRenderCalls.length = 0
|
|
155
156
|
channelActionsMenuProps.length = 0
|
|
156
157
|
messageInputProps.length = 0
|
|
157
|
-
mockIsStarred = false
|
|
158
158
|
})
|
|
159
159
|
|
|
160
160
|
const lastActionsMenuProps = () =>
|
|
@@ -192,6 +192,14 @@ describe('ChannelView', () => {
|
|
|
192
192
|
screen.queryByRole('button', { name: /view info for/i })
|
|
193
193
|
).not.toBeInTheDocument()
|
|
194
194
|
expect(screen.getAllByText('Linker').length).toBeGreaterThanOrEqual(1)
|
|
195
|
+
|
|
196
|
+
const avatars = screen.getAllByTestId('avatar')
|
|
197
|
+
expect(avatars.length).toBeGreaterThanOrEqual(1)
|
|
198
|
+
expect(avatars[0].parentElement).toHaveClass(
|
|
199
|
+
'rounded-[12px]',
|
|
200
|
+
'px-1',
|
|
201
|
+
'py-0.5'
|
|
202
|
+
)
|
|
195
203
|
})
|
|
196
204
|
|
|
197
205
|
it('renders the participant name as a button that fires onParticipantNameClick', () => {
|
|
@@ -209,6 +217,18 @@ describe('ChannelView', () => {
|
|
|
209
217
|
name: /view details for linker/i,
|
|
210
218
|
})
|
|
211
219
|
expect(buttons.length).toBeGreaterThanOrEqual(1)
|
|
220
|
+
expect(buttons[0]).toHaveClass(
|
|
221
|
+
'appearance-none',
|
|
222
|
+
'border-0',
|
|
223
|
+
'bg-transparent',
|
|
224
|
+
'rounded-[12px]',
|
|
225
|
+
'px-1',
|
|
226
|
+
'py-0.5'
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
const avatars = screen.getAllByTestId('avatar')
|
|
230
|
+
expect(avatars.length).toBeGreaterThanOrEqual(1)
|
|
231
|
+
expect(avatars[0].closest('button')).toBe(buttons[0])
|
|
212
232
|
|
|
213
233
|
buttons[0].click()
|
|
214
234
|
expect(onParticipantNameClick).toHaveBeenCalledTimes(1)
|
|
@@ -288,38 +308,54 @@ describe('ChannelView', () => {
|
|
|
288
308
|
expect(screen.queryByTestId('conversation-footer')).not.toBeInTheDocument()
|
|
289
309
|
})
|
|
290
310
|
|
|
291
|
-
it('does not pass starred to avatar when
|
|
292
|
-
|
|
293
|
-
|
|
311
|
+
it('does not pass starred to avatar when channel is starred', () => {
|
|
312
|
+
const channel = createChannel({
|
|
313
|
+
currentUserPinnedAt: '2026-01-01T00:00:00.000Z',
|
|
314
|
+
})
|
|
294
315
|
|
|
295
316
|
renderWithProviders(
|
|
296
|
-
<ChannelView channel={channel} showStarButton={
|
|
317
|
+
<ChannelView channel={channel} showStarButton={true} />
|
|
297
318
|
)
|
|
298
319
|
|
|
299
320
|
expect(avatarRenderCalls.length).toBeGreaterThan(0)
|
|
300
|
-
expect(avatarRenderCalls.every((call) =>
|
|
321
|
+
expect(avatarRenderCalls.every((call) => !('starred' in call))).toBe(true)
|
|
301
322
|
})
|
|
302
323
|
|
|
303
|
-
it('
|
|
304
|
-
mockIsStarred = true
|
|
305
|
-
const channel = createChannel()
|
|
306
|
-
|
|
324
|
+
it('renders built-in customer badges from participant member data', () => {
|
|
307
325
|
renderWithProviders(
|
|
308
|
-
<ChannelView
|
|
326
|
+
<ChannelView
|
|
327
|
+
channel={createChannel({ otherMemberCustomer: true })}
|
|
328
|
+
renderHeaderTitleBadges={() => (
|
|
329
|
+
<span data-testid="custom-header-badge">VIP</span>
|
|
330
|
+
)}
|
|
331
|
+
/>
|
|
309
332
|
)
|
|
310
333
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
})
|
|
334
|
+
const customerBadges = screen.getAllByRole('img', { name: 'Customer' })
|
|
335
|
+
const customBadges = screen.getAllByTestId('custom-header-badge')
|
|
314
336
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
337
|
+
expect(customerBadges.length).toBeGreaterThan(0)
|
|
338
|
+
expect(customBadges.length).toBe(customerBadges.length)
|
|
339
|
+
customerBadges.forEach((badge, index) => {
|
|
340
|
+
expect(
|
|
341
|
+
badge.compareDocumentPosition(customBadges[index]) &
|
|
342
|
+
Node.DOCUMENT_POSITION_FOLLOWING
|
|
343
|
+
).toBeTruthy()
|
|
344
|
+
})
|
|
345
|
+
})
|
|
318
346
|
|
|
319
|
-
|
|
347
|
+
it('renders built-in starred badges from channel star state', () => {
|
|
348
|
+
renderWithProviders(
|
|
349
|
+
<ChannelView
|
|
350
|
+
channel={createChannel({
|
|
351
|
+
currentUserPinnedAt: '2026-01-01T00:00:00.000Z',
|
|
352
|
+
})}
|
|
353
|
+
/>
|
|
354
|
+
)
|
|
320
355
|
|
|
321
|
-
expect(
|
|
322
|
-
|
|
356
|
+
expect(
|
|
357
|
+
screen.getAllByRole('img', { name: 'Starred conversation' }).length
|
|
358
|
+
).toBeGreaterThan(0)
|
|
323
359
|
})
|
|
324
360
|
|
|
325
361
|
it('passes dmAgentEnabled to avatar when dmAgentEnabled is true for visitor view', () => {
|
|
@@ -476,40 +512,6 @@ describe('ChannelView', () => {
|
|
|
476
512
|
expect(screen.getAllByText('Unknown member').length).toBeGreaterThan(0)
|
|
477
513
|
})
|
|
478
514
|
|
|
479
|
-
it('shows the verification badge in the header when the participant is verified', () => {
|
|
480
|
-
renderWithProviders(
|
|
481
|
-
<ChannelView
|
|
482
|
-
channel={createChannel({
|
|
483
|
-
otherUser: {
|
|
484
|
-
id: 'linker-1',
|
|
485
|
-
name: 'Linker',
|
|
486
|
-
is_account: true,
|
|
487
|
-
is_verified: true,
|
|
488
|
-
},
|
|
489
|
-
})}
|
|
490
|
-
/>
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
expect(screen.getAllByLabelText('Verified').length).toBeGreaterThan(0)
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
it('does not show the verification badge in the header when the participant is not verified', () => {
|
|
497
|
-
renderWithProviders(
|
|
498
|
-
<ChannelView
|
|
499
|
-
channel={createChannel({
|
|
500
|
-
otherUser: {
|
|
501
|
-
id: 'linker-1',
|
|
502
|
-
name: 'Linker',
|
|
503
|
-
is_account: true,
|
|
504
|
-
is_verified: false,
|
|
505
|
-
},
|
|
506
|
-
})}
|
|
507
|
-
/>
|
|
508
|
-
)
|
|
509
|
-
|
|
510
|
-
expect(screen.queryByLabelText('Verified')).not.toBeInTheDocument()
|
|
511
|
-
})
|
|
512
|
-
|
|
513
515
|
it('fires onMessageLinkClick with the href and undefined message when the link is not inside a message wrapper', () => {
|
|
514
516
|
const handler = vi.fn()
|
|
515
517
|
const { container } = renderWithProviders(
|
|
@@ -32,7 +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 {
|
|
35
|
+
import { ParticipantBadges } from './ParticipantBadges'
|
|
36
36
|
|
|
37
37
|
const ICON_BTN_CLASS =
|
|
38
38
|
'size-10 rounded-full hover:bg-[#E5E4E1] flex items-center justify-center transition-colors duration-150 focus-ring'
|
|
@@ -93,12 +93,24 @@ const CustomChannelHeader: React.FC<{
|
|
|
93
93
|
|
|
94
94
|
const participantName = resolveParticipantDisplayName(participant?.user)
|
|
95
95
|
const participantImage = participant?.user?.image
|
|
96
|
-
const isParticipantVerified = participant?.user?.is_verified === true
|
|
97
96
|
const isStarred = useChannelStar(channel)
|
|
98
97
|
const headerTitleBadges = renderHeaderTitleBadges?.({
|
|
99
98
|
channel,
|
|
100
99
|
participant,
|
|
101
100
|
})
|
|
101
|
+
const mobileParticipantIdentityClassName =
|
|
102
|
+
'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'
|
|
103
|
+
const desktopParticipantIdentityClassName =
|
|
104
|
+
'flex min-w-0 max-w-full items-center gap-4 rounded-[12px] px-1 py-0.5 text-left text-black/90'
|
|
105
|
+
const participantTitle = (
|
|
106
|
+
<>
|
|
107
|
+
<span className="min-w-0 truncate">{participantName}</span>
|
|
108
|
+
<ParticipantBadges channel={channel} participant={participant} />
|
|
109
|
+
{headerTitleBadges && (
|
|
110
|
+
<span className="shrink-0">{headerTitleBadges}</span>
|
|
111
|
+
)}
|
|
112
|
+
</>
|
|
113
|
+
)
|
|
102
114
|
|
|
103
115
|
const handleStarClick = async () => {
|
|
104
116
|
try {
|
|
@@ -133,42 +145,60 @@ const CustomChannelHeader: React.FC<{
|
|
|
133
145
|
</button>
|
|
134
146
|
)}
|
|
135
147
|
</div>
|
|
136
|
-
<div
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
image={participantImage}
|
|
141
|
-
starred={showStarButton && isStarred}
|
|
142
|
-
dmAgentEnabled={dmAgentEnabled}
|
|
143
|
-
size={48}
|
|
144
|
-
/>
|
|
148
|
+
<div
|
|
149
|
+
className="flex flex-col gap-1 items-center"
|
|
150
|
+
data-dd-privacy="mask"
|
|
151
|
+
>
|
|
145
152
|
{onParticipantNameClick ? (
|
|
146
153
|
<button
|
|
147
154
|
type="button"
|
|
148
155
|
onClick={onParticipantNameClick}
|
|
149
156
|
aria-label={`View details for ${participantName}`}
|
|
150
|
-
className=
|
|
157
|
+
className={classNames(
|
|
158
|
+
mobileParticipantIdentityClassName,
|
|
159
|
+
'appearance-none border-0 bg-transparent transition-opacity hover:opacity-70 focus-ring focus-visible:outline-none'
|
|
160
|
+
)}
|
|
151
161
|
>
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
<Avatar
|
|
163
|
+
id={participant?.user?.id || channel.id || 'unknown'}
|
|
164
|
+
name={participantName}
|
|
165
|
+
image={participantImage}
|
|
166
|
+
dmAgentEnabled={dmAgentEnabled}
|
|
167
|
+
size={48}
|
|
168
|
+
/>
|
|
169
|
+
<span className="flex max-w-full items-center gap-0.5">
|
|
170
|
+
{participantTitle}
|
|
171
|
+
<CaretRightIcon className="size-3 shrink-0" weight="bold" />
|
|
172
|
+
</span>
|
|
173
|
+
{dmAgentEnabled && (
|
|
174
|
+
<div className="flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
175
|
+
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
176
|
+
<span>{DM_AGENT_HEADER_HELPER_TEXT}</span>
|
|
177
|
+
</div>
|
|
156
178
|
)}
|
|
157
|
-
<CaretRightIcon className="size-3 shrink-0" weight="bold" />
|
|
158
179
|
</button>
|
|
159
180
|
) : (
|
|
160
|
-
<
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
181
|
+
<div className={mobileParticipantIdentityClassName}>
|
|
182
|
+
<Avatar
|
|
183
|
+
id={participant?.user?.id || channel.id || 'unknown'}
|
|
184
|
+
name={participantName}
|
|
185
|
+
image={participantImage}
|
|
186
|
+
dmAgentEnabled={dmAgentEnabled}
|
|
187
|
+
size={48}
|
|
188
|
+
/>
|
|
189
|
+
<p className="flex max-w-full items-center justify-center gap-0.5">
|
|
190
|
+
{participantTitle}
|
|
191
|
+
<CaretRightIcon
|
|
192
|
+
className="invisible size-3 shrink-0"
|
|
193
|
+
weight="bold"
|
|
194
|
+
/>
|
|
195
|
+
</p>
|
|
196
|
+
{dmAgentEnabled && (
|
|
197
|
+
<div className="flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
198
|
+
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
199
|
+
<span>{DM_AGENT_HEADER_HELPER_TEXT}</span>
|
|
200
|
+
</div>
|
|
165
201
|
)}
|
|
166
|
-
</p>
|
|
167
|
-
)}
|
|
168
|
-
{dmAgentEnabled && (
|
|
169
|
-
<div className="flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
170
|
-
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
171
|
-
<span>{DM_AGENT_HEADER_HELPER_TEXT}</span>
|
|
172
202
|
</div>
|
|
173
203
|
)}
|
|
174
204
|
</div>
|
|
@@ -213,7 +243,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
213
243
|
</div>
|
|
214
244
|
</div>
|
|
215
245
|
<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]">
|
|
216
|
-
<div className="flex items-center gap-4 min-w-0">
|
|
246
|
+
<div className="flex items-center gap-4 min-w-0" data-dd-privacy="mask">
|
|
217
247
|
{showBackButton && onBack && (
|
|
218
248
|
<button
|
|
219
249
|
className={classNames(
|
|
@@ -227,46 +257,66 @@ const CustomChannelHeader: React.FC<{
|
|
|
227
257
|
<ArrowLeftIcon className="size-5 text-black/90" />
|
|
228
258
|
</button>
|
|
229
259
|
)}
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<
|
|
260
|
+
{onParticipantNameClick ? (
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
onClick={onParticipantNameClick}
|
|
264
|
+
aria-label={`View details for ${participantName}`}
|
|
265
|
+
className={classNames(
|
|
266
|
+
desktopParticipantIdentityClassName,
|
|
267
|
+
'appearance-none border-0 bg-transparent transition-opacity hover:opacity-70 focus-ring focus-visible:outline-none'
|
|
268
|
+
)}
|
|
269
|
+
>
|
|
270
|
+
<Avatar
|
|
271
|
+
id={participant?.user?.id || channel.id || 'unknown'}
|
|
272
|
+
name={participantName}
|
|
273
|
+
image={participantImage}
|
|
274
|
+
dmAgentEnabled={dmAgentEnabled}
|
|
275
|
+
size={48}
|
|
276
|
+
/>
|
|
277
|
+
<div className="min-w-0">
|
|
278
|
+
<h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
|
|
279
|
+
{participantTitle}
|
|
280
|
+
<CaretRightIcon className="size-4 shrink-0" weight="bold" />
|
|
281
|
+
</h1>
|
|
282
|
+
{dmAgentEnabled && (
|
|
283
|
+
<div className="mt-0.5 flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
284
|
+
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
285
|
+
<span className="truncate">
|
|
286
|
+
{DM_AGENT_HEADER_HELPER_TEXT}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
251
289
|
)}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
{
|
|
258
|
-
{
|
|
259
|
-
|
|
290
|
+
</div>
|
|
291
|
+
</button>
|
|
292
|
+
) : (
|
|
293
|
+
<div className={desktopParticipantIdentityClassName}>
|
|
294
|
+
<Avatar
|
|
295
|
+
id={participant?.user?.id || channel.id || 'unknown'}
|
|
296
|
+
name={participantName}
|
|
297
|
+
image={participantImage}
|
|
298
|
+
dmAgentEnabled={dmAgentEnabled}
|
|
299
|
+
size={48}
|
|
300
|
+
/>
|
|
301
|
+
<div className="min-w-0">
|
|
302
|
+
<h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
|
|
303
|
+
{participantTitle}
|
|
304
|
+
<CaretRightIcon
|
|
305
|
+
className="invisible size-4 shrink-0"
|
|
306
|
+
weight="bold"
|
|
307
|
+
/>
|
|
308
|
+
</h1>
|
|
309
|
+
{dmAgentEnabled && (
|
|
310
|
+
<div className="mt-0.5 flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
311
|
+
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
312
|
+
<span className="truncate">
|
|
313
|
+
{DM_AGENT_HEADER_HELPER_TEXT}
|
|
314
|
+
</span>
|
|
315
|
+
</div>
|
|
260
316
|
)}
|
|
261
|
-
</h1>
|
|
262
|
-
)}
|
|
263
|
-
{dmAgentEnabled && (
|
|
264
|
-
<div className="mt-0.5 flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
265
|
-
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
266
|
-
<span className="truncate">{DM_AGENT_HEADER_HELPER_TEXT}</span>
|
|
267
317
|
</div>
|
|
268
|
-
|
|
269
|
-
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
270
320
|
</div>
|
|
271
321
|
<div className="flex items-center gap-2">
|
|
272
322
|
{showStarButton && (
|
|
@@ -346,6 +396,7 @@ const ChannelViewInner: React.FC<{
|
|
|
346
396
|
viewerLanguage?: string
|
|
347
397
|
showChannelInfo?: boolean
|
|
348
398
|
onParticipantNameClick?: () => void
|
|
399
|
+
composerInput?: ChannelViewProps['composerInput']
|
|
349
400
|
}> = ({
|
|
350
401
|
onBack,
|
|
351
402
|
showBackButton,
|
|
@@ -373,6 +424,7 @@ const ChannelViewInner: React.FC<{
|
|
|
373
424
|
viewerLanguage,
|
|
374
425
|
showChannelInfo = true,
|
|
375
426
|
onParticipantNameClick,
|
|
427
|
+
composerInput,
|
|
376
428
|
}) => {
|
|
377
429
|
const { channel } = useChannelStateContext()
|
|
378
430
|
|
|
@@ -394,10 +446,10 @@ const ChannelViewInner: React.FC<{
|
|
|
394
446
|
}, [channel._client?.userID, channel.state?.members])
|
|
395
447
|
|
|
396
448
|
const currentUserIsAccount =
|
|
397
|
-
|
|
449
|
+
currentMember?.user?.is_account ??
|
|
398
450
|
(currentMember as { is_account?: boolean } | undefined)?.is_account
|
|
399
451
|
const participantIsAccount =
|
|
400
|
-
|
|
452
|
+
participant?.user?.is_account ??
|
|
401
453
|
(participant as { is_account?: boolean } | undefined)?.is_account
|
|
402
454
|
|
|
403
455
|
const showDmAgentHeader =
|
|
@@ -489,6 +541,7 @@ const ChannelViewInner: React.FC<{
|
|
|
489
541
|
renderFooter={() => renderMessageInputFooter?.(channel)}
|
|
490
542
|
disabled={composerDisabled}
|
|
491
543
|
disabledReason={composerDisabledReason}
|
|
544
|
+
composerInput={composerInput}
|
|
492
545
|
/>
|
|
493
546
|
</Window>
|
|
494
547
|
</WithComponents>
|
|
@@ -535,6 +588,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
535
588
|
viewerLanguage,
|
|
536
589
|
showChannelInfo = true,
|
|
537
590
|
onParticipantNameClick,
|
|
591
|
+
composerInput,
|
|
538
592
|
}) => {
|
|
539
593
|
// Custom send message handler that:
|
|
540
594
|
// 1. Applies messageMetadata if provided
|
|
@@ -649,6 +703,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
649
703
|
viewerLanguage={viewerLanguage}
|
|
650
704
|
showChannelInfo={showChannelInfo}
|
|
651
705
|
onParticipantNameClick={onParticipantNameClick}
|
|
706
|
+
composerInput={composerInput}
|
|
652
707
|
/>
|
|
653
708
|
</Channel>
|
|
654
709
|
</DmAgentEnabledContext.Provider>
|