@linktr.ee/messaging-react 3.4.0-rc-1782373728 → 3.4.0-rc-1782387968
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-h43tRi_a.js → Card-BOD2Qo_b.js} +3 -3
- package/dist/{Card-h43tRi_a.js.map → Card-BOD2Qo_b.js.map} +1 -1
- package/dist/{Card-TNeK_nFK.js → Card-DcEBssw0.js} +2 -2
- package/dist/{Card-TNeK_nFK.js.map → Card-DcEBssw0.js.map} +1 -1
- package/dist/{Card-C3icBnXT.cjs → Card-DkNdLpVE.cjs} +2 -2
- package/dist/{Card-C3icBnXT.cjs.map → Card-DkNdLpVE.cjs.map} +1 -1
- package/dist/{Card-DVo2e4rF.cjs → Card-UbPYWjnX.cjs} +2 -2
- package/dist/{Card-DVo2e4rF.cjs.map → Card-UbPYWjnX.cjs.map} +1 -1
- package/dist/{Card-DGkh6_ti.js → Card-beoX9cug.js} +2 -2
- package/dist/{Card-DGkh6_ti.js.map → Card-beoX9cug.js.map} +1 -1
- package/dist/{Card-B9Z659Y-.cjs → Card-utKbCGom.cjs} +2 -2
- package/dist/{Card-B9Z659Y-.cjs.map → Card-utKbCGom.cjs.map} +1 -1
- package/dist/{LockedThumbnail-DjFuOEgv.cjs → LockedThumbnail-Bjdf-fTw.cjs} +2 -2
- package/dist/{LockedThumbnail-DjFuOEgv.cjs.map → LockedThumbnail-Bjdf-fTw.cjs.map} +1 -1
- package/dist/{LockedThumbnail-DE3KLtk1.js → LockedThumbnail-bOjjYYyV.js} +2 -2
- package/dist/{LockedThumbnail-DE3KLtk1.js.map → LockedThumbnail-bOjjYYyV.js.map} +1 -1
- package/dist/index-B3oQq9zK.cjs +2 -0
- package/dist/index-B3oQq9zK.cjs.map +1 -0
- package/dist/{index-DKq7HA0h.js → index-DG5LOHey.js} +905 -910
- package/dist/index-DG5LOHey.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +28 -11
- package/dist/index.js +20 -19
- package/package.json +2 -2
- package/src/components/ChannelList/CustomChannelPreview.test.tsx +38 -0
- package/src/components/ChannelList/CustomChannelPreview.tsx +5 -6
- package/src/components/ChannelView.stories.tsx +21 -0
- package/src/components/ChannelView.test.tsx +35 -20
- package/src/components/ChannelView.tsx +28 -43
- package/src/components/MessagingShell/index.tsx +2 -0
- package/src/components/VerifiedBadge/index.tsx +25 -0
- package/src/index.ts +2 -0
- package/src/stream-custom-data.ts +8 -0
- package/src/types.ts +11 -11
- package/dist/index--bjxhW3D.cjs +0 -2
- package/dist/index--bjxhW3D.cjs.map +0 -1
- package/dist/index-DKq7HA0h.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-B3oQq9zK.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.VerifiedBadge=e.VerifiedBadge;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.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Attachment } from 'stream-chat';
|
|
2
2
|
import { Channel } from 'stream-chat';
|
|
3
3
|
import { ChannelFilters } from 'stream-chat';
|
|
4
|
+
import { ChannelMemberResponse } from 'stream-chat';
|
|
4
5
|
import { ChannelSort } from 'stream-chat';
|
|
5
6
|
import { ComponentType } from 'react';
|
|
6
7
|
import { default as default_2 } from 'react';
|
|
@@ -148,7 +149,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
148
149
|
* Props that MessagingShell passes through to ChannelView.
|
|
149
150
|
* ChannelViewProps is the source of truth for these props.
|
|
150
151
|
*/
|
|
151
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo'>;
|
|
152
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderHeaderTitleBadges' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo'>;
|
|
152
153
|
|
|
153
154
|
/**
|
|
154
155
|
* ChannelView component props
|
|
@@ -250,6 +251,14 @@ export declare interface ChannelViewProps {
|
|
|
250
251
|
* and filter by starred/pinned status.
|
|
251
252
|
*/
|
|
252
253
|
showStarButton?: boolean;
|
|
254
|
+
/**
|
|
255
|
+
* Custom badges rendered inline after the participant name in the channel
|
|
256
|
+
* header title.
|
|
257
|
+
*/
|
|
258
|
+
renderHeaderTitleBadges?: (context: {
|
|
259
|
+
channel: Channel;
|
|
260
|
+
participant?: ChannelMemberResponse;
|
|
261
|
+
}) => React.ReactNode;
|
|
253
262
|
/**
|
|
254
263
|
* Show the channel actions menu (the `...` popover) in the header, which
|
|
255
264
|
* exposes the block/report/delete moderation actions. Defaults to true.
|
|
@@ -342,16 +351,6 @@ export declare interface ChannelViewProps {
|
|
|
342
351
|
* Use this to render staged paid or media attachments inside the input.
|
|
343
352
|
*/
|
|
344
353
|
attachmentPreviewList?: ComponentType;
|
|
345
|
-
/**
|
|
346
|
-
* Fired when the participant's name in the channel header is clicked. When
|
|
347
|
-
* provided, the name renders as a button with a trailing caret (an affordance
|
|
348
|
-
* for opening participant details / thread content); when omitted, the name
|
|
349
|
-
* renders as plain text.
|
|
350
|
-
*
|
|
351
|
-
* @example
|
|
352
|
-
* onParticipantNameClick={() => openThreadContentDialog()}
|
|
353
|
-
*/
|
|
354
|
-
onParticipantNameClick?: () => void;
|
|
355
354
|
}
|
|
356
355
|
|
|
357
356
|
export declare interface ComposerCardProps extends LockedAttachmentBaseProps {
|
|
@@ -1390,6 +1389,17 @@ declare interface UseMessageVoteResult {
|
|
|
1390
1389
|
*/
|
|
1391
1390
|
export declare const useMessaging: () => MessagingContextValue;
|
|
1392
1391
|
|
|
1392
|
+
/**
|
|
1393
|
+
* Linktree verification badge shown next to the name of a verified profile.
|
|
1394
|
+
* Rendered when a participant's Stream user has `is_verified` set to true.
|
|
1395
|
+
*/
|
|
1396
|
+
export declare const VerifiedBadge: default_2.FC<VerifiedBadgeProps>;
|
|
1397
|
+
|
|
1398
|
+
export declare interface VerifiedBadgeProps {
|
|
1399
|
+
className?: string;
|
|
1400
|
+
size?: number;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1393
1403
|
declare interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps {
|
|
1394
1404
|
/** Single video — convenience for the most common case. */
|
|
1395
1405
|
src?: string;
|
|
@@ -1462,6 +1472,13 @@ declare module 'stream-chat' {
|
|
|
1462
1472
|
*/
|
|
1463
1473
|
chatbot_paused?: boolean;
|
|
1464
1474
|
}
|
|
1475
|
+
interface CustomUserData {
|
|
1476
|
+
/**
|
|
1477
|
+
* Whether the user is a verified Linktree profile. When true, a
|
|
1478
|
+
* verification badge is shown next to their name in messaging.
|
|
1479
|
+
*/
|
|
1480
|
+
is_verified?: boolean;
|
|
1481
|
+
}
|
|
1465
1482
|
interface CustomMessageData {
|
|
1466
1483
|
/**
|
|
1467
1484
|
* When true, hides the date timestamp in system messages.
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as s, b as t, C as i, c as n, d as o, 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 d, l as h, V as p, m as v, n as A, o as C, p as F, q as k, s as f, t as V, u as b, v as x, w as y, x as B, y as P, z as S, B as q, D as z } from "./index-DG5LOHey.js";
|
|
2
2
|
export {
|
|
3
|
-
|
|
3
|
+
s as ActionButton,
|
|
4
4
|
t as Avatar,
|
|
5
5
|
i as ChannelEmptyState,
|
|
6
6
|
n as ChannelList,
|
|
7
7
|
o as ChannelView,
|
|
8
8
|
m as CustomMessageProvider,
|
|
9
9
|
g as FaqList,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
r as FaqListItem,
|
|
11
|
+
l as LinkAttachment,
|
|
12
12
|
M as LockedAttachment,
|
|
13
13
|
u as MediaMessage,
|
|
14
14
|
L as MessageAttachment,
|
|
15
15
|
c as MessageVoteButtons,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
p as
|
|
19
|
-
v as
|
|
20
|
-
A as
|
|
21
|
-
C as
|
|
22
|
-
F as
|
|
23
|
-
k as
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
d as MessagingProvider,
|
|
17
|
+
h as MessagingShell,
|
|
18
|
+
p as VerifiedBadge,
|
|
19
|
+
v as buildCompactMetaLabel,
|
|
20
|
+
A as formatFileSize,
|
|
21
|
+
C as formatRelativeTime,
|
|
22
|
+
F as getFileExtensionLabel,
|
|
23
|
+
k as getMessageDisplayText,
|
|
24
|
+
f as isLinkAttachment,
|
|
25
|
+
V as isUuidLike,
|
|
26
|
+
b as messageAttachmentGroupPositionFromStream,
|
|
26
27
|
x as normalizeLanguageCode,
|
|
27
28
|
y as resolveLinkAttachment,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
B as resolveMediaFromMessage,
|
|
30
|
+
P as resolveParticipantDisplayName,
|
|
31
|
+
S as useCustomMessage,
|
|
32
|
+
q as useMessageVote,
|
|
33
|
+
z as useMessaging
|
|
33
34
|
};
|
|
34
35
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "3.4.0-rc-
|
|
3
|
+
"version": "3.4.0-rc-1782387968",
|
|
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-
|
|
41
|
+
"@linktr.ee/messaging-core": "2.3.0-rc-1782387968",
|
|
42
42
|
"@phosphor-icons/react": "^2.1.10"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -101,6 +101,44 @@ describe('CustomChannelPreview', () => {
|
|
|
101
101
|
expect(screen.getByText('Unknown member')).toBeInTheDocument()
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
+
it('shows the verification badge when the participant is verified', () => {
|
|
105
|
+
const channel = createMockChannel([])
|
|
106
|
+
channel.state.members['participant-1'] = {
|
|
107
|
+
user: { id: 'participant-1', name: 'Alice', is_verified: true },
|
|
108
|
+
user_id: 'participant-1',
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
renderWithProviders(
|
|
112
|
+
<CustomChannelPreview {...defaultProps} channel={channel} />
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
expect(screen.getByLabelText('Verified')).toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('does not show the verification badge when the participant is not verified', () => {
|
|
119
|
+
const channel = createMockChannel([])
|
|
120
|
+
channel.state.members['participant-1'] = {
|
|
121
|
+
user: { id: 'participant-1', name: 'Alice', is_verified: false },
|
|
122
|
+
user_id: 'participant-1',
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
renderWithProviders(
|
|
126
|
+
<CustomChannelPreview {...defaultProps} channel={channel} />
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
expect(screen.queryByLabelText('Verified')).not.toBeInTheDocument()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('does not show the verification badge when is_verified is absent', () => {
|
|
133
|
+
const channel = createMockChannel([])
|
|
134
|
+
|
|
135
|
+
renderWithProviders(
|
|
136
|
+
<CustomChannelPreview {...defaultProps} channel={channel} />
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
expect(screen.queryByLabelText('Verified')).not.toBeInTheDocument()
|
|
140
|
+
})
|
|
141
|
+
|
|
104
142
|
it('shows the latest non-system message when the last message is a system message', () => {
|
|
105
143
|
const channel = createMockChannel([
|
|
106
144
|
{
|
|
@@ -8,6 +8,7 @@ import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
|
|
|
8
8
|
import { resolveParticipantDisplayName } from '../../utils/resolveParticipantDisplayName'
|
|
9
9
|
import { Avatar } from '../Avatar'
|
|
10
10
|
import { isChatbotMessage } from '../CustomMessage/MessageTag'
|
|
11
|
+
import { VerifiedBadge } from '../VerifiedBadge'
|
|
11
12
|
|
|
12
13
|
import { useChannelListContext } from './ChannelListContext'
|
|
13
14
|
|
|
@@ -48,6 +49,7 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
|
48
49
|
)
|
|
49
50
|
const participantName = resolveParticipantDisplayName(participant?.user)
|
|
50
51
|
const participantImage = participant?.user?.image
|
|
52
|
+
const isParticipantVerified = participant?.user?.is_verified === true
|
|
51
53
|
|
|
52
54
|
// Get last non-system message for preview
|
|
53
55
|
const lastMessage = (() => {
|
|
@@ -146,15 +148,12 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
|
146
148
|
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
|
147
149
|
{/* Name and timestamp row */}
|
|
148
150
|
<div className="flex items-center justify-between gap-2">
|
|
149
|
-
<h3
|
|
150
|
-
className={classNames(
|
|
151
|
-
'text-sm font-medium truncate text-[#191918]'
|
|
152
|
-
)}
|
|
153
|
-
>
|
|
151
|
+
<h3 className="flex min-w-0 items-center gap-1 text-sm font-medium text-[#191918]">
|
|
154
152
|
{isChannelStarred && (
|
|
155
153
|
<span className="sr-only">Starred conversation. </span>
|
|
156
154
|
)}
|
|
157
|
-
{participantName}
|
|
155
|
+
<span className="min-w-0 truncate">{participantName}</span>
|
|
156
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
158
157
|
</h3>
|
|
159
158
|
{lastMessageTime && (
|
|
160
159
|
<span className="text-xs text-[#717070] flex-shrink-0">
|
|
@@ -270,6 +270,27 @@ DmAgentHeader.parameters = {
|
|
|
270
270
|
},
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
export const WithHeaderTitleBadges: StoryFn<TemplateProps> = Template.bind({})
|
|
274
|
+
WithHeaderTitleBadges.args = {
|
|
275
|
+
showBackButton: false,
|
|
276
|
+
renderHeaderTitleBadges: ({ participant }) => (
|
|
277
|
+
<span
|
|
278
|
+
aria-label={`${participant?.user?.name ?? 'Participant'} is a customer`}
|
|
279
|
+
className="flex size-4 items-center justify-center rounded-full bg-[#E8F8EF] text-[10px] font-semibold text-[#136C3A]"
|
|
280
|
+
>
|
|
281
|
+
$
|
|
282
|
+
</span>
|
|
283
|
+
),
|
|
284
|
+
}
|
|
285
|
+
WithHeaderTitleBadges.parameters = {
|
|
286
|
+
docs: {
|
|
287
|
+
description: {
|
|
288
|
+
story:
|
|
289
|
+
'Renders custom badges inline after the participant name in the channel header via renderHeaderTitleBadges.',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
|
|
273
294
|
export const RestrictedOfficialChannel: StoryFn<TemplateProps> = Template.bind(
|
|
274
295
|
{}
|
|
275
296
|
)
|
|
@@ -106,6 +106,7 @@ const createChannel = ({
|
|
|
106
106
|
name?: string
|
|
107
107
|
username?: string
|
|
108
108
|
is_account?: boolean
|
|
109
|
+
is_verified?: boolean
|
|
109
110
|
}
|
|
110
111
|
} = {}) =>
|
|
111
112
|
({
|
|
@@ -193,26 +194,6 @@ describe('ChannelView', () => {
|
|
|
193
194
|
expect(screen.getAllByText('Linker').length).toBeGreaterThanOrEqual(1)
|
|
194
195
|
})
|
|
195
196
|
|
|
196
|
-
it('renders the participant name as a button that fires onParticipantNameClick', () => {
|
|
197
|
-
const onParticipantNameClick = vi.fn()
|
|
198
|
-
renderWithProviders(
|
|
199
|
-
<ChannelView
|
|
200
|
-
channel={createChannel()}
|
|
201
|
-
onParticipantNameClick={onParticipantNameClick}
|
|
202
|
-
/>
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
// Both the mobile and desktop header layouts render the button (one is
|
|
206
|
-
// hidden via CSS), so there is at least one match.
|
|
207
|
-
const buttons = screen.getAllByRole('button', {
|
|
208
|
-
name: /view details for linker/i,
|
|
209
|
-
})
|
|
210
|
-
expect(buttons.length).toBeGreaterThanOrEqual(1)
|
|
211
|
-
|
|
212
|
-
buttons[0].click()
|
|
213
|
-
expect(onParticipantNameClick).toHaveBeenCalledTimes(1)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
197
|
it('renders the actions menu by default', () => {
|
|
217
198
|
renderWithProviders(<ChannelView channel={createChannel()} />)
|
|
218
199
|
|
|
@@ -475,6 +456,40 @@ describe('ChannelView', () => {
|
|
|
475
456
|
expect(screen.getAllByText('Unknown member').length).toBeGreaterThan(0)
|
|
476
457
|
})
|
|
477
458
|
|
|
459
|
+
it('shows the verification badge in the header when the participant is verified', () => {
|
|
460
|
+
renderWithProviders(
|
|
461
|
+
<ChannelView
|
|
462
|
+
channel={createChannel({
|
|
463
|
+
otherUser: {
|
|
464
|
+
id: 'linker-1',
|
|
465
|
+
name: 'Linker',
|
|
466
|
+
is_account: true,
|
|
467
|
+
is_verified: true,
|
|
468
|
+
},
|
|
469
|
+
})}
|
|
470
|
+
/>
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
expect(screen.getAllByLabelText('Verified').length).toBeGreaterThan(0)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('does not show the verification badge in the header when the participant is not verified', () => {
|
|
477
|
+
renderWithProviders(
|
|
478
|
+
<ChannelView
|
|
479
|
+
channel={createChannel({
|
|
480
|
+
otherUser: {
|
|
481
|
+
id: 'linker-1',
|
|
482
|
+
name: 'Linker',
|
|
483
|
+
is_account: true,
|
|
484
|
+
is_verified: false,
|
|
485
|
+
},
|
|
486
|
+
})}
|
|
487
|
+
/>
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
expect(screen.queryByLabelText('Verified')).not.toBeInTheDocument()
|
|
491
|
+
})
|
|
492
|
+
|
|
478
493
|
it('fires onMessageLinkClick with the href and undefined message when the link is not inside a message wrapper', () => {
|
|
479
494
|
const handler = vi.fn()
|
|
480
495
|
const { container } = renderWithProviders(
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrowLeftIcon,
|
|
3
|
-
CaretRightIcon,
|
|
4
|
-
SparkleIcon,
|
|
5
|
-
StarIcon,
|
|
6
|
-
} from '@phosphor-icons/react'
|
|
1
|
+
import { ArrowLeftIcon, SparkleIcon, StarIcon } from '@phosphor-icons/react'
|
|
7
2
|
import classNames from 'classnames'
|
|
8
3
|
import React, { useCallback, useEffect, useRef } from 'react'
|
|
9
4
|
import { Channel as ChannelType } from 'stream-chat'
|
|
@@ -32,6 +27,7 @@ import CustomTypingIndicator from './CustomTypingIndicator'
|
|
|
32
27
|
import { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'
|
|
33
28
|
import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
|
|
34
29
|
import { LoadingState } from './MessagingShell/LoadingState'
|
|
30
|
+
import { VerifiedBadge } from './VerifiedBadge'
|
|
35
31
|
|
|
36
32
|
const ICON_BTN_CLASS =
|
|
37
33
|
'size-10 rounded-full hover:bg-[#E5E4E1] flex items-center justify-center transition-colors duration-150 focus-ring'
|
|
@@ -57,7 +53,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
57
53
|
customChannelActions?: React.ReactNode
|
|
58
54
|
renderChannelActions?: React.ReactNode
|
|
59
55
|
showChannelInfo?: boolean
|
|
60
|
-
|
|
56
|
+
renderHeaderTitleBadges?: ChannelViewProps['renderHeaderTitleBadges']
|
|
61
57
|
}> = ({
|
|
62
58
|
onBack,
|
|
63
59
|
showBackButton,
|
|
@@ -74,7 +70,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
74
70
|
customChannelActions,
|
|
75
71
|
renderChannelActions,
|
|
76
72
|
showChannelInfo = true,
|
|
77
|
-
|
|
73
|
+
renderHeaderTitleBadges,
|
|
78
74
|
}) => {
|
|
79
75
|
const { channel } = useChannelStateContext()
|
|
80
76
|
|
|
@@ -90,7 +86,12 @@ const CustomChannelHeader: React.FC<{
|
|
|
90
86
|
|
|
91
87
|
const participantName = resolveParticipantDisplayName(participant?.user)
|
|
92
88
|
const participantImage = participant?.user?.image
|
|
89
|
+
const isParticipantVerified = participant?.user?.is_verified === true
|
|
93
90
|
const isStarred = useChannelStar(channel)
|
|
91
|
+
const headerTitleBadges = renderHeaderTitleBadges?.({
|
|
92
|
+
channel,
|
|
93
|
+
participant,
|
|
94
|
+
})
|
|
94
95
|
|
|
95
96
|
const handleStarClick = async () => {
|
|
96
97
|
try {
|
|
@@ -134,21 +135,13 @@ const CustomChannelHeader: React.FC<{
|
|
|
134
135
|
dmAgentEnabled={dmAgentEnabled}
|
|
135
136
|
size={48}
|
|
136
137
|
/>
|
|
137
|
-
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<span className="truncate">{participantName}</span>
|
|
145
|
-
<CaretRightIcon className="size-3 shrink-0" weight="bold" />
|
|
146
|
-
</button>
|
|
147
|
-
) : (
|
|
148
|
-
<p className="text-center text-xs font-medium text-black/90">
|
|
149
|
-
{participantName}
|
|
150
|
-
</p>
|
|
151
|
-
)}
|
|
138
|
+
<p className="flex max-w-full items-center justify-center gap-1 text-center text-xs font-medium text-black/90">
|
|
139
|
+
<span className="min-w-0 truncate">{participantName}</span>
|
|
140
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
141
|
+
{headerTitleBadges && (
|
|
142
|
+
<span className="shrink-0">{headerTitleBadges}</span>
|
|
143
|
+
)}
|
|
144
|
+
</p>
|
|
152
145
|
{dmAgentEnabled && (
|
|
153
146
|
<div className="flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
154
147
|
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
@@ -221,21 +214,13 @@ const CustomChannelHeader: React.FC<{
|
|
|
221
214
|
size={48}
|
|
222
215
|
/>
|
|
223
216
|
<div className="min-w-0">
|
|
224
|
-
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<span className="truncate">{participantName}</span>
|
|
232
|
-
<CaretRightIcon className="size-4 shrink-0" weight="bold" />
|
|
233
|
-
</button>
|
|
234
|
-
) : (
|
|
235
|
-
<h1 className="font-medium text-black/90 truncate">
|
|
236
|
-
{participantName}
|
|
237
|
-
</h1>
|
|
238
|
-
)}
|
|
217
|
+
<h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
|
|
218
|
+
<span className="min-w-0 truncate">{participantName}</span>
|
|
219
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
220
|
+
{headerTitleBadges && (
|
|
221
|
+
<span className="shrink-0">{headerTitleBadges}</span>
|
|
222
|
+
)}
|
|
223
|
+
</h1>
|
|
239
224
|
{dmAgentEnabled && (
|
|
240
225
|
<div className="mt-0.5 flex items-center gap-1 text-[10px] leading-3 text-black/55">
|
|
241
226
|
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
@@ -311,6 +296,7 @@ const ChannelViewInner: React.FC<{
|
|
|
311
296
|
showStarButton?: boolean
|
|
312
297
|
chatbotVotingEnabled?: boolean
|
|
313
298
|
renderChannelBanner?: () => React.ReactNode
|
|
299
|
+
renderHeaderTitleBadges?: ChannelViewProps['renderHeaderTitleBadges']
|
|
314
300
|
customChannelActions?: React.ReactNode
|
|
315
301
|
renderChannelActions?: React.ReactNode
|
|
316
302
|
renderMessage?: (
|
|
@@ -320,7 +306,6 @@ const ChannelViewInner: React.FC<{
|
|
|
320
306
|
dmAgentEnabled?: boolean
|
|
321
307
|
viewerLanguage?: string
|
|
322
308
|
showChannelInfo?: boolean
|
|
323
|
-
onParticipantNameClick?: () => void
|
|
324
309
|
}> = ({
|
|
325
310
|
onBack,
|
|
326
311
|
showBackButton,
|
|
@@ -340,13 +325,13 @@ const ChannelViewInner: React.FC<{
|
|
|
340
325
|
showStarButton = false,
|
|
341
326
|
chatbotVotingEnabled = false,
|
|
342
327
|
renderChannelBanner,
|
|
328
|
+
renderHeaderTitleBadges,
|
|
343
329
|
customChannelActions,
|
|
344
330
|
renderChannelActions,
|
|
345
331
|
renderMessage,
|
|
346
332
|
dmAgentEnabled = false,
|
|
347
333
|
viewerLanguage,
|
|
348
334
|
showChannelInfo = true,
|
|
349
|
-
onParticipantNameClick,
|
|
350
335
|
}) => {
|
|
351
336
|
const { channel } = useChannelStateContext()
|
|
352
337
|
|
|
@@ -428,7 +413,7 @@ const ChannelViewInner: React.FC<{
|
|
|
428
413
|
onReportParticipantClick={onReportParticipantClick}
|
|
429
414
|
customChannelActions={customChannelActions}
|
|
430
415
|
renderChannelActions={renderChannelActions}
|
|
431
|
-
|
|
416
|
+
renderHeaderTitleBadges={renderHeaderTitleBadges}
|
|
432
417
|
/>
|
|
433
418
|
</div>
|
|
434
419
|
|
|
@@ -498,6 +483,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
498
483
|
showStarButton = false,
|
|
499
484
|
chatbotVotingEnabled = false,
|
|
500
485
|
renderChannelBanner,
|
|
486
|
+
renderHeaderTitleBadges,
|
|
501
487
|
customChannelActions,
|
|
502
488
|
renderChannelActions,
|
|
503
489
|
renderMessage,
|
|
@@ -506,7 +492,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
506
492
|
attachmentPreviewList,
|
|
507
493
|
viewerLanguage,
|
|
508
494
|
showChannelInfo = true,
|
|
509
|
-
onParticipantNameClick,
|
|
510
495
|
}) => {
|
|
511
496
|
// Custom send message handler that:
|
|
512
497
|
// 1. Applies messageMetadata if provided
|
|
@@ -614,12 +599,12 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
614
599
|
dmAgentEnabled={dmAgentEnabled}
|
|
615
600
|
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
616
601
|
renderChannelBanner={renderChannelBanner}
|
|
602
|
+
renderHeaderTitleBadges={renderHeaderTitleBadges}
|
|
617
603
|
customChannelActions={customChannelActions}
|
|
618
604
|
renderChannelActions={renderChannelActions}
|
|
619
605
|
renderMessage={renderMessage}
|
|
620
606
|
viewerLanguage={viewerLanguage}
|
|
621
607
|
showChannelInfo={showChannelInfo}
|
|
622
|
-
onParticipantNameClick={onParticipantNameClick}
|
|
623
608
|
/>
|
|
624
609
|
</Channel>
|
|
625
610
|
</DmAgentEnabledContext.Provider>
|
|
@@ -31,6 +31,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
31
31
|
onMessageSent,
|
|
32
32
|
chatbotVotingEnabled = false,
|
|
33
33
|
viewerLanguage,
|
|
34
|
+
renderHeaderTitleBadges,
|
|
34
35
|
renderChannelBanner,
|
|
35
36
|
customChannelActions,
|
|
36
37
|
renderChannelActions,
|
|
@@ -255,6 +256,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
255
256
|
onMessageSent={onMessageSent}
|
|
256
257
|
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
257
258
|
viewerLanguage={viewerLanguage}
|
|
259
|
+
renderHeaderTitleBadges={renderHeaderTitleBadges}
|
|
258
260
|
customChannelActions={customChannelActions}
|
|
259
261
|
renderChannelActions={renderChannelActions}
|
|
260
262
|
renderMessage={renderMessage}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SealCheckIcon } from '@phosphor-icons/react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
export interface VerifiedBadgeProps {
|
|
6
|
+
className?: string
|
|
7
|
+
size?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Linktree verification badge shown next to the name of a verified profile.
|
|
12
|
+
* Rendered when a participant's Stream user has `is_verified` set to true.
|
|
13
|
+
*/
|
|
14
|
+
export const VerifiedBadge: React.FC<VerifiedBadgeProps> = ({
|
|
15
|
+
className,
|
|
16
|
+
size = 16,
|
|
17
|
+
}) => (
|
|
18
|
+
<SealCheckIcon
|
|
19
|
+
weight="fill"
|
|
20
|
+
size={size}
|
|
21
|
+
className={classNames('shrink-0 text-[#1D9BF0]', className)}
|
|
22
|
+
aria-label="Verified"
|
|
23
|
+
role="img"
|
|
24
|
+
/>
|
|
25
|
+
)
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { default as LockedAttachment } from './components/LockedAttachment'
|
|
|
10
10
|
export { default as LinkAttachment } from './components/LinkAttachment'
|
|
11
11
|
export { default as MessageAttachment } from './components/MessageAttachment'
|
|
12
12
|
export { Avatar } from './components/Avatar'
|
|
13
|
+
export { VerifiedBadge } from './components/VerifiedBadge'
|
|
14
|
+
export type { VerifiedBadgeProps } from './components/VerifiedBadge'
|
|
13
15
|
export { FaqList } from './components/FaqList'
|
|
14
16
|
export { FaqListItem } from './components/FaqList/FaqListItem'
|
|
15
17
|
export { ChannelEmptyState } from './components/MessagingShell/ChannelEmptyState'
|
|
@@ -98,6 +98,14 @@ declare module 'stream-chat' {
|
|
|
98
98
|
chatbot_paused?: boolean
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
interface CustomUserData {
|
|
102
|
+
/**
|
|
103
|
+
* Whether the user is a verified Linktree profile. When true, a
|
|
104
|
+
* verification badge is shown next to their name in messaging.
|
|
105
|
+
*/
|
|
106
|
+
is_verified?: boolean
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
interface CustomMessageData {
|
|
102
110
|
/**
|
|
103
111
|
* When true, hides the date timestamp in system messages.
|
package/src/types.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ComponentType } from 'react'
|
|
|
6
6
|
import type {
|
|
7
7
|
Channel,
|
|
8
8
|
ChannelFilters,
|
|
9
|
+
ChannelMemberResponse,
|
|
9
10
|
ChannelSort,
|
|
10
11
|
LocalMessage,
|
|
11
12
|
SendMessageAPIResponse,
|
|
@@ -209,6 +210,15 @@ export interface ChannelViewProps {
|
|
|
209
210
|
*/
|
|
210
211
|
showStarButton?: boolean
|
|
211
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Custom badges rendered inline after the participant name in the channel
|
|
215
|
+
* header title.
|
|
216
|
+
*/
|
|
217
|
+
renderHeaderTitleBadges?: (context: {
|
|
218
|
+
channel: Channel
|
|
219
|
+
participant?: ChannelMemberResponse
|
|
220
|
+
}) => React.ReactNode
|
|
221
|
+
|
|
212
222
|
/**
|
|
213
223
|
* Show the channel actions menu (the `...` popover) in the header, which
|
|
214
224
|
* exposes the block/report/delete moderation actions. Defaults to true.
|
|
@@ -313,17 +323,6 @@ export interface ChannelViewProps {
|
|
|
313
323
|
* Use this to render staged paid or media attachments inside the input.
|
|
314
324
|
*/
|
|
315
325
|
attachmentPreviewList?: ComponentType
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Fired when the participant's name in the channel header is clicked. When
|
|
319
|
-
* provided, the name renders as a button with a trailing caret (an affordance
|
|
320
|
-
* for opening participant details / thread content); when omitted, the name
|
|
321
|
-
* renders as plain text.
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* onParticipantNameClick={() => openThreadContentDialog()}
|
|
325
|
-
*/
|
|
326
|
-
onParticipantNameClick?: () => void
|
|
327
326
|
}
|
|
328
327
|
|
|
329
328
|
/**
|
|
@@ -341,6 +340,7 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
341
340
|
| 'onMessageSent'
|
|
342
341
|
| 'chatbotVotingEnabled'
|
|
343
342
|
| 'viewerLanguage'
|
|
343
|
+
| 'renderHeaderTitleBadges'
|
|
344
344
|
| 'renderChannelBanner'
|
|
345
345
|
| 'customChannelActions'
|
|
346
346
|
| 'renderChannelActions'
|