@linktr.ee/messaging-react 2.3.0 → 2.3.3-rc-1779777764
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-DdpdnSh_.js → Card-Bz1cMPwJ.js} +16 -16
- package/dist/{Card-DdpdnSh_.js.map → Card-Bz1cMPwJ.js.map} +1 -1
- package/dist/{Card-ot16XqS2.cjs → Card-CBWof9at.cjs} +2 -2
- package/dist/{Card-ot16XqS2.cjs.map → Card-CBWof9at.cjs.map} +1 -1
- package/dist/{Card-CexShqpK.cjs → Card-D2KIDqPs.cjs} +2 -2
- package/dist/{Card-CexShqpK.cjs.map → Card-D2KIDqPs.cjs.map} +1 -1
- package/dist/{Card-4takoN_-.js → Card-D9iWkHKh.js} +6 -6
- package/dist/{Card-4takoN_-.js.map → Card-D9iWkHKh.js.map} +1 -1
- package/dist/{Card-BuROm0u7.js → Card-DFG3rvzF.js} +19 -19
- package/dist/{Card-BuROm0u7.js.map → Card-DFG3rvzF.js.map} +1 -1
- package/dist/{Card-CgpHBx-W.cjs → Card-DgXoTo6c.cjs} +2 -2
- package/dist/{Card-CgpHBx-W.cjs.map → Card-DgXoTo6c.cjs.map} +1 -1
- package/dist/{LockedThumbnail-Drsh4B5o.js → LockedThumbnail-BkeCwTCw.js} +8 -8
- package/dist/{LockedThumbnail-Drsh4B5o.js.map → LockedThumbnail-BkeCwTCw.js.map} +1 -1
- package/dist/{LockedThumbnail-CydtYOSA.cjs → LockedThumbnail-CD9YTQ0r.cjs} +2 -2
- package/dist/{LockedThumbnail-CydtYOSA.cjs.map → LockedThumbnail-CD9YTQ0r.cjs.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/index-DuGzAVyy.cjs +18 -0
- package/dist/index-DuGzAVyy.cjs.map +1 -0
- package/dist/index-v6yofqub.js +8128 -0
- package/dist/index-v6yofqub.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +8 -2
- package/src/components/CustomMessage/CustomMessage.stories.tsx +140 -0
- package/src/components/CustomMessage/CustomMessageActions.tsx +35 -0
- package/src/components/CustomMessage/index.tsx +20 -15
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +12 -11
- package/src/components/CustomMessageInput/index.tsx +7 -3
- package/src/styles.css +129 -19
- package/dist/index-BCbVXFHI.js +0 -4698
- package/dist/index-BCbVXFHI.js.map +0 -1
- package/dist/index-CQ913euH.cjs +0 -2
- package/dist/index-CQ913euH.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-DuGzAVyy.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.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-v6yofqub.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/package.json
CHANGED
|
@@ -26,6 +26,7 @@ import { Avatar } from './Avatar'
|
|
|
26
26
|
import { ChannelInfoDialog } from './ChannelInfoDialog'
|
|
27
27
|
import { CustomDateSeparator } from './CustomDateSeparator'
|
|
28
28
|
import { CustomMessage } from './CustomMessage'
|
|
29
|
+
import { CustomMessageActions } from './CustomMessage/CustomMessageActions'
|
|
29
30
|
import { CustomMessageInput } from './CustomMessageInput'
|
|
30
31
|
import { CustomSystemMessage } from './CustomSystemMessage'
|
|
31
32
|
import CustomTypingIndicator from './CustomTypingIndicator'
|
|
@@ -35,6 +36,7 @@ import { LoadingState } from './MessagingShell/LoadingState'
|
|
|
35
36
|
|
|
36
37
|
const ICON_BTN_CLASS =
|
|
37
38
|
'size-10 rounded-full bg-[#F1F0EE] hover:bg-[#E5E4E1] flex items-center justify-center transition-colors duration-150 focus-ring'
|
|
39
|
+
|
|
38
40
|
const DM_AGENT_HEADER_HELPER_TEXT = 'Replies instantly with AI assistant'
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -374,7 +376,12 @@ const ChannelViewInner: React.FC<{
|
|
|
374
376
|
|
|
375
377
|
return (
|
|
376
378
|
<>
|
|
377
|
-
<WithComponents
|
|
379
|
+
<WithComponents
|
|
380
|
+
overrides={{
|
|
381
|
+
Message: MessageOverride,
|
|
382
|
+
MessageActions: CustomMessageActions,
|
|
383
|
+
}}
|
|
384
|
+
>
|
|
378
385
|
<Window>
|
|
379
386
|
{/* Custom Channel Header */}
|
|
380
387
|
<div key="lt-channel-header" className="p-4">
|
|
@@ -404,7 +411,6 @@ const ChannelViewInner: React.FC<{
|
|
|
404
411
|
<MessageList
|
|
405
412
|
hideDeletedMessages
|
|
406
413
|
hideNewMessageSeparator={false}
|
|
407
|
-
messageActions={undefined}
|
|
408
414
|
/>
|
|
409
415
|
</div>
|
|
410
416
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../../stream-custom-data'
|
|
2
2
|
|
|
3
3
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
4
|
+
import { expect, userEvent, within } from '@storybook/test'
|
|
4
5
|
import React, { useEffect } from 'react'
|
|
5
6
|
import {
|
|
6
7
|
Channel as ChannelType,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
MessageList,
|
|
14
15
|
MessageUIComponentProps,
|
|
15
16
|
Window,
|
|
17
|
+
WithComponents,
|
|
16
18
|
} from 'stream-chat-react'
|
|
17
19
|
|
|
18
20
|
import {
|
|
@@ -22,6 +24,8 @@ import {
|
|
|
22
24
|
} from '../../stories/decorators/storyUser'
|
|
23
25
|
import CustomTypingIndicator from '../CustomTypingIndicator'
|
|
24
26
|
|
|
27
|
+
import { CustomMessageActions } from './CustomMessageActions'
|
|
28
|
+
|
|
25
29
|
import { CustomMessage } from './index'
|
|
26
30
|
|
|
27
31
|
const meta: Meta = {
|
|
@@ -58,6 +62,8 @@ const createMockChannel = async (
|
|
|
58
62
|
|
|
59
63
|
const channelData = {
|
|
60
64
|
members: [storyUsers.creator.id, storyUsers.visitor.id],
|
|
65
|
+
// Required for useUserRole: canDelete needs delete-own-message, canFlag needs flag-message
|
|
66
|
+
own_capabilities: ['delete-own-message', 'flag-message'],
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
const channel = client.channel(
|
|
@@ -352,6 +358,8 @@ LockedAttachment.args = {
|
|
|
352
358
|
],
|
|
353
359
|
}
|
|
354
360
|
|
|
361
|
+
|
|
362
|
+
|
|
355
363
|
export const ChatbotVariants: StoryFn<TemplateProps> = Template.bind({})
|
|
356
364
|
ChatbotVariants.args = {
|
|
357
365
|
messages: [
|
|
@@ -445,3 +453,135 @@ WithTypingIndicatorComparison.parameters = {
|
|
|
445
453
|
},
|
|
446
454
|
},
|
|
447
455
|
}
|
|
456
|
+
|
|
457
|
+
// ─── Message Actions ──────────────────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Same as TemplateInner but wraps with WithComponents so CustomMessageActions
|
|
461
|
+
* is wired in — matching what ChannelView does in production.
|
|
462
|
+
*/
|
|
463
|
+
const WithActionsTemplateInner: React.FC<{
|
|
464
|
+
currentUser: StoryUser
|
|
465
|
+
messages: TemplateProps['messages']
|
|
466
|
+
}> = ({ currentUser, messages }) => {
|
|
467
|
+
const [client] = React.useState(() => {
|
|
468
|
+
const c = new StreamChat('mock-api-key', { allowServerSideConnect: true })
|
|
469
|
+
c.userID = currentUser.id
|
|
470
|
+
c.user = currentUser
|
|
471
|
+
return c
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
475
|
+
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
createMockChannel(client, messages).then(setChannel)
|
|
478
|
+
}, [client, messages])
|
|
479
|
+
|
|
480
|
+
const MessageComponent = React.useMemo(() => {
|
|
481
|
+
return function CustomMessageComponent(props: MessageUIComponentProps) {
|
|
482
|
+
return <CustomMessage {...props} />
|
|
483
|
+
}
|
|
484
|
+
}, [])
|
|
485
|
+
|
|
486
|
+
if (!channel) {
|
|
487
|
+
return <div className="p-4">Loading...</div>
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<Chat client={client}>
|
|
492
|
+
<div className="h-screen w-full bg-white">
|
|
493
|
+
<Channel channel={channel} TypingIndicator={CustomTypingIndicator}>
|
|
494
|
+
<WithComponents
|
|
495
|
+
overrides={{
|
|
496
|
+
Message: MessageComponent,
|
|
497
|
+
MessageActions: CustomMessageActions,
|
|
498
|
+
}}
|
|
499
|
+
>
|
|
500
|
+
<Window>
|
|
501
|
+
<MessageList />
|
|
502
|
+
</Window>
|
|
503
|
+
</WithComponents>
|
|
504
|
+
</Channel>
|
|
505
|
+
</div>
|
|
506
|
+
</Chat>
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const WithActionsTemplate: StoryFn<TemplateProps> = ({
|
|
511
|
+
currentUser = storyUsers.creator,
|
|
512
|
+
messages,
|
|
513
|
+
}) => (
|
|
514
|
+
<WithActionsTemplateInner
|
|
515
|
+
key={currentUser.id}
|
|
516
|
+
currentUser={currentUser}
|
|
517
|
+
messages={messages}
|
|
518
|
+
/>
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
export const DeleteOwnMessage: StoryFn<TemplateProps> =
|
|
522
|
+
WithActionsTemplate.bind({})
|
|
523
|
+
DeleteOwnMessage.args = {
|
|
524
|
+
currentUser: storyUsers.creator,
|
|
525
|
+
messages: [
|
|
526
|
+
{
|
|
527
|
+
id: 'msg-1',
|
|
528
|
+
text: 'Hey, how are you?',
|
|
529
|
+
user: storyUsers.visitor,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
id: 'msg-2',
|
|
533
|
+
text: 'Doing great — hover me to delete!',
|
|
534
|
+
user: storyUsers.creator,
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
}
|
|
538
|
+
DeleteOwnMessage.play = async ({ canvasElement }) => {
|
|
539
|
+
const canvas = within(canvasElement)
|
|
540
|
+
const ownBubble = await canvas.findByText('Doing great — hover me to delete!')
|
|
541
|
+
await userEvent.hover(ownBubble)
|
|
542
|
+
const toggle = await canvas.findByTestId('message-actions-toggle-button')
|
|
543
|
+
await userEvent.click(toggle)
|
|
544
|
+
await expect(canvas.getByText('Delete')).toBeInTheDocument()
|
|
545
|
+
}
|
|
546
|
+
DeleteOwnMessage.parameters = {
|
|
547
|
+
docs: {
|
|
548
|
+
description: {
|
|
549
|
+
story:
|
|
550
|
+
'Current user hovers their own message. Only "Delete" is shown — "Report" is hidden.',
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export const ReportOthersMessage: StoryFn<TemplateProps> =
|
|
556
|
+
WithActionsTemplate.bind({})
|
|
557
|
+
ReportOthersMessage.args = {
|
|
558
|
+
currentUser: storyUsers.creator,
|
|
559
|
+
messages: [
|
|
560
|
+
{
|
|
561
|
+
id: 'msg-1',
|
|
562
|
+
text: 'Hover me to report!',
|
|
563
|
+
user: storyUsers.visitor,
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: 'msg-2',
|
|
567
|
+
text: "That's a fine message.",
|
|
568
|
+
user: storyUsers.creator,
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
}
|
|
572
|
+
ReportOthersMessage.play = async ({ canvasElement }) => {
|
|
573
|
+
const canvas = within(canvasElement)
|
|
574
|
+
const otherBubble = await canvas.findByText('Hover me to report!')
|
|
575
|
+
await userEvent.hover(otherBubble)
|
|
576
|
+
const toggle = await canvas.findByTestId('message-actions-toggle-button')
|
|
577
|
+
await userEvent.click(toggle)
|
|
578
|
+
await expect(canvas.getByText('Report')).toBeInTheDocument()
|
|
579
|
+
}
|
|
580
|
+
ReportOthersMessage.parameters = {
|
|
581
|
+
docs: {
|
|
582
|
+
description: {
|
|
583
|
+
story:
|
|
584
|
+
"Current user hovers the other participant's message. Only \"Report\" is shown — \"Delete\" is hidden.",
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useMessageContext, useTranslationContext } from 'stream-chat-react'
|
|
3
|
+
import {
|
|
4
|
+
DefaultDropdownActionButton,
|
|
5
|
+
MessageActions,
|
|
6
|
+
type MessageActionSetItem,
|
|
7
|
+
} from 'stream-chat-react/experimental'
|
|
8
|
+
|
|
9
|
+
const DeleteAction = () => {
|
|
10
|
+
const { handleDelete } = useMessageContext('CustomMessageActions')
|
|
11
|
+
const { t } = useTranslationContext('CustomMessageActions')
|
|
12
|
+
return (
|
|
13
|
+
<DefaultDropdownActionButton onClick={handleDelete}>
|
|
14
|
+
{t('Delete')}
|
|
15
|
+
</DefaultDropdownActionButton>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ReportAction = () => {
|
|
20
|
+
const { handleFlag } = useMessageContext('CustomMessageActions')
|
|
21
|
+
return (
|
|
22
|
+
<DefaultDropdownActionButton onClick={handleFlag}>
|
|
23
|
+
Report
|
|
24
|
+
</DefaultDropdownActionButton>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MESSAGE_ACTION_SET: MessageActionSetItem[] = [
|
|
29
|
+
{ Component: DeleteAction, placement: 'dropdown', type: 'delete' },
|
|
30
|
+
{ Component: ReportAction, placement: 'dropdown', type: 'flag' },
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
export const CustomMessageActions = () => (
|
|
34
|
+
<MessageActions messageActionSet={MESSAGE_ACTION_SET} />
|
|
35
|
+
)
|
|
@@ -85,6 +85,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
85
85
|
const {
|
|
86
86
|
Attachment = DefaultAttachment,
|
|
87
87
|
EditMessageModal = DefaultEditMessageModal,
|
|
88
|
+
MessageActions,
|
|
88
89
|
MessageBlocked = DefaultMessageBlocked,
|
|
89
90
|
MessageBouncePrompt = DefaultMessageBouncePrompt,
|
|
90
91
|
MessageDeleted = DefaultMessageDeleted,
|
|
@@ -297,24 +298,28 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
297
298
|
)}
|
|
298
299
|
<MessageErrorIcon />
|
|
299
300
|
</div>
|
|
300
|
-
{/* Tip/paid tags stay outside; chatbot attachment indicator stays outside too */}
|
|
301
|
-
{(!isChatbot || useAttachmentFooterChatbotTag) && (
|
|
302
|
-
<MessageTag
|
|
303
|
-
message={message}
|
|
304
|
-
hasAttachment={hasRenderableAttachments}
|
|
305
|
-
isMyMessage={isMine}
|
|
306
|
-
/>
|
|
307
|
-
)}
|
|
308
|
-
{chatbotVotingEnabled && isChatbot && (
|
|
309
|
-
<MessageVoteButtons
|
|
310
|
-
selected={voteState}
|
|
311
|
-
onVoteUp={voteUp}
|
|
312
|
-
onVoteDown={voteDown}
|
|
313
|
-
/>
|
|
314
|
-
)}
|
|
315
301
|
</div>
|
|
316
302
|
)}
|
|
303
|
+
{MessageActions && <MessageActions />}
|
|
317
304
|
</div>
|
|
305
|
+
{!isAttachment && !isTipOnly && (
|
|
306
|
+
<div className="str-chat__message-footer">
|
|
307
|
+
{(!isChatbot || useAttachmentFooterChatbotTag) && (
|
|
308
|
+
<MessageTag
|
|
309
|
+
message={message}
|
|
310
|
+
hasAttachment={hasRenderableAttachments}
|
|
311
|
+
isMyMessage={isMine}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
314
|
+
{chatbotVotingEnabled && isChatbot && (
|
|
315
|
+
<MessageVoteButtons
|
|
316
|
+
selected={voteState}
|
|
317
|
+
onVoteUp={voteUp}
|
|
318
|
+
onVoteDown={voteDown}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
318
323
|
{showReplyCountButton && (
|
|
319
324
|
<MessageRepliesCountButton
|
|
320
325
|
onClick={handleOpenThread}
|
|
@@ -23,11 +23,7 @@ let mockContextSendButton:
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
vi.mock('stream-chat-react', () => ({
|
|
26
|
-
MessageInput: ({
|
|
27
|
-
Input,
|
|
28
|
-
}: {
|
|
29
|
-
Input: React.ComponentType
|
|
30
|
-
}) => (
|
|
26
|
+
MessageInput: ({ Input }: { Input: React.ComponentType }) => (
|
|
31
27
|
<div data-testid="stream-message-input">
|
|
32
28
|
<Input />
|
|
33
29
|
</div>
|
|
@@ -38,9 +34,9 @@ vi.mock('stream-chat-react', () => ({
|
|
|
38
34
|
TextareaComposer: ({
|
|
39
35
|
maxRows: _maxRows,
|
|
40
36
|
...props
|
|
41
|
-
}: React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
42
|
-
|
|
43
|
-
)
|
|
37
|
+
}: React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
38
|
+
maxRows?: number
|
|
39
|
+
}) => <textarea data-testid="textarea-composer" {...props} />,
|
|
44
40
|
AttachmentPreviewList: () => <div data-testid="attachment-preview-list" />,
|
|
45
41
|
QuotedMessagePreview: () => <div data-testid="quoted-message-preview" />,
|
|
46
42
|
useChannelStateContext: () => ({
|
|
@@ -81,7 +77,6 @@ describe('CustomMessageInput', () => {
|
|
|
81
77
|
expect(messageInput).not.toHaveAttribute('aria-disabled')
|
|
82
78
|
expect(messageInput).not.toHaveAttribute('inert')
|
|
83
79
|
expect(screen.getByTestId('stream-message-input')).toBeInTheDocument()
|
|
84
|
-
expect(screen.getByTestId('textarea-composer')).not.toHaveAttribute('autofocus')
|
|
85
80
|
})
|
|
86
81
|
|
|
87
82
|
it('renders the frozen message input when channel is frozen', () => {
|
|
@@ -159,7 +154,11 @@ describe('CustomMessageInput', () => {
|
|
|
159
154
|
it('renders the custom SendButton from component context', () => {
|
|
160
155
|
mockChannelData = {}
|
|
161
156
|
mockContextSendButton = () => (
|
|
162
|
-
<button
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
data-testid="custom-context-send-button"
|
|
160
|
+
aria-label="Custom Send"
|
|
161
|
+
/>
|
|
163
162
|
)
|
|
164
163
|
|
|
165
164
|
renderWithProviders(<CustomMessageInput />)
|
|
@@ -175,7 +174,9 @@ describe('CustomMessageInput', () => {
|
|
|
175
174
|
renderWithProviders(<CustomMessageInput />)
|
|
176
175
|
|
|
177
176
|
expect(screen.getByTestId('send-button')).toBeInTheDocument()
|
|
178
|
-
expect(
|
|
177
|
+
expect(
|
|
178
|
+
screen.queryByTestId('custom-context-send-button')
|
|
179
|
+
).not.toBeInTheDocument()
|
|
179
180
|
})
|
|
180
181
|
|
|
181
182
|
it('preserves disabled state on the send button when there is no sendable data', () => {
|
|
@@ -34,9 +34,8 @@ const CustomMessageInputInner: React.FC = () => {
|
|
|
34
34
|
const { channel } = useChannelStateContext()
|
|
35
35
|
const isFrozen = channel?.data?.frozen === true
|
|
36
36
|
const { handleSubmit } = useMessageInputContext()
|
|
37
|
-
const { SendButton: SendButtonFromContext } =
|
|
38
|
-
'CustomMessageInput'
|
|
39
|
-
)
|
|
37
|
+
const { SendButton: SendButtonFromContext } =
|
|
38
|
+
useComponentContext('CustomMessageInput')
|
|
40
39
|
const SendButton = SendButtonFromContext ?? DefaultSendButton
|
|
41
40
|
const hasSendableData = useMessageComposerHasSendableData()
|
|
42
41
|
const isSendDisabled = isFrozen || !hasSendableData
|
|
@@ -55,6 +54,11 @@ const CustomMessageInputInner: React.FC = () => {
|
|
|
55
54
|
<TextareaComposer
|
|
56
55
|
aria-disabled={isFrozen || undefined}
|
|
57
56
|
className="w-full resize-none outline-none leading-6"
|
|
57
|
+
// While this might usually be considered an anti-pattern, in most
|
|
58
|
+
// cases, when a message thread is rendered, we want the input to
|
|
59
|
+
// gain focus automatically.
|
|
60
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
61
|
+
autoFocus={!isFrozen}
|
|
58
62
|
maxRows={4}
|
|
59
63
|
readOnly={isFrozen}
|
|
60
64
|
tabIndex={isFrozen ? -1 : undefined}
|
package/src/styles.css
CHANGED
|
@@ -317,32 +317,82 @@
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
.str-chat__li .str-chat__message-inner {
|
|
320
|
-
grid-column-gap:
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
.str-chat__li .str-chat__message--me .str-chat__message-bubble-wrapper {
|
|
324
|
-
display: flex;
|
|
325
|
-
flex-direction: column;
|
|
326
|
-
align-items: flex-end;
|
|
327
|
-
gap: 2px;
|
|
320
|
+
grid-column-gap: 4px;
|
|
328
321
|
}
|
|
329
322
|
|
|
323
|
+
/* Outer grid for other (incoming) messages — add footer row after message */
|
|
330
324
|
.str-chat__li .str-chat__message--other {
|
|
331
|
-
|
|
325
|
+
grid-template-areas:
|
|
326
|
+
'. message-reminder'
|
|
327
|
+
'avatar message'
|
|
328
|
+
'. footer'
|
|
329
|
+
'. replies'
|
|
330
|
+
'. translation-notice'
|
|
331
|
+
'. custom-metadata'
|
|
332
|
+
'. metadata';
|
|
332
333
|
grid-template-columns: 28px 1fr;
|
|
334
|
+
justify-items: flex-start;
|
|
333
335
|
}
|
|
334
336
|
|
|
335
|
-
|
|
337
|
+
/* Outer grid for me (outgoing) messages — add footer row after message */
|
|
338
|
+
.str-chat__li .str-chat__message--me {
|
|
339
|
+
grid-template-areas:
|
|
340
|
+
'message-reminder'
|
|
341
|
+
'message'
|
|
342
|
+
'footer'
|
|
343
|
+
'replies'
|
|
344
|
+
'translation-notice'
|
|
345
|
+
'custom-metadata'
|
|
346
|
+
'metadata';
|
|
347
|
+
justify-items: end;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Avatar: proper grid item aligned to bottom of its row */
|
|
351
|
+
.str-chat__li .str-chat__message--other .str-chat__message-sender-avatar {
|
|
352
|
+
grid-area: avatar;
|
|
353
|
+
align-self: end;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* message-inner sits in the message grid area */
|
|
357
|
+
.str-chat__li .str-chat__message-inner {
|
|
358
|
+
grid-area: message;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Inner grid: bubble beside options only (no footer row) */
|
|
362
|
+
.str-chat__li .str-chat__message--other .str-chat__message-inner {
|
|
363
|
+
grid-template-areas: 'message-bubble options';
|
|
364
|
+
grid-template-columns: auto auto;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.str-chat__li .str-chat__message--me .str-chat__message-inner {
|
|
368
|
+
grid-template-areas: 'options message-bubble';
|
|
369
|
+
grid-template-columns: auto auto;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Bubble wrapper occupies the message-bubble grid area */
|
|
373
|
+
.str-chat__li .str-chat__message-inner > .str-chat__message-bubble-wrapper {
|
|
374
|
+
grid-area: message-bubble;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Actions button centers vertically with the bubble */
|
|
378
|
+
.str-chat__li .str-chat__message-inner .str-chat__message-options {
|
|
379
|
+
align-self: center;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* Footer: tag then vote buttons stacked, in outer grid footer area */
|
|
383
|
+
.str-chat__li .str-chat__message-footer {
|
|
384
|
+
grid-area: footer;
|
|
336
385
|
display: flex;
|
|
337
386
|
flex-direction: column;
|
|
338
|
-
align-items: flex-start;
|
|
339
387
|
gap: 2px;
|
|
340
388
|
}
|
|
341
389
|
|
|
342
|
-
.str-chat__li .str-chat__message--other .str-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
390
|
+
.str-chat__li .str-chat__message--other .str-chat__message-footer {
|
|
391
|
+
align-items: flex-start;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.str-chat__li .str-chat__message--me .str-chat__message-footer {
|
|
395
|
+
align-items: flex-end;
|
|
346
396
|
}
|
|
347
397
|
|
|
348
398
|
/* Channel list load-more button overrides */
|
|
@@ -415,9 +465,7 @@
|
|
|
415
465
|
}
|
|
416
466
|
|
|
417
467
|
.message-chatbot-indicator--attachment {
|
|
418
|
-
width: 100%;
|
|
419
468
|
gap: 8px;
|
|
420
|
-
margin-top: 4px;
|
|
421
469
|
color: rgba(0, 0, 0, 0.3);
|
|
422
470
|
}
|
|
423
471
|
|
|
@@ -436,7 +484,6 @@
|
|
|
436
484
|
display: inline-flex;
|
|
437
485
|
align-items: center;
|
|
438
486
|
gap: 2px;
|
|
439
|
-
margin-top: 4px;
|
|
440
487
|
}
|
|
441
488
|
|
|
442
489
|
.message-vote-button {
|
|
@@ -535,8 +582,52 @@
|
|
|
535
582
|
word-break: break-word;
|
|
536
583
|
}
|
|
537
584
|
|
|
538
|
-
.str-chat__message
|
|
585
|
+
.str-chat__message
|
|
586
|
+
.str-chat__message-inner
|
|
587
|
+
.str-chat__message-bubble
|
|
588
|
+
.str-chat__message-text
|
|
589
|
+
.str-chat__message-text-inner
|
|
590
|
+
p,
|
|
591
|
+
.str-chat__quoted-message-preview
|
|
592
|
+
.str-chat__message-inner
|
|
593
|
+
.str-chat__message-bubble
|
|
594
|
+
.str-chat__message-text
|
|
595
|
+
.str-chat__message-text-inner
|
|
596
|
+
p {
|
|
539
597
|
margin: 0.25rem 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.str-chat__message
|
|
601
|
+
.str-chat__message-inner
|
|
602
|
+
.str-chat__message-bubble
|
|
603
|
+
.str-chat__message-text
|
|
604
|
+
.str-chat__message-text-inner
|
|
605
|
+
> p:first-child,
|
|
606
|
+
.str-chat__quoted-message-preview
|
|
607
|
+
.str-chat__message-inner
|
|
608
|
+
.str-chat__message-bubble
|
|
609
|
+
.str-chat__message-text
|
|
610
|
+
.str-chat__message-text-inner
|
|
611
|
+
> p:first-child {
|
|
612
|
+
margin-top: 0;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.str-chat__message
|
|
616
|
+
.str-chat__message-inner
|
|
617
|
+
.str-chat__message-bubble
|
|
618
|
+
.str-chat__message-text
|
|
619
|
+
.str-chat__message-text-inner
|
|
620
|
+
> p:last-child,
|
|
621
|
+
.str-chat__quoted-message-preview
|
|
622
|
+
.str-chat__message-inner
|
|
623
|
+
.str-chat__message-bubble
|
|
624
|
+
.str-chat__message-text
|
|
625
|
+
.str-chat__message-text-inner
|
|
626
|
+
> p:last-child {
|
|
627
|
+
margin-bottom: 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.str-chat__message-text-inner p {
|
|
540
631
|
text-wrap: pretty;
|
|
541
632
|
}
|
|
542
633
|
.str-chat__message-text-inner > :first-child {
|
|
@@ -546,6 +637,25 @@
|
|
|
546
637
|
margin-bottom: 0;
|
|
547
638
|
}
|
|
548
639
|
|
|
640
|
+
/* Locked attachment wrapper: stack card + text bubble in the correct direction */
|
|
641
|
+
.str-chat__li .str-chat__message--me .str-chat__message-bubble-wrapper {
|
|
642
|
+
display: flex;
|
|
643
|
+
flex-direction: column;
|
|
644
|
+
align-items: flex-end;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.str-chat__li .str-chat__message--other .str-chat__message-bubble-wrapper {
|
|
648
|
+
display: flex;
|
|
649
|
+
flex-direction: column;
|
|
650
|
+
align-items: flex-start;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* Gap between locked attachment card and accompanying text bubble */
|
|
654
|
+
.str-chat__message-bubble-wrapper
|
|
655
|
+
> .str-chat__message-bubble:not(:first-child) {
|
|
656
|
+
margin-top: 4px;
|
|
657
|
+
}
|
|
658
|
+
|
|
549
659
|
/* Standalone tip message (tip without text) */
|
|
550
660
|
.message-tip-standalone {
|
|
551
661
|
display: inline-flex;
|