@linktr.ee/messaging-react 3.6.1 → 3.6.2-rc-1783304704
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-d0_xEfdM.cjs → Card-6FOd4_j_.cjs} +2 -2
- package/dist/{Card-d0_xEfdM.cjs.map → Card-6FOd4_j_.cjs.map} +1 -1
- package/dist/{Card-DcUPqd79.cjs → Card-BhIG7T7c.cjs} +2 -2
- package/dist/{Card-DcUPqd79.cjs.map → Card-BhIG7T7c.cjs.map} +1 -1
- package/dist/{Card-BuR6mbUu.cjs → Card-CA63InbA.cjs} +2 -2
- package/dist/{Card-BuR6mbUu.cjs.map → Card-CA63InbA.cjs.map} +1 -1
- package/dist/{Card-B0lzpuYf.js → Card-C_TfROjB.js} +3 -3
- package/dist/{Card-B0lzpuYf.js.map → Card-C_TfROjB.js.map} +1 -1
- package/dist/{Card-BrC7mKtr.js → Card-DN8P8IeT.js} +2 -2
- package/dist/{Card-BrC7mKtr.js.map → Card-DN8P8IeT.js.map} +1 -1
- package/dist/{Card-CxS7GNqp.js → Card-DgWCIgWw.js} +2 -2
- package/dist/{Card-CxS7GNqp.js.map → Card-DgWCIgWw.js.map} +1 -1
- package/dist/{LockedThumbnail-BaDgF8qJ.js → LockedThumbnail-DNy4CoCm.js} +2 -2
- package/dist/{LockedThumbnail-BaDgF8qJ.js.map → LockedThumbnail-DNy4CoCm.js.map} +1 -1
- package/dist/{LockedThumbnail-BDtKMJjz.cjs → LockedThumbnail-DPlNLdpK.cjs} +2 -2
- package/dist/{LockedThumbnail-BDtKMJjz.cjs.map → LockedThumbnail-DPlNLdpK.cjs.map} +1 -1
- package/dist/index-ClREqfMm.cjs +2 -0
- package/dist/index-ClREqfMm.cjs.map +1 -0
- package/dist/{index-BPfehHDD.js → index-CxGOKFnx.js} +3786 -3876
- package/dist/index-CxGOKFnx.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/components/ChannelHeaderRedesign.tsx +231 -0
- package/src/components/ChannelView.stories.tsx +68 -11
- package/src/components/ChannelView.test.tsx +54 -13
- package/src/components/ChannelView.tsx +74 -28
- package/src/components/CustomMessage/index.tsx +0 -67
- package/dist/index-BPfehHDD.js.map +0 -1
- package/dist/index-G4pnfm4m.cjs +0 -2
- package/dist/index-G4pnfm4m.cjs.map +0 -1
- package/src/components/CustomMessage/StreamAttachmentMessage.stories.tsx +0 -178
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +0 -186
- package/src/components/CustomMessage/StreamAttachmentMessage.tsx +0 -417
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-ClREqfMm.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.resolveConversationParticipant=e.resolveConversationParticipant;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.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 r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-
|
|
1
|
+
import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-CxGOKFnx.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.6.
|
|
3
|
+
"version": "3.6.2-rc-1783304704",
|
|
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": "
|
|
41
|
+
"@linktr.ee/messaging-core": "2.3.0-rc-1783304704",
|
|
42
42
|
"@phosphor-icons/react": "^2.1.10"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowLeftIcon,
|
|
3
|
+
CaretRightIcon,
|
|
4
|
+
CurrencyDollarIcon,
|
|
5
|
+
SparkleIcon,
|
|
6
|
+
StarIcon,
|
|
7
|
+
} from '@phosphor-icons/react'
|
|
8
|
+
import classNames from 'classnames'
|
|
9
|
+
import React from 'react'
|
|
10
|
+
import type { ChannelMemberResponse, Channel as ChannelType } from 'stream-chat'
|
|
11
|
+
import { useChannelStateContext, useChatContext } from 'stream-chat-react'
|
|
12
|
+
|
|
13
|
+
import { useChannelStar } from '../hooks/useChannelStar'
|
|
14
|
+
import { resolveConversationParticipant } from '../utils/resolveConversationParticipant'
|
|
15
|
+
import { resolveParticipantDisplayName } from '../utils/resolveParticipantDisplayName'
|
|
16
|
+
|
|
17
|
+
import { Avatar } from './Avatar'
|
|
18
|
+
import { ChannelActionsMenu } from './ChannelActionsMenu'
|
|
19
|
+
|
|
20
|
+
const ICON_BTN_CLASS =
|
|
21
|
+
'flex size-10 flex-shrink-0 items-center justify-center rounded-full bg-white/65 shadow-[0px_4px_8px_0px_rgba(0,0,0,0.06),0px_0px_0px_1px_rgba(0,0,0,0.04)] backdrop-blur-[40px] transition-filter duration-150 hover:brightness-95 aria-expanded:brightness-95 focus-ring'
|
|
22
|
+
|
|
23
|
+
const CUSTOMER_PAID_TAG = 'CUSTOMER_PAID'
|
|
24
|
+
const DM_AGENT_HEADER_HELPER_TEXT = 'Replies instantly with AI assistant'
|
|
25
|
+
|
|
26
|
+
const isCustomerPaidTag = (value: unknown) =>
|
|
27
|
+
typeof value === 'string' &&
|
|
28
|
+
value
|
|
29
|
+
.trim()
|
|
30
|
+
.replace(/[\s-]+/g, '_')
|
|
31
|
+
.toUpperCase() === CUSTOMER_PAID_TAG
|
|
32
|
+
|
|
33
|
+
const hasCustomerPaidTag = (participant?: ChannelMemberResponse) => {
|
|
34
|
+
const member = participant as
|
|
35
|
+
| {
|
|
36
|
+
customerTag?: unknown
|
|
37
|
+
customer_tag?: unknown
|
|
38
|
+
}
|
|
39
|
+
| undefined
|
|
40
|
+
|
|
41
|
+
return [member?.customerTag, member?.customer_tag].some(isCustomerPaidTag)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ChannelHeaderRedesignProps {
|
|
45
|
+
onBack?: () => void
|
|
46
|
+
showBackButton: boolean
|
|
47
|
+
showStarButton?: boolean
|
|
48
|
+
dmAgentEnabled?: boolean
|
|
49
|
+
onLeaveConversation?: (channel: ChannelType) => void
|
|
50
|
+
onBlockParticipant?: (participantId?: string) => void
|
|
51
|
+
showDeleteConversation?: boolean
|
|
52
|
+
showBlockParticipant?: boolean
|
|
53
|
+
showReportParticipant?: boolean
|
|
54
|
+
onDeleteConversationClick?: () => void
|
|
55
|
+
onBlockParticipantClick?: () => void
|
|
56
|
+
onReportParticipantClick?: () => void
|
|
57
|
+
customChannelActions?: React.ReactNode
|
|
58
|
+
renderChannelActions?: React.ReactNode
|
|
59
|
+
showChannelInfo?: boolean
|
|
60
|
+
onParticipantNameClick: () => void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const ChannelHeaderRedesign: React.FC<ChannelHeaderRedesignProps> = ({
|
|
64
|
+
onBack,
|
|
65
|
+
showBackButton,
|
|
66
|
+
showStarButton = false,
|
|
67
|
+
dmAgentEnabled = false,
|
|
68
|
+
onLeaveConversation,
|
|
69
|
+
onBlockParticipant,
|
|
70
|
+
showDeleteConversation = true,
|
|
71
|
+
showBlockParticipant = true,
|
|
72
|
+
showReportParticipant = true,
|
|
73
|
+
onDeleteConversationClick,
|
|
74
|
+
onBlockParticipantClick,
|
|
75
|
+
onReportParticipantClick,
|
|
76
|
+
customChannelActions,
|
|
77
|
+
renderChannelActions,
|
|
78
|
+
showChannelInfo = true,
|
|
79
|
+
onParticipantNameClick,
|
|
80
|
+
}) => {
|
|
81
|
+
const { channel } = useChannelStateContext()
|
|
82
|
+
const { client } = useChatContext()
|
|
83
|
+
|
|
84
|
+
const participant = React.useMemo(
|
|
85
|
+
() =>
|
|
86
|
+
resolveConversationParticipant(
|
|
87
|
+
channel.state?.members,
|
|
88
|
+
client?.userID,
|
|
89
|
+
channel.type
|
|
90
|
+
),
|
|
91
|
+
[channel.state?.members, client?.userID, channel.type]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const participantName = resolveParticipantDisplayName(participant?.user)
|
|
95
|
+
const participantImage = participant?.user?.image
|
|
96
|
+
const isStarred = useChannelStar(channel)
|
|
97
|
+
const isPaidCustomer = hasCustomerPaidTag(participant)
|
|
98
|
+
|
|
99
|
+
const handleStarClick = async () => {
|
|
100
|
+
try {
|
|
101
|
+
if (isStarred) {
|
|
102
|
+
await (channel as ChannelType).unpin()
|
|
103
|
+
} else {
|
|
104
|
+
await (channel as ChannelType).pin()
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(
|
|
108
|
+
'[ChannelHeaderRedesign] Failed to update pinned status:',
|
|
109
|
+
error
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="@container w-full bg-[linear-gradient(180deg,#FBFAF9_0%,rgba(251,250,249,0.92)_58%,rgba(251,250,249,0)_100%)]">
|
|
116
|
+
<div className="grid min-h-[100px] grid-cols-[minmax(40px,1fr)_auto_minmax(40px,1fr)] items-start pb-2 pt-5 @lg:min-h-[104px] @lg:pt-6">
|
|
117
|
+
<div className="flex items-start">
|
|
118
|
+
{showBackButton && (
|
|
119
|
+
<button
|
|
120
|
+
className={classNames(
|
|
121
|
+
ICON_BTN_CLASS,
|
|
122
|
+
'messaging-channel-view-back-button-mobile messaging-channel-view-back-button-desktop'
|
|
123
|
+
)}
|
|
124
|
+
onClick={onBack || (() => {})}
|
|
125
|
+
type="button"
|
|
126
|
+
aria-label="Back to conversations"
|
|
127
|
+
>
|
|
128
|
+
<ArrowLeftIcon className="size-5 text-black/90" />
|
|
129
|
+
</button>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div className="flex min-w-0 justify-center" data-dd-privacy="mask">
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
onClick={onParticipantNameClick}
|
|
137
|
+
aria-label={`View details for ${participantName}`}
|
|
138
|
+
className="group flex min-w-0 max-w-full appearance-none flex-col items-center border-0 bg-transparent px-2 focus-ring focus-visible:outline-none"
|
|
139
|
+
>
|
|
140
|
+
<Avatar
|
|
141
|
+
id={participant?.user?.id || channel.id || 'unknown'}
|
|
142
|
+
name={participantName}
|
|
143
|
+
image={participantImage}
|
|
144
|
+
dmAgentEnabled={dmAgentEnabled}
|
|
145
|
+
shape="circle"
|
|
146
|
+
size={48}
|
|
147
|
+
className="-mb-2"
|
|
148
|
+
/>
|
|
149
|
+
<span className="relative z-10 flex max-w-full flex-col items-center justify-center rounded-full border border-white/10 bg-white/65 py-1.5 pl-3 pr-2 shadow-[0px_0px_0px_0px_rgba(0,0,0,0.04),0px_1px_2px_0px_rgba(0,0,0,0.04),0px_8px_32px_0px_rgba(0,0,0,0.1)] backdrop-blur-[2px] transition-filter duration-150 group-hover:brightness-95">
|
|
150
|
+
<span className="flex max-w-full items-center justify-center">
|
|
151
|
+
<span className="min-w-0 truncate text-sm font-bold leading-[1.43] tracking-[0.28px] text-black/90">
|
|
152
|
+
{participantName}
|
|
153
|
+
</span>
|
|
154
|
+
<span className="ml-2 flex gap-1">
|
|
155
|
+
{isPaidCustomer && (
|
|
156
|
+
<span
|
|
157
|
+
aria-label="Paid customer"
|
|
158
|
+
className="flex size-4 shrink-0 items-center justify-center rounded-full bg-[#34C759] text-white"
|
|
159
|
+
>
|
|
160
|
+
<CurrencyDollarIcon className="size-2.5" weight="bold" />
|
|
161
|
+
</span>
|
|
162
|
+
)}
|
|
163
|
+
{showStarButton && isStarred && (
|
|
164
|
+
<span
|
|
165
|
+
aria-label="Starred conversation"
|
|
166
|
+
className="flex size-4 shrink-0 items-center justify-center rounded-full bg-[#FFD60A] text-white"
|
|
167
|
+
>
|
|
168
|
+
<StarIcon className="size-2.5" weight="fill" />
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
<CaretRightIcon
|
|
172
|
+
aria-hidden="true"
|
|
173
|
+
className="size-4 shrink-0 text-black/65"
|
|
174
|
+
weight="bold"
|
|
175
|
+
/>
|
|
176
|
+
</span>
|
|
177
|
+
</span>
|
|
178
|
+
{dmAgentEnabled && (
|
|
179
|
+
<span className="mt-1 flex items-center gap-1 px-1 pb-0.5 text-[10px] leading-3 text-black/55">
|
|
180
|
+
<SparkleIcon className="size-3 shrink-0 text-black/55" />
|
|
181
|
+
<span>{DM_AGENT_HEADER_HELPER_TEXT}</span>
|
|
182
|
+
</span>
|
|
183
|
+
)}
|
|
184
|
+
</span>
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div className="flex items-start justify-end gap-2">
|
|
189
|
+
{showStarButton && (
|
|
190
|
+
<button
|
|
191
|
+
className={ICON_BTN_CLASS}
|
|
192
|
+
onClick={handleStarClick}
|
|
193
|
+
type="button"
|
|
194
|
+
aria-pressed={isStarred}
|
|
195
|
+
aria-label={
|
|
196
|
+
isStarred ? 'Unstar conversation' : 'Star conversation'
|
|
197
|
+
}
|
|
198
|
+
>
|
|
199
|
+
<StarIcon
|
|
200
|
+
className={classNames('size-5', {
|
|
201
|
+
'text-[#FFD60A]': isStarred,
|
|
202
|
+
'text-black/90': !isStarred,
|
|
203
|
+
})}
|
|
204
|
+
weight={isStarred ? 'fill' : 'regular'}
|
|
205
|
+
/>
|
|
206
|
+
</button>
|
|
207
|
+
)}
|
|
208
|
+
{showChannelInfo &&
|
|
209
|
+
(renderChannelActions !== undefined ? (
|
|
210
|
+
renderChannelActions
|
|
211
|
+
) : (
|
|
212
|
+
<ChannelActionsMenu
|
|
213
|
+
channel={channel as ChannelType}
|
|
214
|
+
participant={participant}
|
|
215
|
+
showDeleteConversation={showDeleteConversation}
|
|
216
|
+
showBlockParticipant={showBlockParticipant}
|
|
217
|
+
showReportParticipant={showReportParticipant}
|
|
218
|
+
onLeaveConversation={onLeaveConversation}
|
|
219
|
+
onBlockParticipant={onBlockParticipant}
|
|
220
|
+
onDeleteConversationClick={onDeleteConversationClick}
|
|
221
|
+
onBlockParticipantClick={onBlockParticipantClick}
|
|
222
|
+
onReportParticipantClick={onReportParticipantClick}
|
|
223
|
+
customChannelActions={customChannelActions}
|
|
224
|
+
triggerClassName={ICON_BTN_CLASS}
|
|
225
|
+
/>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
@@ -16,6 +16,7 @@ import { ChannelView } from './ChannelView'
|
|
|
16
16
|
import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
|
|
17
17
|
|
|
18
18
|
type ComponentProps = React.ComponentProps<typeof ChannelView>
|
|
19
|
+
const SCROLLABLE_MESSAGE_COUNT = 10
|
|
19
20
|
|
|
20
21
|
const meta: Meta<ComponentProps> = {
|
|
21
22
|
title: 'ChannelView',
|
|
@@ -41,7 +42,9 @@ const createMockChannel = async (
|
|
|
41
42
|
client: StreamChat,
|
|
42
43
|
hasMessages = true,
|
|
43
44
|
followerStatus?: string | boolean,
|
|
44
|
-
isFrozen = false
|
|
45
|
+
isFrozen = false,
|
|
46
|
+
participantCustomerTag?: string,
|
|
47
|
+
hasScrollableMessages = false
|
|
45
48
|
) => {
|
|
46
49
|
const participant = mockParticipants[0]
|
|
47
50
|
|
|
@@ -101,6 +104,34 @@ const createMockChannel = async (
|
|
|
101
104
|
cid: 'messaging:storybook-channel-1',
|
|
102
105
|
mentioned_users: [],
|
|
103
106
|
},
|
|
107
|
+
...(hasScrollableMessages
|
|
108
|
+
? Array.from({ length: SCROLLABLE_MESSAGE_COUNT }, (_, index) => {
|
|
109
|
+
const isParticipantMessage = index % 2 === 0
|
|
110
|
+
const user = isParticipantMessage ? participant : mockUser
|
|
111
|
+
const text = isParticipantMessage
|
|
112
|
+
? `Here is another message to show how the redesigned header floats above the thread (${index + 1}).`
|
|
113
|
+
: `Replying so the conversation has enough content to scroll under the header (${index + 1}).`
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
id: `msg-extra-${index + 1}`,
|
|
117
|
+
text,
|
|
118
|
+
type: 'regular' as const,
|
|
119
|
+
created_at: minutesAgo(Math.max(1, 25 - index)),
|
|
120
|
+
updated_at: minutesAgo(Math.max(1, 25 - index)),
|
|
121
|
+
user,
|
|
122
|
+
html: `<p>${text}</p>`,
|
|
123
|
+
attachments: [],
|
|
124
|
+
latest_reactions: [],
|
|
125
|
+
own_reactions: [],
|
|
126
|
+
reaction_counts: {},
|
|
127
|
+
reaction_scores: {},
|
|
128
|
+
reply_count: 0,
|
|
129
|
+
status: 'received',
|
|
130
|
+
cid: 'messaging:storybook-channel-1',
|
|
131
|
+
mentioned_users: [],
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
: []),
|
|
104
135
|
]
|
|
105
136
|
: []
|
|
106
137
|
|
|
@@ -118,7 +149,11 @@ const createMockChannel = async (
|
|
|
118
149
|
}
|
|
119
150
|
|
|
120
151
|
// Create a real channel using the client
|
|
121
|
-
const channel = client.channel(
|
|
152
|
+
const channel = client.channel(
|
|
153
|
+
'messaging',
|
|
154
|
+
'storybook-channel-1',
|
|
155
|
+
channelData
|
|
156
|
+
)
|
|
122
157
|
|
|
123
158
|
// Mock the watch method to return mocked data
|
|
124
159
|
channel.watch = async () => {
|
|
@@ -142,6 +177,7 @@ const createMockChannel = async (
|
|
|
142
177
|
user_id: participant.id,
|
|
143
178
|
role: 'member',
|
|
144
179
|
is_account: true,
|
|
180
|
+
customerTag: participantCustomerTag,
|
|
145
181
|
},
|
|
146
182
|
}
|
|
147
183
|
return {
|
|
@@ -169,6 +205,7 @@ const createMockChannel = async (
|
|
|
169
205
|
}
|
|
170
206
|
|
|
171
207
|
type TemplateProps = ComponentProps & {
|
|
208
|
+
hasMessages?: boolean
|
|
172
209
|
followerStatus?: string | boolean
|
|
173
210
|
isFrozen?: boolean
|
|
174
211
|
typingUser?: {
|
|
@@ -176,12 +213,17 @@ type TemplateProps = ComponentProps & {
|
|
|
176
213
|
name?: string
|
|
177
214
|
image?: string
|
|
178
215
|
}
|
|
216
|
+
participantCustomerTag?: string
|
|
217
|
+
hasScrollableMessages?: boolean
|
|
179
218
|
}
|
|
180
219
|
|
|
181
220
|
const Template: StoryFn<TemplateProps> = (args) => {
|
|
182
221
|
const {
|
|
222
|
+
hasMessages = true,
|
|
183
223
|
followerStatus,
|
|
184
224
|
isFrozen = false,
|
|
225
|
+
participantCustomerTag,
|
|
226
|
+
hasScrollableMessages,
|
|
185
227
|
typingUser,
|
|
186
228
|
...channelViewProps
|
|
187
229
|
} = args
|
|
@@ -193,16 +235,28 @@ const Template: StoryFn<TemplateProps> = (args) => {
|
|
|
193
235
|
client.user = mockUser
|
|
194
236
|
return client
|
|
195
237
|
})
|
|
196
|
-
|
|
238
|
+
|
|
197
239
|
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
198
240
|
|
|
199
241
|
useEffect(() => {
|
|
200
|
-
createMockChannel(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
242
|
+
createMockChannel(
|
|
243
|
+
client,
|
|
244
|
+
hasMessages,
|
|
245
|
+
followerStatus,
|
|
246
|
+
isFrozen,
|
|
247
|
+
participantCustomerTag,
|
|
248
|
+
hasScrollableMessages
|
|
249
|
+
).then((mockChannel) => {
|
|
250
|
+
setChannel(mockChannel)
|
|
251
|
+
})
|
|
252
|
+
}, [
|
|
253
|
+
client,
|
|
254
|
+
hasMessages,
|
|
255
|
+
followerStatus,
|
|
256
|
+
isFrozen,
|
|
257
|
+
participantCustomerTag,
|
|
258
|
+
hasScrollableMessages,
|
|
259
|
+
])
|
|
206
260
|
|
|
207
261
|
useEffect(() => {
|
|
208
262
|
if (!channel || !typingUser) {
|
|
@@ -256,6 +310,9 @@ export const ParticipantIdentityTrigger: StoryFn<TemplateProps> = Template.bind(
|
|
|
256
310
|
)
|
|
257
311
|
ParticipantIdentityTrigger.args = {
|
|
258
312
|
showBackButton: false,
|
|
313
|
+
showStarButton: true,
|
|
314
|
+
participantCustomerTag: 'CUSTOMER_PAID',
|
|
315
|
+
hasScrollableMessages: true,
|
|
259
316
|
onParticipantNameClick: () =>
|
|
260
317
|
// eslint-disable-next-line no-alert
|
|
261
318
|
window.alert('Participant details clicked — open Thread content'),
|
|
@@ -264,7 +321,7 @@ ParticipantIdentityTrigger.parameters = {
|
|
|
264
321
|
docs: {
|
|
265
322
|
description: {
|
|
266
323
|
story:
|
|
267
|
-
'Header with `onParticipantNameClick` set: the
|
|
324
|
+
'Header with `onParticipantNameClick` set: the redesigned floating header centers the avatar and name pill, shows paid/star badges when present, and fires the callback when clicked (shows an alert here). With the prop omitted (see Default) the previous header remains in use.',
|
|
268
325
|
},
|
|
269
326
|
},
|
|
270
327
|
}
|
|
@@ -542,7 +599,7 @@ const EmptyTemplate: StoryFn<ComponentProps> = (args) => {
|
|
|
542
599
|
client.user = mockUser
|
|
543
600
|
return client
|
|
544
601
|
})
|
|
545
|
-
|
|
602
|
+
|
|
546
603
|
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
547
604
|
|
|
548
605
|
useEffect(() => {
|
|
@@ -22,7 +22,9 @@ vi.mock('stream-chat-react', () => ({
|
|
|
22
22
|
Window: ({ children }: { children: React.ReactNode }) => (
|
|
23
23
|
<div data-testid="window">{children}</div>
|
|
24
24
|
),
|
|
25
|
-
MessageList: () =>
|
|
25
|
+
MessageList: ({ head }: { head?: React.ReactElement }) => (
|
|
26
|
+
<div data-testid="message-list">{head}</div>
|
|
27
|
+
),
|
|
26
28
|
WithComponents: ({ children }: { children: React.ReactNode }) => (
|
|
27
29
|
<>{children}</>
|
|
28
30
|
),
|
|
@@ -94,15 +96,19 @@ vi.mock('../hooks/useChannelStar', () => ({
|
|
|
94
96
|
const createChannel = ({
|
|
95
97
|
currentUserId = 'visitor-1',
|
|
96
98
|
currentUserRole = 'owner',
|
|
99
|
+
messages = [],
|
|
97
100
|
otherUser = {
|
|
98
101
|
id: 'linker-1' as const,
|
|
99
102
|
name: 'Linker' as string | undefined,
|
|
100
103
|
username: undefined as string | undefined,
|
|
101
104
|
is_account: true,
|
|
102
105
|
},
|
|
106
|
+
otherMemberCustomerTag,
|
|
103
107
|
}: {
|
|
104
108
|
currentUserId?: 'visitor-1' | 'linker-1'
|
|
105
109
|
currentUserRole?: 'owner' | 'member'
|
|
110
|
+
messages?: Channel['state']['messages']
|
|
111
|
+
otherMemberCustomerTag?: string
|
|
106
112
|
otherUser?: {
|
|
107
113
|
id: string
|
|
108
114
|
name?: string
|
|
@@ -134,11 +140,12 @@ const createChannel = ({
|
|
|
134
140
|
is_account:
|
|
135
141
|
otherUser.is_account ?? currentUserId === 'visitor-1',
|
|
136
142
|
},
|
|
143
|
+
customerTag: otherMemberCustomerTag,
|
|
137
144
|
role: currentUserRole === 'owner' ? 'member' : 'owner',
|
|
138
145
|
},
|
|
139
146
|
},
|
|
140
147
|
membership: {},
|
|
141
|
-
messages
|
|
148
|
+
messages,
|
|
142
149
|
},
|
|
143
150
|
on: vi.fn(),
|
|
144
151
|
off: vi.fn(),
|
|
@@ -203,7 +210,7 @@ describe('ChannelView', () => {
|
|
|
203
210
|
)
|
|
204
211
|
})
|
|
205
212
|
|
|
206
|
-
it('renders the
|
|
213
|
+
it('renders the redesigned header button when onParticipantNameClick is provided', () => {
|
|
207
214
|
const onParticipantNameClick = vi.fn()
|
|
208
215
|
renderWithProviders(
|
|
209
216
|
<ChannelView
|
|
@@ -212,29 +219,63 @@ describe('ChannelView', () => {
|
|
|
212
219
|
/>
|
|
213
220
|
)
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
// hidden via CSS), so there is at least one match.
|
|
217
|
-
const buttons = screen.getAllByRole('button', {
|
|
222
|
+
const button = screen.getByRole('button', {
|
|
218
223
|
name: /view details for linker/i,
|
|
219
224
|
})
|
|
220
|
-
expect(
|
|
221
|
-
expect(buttons[0]).toHaveClass(
|
|
225
|
+
expect(button).toHaveClass(
|
|
222
226
|
'appearance-none',
|
|
223
227
|
'border-0',
|
|
224
228
|
'bg-transparent',
|
|
225
|
-
'
|
|
226
|
-
'px-1',
|
|
227
|
-
'py-0.5'
|
|
229
|
+
'flex-col'
|
|
228
230
|
)
|
|
229
231
|
|
|
230
232
|
const avatars = screen.getAllByTestId('avatar')
|
|
231
233
|
expect(avatars.length).toBeGreaterThanOrEqual(1)
|
|
232
|
-
expect(avatars[0].closest('button')).toBe(
|
|
234
|
+
expect(avatars[0].closest('button')).toBe(button)
|
|
235
|
+
expect(screen.getByTestId('channel-actions-menu')).toBeInTheDocument()
|
|
233
236
|
|
|
234
|
-
|
|
237
|
+
button.click()
|
|
235
238
|
expect(onParticipantNameClick).toHaveBeenCalledTimes(1)
|
|
236
239
|
})
|
|
237
240
|
|
|
241
|
+
it('renders redesigned header badges for paid and starred conversations', () => {
|
|
242
|
+
mockIsStarred = true
|
|
243
|
+
|
|
244
|
+
renderWithProviders(
|
|
245
|
+
<ChannelView
|
|
246
|
+
channel={createChannel({
|
|
247
|
+
otherMemberCustomerTag: 'CUSTOMER_PAID',
|
|
248
|
+
})}
|
|
249
|
+
onParticipantNameClick={vi.fn()}
|
|
250
|
+
showStarButton={true}
|
|
251
|
+
/>
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
expect(screen.getByLabelText('Paid customer')).toBeInTheDocument()
|
|
255
|
+
expect(screen.getByLabelText('Starred conversation')).toBeInTheDocument()
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('renders the redesigned header outside MessageList head when all messages are deleted', () => {
|
|
259
|
+
renderWithProviders(
|
|
260
|
+
<ChannelView
|
|
261
|
+
channel={createChannel({
|
|
262
|
+
messages: [
|
|
263
|
+
{
|
|
264
|
+
id: 'deleted-message-1',
|
|
265
|
+
type: 'deleted',
|
|
266
|
+
} as Channel['state']['messages'][number],
|
|
267
|
+
],
|
|
268
|
+
})}
|
|
269
|
+
onParticipantNameClick={vi.fn()}
|
|
270
|
+
/>
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
const button = screen.getByRole('button', {
|
|
274
|
+
name: /view details for linker/i,
|
|
275
|
+
})
|
|
276
|
+
expect(button.closest('[data-testid="message-list"]')).toBeNull()
|
|
277
|
+
})
|
|
278
|
+
|
|
238
279
|
it('renders the actions menu by default', () => {
|
|
239
280
|
renderWithProviders(<ChannelView channel={createChannel()} />)
|
|
240
281
|
|