@linktr.ee/messaging-react 2.5.2 → 2.6.0-rc-1780281100
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-Bp3XzYXV.cjs → Card-BHrhU1uR.cjs} +2 -2
- package/dist/{Card-Bp3XzYXV.cjs.map → Card-BHrhU1uR.cjs.map} +1 -1
- package/dist/{Card-Obhvbcgk.cjs → Card-BdyCnBOJ.cjs} +2 -2
- package/dist/{Card-Obhvbcgk.cjs.map → Card-BdyCnBOJ.cjs.map} +1 -1
- package/dist/{Card-cl8fYGrO.js → Card-CD9KC9WP.js} +3 -3
- package/dist/{Card-cl8fYGrO.js.map → Card-CD9KC9WP.js.map} +1 -1
- package/dist/{Card-C8C9ZL55.cjs → Card-CJMJGeFI.cjs} +2 -2
- package/dist/{Card-C8C9ZL55.cjs.map → Card-CJMJGeFI.cjs.map} +1 -1
- package/dist/{Card-CBeF7eUm.js → Card-CWjGaRH2.js} +2 -2
- package/dist/{Card-CBeF7eUm.js.map → Card-CWjGaRH2.js.map} +1 -1
- package/dist/{Card-CSvw2WUm.js → Card-CbDg3PRA.js} +2 -2
- package/dist/{Card-CSvw2WUm.js.map → Card-CbDg3PRA.js.map} +1 -1
- package/dist/{LockedThumbnail-BQ5gB3Gj.js → LockedThumbnail-DzSYmmYA.js} +2 -2
- package/dist/{LockedThumbnail-BQ5gB3Gj.js.map → LockedThumbnail-DzSYmmYA.js.map} +1 -1
- package/dist/{LockedThumbnail-DR1i_N1L.cjs → LockedThumbnail-ineY4nwg.cjs} +2 -2
- package/dist/{LockedThumbnail-DR1i_N1L.cjs.map → LockedThumbnail-ineY4nwg.cjs.map} +1 -1
- package/dist/index-Be5pkdgH.cjs +2 -0
- package/dist/index-Be5pkdgH.cjs.map +1 -0
- package/dist/{index-BGwytfkX.js → index-n6fP9phm.js} +1571 -1518
- package/dist/index-n6fP9phm.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +7 -0
- package/src/components/CustomMessage/CustomMessage.stories.tsx +43 -38
- package/src/components/CustomMessage/CustomMessageActions.tsx +30 -16
- package/src/components/CustomMessage/index.tsx +37 -28
- package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +126 -30
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +2 -13
- package/src/components/CustomMessageInput/index.tsx +51 -45
- package/src/components/MessagingShell/index.tsx +4 -0
- package/src/types.ts +10 -0
- package/dist/index-BGwytfkX.js.map +0 -1
- package/dist/index-Dgh0_9UW.cjs +0 -2
- package/dist/index-Dgh0_9UW.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-Be5pkdgH.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -162,7 +162,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
162
162
|
* Props that MessagingShell passes through to ChannelView.
|
|
163
163
|
* ChannelViewProps is the source of truth for these props.
|
|
164
164
|
*/
|
|
165
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'onMessageLinkClick' | 'sendButton'>;
|
|
165
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderMessageInputFooter' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'onMessageLinkClick' | 'sendButton' | 'attachmentPreviewList'>;
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
168
|
* ChannelView component props
|
|
@@ -172,6 +172,7 @@ export declare interface ChannelViewProps {
|
|
|
172
172
|
onBack?: () => void;
|
|
173
173
|
showBackButton?: boolean;
|
|
174
174
|
renderMessageInputActions?: (channel: Channel) => React.ReactNode;
|
|
175
|
+
renderMessageInputFooter?: (channel: Channel) => React.ReactNode;
|
|
175
176
|
renderConversationFooter?: (channel: Channel) => React.ReactNode;
|
|
176
177
|
onLeaveConversation?: (channel: Channel) => void;
|
|
177
178
|
onBlockParticipant?: (participantId?: string) => void;
|
|
@@ -298,6 +299,12 @@ export declare interface ChannelViewProps {
|
|
|
298
299
|
* sendButton={MediaSendButton}
|
|
299
300
|
*/
|
|
300
301
|
sendButton?: ComponentType<any>;
|
|
302
|
+
/**
|
|
303
|
+
* Custom component rendered inside the message composer bubble (above the
|
|
304
|
+
* textarea). Passed to Stream `Channel` as `AttachmentPreviewList`.
|
|
305
|
+
* Use this to render staged paid or media attachments inside the input.
|
|
306
|
+
*/
|
|
307
|
+
attachmentPreviewList?: ComponentType;
|
|
301
308
|
}
|
|
302
309
|
|
|
303
310
|
export declare interface ComposerCardProps extends LockedAttachmentBaseProps {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B } from "./index-
|
|
1
|
+
import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B } from "./index-n6fP9phm.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/package.json
CHANGED
|
@@ -254,6 +254,7 @@ const ChannelViewInner: React.FC<{
|
|
|
254
254
|
onBack?: () => void
|
|
255
255
|
showBackButton: boolean
|
|
256
256
|
renderMessageInputActions?: (channel: ChannelType) => React.ReactNode
|
|
257
|
+
renderMessageInputFooter?: (channel: ChannelType) => React.ReactNode
|
|
257
258
|
renderConversationFooter?: (channel: ChannelType) => React.ReactNode
|
|
258
259
|
onLeaveConversation?: (channel: ChannelType) => void
|
|
259
260
|
onBlockParticipant?: (participantId?: string) => void
|
|
@@ -280,6 +281,7 @@ const ChannelViewInner: React.FC<{
|
|
|
280
281
|
onBack,
|
|
281
282
|
showBackButton,
|
|
282
283
|
renderMessageInputActions,
|
|
284
|
+
renderMessageInputFooter,
|
|
283
285
|
renderConversationFooter,
|
|
284
286
|
onLeaveConversation,
|
|
285
287
|
onBlockParticipant,
|
|
@@ -430,6 +432,7 @@ const ChannelViewInner: React.FC<{
|
|
|
430
432
|
<CustomMessageInput
|
|
431
433
|
key="lt-channel-message-input"
|
|
432
434
|
renderActions={() => renderMessageInputActions?.(channel)}
|
|
435
|
+
renderFooter={() => renderMessageInputFooter?.(channel)}
|
|
433
436
|
/>
|
|
434
437
|
</Window>
|
|
435
438
|
</WithComponents>
|
|
@@ -464,6 +467,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
464
467
|
onBack,
|
|
465
468
|
showBackButton = false,
|
|
466
469
|
renderMessageInputActions,
|
|
470
|
+
renderMessageInputFooter,
|
|
467
471
|
renderConversationFooter,
|
|
468
472
|
onLeaveConversation,
|
|
469
473
|
onBlockParticipant,
|
|
@@ -484,6 +488,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
484
488
|
renderMessage,
|
|
485
489
|
onMessageLinkClick,
|
|
486
490
|
sendButton,
|
|
491
|
+
attachmentPreviewList,
|
|
487
492
|
viewerLanguage,
|
|
488
493
|
getParticipantDisplayName: getParticipantDisplayNameProp,
|
|
489
494
|
}) => {
|
|
@@ -577,11 +582,13 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
577
582
|
TypingIndicator={CustomTypingIndicator}
|
|
578
583
|
doSendMessageRequest={doSendMessageRequest}
|
|
579
584
|
{...(sendButton ? { SendButton: sendButton } : {})}
|
|
585
|
+
{...(attachmentPreviewList ? { AttachmentPreviewList: attachmentPreviewList } : {})}
|
|
580
586
|
>
|
|
581
587
|
<ChannelViewInner
|
|
582
588
|
onBack={onBack}
|
|
583
589
|
showBackButton={showBackButton}
|
|
584
590
|
renderMessageInputActions={renderMessageInputActions}
|
|
591
|
+
renderMessageInputFooter={renderMessageInputFooter}
|
|
585
592
|
renderConversationFooter={renderConversationFooter}
|
|
586
593
|
onLeaveConversation={onLeaveConversation}
|
|
587
594
|
onBlockParticipant={onBlockParticipant}
|
|
@@ -518,70 +518,75 @@ const WithActionsTemplate: StoryFn<TemplateProps> = ({
|
|
|
518
518
|
/>
|
|
519
519
|
)
|
|
520
520
|
|
|
521
|
-
export const
|
|
521
|
+
export const ConversationWithPaidAttachments: StoryFn<TemplateProps> =
|
|
522
522
|
WithActionsTemplate.bind({})
|
|
523
|
-
|
|
523
|
+
ConversationWithPaidAttachments.args = {
|
|
524
524
|
currentUser: storyUsers.creator,
|
|
525
525
|
messages: [
|
|
526
526
|
{
|
|
527
527
|
id: 'msg-1',
|
|
528
|
-
text: '
|
|
528
|
+
text: 'Can I have your workout plan?',
|
|
529
529
|
user: storyUsers.visitor,
|
|
530
530
|
},
|
|
531
531
|
{
|
|
532
532
|
id: 'msg-2',
|
|
533
|
-
text: '
|
|
533
|
+
text: 'Yes, of course!',
|
|
534
534
|
user: storyUsers.creator,
|
|
535
535
|
},
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
story:
|
|
550
|
-
'Current user hovers their own message. Only "Delete" is shown — "Report" is hidden.',
|
|
536
|
+
{
|
|
537
|
+
id: 'msg-3',
|
|
538
|
+
text: 'Let me know if you have any questions!',
|
|
539
|
+
user: storyUsers.creator,
|
|
540
|
+
metadata: {
|
|
541
|
+
custom_type: 'MESSAGE_ATTACHMENT',
|
|
542
|
+
payment_status: 'paid',
|
|
543
|
+
amount_text: 'AU$9.99',
|
|
544
|
+
attachment_title: "Alicia's Workout Plan",
|
|
545
|
+
attachment_mime_type: 'video/mp4',
|
|
546
|
+
attachment_thumbnail: '/video-thumbnail-blurred.jpg',
|
|
547
|
+
attachment_detail: '1:20',
|
|
548
|
+
},
|
|
551
549
|
},
|
|
552
|
-
},
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
export const ReportOthersMessage: StoryFn<TemplateProps> =
|
|
556
|
-
WithActionsTemplate.bind({})
|
|
557
|
-
ReportOthersMessage.args = {
|
|
558
|
-
currentUser: storyUsers.creator,
|
|
559
|
-
messages: [
|
|
560
550
|
{
|
|
561
|
-
id: 'msg-
|
|
562
|
-
text: '
|
|
551
|
+
id: 'msg-4',
|
|
552
|
+
text: 'Looks amazing, unlocking now!',
|
|
563
553
|
user: storyUsers.visitor,
|
|
564
554
|
},
|
|
565
555
|
{
|
|
566
|
-
id: 'msg-
|
|
567
|
-
text:
|
|
568
|
-
user: storyUsers.
|
|
556
|
+
id: 'msg-5',
|
|
557
|
+
text: 'Here is another resource for you.',
|
|
558
|
+
user: storyUsers.visitor,
|
|
559
|
+
metadata: {
|
|
560
|
+
custom_type: 'MESSAGE_ATTACHMENT',
|
|
561
|
+
amount_text: 'AU$4.99',
|
|
562
|
+
attachment_title: 'Nutrition Guide',
|
|
563
|
+
attachment_mime_type: 'application/pdf',
|
|
564
|
+
attachment_thumbnail: '/video-thumbnail-blurred.jpg',
|
|
565
|
+
attachment_detail: '12 pages',
|
|
566
|
+
},
|
|
569
567
|
},
|
|
570
568
|
],
|
|
571
569
|
}
|
|
572
|
-
|
|
570
|
+
ConversationWithPaidAttachments.play = async ({ canvasElement }) => {
|
|
573
571
|
const canvas = within(canvasElement)
|
|
574
|
-
const
|
|
575
|
-
await userEvent.hover(
|
|
572
|
+
const attachmentMsg = canvasElement.querySelector('[data-message-id="msg-3"]')
|
|
573
|
+
await userEvent.hover(attachmentMsg!)
|
|
576
574
|
const toggle = await canvas.findByTestId('message-actions-toggle-button')
|
|
577
575
|
await userEvent.click(toggle)
|
|
578
|
-
await
|
|
576
|
+
const deleteButton = await canvas.findByRole('button', { name: 'Delete' })
|
|
577
|
+
await userEvent.click(deleteButton)
|
|
578
|
+
await expect(
|
|
579
|
+
canvas.getByRole('heading', { name: 'Delete attachment?' })
|
|
580
|
+
).toBeInTheDocument()
|
|
581
|
+
await expect(
|
|
582
|
+
canvas.getByText(/Deleting it will remove access for the buyer/)
|
|
583
|
+
).toBeInTheDocument()
|
|
579
584
|
}
|
|
580
|
-
|
|
585
|
+
ConversationWithPaidAttachments.parameters = {
|
|
581
586
|
docs: {
|
|
582
587
|
description: {
|
|
583
588
|
story:
|
|
584
|
-
|
|
589
|
+
'Report and Delete actions only appear on paid attachment messages — regular messages show no actions. Hovering msg-3 (a purchased attachment) shows the delete confirmation with buyer warning.',
|
|
585
590
|
},
|
|
586
591
|
},
|
|
587
592
|
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
+
import { FlagIcon, TrashSimpleIcon } from '@phosphor-icons/react'
|
|
1
2
|
import React from 'react'
|
|
2
|
-
import { useMessageContext
|
|
3
|
+
import { useMessageContext } from 'stream-chat-react'
|
|
3
4
|
import {
|
|
4
5
|
DefaultDropdownActionButton,
|
|
5
6
|
MessageActions,
|
|
6
|
-
type MessageActionSetItem,
|
|
7
7
|
} from 'stream-chat-react/experimental'
|
|
8
8
|
|
|
9
9
|
const DeleteAction = () => {
|
|
10
|
-
const { handleDelete } = useMessageContext('CustomMessageActions')
|
|
11
|
-
|
|
10
|
+
const { handleDelete, message } = useMessageContext('CustomMessageActions')
|
|
11
|
+
if (message.metadata?.payment_status !== 'paid') return null
|
|
12
12
|
return (
|
|
13
|
-
<DefaultDropdownActionButton
|
|
14
|
-
{
|
|
13
|
+
<DefaultDropdownActionButton
|
|
14
|
+
onClick={handleDelete}
|
|
15
|
+
aria-label="Delete"
|
|
16
|
+
title="Delete"
|
|
17
|
+
className="bg-marble rounded-full p-2 hover:bg-sand transition-all"
|
|
18
|
+
>
|
|
19
|
+
<TrashSimpleIcon size={16} weight="light" aria-hidden />
|
|
15
20
|
</DefaultDropdownActionButton>
|
|
16
21
|
)
|
|
17
22
|
}
|
|
@@ -19,17 +24,26 @@ const DeleteAction = () => {
|
|
|
19
24
|
const ReportAction = () => {
|
|
20
25
|
const { handleFlag } = useMessageContext('CustomMessageActions')
|
|
21
26
|
return (
|
|
22
|
-
<DefaultDropdownActionButton
|
|
23
|
-
|
|
27
|
+
<DefaultDropdownActionButton
|
|
28
|
+
onClick={handleFlag}
|
|
29
|
+
aria-label="Report"
|
|
30
|
+
title="Report"
|
|
31
|
+
className="bg-marble rounded-full p-2 hover:bg-sand transition-all"
|
|
32
|
+
>
|
|
33
|
+
<FlagIcon size={16} weight="light" aria-hidden />
|
|
24
34
|
</DefaultDropdownActionButton>
|
|
25
35
|
)
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
const
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
export const CustomMessageActions = () => {
|
|
39
|
+
const { message } = useMessageContext('CustomMessageActions')
|
|
40
|
+
if (message.metadata?.custom_type !== 'MESSAGE_ATTACHMENT') return null
|
|
41
|
+
return (
|
|
42
|
+
<MessageActions
|
|
43
|
+
messageActionSet={[
|
|
44
|
+
{ Component: DeleteAction, placement: 'quick', type: 'delete' },
|
|
45
|
+
{ Component: ReportAction, placement: 'quick', type: 'flag' },
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -194,7 +194,11 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
194
194
|
open={isBounceDialogOpen}
|
|
195
195
|
/>
|
|
196
196
|
)}
|
|
197
|
-
<div
|
|
197
|
+
<div
|
|
198
|
+
className={rootClassName}
|
|
199
|
+
key={message.id}
|
|
200
|
+
data-message-id={message.id}
|
|
201
|
+
>
|
|
198
202
|
{PinIndicator && <PinIndicator />}
|
|
199
203
|
{!!reminder && <ReminderNotification reminder={reminder} />}
|
|
200
204
|
{message.user && (
|
|
@@ -228,33 +232,39 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
228
232
|
{isAttachment ? (
|
|
229
233
|
<div className="str-chat__message-bubble-wrapper">
|
|
230
234
|
{isMine ? (
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
235
|
+
<div className="flex items-center gap-2">
|
|
236
|
+
{MessageActions && <MessageActions />}
|
|
237
|
+
<LockedAttachment.Sent
|
|
238
|
+
title={message.metadata?.attachment_title}
|
|
239
|
+
mimeType={message.metadata?.attachment_mime_type}
|
|
240
|
+
thumbnailUrl={message.metadata?.attachment_thumbnail}
|
|
241
|
+
amountText={message.metadata?.amount_text}
|
|
242
|
+
detail={message.metadata?.attachment_detail}
|
|
243
|
+
paymentStatus={message.metadata?.payment_status}
|
|
244
|
+
onPreviewClick={() => onUnlockClick?.(message, channel)}
|
|
245
|
+
onFetchSource={async () =>
|
|
246
|
+
await onFetchSource?.(message, channel)
|
|
247
|
+
}
|
|
248
|
+
/>
|
|
249
|
+
</div>
|
|
243
250
|
) : (
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
251
|
+
<div className="flex items-center gap-2">
|
|
252
|
+
<LockedAttachment.Received
|
|
253
|
+
title={message.metadata?.attachment_title}
|
|
254
|
+
mimeType={message.metadata?.attachment_mime_type}
|
|
255
|
+
thumbnailUrl={message.metadata?.attachment_thumbnail}
|
|
256
|
+
amountText={message.metadata?.amount_text}
|
|
257
|
+
detail={message.metadata?.attachment_detail}
|
|
258
|
+
paymentStatus={message.metadata?.payment_status}
|
|
259
|
+
isUnlocking={isUnlocking(message.id)}
|
|
260
|
+
onUnlockClick={() => onUnlockClick?.(message, channel)}
|
|
261
|
+
onFetchSource={async () =>
|
|
262
|
+
await onFetchSource?.(message, channel)
|
|
263
|
+
}
|
|
264
|
+
onDownloadClick={() => onDownloadClick?.(message, channel)}
|
|
265
|
+
/>
|
|
266
|
+
{MessageActions && <MessageActions />}
|
|
267
|
+
</div>
|
|
258
268
|
)}
|
|
259
269
|
{message.text && (
|
|
260
270
|
<div className="str-chat__message-bubble">
|
|
@@ -300,7 +310,6 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
300
310
|
</div>
|
|
301
311
|
</div>
|
|
302
312
|
)}
|
|
303
|
-
{MessageActions && <MessageActions />}
|
|
304
313
|
</div>
|
|
305
314
|
{!isAttachment && !isTipOnly && (
|
|
306
315
|
<div className="str-chat__message-footer">
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { PlusIcon, XIcon } from '@phosphor-icons/react'
|
|
1
2
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
3
|
import React, { useEffect } from 'react'
|
|
3
4
|
import { QueryChannelAPIResponse, StreamChat } from 'stream-chat'
|
|
4
|
-
import { Channel, Chat } from 'stream-chat-react'
|
|
5
|
+
import { Channel, Chat, type SendButtonProps } from 'stream-chat-react'
|
|
6
|
+
|
|
7
|
+
import LockedAttachment from '../LockedAttachment'
|
|
5
8
|
|
|
6
9
|
import { CustomMessageInput } from '.'
|
|
7
10
|
|
|
@@ -25,20 +28,37 @@ const createMockChannel = async (
|
|
|
25
28
|
client: StreamChat,
|
|
26
29
|
opts: { frozen?: boolean } = {}
|
|
27
30
|
) => {
|
|
28
|
-
const channelData = {
|
|
31
|
+
const channelData = {
|
|
32
|
+
members: [mockUser.id, mockParticipant.id],
|
|
33
|
+
frozen: opts.frozen ?? false,
|
|
34
|
+
}
|
|
29
35
|
const ch = client.channel('messaging', 'storybook-cmi-channel', channelData)
|
|
30
36
|
|
|
31
37
|
ch.watch = async () => {
|
|
32
38
|
ch.state.members = {
|
|
33
39
|
[mockUser.id]: { user: mockUser, user_id: mockUser.id },
|
|
34
|
-
[mockParticipant.id]: {
|
|
40
|
+
[mockParticipant.id]: {
|
|
41
|
+
user: mockParticipant,
|
|
42
|
+
user_id: mockParticipant.id,
|
|
43
|
+
},
|
|
35
44
|
}
|
|
36
45
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
46
|
;(ch as any)._data = channelData
|
|
38
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
channel: channelData,
|
|
49
|
+
members: [],
|
|
50
|
+
messages: [],
|
|
51
|
+
watchers: [],
|
|
52
|
+
pinned_messages: [],
|
|
53
|
+
duration: '0ms',
|
|
54
|
+
} as unknown as QueryChannelAPIResponse
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
try {
|
|
57
|
+
try {
|
|
58
|
+
await ch.watch()
|
|
59
|
+
} catch (_) {
|
|
60
|
+
/* mock */
|
|
61
|
+
}
|
|
42
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
63
|
;(ch as any)._data = channelData
|
|
44
64
|
return ch
|
|
@@ -50,15 +70,15 @@ const createMockChannel = async (
|
|
|
50
70
|
|
|
51
71
|
type WrapperProps = {
|
|
52
72
|
frozen?: boolean
|
|
53
|
-
sendButton?: React.ComponentType<{
|
|
54
|
-
sendMessage: (...args: unknown[]) => unknown
|
|
55
|
-
disabled?: boolean
|
|
56
|
-
[key: string]: unknown
|
|
57
|
-
}>
|
|
58
73
|
renderActions?: () => React.ReactNode
|
|
74
|
+
sendButton?: React.ComponentType<SendButtonProps>
|
|
75
|
+
attachmentPreviewList?: React.ComponentType
|
|
76
|
+
renderFooter?: () => React.ReactNode
|
|
59
77
|
}
|
|
60
78
|
|
|
61
|
-
const Wrapper: React.FC<WrapperProps> = (
|
|
79
|
+
const Wrapper: React.FC<WrapperProps> = (props) => {
|
|
80
|
+
const { frozen, renderActions, sendButton, attachmentPreviewList, renderFooter } = props
|
|
81
|
+
|
|
62
82
|
const [client] = React.useState(() => {
|
|
63
83
|
const c = new StreamChat('mock-api-key', { allowServerSideConnect: true })
|
|
64
84
|
c.userID = mockUser.id
|
|
@@ -79,9 +99,10 @@ const Wrapper: React.FC<WrapperProps> = ({ frozen, sendButton, renderActions })
|
|
|
79
99
|
<Channel
|
|
80
100
|
channel={channel}
|
|
81
101
|
{...(sendButton ? { SendButton: sendButton } : {})}
|
|
102
|
+
{...(attachmentPreviewList ? { AttachmentPreviewList: attachmentPreviewList } : {})}
|
|
82
103
|
>
|
|
83
104
|
<div className="bg-white" style={{ minWidth: 360 }}>
|
|
84
|
-
<CustomMessageInput renderActions={renderActions} />
|
|
105
|
+
<CustomMessageInput renderActions={renderActions} renderFooter={renderFooter} />
|
|
85
106
|
</div>
|
|
86
107
|
</Channel>
|
|
87
108
|
</Chat>
|
|
@@ -150,23 +171,22 @@ WithCustomActionButton.parameters = {
|
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
/** A custom send button that turns purple to signal a media attachment is staged. */
|
|
153
|
-
const PurpleMediaSendButton: React.FC<{
|
|
154
|
-
sendMessage
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
)
|
|
174
|
+
const PurpleMediaSendButton: React.FC<SendButtonProps> = (props) => {
|
|
175
|
+
const { sendMessage, disabled, ...rest } = props
|
|
176
|
+
return (
|
|
177
|
+
<button
|
|
178
|
+
{...rest}
|
|
179
|
+
type="button"
|
|
180
|
+
aria-label="Send"
|
|
181
|
+
disabled={disabled}
|
|
182
|
+
onClick={sendMessage}
|
|
183
|
+
className="str-chat__send-button mt-auto flex items-center justify-center shrink-0 rounded-full size-8 bg-[#6D28D9] disabled:bg-[#F1F0EE] disabled:text-black/20 text-white"
|
|
184
|
+
style={{ fontSize: 15 }}
|
|
185
|
+
>
|
|
186
|
+
🚀
|
|
187
|
+
</button>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
170
190
|
|
|
171
191
|
export const WithCustomSendButton: StoryFn<WrapperProps> = (args) => <Wrapper {...args} />
|
|
172
192
|
WithCustomSendButton.args = { sendButton: PurpleMediaSendButton }
|
|
@@ -174,7 +194,83 @@ WithCustomSendButton.parameters = {
|
|
|
174
194
|
docs: {
|
|
175
195
|
description: {
|
|
176
196
|
story:
|
|
177
|
-
|
|
197
|
+
"Passes a custom `SendButton` via `Channel`'s `SendButton` prop (which is how `ChannelView`'s `sendButton` prop surfaces it). `CustomMessageInput` reads the button from `useComponentContext` and falls back to the default when none is provided.",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const WithAttachments: StoryFn<WrapperProps> = (args) => <Wrapper {...args} />
|
|
203
|
+
WithAttachments.args = {
|
|
204
|
+
attachmentPreviewList: () => (
|
|
205
|
+
<div className="relative size-20 shrink-0 overflow-hidden rounded-2xl border border-black/8">
|
|
206
|
+
<img
|
|
207
|
+
src="https://picsum.photos/seed/attachment/160/160"
|
|
208
|
+
alt="Staged attachment"
|
|
209
|
+
className="size-full object-cover"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
),
|
|
213
|
+
renderActions: () => (
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
aria-label="Add attachment"
|
|
217
|
+
onClick={() => console.log('add attachment')}
|
|
218
|
+
className="flex items-center justify-center size-12 rounded-full bg-[#F1F0EE] hover:bg-[#E5E4E1]"
|
|
219
|
+
>
|
|
220
|
+
<PlusIcon className="size-5" weight="bold" />
|
|
221
|
+
</button>
|
|
222
|
+
),
|
|
223
|
+
}
|
|
224
|
+
WithAttachments.parameters = {
|
|
225
|
+
docs: {
|
|
226
|
+
description: {
|
|
227
|
+
story:
|
|
228
|
+
"Message input with a regular image attachment staged. In production thumbnails are portaled into `.central-container` by `ComposerPendingMediaPortal`; here we use `Channel`'s `AttachmentPreviewList` override to achieve the same visual placement via the React tree.",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const WithPaidAttachment: StoryFn<WrapperProps> = (args) => <Wrapper {...args} />
|
|
234
|
+
WithPaidAttachment.args = {
|
|
235
|
+
attachmentPreviewList: () => (
|
|
236
|
+
<LockedAttachment.Composer
|
|
237
|
+
title="Exclusive Content"
|
|
238
|
+
mimeType="image/jpeg"
|
|
239
|
+
thumbnailUrl="https://picsum.photos/seed/paid/320/180"
|
|
240
|
+
detail="3.2 MB"
|
|
241
|
+
amountText="AU$9.99"
|
|
242
|
+
onDismiss={() => console.log('dismiss paid attachment')}
|
|
243
|
+
onEditClick={() => console.log('edit paid attachment')}
|
|
244
|
+
/>
|
|
245
|
+
),
|
|
246
|
+
renderActions: () => (
|
|
247
|
+
<button
|
|
248
|
+
type="button"
|
|
249
|
+
aria-label="Add attachment"
|
|
250
|
+
onClick={() => console.log('add attachment')}
|
|
251
|
+
className="flex items-center justify-center size-12 rounded-full bg-[#F1F0EE] hover:bg-[#E5E4E1]"
|
|
252
|
+
>
|
|
253
|
+
<PlusIcon className="size-5" weight="bold" />
|
|
254
|
+
</button>
|
|
255
|
+
),
|
|
256
|
+
renderFooter: () => (
|
|
257
|
+
<div className="flex items-center justify-between gap-2 rounded-lg bg-[#E8EEFF] px-4 py-3 text-sm text-black/70">
|
|
258
|
+
<span>Tap to add a price</span>
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
aria-label="Dismiss"
|
|
262
|
+
className="flex shrink-0 items-center justify-center text-black/40 hover:text-black/70 bg-black/10 rounded-full p-1"
|
|
263
|
+
>
|
|
264
|
+
<XIcon className="size-4" />
|
|
265
|
+
</button>
|
|
266
|
+
</div>
|
|
267
|
+
),
|
|
268
|
+
}
|
|
269
|
+
WithPaidAttachment.parameters = {
|
|
270
|
+
docs: {
|
|
271
|
+
description: {
|
|
272
|
+
story:
|
|
273
|
+
"Message input with a paid attachment staged inside the input bubble. Uses `Channel`'s `AttachmentPreviewList` override to render a `LockedAttachment.Composer` card at the top of the input. `onDismiss` removes it; `onEditClick` reopens the paid content wizard.",
|
|
178
274
|
},
|
|
179
275
|
},
|
|
180
276
|
}
|
|
@@ -28,9 +28,6 @@ vi.mock('stream-chat-react', () => ({
|
|
|
28
28
|
<Input />
|
|
29
29
|
</div>
|
|
30
30
|
),
|
|
31
|
-
SimpleAttachmentSelector: () => (
|
|
32
|
-
<div data-testid="simple-attachment-selector" />
|
|
33
|
-
),
|
|
34
31
|
TextareaComposer: ({
|
|
35
32
|
maxRows: _maxRows,
|
|
36
33
|
...props
|
|
@@ -84,7 +81,7 @@ describe('CustomMessageInput', () => {
|
|
|
84
81
|
|
|
85
82
|
const { container } = renderWithProviders(<CustomMessageInput />)
|
|
86
83
|
|
|
87
|
-
const messageInput = container.
|
|
84
|
+
const messageInput = container.querySelector('.message-input')
|
|
88
85
|
expect(messageInput).toHaveAttribute('aria-disabled', 'true')
|
|
89
86
|
expect(messageInput).toHaveAttribute('inert')
|
|
90
87
|
expect(screen.getByTestId('stream-message-input')).toBeInTheDocument()
|
|
@@ -105,14 +102,6 @@ describe('CustomMessageInput', () => {
|
|
|
105
102
|
expect(sendButton).toBeDisabled()
|
|
106
103
|
})
|
|
107
104
|
|
|
108
|
-
it('renders the existing attachment selector when channel is frozen', () => {
|
|
109
|
-
mockChannelData = { frozen: true }
|
|
110
|
-
|
|
111
|
-
renderWithProviders(<CustomMessageInput />)
|
|
112
|
-
|
|
113
|
-
expect(screen.getByTestId('simple-attachment-selector')).toBeInTheDocument()
|
|
114
|
-
})
|
|
115
|
-
|
|
116
105
|
it('renders adjacent actions inside the disabled container when frozen', () => {
|
|
117
106
|
mockChannelData = { frozen: true }
|
|
118
107
|
|
|
@@ -126,7 +115,7 @@ describe('CustomMessageInput', () => {
|
|
|
126
115
|
/>
|
|
127
116
|
)
|
|
128
117
|
|
|
129
|
-
const messageInput = container.
|
|
118
|
+
const messageInput = container.querySelector('.message-input')
|
|
130
119
|
const action = screen.getByTestId('custom-action')
|
|
131
120
|
|
|
132
121
|
expect(messageInput).toHaveAttribute('aria-disabled', 'true')
|