@linktr.ee/messaging-react 3.4.2-rc-1782395074 → 3.5.0-rc-1782408966
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-mpVcQ8-W.js → Card-BNzC_bp8.js} +3 -3
- package/dist/{Card-mpVcQ8-W.js.map → Card-BNzC_bp8.js.map} +1 -1
- package/dist/{Card-BKalDyyu.js → Card-CUHEDe8O.js} +2 -2
- package/dist/{Card-BKalDyyu.js.map → Card-CUHEDe8O.js.map} +1 -1
- package/dist/{Card-DFy_ZvFH.cjs → Card-DEa50m9p.cjs} +2 -2
- package/dist/{Card-DFy_ZvFH.cjs.map → Card-DEa50m9p.cjs.map} +1 -1
- package/dist/{Card-V-bNUxcw.cjs → Card-DJtDI03Y.cjs} +2 -2
- package/dist/{Card-V-bNUxcw.cjs.map → Card-DJtDI03Y.cjs.map} +1 -1
- package/dist/{Card-DxroYNrS.cjs → Card-DuMr5KMP.cjs} +2 -2
- package/dist/{Card-DxroYNrS.cjs.map → Card-DuMr5KMP.cjs.map} +1 -1
- package/dist/{Card-CphU_jG-.js → Card-iekRe643.js} +2 -2
- package/dist/{Card-CphU_jG-.js.map → Card-iekRe643.js.map} +1 -1
- package/dist/{LockedThumbnail-BD7qtcso.js → LockedThumbnail-CvpRZFi-.js} +2 -2
- package/dist/{LockedThumbnail-BD7qtcso.js.map → LockedThumbnail-CvpRZFi-.js.map} +1 -1
- package/dist/{LockedThumbnail-B3W7upHs.cjs → LockedThumbnail-aME0f_ZF.cjs} +2 -2
- package/dist/{LockedThumbnail-B3W7upHs.cjs.map → LockedThumbnail-aME0f_ZF.cjs.map} +1 -1
- package/dist/index-BIWS3Inh.cjs +2 -0
- package/dist/index-BIWS3Inh.cjs.map +1 -0
- package/dist/{index-CjwQ2Hj3.js → index-BlpDX5fl.js} +1369 -1367
- package/dist/index-BlpDX5fl.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +19 -25
- package/dist/index.js +19 -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.test.tsx +35 -0
- package/src/components/ChannelView.tsx +6 -5
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +0 -34
- package/src/components/CustomMessageInput/index.tsx +1 -23
- package/src/components/MessagingShell/index.tsx +0 -2
- package/src/components/VerifiedBadge/index.tsx +25 -0
- package/src/index.ts +2 -1
- package/src/stream-custom-data.ts +8 -0
- package/src/types.ts +0 -18
- package/dist/index-CjwQ2Hj3.js.map +0 -1
- package/dist/index-tI-4cxV1.cjs +0 -2
- package/dist/index-tI-4cxV1.cjs.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-BIWS3Inh.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
|
@@ -149,7 +149,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
149
149
|
* Props that MessagingShell passes through to ChannelView.
|
|
150
150
|
* ChannelViewProps is the source of truth for these props.
|
|
151
151
|
*/
|
|
152
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderHeaderTitleBadges' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo'
|
|
152
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderHeaderTitleBadges' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo'>;
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
155
|
* ChannelView component props
|
|
@@ -351,22 +351,6 @@ export declare interface ChannelViewProps {
|
|
|
351
351
|
* Use this to render staged paid or media attachments inside the input.
|
|
352
352
|
*/
|
|
353
353
|
attachmentPreviewList?: ComponentType;
|
|
354
|
-
/**
|
|
355
|
-
* Replace the entire inner composer — the textarea + send arrangement — with
|
|
356
|
-
* a host-owned component. Passed to Stream's `<MessageInput>` as the `Input`
|
|
357
|
-
* component, so it runs inside the message-input context and reuses the
|
|
358
|
-
* library's send pipeline (`doSendMessageRequest`, message metadata, DM-agent
|
|
359
|
-
* suppression), attachments, and previews via the Stream hooks
|
|
360
|
-
* (`useMessageInputContext`, `useMessageComposerHasSendableData`,
|
|
361
|
-
* `TextareaComposer`, …). Use it when a surface needs a different composer
|
|
362
|
-
* layout (e.g. a full-width textarea above a toolbar row) than the default.
|
|
363
|
-
*
|
|
364
|
-
* The outer wrapper — frozen handling and the `renderMessageInputActions` /
|
|
365
|
-
* `renderMessageInputFooter` slots — is unchanged. Read the exported
|
|
366
|
-
* `useComposerLocked()` hook to honour the frozen state. Defaults to the
|
|
367
|
-
* built-in composer when omitted.
|
|
368
|
-
*/
|
|
369
|
-
composerInput?: ComponentType;
|
|
370
354
|
/**
|
|
371
355
|
* Fired when the participant's name in the channel header is clicked. When
|
|
372
356
|
* provided, the name renders as a button with a trailing caret (an affordance
|
|
@@ -1392,14 +1376,6 @@ export declare interface SentCardProps extends LockedAttachmentBaseProps {
|
|
|
1392
1376
|
onFetchSource?: () => Promise<LockedAttachmentSource | void>;
|
|
1393
1377
|
}
|
|
1394
1378
|
|
|
1395
|
-
/**
|
|
1396
|
-
* Read the composer's locked (frozen-channel) state from inside a custom
|
|
1397
|
-
* composer supplied via `composerInput`. Returns true when the channel is
|
|
1398
|
-
* frozen; mirror the default composer by rendering the textarea read-only and
|
|
1399
|
-
* disabling the send control.
|
|
1400
|
-
*/
|
|
1401
|
-
export declare const useComposerLocked: () => boolean;
|
|
1402
|
-
|
|
1403
1379
|
export declare function useCustomMessage<K extends keyof CustomMessageRegistry>(key: K): CustomMessageRegistry[K];
|
|
1404
1380
|
|
|
1405
1381
|
/**
|
|
@@ -1423,6 +1399,17 @@ declare interface UseMessageVoteResult {
|
|
|
1423
1399
|
*/
|
|
1424
1400
|
export declare const useMessaging: () => MessagingContextValue;
|
|
1425
1401
|
|
|
1402
|
+
/**
|
|
1403
|
+
* Linktree verification badge shown next to the name of a verified profile.
|
|
1404
|
+
* Rendered when a participant's Stream user has `is_verified` set to true.
|
|
1405
|
+
*/
|
|
1406
|
+
export declare const VerifiedBadge: default_2.FC<VerifiedBadgeProps>;
|
|
1407
|
+
|
|
1408
|
+
export declare interface VerifiedBadgeProps {
|
|
1409
|
+
className?: string;
|
|
1410
|
+
size?: number;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1426
1413
|
declare interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps {
|
|
1427
1414
|
/** Single video — convenience for the most common case. */
|
|
1428
1415
|
src?: string;
|
|
@@ -1495,6 +1482,13 @@ declare module 'stream-chat' {
|
|
|
1495
1482
|
*/
|
|
1496
1483
|
chatbot_paused?: boolean;
|
|
1497
1484
|
}
|
|
1485
|
+
interface CustomUserData {
|
|
1486
|
+
/**
|
|
1487
|
+
* Whether the user is a verified Linktree profile. When true, a
|
|
1488
|
+
* verification badge is shown next to their name in messaging.
|
|
1489
|
+
*/
|
|
1490
|
+
is_verified?: boolean;
|
|
1491
|
+
}
|
|
1498
1492
|
interface CustomMessageData {
|
|
1499
1493
|
/**
|
|
1500
1494
|
* When true, hides the date timestamp in system messages.
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-BlpDX5fl.js";
|
|
2
2
|
export {
|
|
3
|
-
|
|
3
|
+
s as ActionButton,
|
|
4
4
|
t as Avatar,
|
|
5
5
|
i as ChannelEmptyState,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
n as ChannelList,
|
|
7
|
+
o as ChannelView,
|
|
8
8
|
m as CustomMessageProvider,
|
|
9
9
|
g as FaqList,
|
|
10
10
|
r as FaqListItem,
|
|
@@ -13,23 +13,23 @@ export {
|
|
|
13
13
|
u as MediaMessage,
|
|
14
14
|
L as MessageAttachment,
|
|
15
15
|
c as MessageVoteButtons,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
p as
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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,
|
|
22
23
|
k as getMessageDisplayText,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
33
|
-
D as useMessaging
|
|
29
|
+
B as resolveMediaFromMessage,
|
|
30
|
+
P as resolveParticipantDisplayName,
|
|
31
|
+
S as useCustomMessage,
|
|
32
|
+
q as useMessageVote,
|
|
33
|
+
z as useMessaging
|
|
34
34
|
};
|
|
35
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.
|
|
3
|
+
"version": "3.5.0-rc-1782408966",
|
|
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-1782408966",
|
|
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">
|
|
@@ -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
|
({
|
|
@@ -475,6 +476,40 @@ describe('ChannelView', () => {
|
|
|
475
476
|
expect(screen.getAllByText('Unknown member').length).toBeGreaterThan(0)
|
|
476
477
|
})
|
|
477
478
|
|
|
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
|
+
|
|
478
513
|
it('fires onMessageLinkClick with the href and undefined message when the link is not inside a message wrapper', () => {
|
|
479
514
|
const handler = vi.fn()
|
|
480
515
|
const { container } = renderWithProviders(
|
|
@@ -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 { VerifiedBadge } from './VerifiedBadge'
|
|
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'
|
|
@@ -92,6 +93,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
92
93
|
|
|
93
94
|
const participantName = resolveParticipantDisplayName(participant?.user)
|
|
94
95
|
const participantImage = participant?.user?.image
|
|
96
|
+
const isParticipantVerified = participant?.user?.is_verified === true
|
|
95
97
|
const isStarred = useChannelStar(channel)
|
|
96
98
|
const headerTitleBadges = renderHeaderTitleBadges?.({
|
|
97
99
|
channel,
|
|
@@ -148,6 +150,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
148
150
|
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
151
|
>
|
|
150
152
|
<span className="min-w-0 truncate">{participantName}</span>
|
|
153
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
151
154
|
{headerTitleBadges && (
|
|
152
155
|
<span className="shrink-0">{headerTitleBadges}</span>
|
|
153
156
|
)}
|
|
@@ -156,6 +159,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
156
159
|
) : (
|
|
157
160
|
<p className="flex max-w-full items-center justify-center gap-1 text-center text-xs font-medium text-black/90">
|
|
158
161
|
<span className="min-w-0 truncate">{participantName}</span>
|
|
162
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
159
163
|
{headerTitleBadges && (
|
|
160
164
|
<span className="shrink-0">{headerTitleBadges}</span>
|
|
161
165
|
)}
|
|
@@ -241,6 +245,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
241
245
|
className="flex min-w-0 max-w-full items-center gap-1 rounded font-medium text-black/90 transition-opacity hover:opacity-70"
|
|
242
246
|
>
|
|
243
247
|
<span className="min-w-0 truncate">{participantName}</span>
|
|
248
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
244
249
|
{headerTitleBadges && (
|
|
245
250
|
<span className="shrink-0">{headerTitleBadges}</span>
|
|
246
251
|
)}
|
|
@@ -249,6 +254,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
249
254
|
) : (
|
|
250
255
|
<h1 className="flex min-w-0 items-center gap-1 font-medium text-black/90">
|
|
251
256
|
<span className="min-w-0 truncate">{participantName}</span>
|
|
257
|
+
{isParticipantVerified && <VerifiedBadge />}
|
|
252
258
|
{headerTitleBadges && (
|
|
253
259
|
<span className="shrink-0">{headerTitleBadges}</span>
|
|
254
260
|
)}
|
|
@@ -340,7 +346,6 @@ const ChannelViewInner: React.FC<{
|
|
|
340
346
|
viewerLanguage?: string
|
|
341
347
|
showChannelInfo?: boolean
|
|
342
348
|
onParticipantNameClick?: () => void
|
|
343
|
-
composerInput?: ChannelViewProps['composerInput']
|
|
344
349
|
}> = ({
|
|
345
350
|
onBack,
|
|
346
351
|
showBackButton,
|
|
@@ -368,7 +373,6 @@ const ChannelViewInner: React.FC<{
|
|
|
368
373
|
viewerLanguage,
|
|
369
374
|
showChannelInfo = true,
|
|
370
375
|
onParticipantNameClick,
|
|
371
|
-
composerInput,
|
|
372
376
|
}) => {
|
|
373
377
|
const { channel } = useChannelStateContext()
|
|
374
378
|
|
|
@@ -485,7 +489,6 @@ const ChannelViewInner: React.FC<{
|
|
|
485
489
|
renderFooter={() => renderMessageInputFooter?.(channel)}
|
|
486
490
|
disabled={composerDisabled}
|
|
487
491
|
disabledReason={composerDisabledReason}
|
|
488
|
-
composerInput={composerInput}
|
|
489
492
|
/>
|
|
490
493
|
</Window>
|
|
491
494
|
</WithComponents>
|
|
@@ -532,7 +535,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
532
535
|
viewerLanguage,
|
|
533
536
|
showChannelInfo = true,
|
|
534
537
|
onParticipantNameClick,
|
|
535
|
-
composerInput,
|
|
536
538
|
}) => {
|
|
537
539
|
// Custom send message handler that:
|
|
538
540
|
// 1. Applies messageMetadata if provided
|
|
@@ -647,7 +649,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
647
649
|
viewerLanguage={viewerLanguage}
|
|
648
650
|
showChannelInfo={showChannelInfo}
|
|
649
651
|
onParticipantNameClick={onParticipantNameClick}
|
|
650
|
-
composerInput={composerInput}
|
|
651
652
|
/>
|
|
652
653
|
</Channel>
|
|
653
654
|
</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={
|
|
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,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'
|
|
@@ -30,7 +32,6 @@ export { MessagingProvider } from './providers/MessagingProvider'
|
|
|
30
32
|
export { CustomMessageProvider } from './components/CustomMessage/context'
|
|
31
33
|
|
|
32
34
|
// Hooks
|
|
33
|
-
export { useComposerLocked } from './components/CustomMessageInput'
|
|
34
35
|
export { useMessaging } from './hooks/useMessaging'
|
|
35
36
|
export { useMessageVote } from './hooks/useMessageVote'
|
|
36
37
|
export { useCustomMessage } from './components/CustomMessage/context'
|
|
@@ -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
|
@@ -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
|
/**
|