@linktr.ee/messaging-react 3.3.4 → 3.3.6-rc-1780987607
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-DLmUSU4A.cjs → Card-BlviN8Fb.cjs} +2 -2
- package/dist/{Card-DLmUSU4A.cjs.map → Card-BlviN8Fb.cjs.map} +1 -1
- package/dist/{Card-DmPpcrSU.js → Card-C4ncqjxJ.js} +2 -2
- package/dist/{Card-DmPpcrSU.js.map → Card-C4ncqjxJ.js.map} +1 -1
- package/dist/{Card-0BgubwgM.cjs → Card-Cn7Zxc6U.cjs} +2 -2
- package/dist/{Card-0BgubwgM.cjs.map → Card-Cn7Zxc6U.cjs.map} +1 -1
- package/dist/{Card-DchJqvYq.js → Card-DE5bfj0l.js} +2 -2
- package/dist/{Card-DchJqvYq.js.map → Card-DE5bfj0l.js.map} +1 -1
- package/dist/{Card-B7AF5uOB.js → Card-IjOI7UXs.js} +3 -3
- package/dist/{Card-B7AF5uOB.js.map → Card-IjOI7UXs.js.map} +1 -1
- package/dist/{Card-CvBbAoUo.cjs → Card-KgQxeR-B.cjs} +2 -2
- package/dist/{Card-CvBbAoUo.cjs.map → Card-KgQxeR-B.cjs.map} +1 -1
- package/dist/{LockedThumbnail-BQjA4HaB.js → LockedThumbnail-4-54cyJG.js} +2 -2
- package/dist/{LockedThumbnail-BQjA4HaB.js.map → LockedThumbnail-4-54cyJG.js.map} +1 -1
- package/dist/{LockedThumbnail-D9fSb4N-.cjs → LockedThumbnail-DL5NZzWJ.cjs} +2 -2
- package/dist/{LockedThumbnail-D9fSb4N-.cjs.map → LockedThumbnail-DL5NZzWJ.cjs.map} +1 -1
- package/dist/{index-BcHUpyyw.js → index-C2wfgpUU.js} +855 -823
- package/dist/index-C2wfgpUU.js.map +1 -0
- package/dist/index-nanry0Io.cjs +2 -0
- package/dist/index-nanry0Io.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/src/components/ActionButton/ActionButton.test.tsx +0 -25
- package/src/components/AttachmentCard/AttachmentCard.stories.tsx +226 -0
- package/src/components/Avatar/Avatar.stories.tsx +20 -0
- package/src/components/ChannelActionsMenu/ChannelActionsMenu.test.tsx +33 -8
- package/src/components/ChannelList/ChannelList.stories.tsx +5 -0
- package/src/components/ChannelList/CustomChannelPreview.stories.tsx +77 -47
- package/src/components/ChannelView.stories.tsx +8 -7
- package/src/components/ChannelView.test.tsx +12 -1
- package/src/components/ChannelView.tsx +34 -17
- package/src/components/CloseButton/CloseButton.stories.tsx +31 -0
- package/src/components/CustomDateSeparator/CustomDateSeparator.stories.tsx +33 -0
- package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.stories.tsx +63 -0
- package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.tsx +57 -0
- package/src/components/CustomLinkPreviewList/index.tsx +2 -54
- package/src/components/CustomMessage/CustomMessage.stories.tsx +3 -18
- package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +13 -0
- package/src/components/CustomMessage/MessageTag.stories.tsx +22 -2
- package/src/components/CustomMessage/MessageVoteButtons.stories.tsx +9 -0
- package/src/components/CustomMessageInput/index.tsx +14 -4
- package/src/components/CustomSystemMessage/CustomSystemMessage.stories.tsx +54 -0
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +7 -0
- package/src/components/LinkAttachment/LinkAttachment.stories.tsx +11 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +4 -0
- package/src/components/MediaMessage/MediaMessage.stories.tsx +4 -2
- package/src/components/MediaMessage/MediaMessage.test.tsx +0 -38
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +25 -84
- package/src/components/SearchInput/SearchInput.test.tsx +0 -8
- package/src/hooks/useChannelModerationActions.ts +32 -14
- package/src/stories/decorators/storyTime.ts +31 -0
- package/src/utils/formatRelativeTime.test.ts +1 -32
- package/dist/index-BcHUpyyw.js.map +0 -1
- package/dist/index-DTZNltUC.cjs +0 -2
- package/dist/index-DTZNltUC.cjs.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { LinkPreview, LinkPreviewStatus } from 'stream-chat'
|
|
4
|
+
|
|
5
|
+
import CustomLinkPreviewCard from './CustomLinkPreviewCard'
|
|
6
|
+
|
|
7
|
+
type ComponentProps = React.ComponentProps<typeof CustomLinkPreviewCard>
|
|
8
|
+
|
|
9
|
+
const meta: Meta<ComponentProps> = {
|
|
10
|
+
title: 'CustomLinkPreviewCard',
|
|
11
|
+
component: CustomLinkPreviewCard,
|
|
12
|
+
parameters: { layout: 'centered' },
|
|
13
|
+
argTypes: {
|
|
14
|
+
onDismiss: { action: 'dismissed' },
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
export default meta
|
|
18
|
+
|
|
19
|
+
const makePreview = (overrides: Partial<LinkPreview>): LinkPreview => ({
|
|
20
|
+
og_scrape_url: 'https://example.com',
|
|
21
|
+
status: LinkPreviewStatus.LOADED,
|
|
22
|
+
...overrides,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const Template: StoryFn<ComponentProps> = (args) => (
|
|
26
|
+
<div className="p-12">
|
|
27
|
+
<CustomLinkPreviewCard {...args} />
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const WithTitleAndImage: StoryFn<ComponentProps> = Template.bind({})
|
|
32
|
+
WithTitleAndImage.args = {
|
|
33
|
+
link: makePreview({
|
|
34
|
+
og_scrape_url: 'https://linktr.ee/example',
|
|
35
|
+
title: 'Linktree — the link in your bio',
|
|
36
|
+
image_url: '/image-thumbnail.jpg',
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const TitleOnly: StoryFn<ComponentProps> = Template.bind({})
|
|
41
|
+
TitleOnly.args = {
|
|
42
|
+
link: makePreview({
|
|
43
|
+
og_scrape_url: 'https://example.com/blog/post',
|
|
44
|
+
title: 'A long article title that explains why widgets are important',
|
|
45
|
+
}),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const UrlOnly: StoryFn<ComponentProps> = Template.bind({})
|
|
49
|
+
UrlOnly.args = {
|
|
50
|
+
link: makePreview({
|
|
51
|
+
og_scrape_url: 'https://example.com/raw',
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const VeryLongUrl: StoryFn<ComponentProps> = Template.bind({})
|
|
56
|
+
VeryLongUrl.args = {
|
|
57
|
+
link: makePreview({
|
|
58
|
+
og_scrape_url:
|
|
59
|
+
'https://example.com/very/long/path/with/many/segments/and/query/parameters?param1=value1¶m2=value2',
|
|
60
|
+
title: 'Page with an extremely long URL',
|
|
61
|
+
image_url: '/image-thumbnail.jpg',
|
|
62
|
+
}),
|
|
63
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { XIcon } from '@phosphor-icons/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { LinkPreview } from 'stream-chat'
|
|
4
|
+
|
|
5
|
+
interface CustomLinkPreviewCardProps {
|
|
6
|
+
link: LinkPreview
|
|
7
|
+
onDismiss: (url: string) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CustomLinkPreviewCard: React.FC<CustomLinkPreviewCardProps> = ({
|
|
11
|
+
link,
|
|
12
|
+
onDismiss,
|
|
13
|
+
}) => {
|
|
14
|
+
const { og_scrape_url, title, image_url } = link
|
|
15
|
+
|
|
16
|
+
const handleDismissLink = (e: React.MouseEvent) => {
|
|
17
|
+
e.preventDefault()
|
|
18
|
+
onDismiss(og_scrape_url)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<a
|
|
23
|
+
href={og_scrape_url}
|
|
24
|
+
target="_blank"
|
|
25
|
+
rel="noopener noreferrer"
|
|
26
|
+
className="relative block w-[280px] max-w-full rounded-[24px] bg-[#121110] p-2 no-underline transition-opacity hover:opacity-90"
|
|
27
|
+
>
|
|
28
|
+
{image_url && (
|
|
29
|
+
<img
|
|
30
|
+
src={image_url}
|
|
31
|
+
alt={title || ''}
|
|
32
|
+
className="h-[180px] w-full rounded-[20px] object-cover"
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
onClick={handleDismissLink}
|
|
38
|
+
className="absolute right-4 top-4 flex size-6 items-center justify-center rounded-full border border-white/40 bg-white/70 backdrop-blur-2xl focus-ring"
|
|
39
|
+
aria-label="Close link preview"
|
|
40
|
+
>
|
|
41
|
+
<XIcon className="size-4 text-black/90" />
|
|
42
|
+
</button>
|
|
43
|
+
<div className="p-2">
|
|
44
|
+
{title && (
|
|
45
|
+
<div className="text-[14px] font-medium leading-5 text-white">
|
|
46
|
+
{title}
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
<div className="text-[12px] leading-4 text-white/55">
|
|
50
|
+
{og_scrape_url}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</a>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default CustomLinkPreviewCard
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { XIcon } from '@phosphor-icons/react'
|
|
2
1
|
import React from 'react'
|
|
3
2
|
import {
|
|
4
|
-
LinkPreview,
|
|
5
3
|
LinkPreviewsManager,
|
|
6
4
|
LinkPreviewsManagerState,
|
|
7
5
|
} from 'stream-chat'
|
|
8
6
|
import { useMessageComposer, useStateStore } from 'stream-chat-react'
|
|
9
7
|
|
|
8
|
+
import CustomLinkPreviewCard from './CustomLinkPreviewCard'
|
|
9
|
+
|
|
10
10
|
const linkPreviewsManagerStateSelector = (state: LinkPreviewsManagerState) => ({
|
|
11
11
|
linkPreviews: Array.from(state.previews.values()).filter(
|
|
12
12
|
(preview) =>
|
|
@@ -15,58 +15,6 @@ const linkPreviewsManagerStateSelector = (state: LinkPreviewsManagerState) => ({
|
|
|
15
15
|
),
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
interface CustomLinkPreviewCardProps {
|
|
19
|
-
link: LinkPreview
|
|
20
|
-
onDismiss: (url: string) => void
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const CustomLinkPreviewCard: React.FC<CustomLinkPreviewCardProps> = ({
|
|
24
|
-
link,
|
|
25
|
-
onDismiss,
|
|
26
|
-
}) => {
|
|
27
|
-
const { og_scrape_url, title, image_url } = link
|
|
28
|
-
|
|
29
|
-
const handleDismissLink = (e: React.MouseEvent) => {
|
|
30
|
-
e.preventDefault()
|
|
31
|
-
onDismiss(og_scrape_url)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<a
|
|
36
|
-
href={og_scrape_url}
|
|
37
|
-
target="_blank"
|
|
38
|
-
rel="noopener noreferrer"
|
|
39
|
-
className="relative block w-[280px] max-w-full rounded-[24px] bg-[#121110] p-2 no-underline transition-opacity hover:opacity-90"
|
|
40
|
-
>
|
|
41
|
-
{image_url && (
|
|
42
|
-
<img
|
|
43
|
-
src={image_url}
|
|
44
|
-
alt={title || ''}
|
|
45
|
-
className="h-[180px] w-full rounded-[20px] object-cover"
|
|
46
|
-
/>
|
|
47
|
-
)}
|
|
48
|
-
<button
|
|
49
|
-
type="button"
|
|
50
|
-
onClick={handleDismissLink}
|
|
51
|
-
className="absolute right-4 top-4 flex size-6 items-center justify-center rounded-full border border-white/40 bg-white/70 backdrop-blur-2xl focus-ring"
|
|
52
|
-
aria-label="Close link preview"
|
|
53
|
-
>
|
|
54
|
-
<XIcon className="size-4 text-black/90" />
|
|
55
|
-
</button>
|
|
56
|
-
<div className="p-2">
|
|
57
|
-
{title && (
|
|
58
|
-
<div className="text-[14px] font-medium leading-5 text-white">
|
|
59
|
-
{title}
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
|
-
<div className="text-[12px] leading-4 text-white/55">
|
|
63
|
-
{og_scrape_url}
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</a>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
18
|
export const CustomLinkPreviewList = () => {
|
|
71
19
|
const { linkPreviewsManager } = useMessageComposer()
|
|
72
20
|
|
|
@@ -1,7 +1,6 @@
|
|
|
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'
|
|
5
4
|
import React, { useEffect } from 'react'
|
|
6
5
|
import {
|
|
7
6
|
Channel as ChannelType,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
WithComponents,
|
|
18
17
|
} from 'stream-chat-react'
|
|
19
18
|
|
|
19
|
+
import { minutesAgo } from '../../stories/decorators/storyTime'
|
|
20
20
|
import {
|
|
21
21
|
currentUserArgType,
|
|
22
22
|
StoryUser,
|
|
@@ -46,8 +46,8 @@ const createMockChannel = async (
|
|
|
46
46
|
const mockMessages = messages.map((msg, index) => ({
|
|
47
47
|
...msg,
|
|
48
48
|
type: msg.type ?? ('regular' as const),
|
|
49
|
-
created_at:
|
|
50
|
-
updated_at:
|
|
49
|
+
created_at: minutesAgo(messages.length - index),
|
|
50
|
+
updated_at: minutesAgo(messages.length - index),
|
|
51
51
|
html: `<p>${msg.text}</p>`,
|
|
52
52
|
attachments: msg.attachments ?? [],
|
|
53
53
|
latest_reactions: [],
|
|
@@ -567,21 +567,6 @@ ConversationWithPaidAttachments.args = {
|
|
|
567
567
|
},
|
|
568
568
|
],
|
|
569
569
|
}
|
|
570
|
-
ConversationWithPaidAttachments.play = async ({ canvasElement }) => {
|
|
571
|
-
const canvas = within(canvasElement)
|
|
572
|
-
const attachmentMsg = canvasElement.querySelector('[data-message-id="msg-3"]')
|
|
573
|
-
await userEvent.hover(attachmentMsg!)
|
|
574
|
-
const toggle = await canvas.findByTestId('message-actions-toggle-button')
|
|
575
|
-
await userEvent.click(toggle)
|
|
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()
|
|
584
|
-
}
|
|
585
570
|
ConversationWithPaidAttachments.parameters = {
|
|
586
571
|
docs: {
|
|
587
572
|
description: {
|
|
@@ -223,6 +223,14 @@ interface ConversationStoryArgs {
|
|
|
223
223
|
currentUser: StoryUser
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
// Single-attachment-type conversations (`ConversationWithImages`,
|
|
227
|
+
// `ConversationWithVideos`, etc.) are useful for design review but the
|
|
228
|
+
// per-attachment-type RENDERING is already covered by the dedicated
|
|
229
|
+
// `MessageAttachment/{Image,Video,Audio,Pdf,File}` stories. The
|
|
230
|
+
// "mixed" conversations below exercise composition that the per-type
|
|
231
|
+
// stories don't, so those keep their snapshots.
|
|
232
|
+
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
233
|
+
|
|
226
234
|
const HERO_PHOTO = 'https://picsum.photos/seed/portrait/720/720'
|
|
227
235
|
const STUDIO_PHOTOS = [
|
|
228
236
|
{ src: 'https://picsum.photos/seed/studio-1/720/720', alt: 'Studio shot 1' },
|
|
@@ -291,6 +299,7 @@ export const ConversationWithImages: StoryFn<ConversationStoryArgs> = ({
|
|
|
291
299
|
/>
|
|
292
300
|
)
|
|
293
301
|
}
|
|
302
|
+
ConversationWithImages.parameters = skipInChromatic
|
|
294
303
|
|
|
295
304
|
/**
|
|
296
305
|
* Video attachments — receiver sends a single clip with a caption,
|
|
@@ -355,6 +364,7 @@ export const ConversationWithVideos: StoryFn<ConversationStoryArgs> = ({
|
|
|
355
364
|
/>
|
|
356
365
|
)
|
|
357
366
|
}
|
|
367
|
+
ConversationWithVideos.parameters = skipInChromatic
|
|
358
368
|
|
|
359
369
|
/**
|
|
360
370
|
* Audio attachments — voice memo back-and-forth, plus a stacked
|
|
@@ -426,6 +436,7 @@ export const ConversationWithAudio: StoryFn<ConversationStoryArgs> = ({
|
|
|
426
436
|
/>
|
|
427
437
|
)
|
|
428
438
|
}
|
|
439
|
+
ConversationWithAudio.parameters = skipInChromatic
|
|
429
440
|
|
|
430
441
|
/**
|
|
431
442
|
* PDF attachments — single document with a caption and a stacked
|
|
@@ -498,6 +509,7 @@ export const ConversationWithPdfs: StoryFn<ConversationStoryArgs> = ({
|
|
|
498
509
|
/>
|
|
499
510
|
)
|
|
500
511
|
}
|
|
512
|
+
ConversationWithPdfs.parameters = skipInChromatic
|
|
501
513
|
|
|
502
514
|
/**
|
|
503
515
|
* Generic file attachments — non-PDF documents with a download
|
|
@@ -583,6 +595,7 @@ export const ConversationWithFiles: StoryFn<ConversationStoryArgs> = ({
|
|
|
583
595
|
/>
|
|
584
596
|
)
|
|
585
597
|
}
|
|
598
|
+
ConversationWithFiles.parameters = skipInChromatic
|
|
586
599
|
|
|
587
600
|
/**
|
|
588
601
|
* Mixed conversation — a realistic chat where the same thread carries
|
|
@@ -2,6 +2,8 @@ import type { Meta, StoryFn } from '@storybook/react'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { LocalMessage } from 'stream-chat'
|
|
4
4
|
|
|
5
|
+
import { now } from '../../stories/decorators/storyTime'
|
|
6
|
+
|
|
5
7
|
import { MessageTag } from './MessageTag'
|
|
6
8
|
|
|
7
9
|
type ComponentProps = React.ComponentProps<typeof MessageTag>
|
|
@@ -28,8 +30,8 @@ const createMockMessage = (options?: MockMessageOptions): LocalMessage =>
|
|
|
28
30
|
id: 'msg-1',
|
|
29
31
|
text: options?.text ?? 'Hello world',
|
|
30
32
|
type: 'regular',
|
|
31
|
-
created_at:
|
|
32
|
-
updated_at:
|
|
33
|
+
created_at: now(),
|
|
34
|
+
updated_at: now(),
|
|
33
35
|
metadata: options?.metadata,
|
|
34
36
|
}) as LocalMessage
|
|
35
37
|
|
|
@@ -41,12 +43,18 @@ const Template: StoryFn<ComponentProps> = (args) => {
|
|
|
41
43
|
)
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
// Per-variant stories stay in Storybook for browsing/design review but are
|
|
47
|
+
// individually covered by rows in AllVariants — skip in Chromatic to keep
|
|
48
|
+
// the variant matrix as the single snapshot source of truth.
|
|
49
|
+
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
50
|
+
|
|
44
51
|
export const Tip: StoryFn<ComponentProps> = Template.bind({})
|
|
45
52
|
Tip.args = {
|
|
46
53
|
message: createMockMessage({
|
|
47
54
|
metadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.50' },
|
|
48
55
|
}),
|
|
49
56
|
}
|
|
57
|
+
Tip.parameters = skipInChromatic
|
|
50
58
|
|
|
51
59
|
export const TipStandalone: StoryFn<ComponentProps> = Template.bind({})
|
|
52
60
|
TipStandalone.args = {
|
|
@@ -56,6 +64,7 @@ TipStandalone.args = {
|
|
|
56
64
|
}),
|
|
57
65
|
standalone: true,
|
|
58
66
|
}
|
|
67
|
+
TipStandalone.parameters = skipInChromatic
|
|
59
68
|
|
|
60
69
|
export const Paid: StoryFn<ComponentProps> = Template.bind({})
|
|
61
70
|
Paid.args = {
|
|
@@ -63,11 +72,13 @@ Paid.args = {
|
|
|
63
72
|
metadata: { custom_type: 'MESSAGE_PAID', amount_text: '$25.00' },
|
|
64
73
|
}),
|
|
65
74
|
}
|
|
75
|
+
Paid.parameters = skipInChromatic
|
|
66
76
|
|
|
67
77
|
export const ChatbotReceiverText: StoryFn<ComponentProps> = Template.bind({})
|
|
68
78
|
ChatbotReceiverText.args = {
|
|
69
79
|
message: createMockMessage({ metadata: { custom_type: 'MESSAGE_CHATBOT' } }),
|
|
70
80
|
}
|
|
81
|
+
ChatbotReceiverText.parameters = skipInChromatic
|
|
71
82
|
|
|
72
83
|
export const ChatbotSenderText: StoryFn<ComponentProps> = (args) => {
|
|
73
84
|
return (
|
|
@@ -82,6 +93,7 @@ ChatbotSenderText.args = {
|
|
|
82
93
|
message: createMockMessage({ metadata: { custom_type: 'MESSAGE_CHATBOT' } }),
|
|
83
94
|
isMyMessage: true,
|
|
84
95
|
}
|
|
96
|
+
ChatbotSenderText.parameters = skipInChromatic
|
|
85
97
|
|
|
86
98
|
export const ChatbotSenderAttachment: StoryFn<ComponentProps> = Template.bind(
|
|
87
99
|
{}
|
|
@@ -91,11 +103,13 @@ ChatbotSenderAttachment.args = {
|
|
|
91
103
|
isMyMessage: true,
|
|
92
104
|
hasAttachment: true,
|
|
93
105
|
}
|
|
106
|
+
ChatbotSenderAttachment.parameters = skipInChromatic
|
|
94
107
|
|
|
95
108
|
export const NoTag: StoryFn<ComponentProps> = Template.bind({})
|
|
96
109
|
NoTag.args = {
|
|
97
110
|
message: createMockMessage(),
|
|
98
111
|
}
|
|
112
|
+
NoTag.parameters = skipInChromatic
|
|
99
113
|
|
|
100
114
|
export const AllVariants: StoryFn = () => {
|
|
101
115
|
return (
|
|
@@ -165,3 +179,9 @@ export const AllVariants: StoryFn = () => {
|
|
|
165
179
|
</div>
|
|
166
180
|
)
|
|
167
181
|
}
|
|
182
|
+
// AllVariants is a design-review kitchen sink — every per-variant story
|
|
183
|
+
// (Tip, Paid, TipStandalone, ChatbotReceiverText, ChatbotSenderText,
|
|
184
|
+
// ChatbotSenderAttachment, NoTag) already covers its own row independently.
|
|
185
|
+
// Skip in Chromatic to avoid a "any one row changes → whole snapshot diffs"
|
|
186
|
+
// problem that masks which variant actually drifted.
|
|
187
|
+
AllVariants.parameters = { chromatic: { disableSnapshot: true } }
|
|
@@ -22,12 +22,18 @@ const Template: StoryFn<ComponentProps> = (args) => (
|
|
|
22
22
|
</div>
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
+
// Per-state stories (Unselected, GoodResponse, BadResponse) are individually
|
|
26
|
+
// covered by rows of AllVariants — skip in Chromatic. Interactive has no
|
|
27
|
+
// useful steady-state snapshot.
|
|
28
|
+
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
29
|
+
|
|
25
30
|
export const Unselected: StoryFn<ComponentProps> = Template.bind({})
|
|
26
31
|
Unselected.args = {
|
|
27
32
|
selected: null,
|
|
28
33
|
onVoteUp: () => {},
|
|
29
34
|
onVoteDown: () => {},
|
|
30
35
|
}
|
|
36
|
+
Unselected.parameters = skipInChromatic
|
|
31
37
|
|
|
32
38
|
export const GoodResponse: StoryFn<ComponentProps> = Template.bind({})
|
|
33
39
|
GoodResponse.args = {
|
|
@@ -35,6 +41,7 @@ GoodResponse.args = {
|
|
|
35
41
|
onVoteUp: () => {},
|
|
36
42
|
onVoteDown: () => {},
|
|
37
43
|
}
|
|
44
|
+
GoodResponse.parameters = skipInChromatic
|
|
38
45
|
|
|
39
46
|
export const BadResponse: StoryFn<ComponentProps> = Template.bind({})
|
|
40
47
|
BadResponse.args = {
|
|
@@ -42,6 +49,7 @@ BadResponse.args = {
|
|
|
42
49
|
onVoteUp: () => {},
|
|
43
50
|
onVoteDown: () => {},
|
|
44
51
|
}
|
|
52
|
+
BadResponse.parameters = skipInChromatic
|
|
45
53
|
|
|
46
54
|
export const Interactive: StoryFn = () => {
|
|
47
55
|
const [selected, setSelected] = useState<VoteSelection>(null)
|
|
@@ -55,6 +63,7 @@ export const Interactive: StoryFn = () => {
|
|
|
55
63
|
</div>
|
|
56
64
|
)
|
|
57
65
|
}
|
|
66
|
+
Interactive.parameters = skipInChromatic
|
|
58
67
|
|
|
59
68
|
export const AllVariants: StoryFn = () => (
|
|
60
69
|
<div className="p-12 flex flex-col gap-6">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ArrowUpIcon } from '@phosphor-icons/react'
|
|
2
|
+
import classNames from 'classnames'
|
|
2
3
|
import React, { useContext } from 'react'
|
|
3
4
|
import {
|
|
4
5
|
AttachmentPreviewList as DefaultAttachmentPreviewList,
|
|
@@ -101,9 +102,11 @@ export interface CustomMessageInputProps {
|
|
|
101
102
|
* `disabled` is true.
|
|
102
103
|
*/
|
|
103
104
|
disabledReason?: string
|
|
105
|
+
className?: string
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
|
|
109
|
+
className,
|
|
107
110
|
renderActions,
|
|
108
111
|
renderFooter,
|
|
109
112
|
disabled = false,
|
|
@@ -117,7 +120,12 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
|
|
|
117
120
|
if (disabled) {
|
|
118
121
|
return (
|
|
119
122
|
<>
|
|
120
|
-
<div
|
|
123
|
+
<div
|
|
124
|
+
className={classNames(
|
|
125
|
+
'messaging-composer-locked-panel flex w-full flex-col items-center justify-center gap-3',
|
|
126
|
+
className
|
|
127
|
+
)}
|
|
128
|
+
>
|
|
121
129
|
{disabledReason ? (
|
|
122
130
|
<p className="max-w-[345px] text-center text-xs font-normal leading-[1.3] tracking-[0.12px] text-black/40">
|
|
123
131
|
{disabledReason}
|
|
@@ -129,17 +137,19 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
|
|
|
129
137
|
)
|
|
130
138
|
}
|
|
131
139
|
|
|
140
|
+
const actions = renderActions?.()
|
|
141
|
+
|
|
132
142
|
return (
|
|
133
|
-
<div className=
|
|
143
|
+
<div className={classNames('flex flex-col gap-4', className)}>
|
|
134
144
|
<div
|
|
135
145
|
// @ts-expect-error Only React 19 onwards has `inert` in its types.
|
|
136
146
|
inert={isFrozen ? '' : undefined}
|
|
137
147
|
aria-disabled={isFrozen || undefined}
|
|
138
148
|
className="message-input flex items-end gap-4 aria-disabled:opacity-40"
|
|
139
149
|
>
|
|
140
|
-
{
|
|
150
|
+
{actions && (
|
|
141
151
|
<div className="flex h-12 shrink-0 items-center justify-center">
|
|
142
|
-
{
|
|
152
|
+
{actions}
|
|
143
153
|
</div>
|
|
144
154
|
)}
|
|
145
155
|
<ComposerLockedContext.Provider value={isFrozen}>
|
|
@@ -31,6 +31,10 @@ const Template: StoryFn<EventComponentProps> = (args) => (
|
|
|
31
31
|
</div>
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
+
// Per-variant stories stay in Storybook for browsing/design review but are
|
|
35
|
+
// individually covered by rows in AllVariants — skip in Chromatic.
|
|
36
|
+
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
37
|
+
|
|
34
38
|
export const DmAgentPaused: StoryFn<EventComponentProps> = Template.bind({})
|
|
35
39
|
DmAgentPaused.args = createStoryProps({
|
|
36
40
|
text: 'DM Agent paused for this conversation',
|
|
@@ -38,6 +42,7 @@ DmAgentPaused.args = createStoryProps({
|
|
|
38
42
|
custom_type: 'SYSTEM_DM_AGENT_PAUSED',
|
|
39
43
|
},
|
|
40
44
|
})
|
|
45
|
+
DmAgentPaused.parameters = skipInChromatic
|
|
41
46
|
|
|
42
47
|
export const DmAgentResumed: StoryFn<EventComponentProps> = Template.bind({})
|
|
43
48
|
DmAgentResumed.args = createStoryProps({
|
|
@@ -46,6 +51,7 @@ DmAgentResumed.args = createStoryProps({
|
|
|
46
51
|
custom_type: 'SYSTEM_DM_AGENT_RESUMED',
|
|
47
52
|
},
|
|
48
53
|
})
|
|
54
|
+
DmAgentResumed.parameters = skipInChromatic
|
|
49
55
|
|
|
50
56
|
export const AgeSafetyBlocked: StoryFn<EventComponentProps> = Template.bind({})
|
|
51
57
|
AgeSafetyBlocked.args = createStoryProps({
|
|
@@ -54,6 +60,7 @@ AgeSafetyBlocked.args = createStoryProps({
|
|
|
54
60
|
custom_type: 'SYSTEM_AGE_SAFETY_BLOCKED',
|
|
55
61
|
},
|
|
56
62
|
})
|
|
63
|
+
AgeSafetyBlocked.parameters = skipInChromatic
|
|
57
64
|
|
|
58
65
|
export const GenericFallback: StoryFn<EventComponentProps> = Template.bind({})
|
|
59
66
|
GenericFallback.args = createStoryProps({
|
|
@@ -62,3 +69,50 @@ GenericFallback.args = createStoryProps({
|
|
|
62
69
|
custom_type: 'MESSAGE_CHATBOT',
|
|
63
70
|
},
|
|
64
71
|
})
|
|
72
|
+
GenericFallback.parameters = skipInChromatic
|
|
73
|
+
|
|
74
|
+
export const AllVariants: StoryFn = () => {
|
|
75
|
+
const variants: { label: string; props: EventComponentProps }[] = [
|
|
76
|
+
{
|
|
77
|
+
label: 'DM Agent paused',
|
|
78
|
+
props: createStoryProps({
|
|
79
|
+
text: 'DM Agent paused for this conversation',
|
|
80
|
+
metadata: { custom_type: 'SYSTEM_DM_AGENT_PAUSED' },
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'DM Agent resumed',
|
|
85
|
+
props: createStoryProps({
|
|
86
|
+
text: 'DM Agent resumed for this conversation',
|
|
87
|
+
metadata: { custom_type: 'SYSTEM_DM_AGENT_RESUMED' },
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: 'Age safety blocked',
|
|
92
|
+
props: createStoryProps({
|
|
93
|
+
text: ' ',
|
|
94
|
+
metadata: { custom_type: 'SYSTEM_AGE_SAFETY_BLOCKED' },
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
label: 'Generic fallback',
|
|
99
|
+
props: createStoryProps({
|
|
100
|
+
text: 'Message activity event',
|
|
101
|
+
metadata: { custom_type: 'MESSAGE_CHATBOT' },
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="flex flex-col gap-4 bg-white p-6">
|
|
108
|
+
{variants.map(({ label, props }) => (
|
|
109
|
+
<div key={label} className="flex flex-col gap-1">
|
|
110
|
+
<span className="text-xs text-stone">{label}</span>
|
|
111
|
+
<div className="w-[420px]">
|
|
112
|
+
<CustomSystemMessage {...props} />
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -123,6 +123,13 @@ const meta: Meta<StoryProps> = {
|
|
|
123
123
|
component: CustomTypingIndicator,
|
|
124
124
|
parameters: {
|
|
125
125
|
layout: 'centered',
|
|
126
|
+
// CustomTypingIndicator is animation-dominant (SMIL spinner + DOM
|
|
127
|
+
// appear/disappear on typing-state transitions). Even with the SMIL
|
|
128
|
+
// pause decorator, a snapshot here is mostly testing what frame the
|
|
129
|
+
// animation paused at — low value for visual regression, high flake
|
|
130
|
+
// potential. Skip it in Chromatic; the stories stay browsable in
|
|
131
|
+
// Storybook for design review.
|
|
132
|
+
chromatic: { disableSnapshot: true },
|
|
126
133
|
},
|
|
127
134
|
}
|
|
128
135
|
export default meta
|
|
@@ -13,7 +13,17 @@ const IMAGE_THUMBNAIL = '/image-thumbnail.jpg'
|
|
|
13
13
|
|
|
14
14
|
const meta: Meta = {
|
|
15
15
|
title: 'LinkAttachment',
|
|
16
|
-
parameters: {
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'fullscreen',
|
|
18
|
+
// The single LinkApps story is a large matrix of every link-app card
|
|
19
|
+
// crossed with featured/classic layouts and locked/unlocked states.
|
|
20
|
+
// It's intended as a design-review canvas, not a regression target —
|
|
21
|
+
// a single pixel change in any cell would diff the whole thing. Skip
|
|
22
|
+
// it in Chromatic; the per-app/state behavior is exercised
|
|
23
|
+
// implicitly through the message-attachment stories that use these
|
|
24
|
+
// cards.
|
|
25
|
+
chromatic: { disableSnapshot: true },
|
|
26
|
+
},
|
|
17
27
|
}
|
|
18
28
|
export default meta
|
|
19
29
|
|
|
@@ -444,3 +444,7 @@ export const Received: StoryFn = () => {
|
|
|
444
444
|
</Table>
|
|
445
445
|
)
|
|
446
446
|
}
|
|
447
|
+
// LockedAttachment.Received autoplays the video preview once paid; even with
|
|
448
|
+
// our SMIL/CSS animation freeze the <video> element advances frames
|
|
449
|
+
// independently and produces a non-deterministic snapshot. Skip in Chromatic.
|
|
450
|
+
Received.parameters = { chromatic: { disableSnapshot: true } }
|
|
@@ -2,6 +2,8 @@ import type { Meta, StoryFn } from '@storybook/react'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import type { LocalMessage } from 'stream-chat'
|
|
4
4
|
|
|
5
|
+
import { now } from '../../stories/decorators/storyTime'
|
|
6
|
+
|
|
5
7
|
import { MediaMessage } from '.'
|
|
6
8
|
|
|
7
9
|
const meta: Meta<typeof MediaMessage> = {
|
|
@@ -20,8 +22,8 @@ const base = (overrides: Partial<LocalMessage> = {}): LocalMessage => {
|
|
|
20
22
|
id: 'msg-1',
|
|
21
23
|
text: '',
|
|
22
24
|
type: 'regular',
|
|
23
|
-
created_at:
|
|
24
|
-
updated_at:
|
|
25
|
+
created_at: now(),
|
|
26
|
+
updated_at: now(),
|
|
25
27
|
deleted_at: null,
|
|
26
28
|
pinned_at: null,
|
|
27
29
|
status: 'received',
|
|
@@ -253,44 +253,6 @@ describe('MediaMessage', () => {
|
|
|
253
253
|
expect(container.firstChild).toBeNull()
|
|
254
254
|
})
|
|
255
255
|
|
|
256
|
-
it('uses dark card background for sent (Creator) messages', () => {
|
|
257
|
-
const { container } = renderWithProviders(
|
|
258
|
-
<MediaMessage
|
|
259
|
-
isMyMessage={true}
|
|
260
|
-
message={msg({
|
|
261
|
-
attachments: [
|
|
262
|
-
{
|
|
263
|
-
type: 'image',
|
|
264
|
-
image_url: 'https://cdn.example.com/photo.jpg',
|
|
265
|
-
mime_type: 'image/jpeg',
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
})}
|
|
269
|
-
/>
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
expect(container.querySelector('.bg-\\[\\#121110\\]')).toBeInTheDocument()
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
it('uses light card background for received (Visitor) messages', () => {
|
|
276
|
-
const { container } = renderWithProviders(
|
|
277
|
-
<MediaMessage
|
|
278
|
-
isMyMessage={false}
|
|
279
|
-
message={msg({
|
|
280
|
-
attachments: [
|
|
281
|
-
{
|
|
282
|
-
type: 'image',
|
|
283
|
-
image_url: 'https://cdn.example.com/photo.jpg',
|
|
284
|
-
mime_type: 'image/jpeg',
|
|
285
|
-
},
|
|
286
|
-
],
|
|
287
|
-
})}
|
|
288
|
-
/>
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
expect(container.querySelector('.bg-white')).toBeInTheDocument()
|
|
292
|
-
})
|
|
293
|
-
|
|
294
256
|
it('shows Download action for received (Visitor) image attachment', () => {
|
|
295
257
|
renderWithProviders(
|
|
296
258
|
<MediaMessage
|