@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
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-nanry0Io.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-C2wfgpUU.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.3.
|
|
3
|
+
"version": "3.3.6-rc-1780987607",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
"lint:fix": "npx --no-install eslint . --ext .ts,.tsx --fix",
|
|
33
33
|
"verify": "yarn type-check && yarn lint && yarn test:ci && yarn build",
|
|
34
34
|
"storybook": "storybook dev -p 6006",
|
|
35
|
-
"storybook:build": "storybook build"
|
|
35
|
+
"storybook:build": "storybook build --stats-json",
|
|
36
|
+
"chromatic:ci:feature": "chromatic --ci --exit-zero-on-changes --exit-once-uploaded",
|
|
37
|
+
"chromatic:ci:main": "chromatic --ci --auto-accept-changes"
|
|
36
38
|
},
|
|
37
39
|
"dependencies": {
|
|
38
40
|
"@linktr.ee/component-library": "11.8.6",
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
"@vitejs/plugin-react": "^4.2.1",
|
|
57
59
|
"@vitest/ui": "^1.0.4",
|
|
58
60
|
"autoprefixer": "^10.4.20",
|
|
61
|
+
"chromatic": "^11.20.0",
|
|
59
62
|
"classnames": "^2.3.2",
|
|
60
63
|
"jsdom": "^23.0.1",
|
|
61
64
|
"postcss": "^8.4.49",
|
|
@@ -10,24 +10,6 @@ describe('ActionButton', () => {
|
|
|
10
10
|
renderWithProviders(<ActionButton>Click me</ActionButton>);
|
|
11
11
|
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
|
|
12
12
|
});
|
|
13
|
-
|
|
14
|
-
it('renders with default variant', () => {
|
|
15
|
-
renderWithProviders(<ActionButton>Default</ActionButton>);
|
|
16
|
-
const button = screen.getByRole('button');
|
|
17
|
-
expect(button).toHaveClass('text-charcoal');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('renders with danger variant', () => {
|
|
21
|
-
renderWithProviders(<ActionButton variant="danger">Delete</ActionButton>);
|
|
22
|
-
const button = screen.getByRole('button');
|
|
23
|
-
expect(button).toHaveClass('text-danger');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('has full width by default', () => {
|
|
27
|
-
renderWithProviders(<ActionButton>Full Width</ActionButton>);
|
|
28
|
-
const button = screen.getByRole('button');
|
|
29
|
-
expect(button).toHaveClass('w-full');
|
|
30
|
-
});
|
|
31
13
|
});
|
|
32
14
|
|
|
33
15
|
describe('Disabled State', () => {
|
|
@@ -52,13 +34,6 @@ describe('ActionButton', () => {
|
|
|
52
34
|
|
|
53
35
|
expect(handleClick).not.toHaveBeenCalled();
|
|
54
36
|
});
|
|
55
|
-
|
|
56
|
-
it('applies disabled styles', () => {
|
|
57
|
-
renderWithProviders(<ActionButton disabled>Disabled</ActionButton>);
|
|
58
|
-
const button = screen.getByRole('button');
|
|
59
|
-
expect(button).toHaveClass('disabled:opacity-60');
|
|
60
|
-
expect(button).toHaveClass('disabled:cursor-not-allowed');
|
|
61
|
-
});
|
|
62
37
|
});
|
|
63
38
|
|
|
64
39
|
describe('Click Handling', () => {
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import AttachmentCard, { AttachmentThumbnail } from '.'
|
|
5
|
+
|
|
6
|
+
type ComponentProps = React.ComponentProps<typeof AttachmentCard>
|
|
7
|
+
|
|
8
|
+
const meta: Meta<ComponentProps> = {
|
|
9
|
+
title: 'AttachmentCard',
|
|
10
|
+
component: AttachmentCard,
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
argTypes: {
|
|
13
|
+
variant: { control: { type: 'inline-radio' }, options: ['light', 'dark'] },
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
export default meta
|
|
17
|
+
|
|
18
|
+
const IMAGE_URL = '/image-thumbnail.jpg'
|
|
19
|
+
|
|
20
|
+
const Template: StoryFn<ComponentProps> = (args) => <AttachmentCard {...args} />
|
|
21
|
+
|
|
22
|
+
// Per-variant stories stay in Storybook for browsing/design review but are
|
|
23
|
+
// individually covered by cells in AllVariants — skip in Chromatic.
|
|
24
|
+
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
25
|
+
|
|
26
|
+
export const LightImage: StoryFn<ComponentProps> = Template.bind({})
|
|
27
|
+
LightImage.args = {
|
|
28
|
+
variant: 'light',
|
|
29
|
+
title: 'sunset.jpg',
|
|
30
|
+
mimeType: 'image/jpeg',
|
|
31
|
+
detail: '2.4 MB',
|
|
32
|
+
thumbnail: (
|
|
33
|
+
<AttachmentThumbnail
|
|
34
|
+
mimeType="image/jpeg"
|
|
35
|
+
sourceUrl={IMAGE_URL}
|
|
36
|
+
title="sunset"
|
|
37
|
+
variant="light"
|
|
38
|
+
/>
|
|
39
|
+
),
|
|
40
|
+
}
|
|
41
|
+
LightImage.parameters = skipInChromatic
|
|
42
|
+
|
|
43
|
+
export const DarkImage: StoryFn<ComponentProps> = Template.bind({})
|
|
44
|
+
DarkImage.args = {
|
|
45
|
+
variant: 'dark',
|
|
46
|
+
title: 'sunset.jpg',
|
|
47
|
+
mimeType: 'image/jpeg',
|
|
48
|
+
detail: '2.4 MB',
|
|
49
|
+
thumbnail: (
|
|
50
|
+
<AttachmentThumbnail
|
|
51
|
+
mimeType="image/jpeg"
|
|
52
|
+
sourceUrl={IMAGE_URL}
|
|
53
|
+
title="sunset"
|
|
54
|
+
variant="dark"
|
|
55
|
+
/>
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
DarkImage.parameters = skipInChromatic
|
|
59
|
+
|
|
60
|
+
export const PdfPlaceholder: StoryFn<ComponentProps> = Template.bind({})
|
|
61
|
+
PdfPlaceholder.args = {
|
|
62
|
+
variant: 'light',
|
|
63
|
+
title: 'invoice.pdf',
|
|
64
|
+
mimeType: 'application/pdf',
|
|
65
|
+
detail: '120 KB',
|
|
66
|
+
thumbnail: <AttachmentThumbnail mimeType="application/pdf" variant="light" />,
|
|
67
|
+
}
|
|
68
|
+
PdfPlaceholder.parameters = skipInChromatic
|
|
69
|
+
|
|
70
|
+
export const AudioPosterOnly: StoryFn<ComponentProps> = Template.bind({})
|
|
71
|
+
AudioPosterOnly.args = {
|
|
72
|
+
variant: 'dark',
|
|
73
|
+
title: 'voice-memo.m4a',
|
|
74
|
+
mimeType: 'audio/m4a',
|
|
75
|
+
detail: '0:42',
|
|
76
|
+
thumbnail: (
|
|
77
|
+
<AttachmentThumbnail
|
|
78
|
+
mimeType="audio/m4a"
|
|
79
|
+
thumbnailUrl={IMAGE_URL}
|
|
80
|
+
title="voice memo"
|
|
81
|
+
variant="dark"
|
|
82
|
+
/>
|
|
83
|
+
),
|
|
84
|
+
}
|
|
85
|
+
AudioPosterOnly.parameters = skipInChromatic
|
|
86
|
+
|
|
87
|
+
export const UntitledDark: StoryFn<ComponentProps> = Template.bind({})
|
|
88
|
+
UntitledDark.args = {
|
|
89
|
+
variant: 'dark',
|
|
90
|
+
mimeType: 'image/png',
|
|
91
|
+
detail: '1.1 MB',
|
|
92
|
+
thumbnail: <AttachmentThumbnail mimeType="image/png" variant="dark" />,
|
|
93
|
+
}
|
|
94
|
+
UntitledDark.parameters = skipInChromatic
|
|
95
|
+
|
|
96
|
+
export const WithTopBadges: StoryFn<ComponentProps> = Template.bind({})
|
|
97
|
+
WithTopBadges.args = {
|
|
98
|
+
variant: 'light',
|
|
99
|
+
title: 'receipt.pdf',
|
|
100
|
+
mimeType: 'application/pdf',
|
|
101
|
+
detail: 'Paid',
|
|
102
|
+
thumbnail: <AttachmentThumbnail mimeType="application/pdf" variant="light" />,
|
|
103
|
+
topLeft: (
|
|
104
|
+
<span className="rounded-full bg-black/80 px-2 py-0.5 text-xs font-medium text-white">
|
|
105
|
+
Locked
|
|
106
|
+
</span>
|
|
107
|
+
),
|
|
108
|
+
topRight: (
|
|
109
|
+
<span className="rounded-full bg-white/90 px-2 py-0.5 text-xs font-medium text-black">
|
|
110
|
+
$5.00
|
|
111
|
+
</span>
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
WithTopBadges.parameters = skipInChromatic
|
|
115
|
+
|
|
116
|
+
export const AllVariants: StoryFn = () => {
|
|
117
|
+
const cards: { label: string; props: ComponentProps }[] = [
|
|
118
|
+
{
|
|
119
|
+
label: 'Light image',
|
|
120
|
+
props: {
|
|
121
|
+
variant: 'light',
|
|
122
|
+
title: 'sunset.jpg',
|
|
123
|
+
mimeType: 'image/jpeg',
|
|
124
|
+
detail: '2.4 MB',
|
|
125
|
+
thumbnail: (
|
|
126
|
+
<AttachmentThumbnail
|
|
127
|
+
mimeType="image/jpeg"
|
|
128
|
+
sourceUrl={IMAGE_URL}
|
|
129
|
+
title="sunset"
|
|
130
|
+
variant="light"
|
|
131
|
+
/>
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: 'Dark image',
|
|
137
|
+
props: {
|
|
138
|
+
variant: 'dark',
|
|
139
|
+
title: 'sunset.jpg',
|
|
140
|
+
mimeType: 'image/jpeg',
|
|
141
|
+
detail: '2.4 MB',
|
|
142
|
+
thumbnail: (
|
|
143
|
+
<AttachmentThumbnail
|
|
144
|
+
mimeType="image/jpeg"
|
|
145
|
+
sourceUrl={IMAGE_URL}
|
|
146
|
+
title="sunset"
|
|
147
|
+
variant="dark"
|
|
148
|
+
/>
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: 'PDF placeholder',
|
|
154
|
+
props: {
|
|
155
|
+
variant: 'light',
|
|
156
|
+
title: 'invoice.pdf',
|
|
157
|
+
mimeType: 'application/pdf',
|
|
158
|
+
detail: '120 KB',
|
|
159
|
+
thumbnail: (
|
|
160
|
+
<AttachmentThumbnail mimeType="application/pdf" variant="light" />
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
label: 'Audio poster',
|
|
166
|
+
props: {
|
|
167
|
+
variant: 'dark',
|
|
168
|
+
title: 'voice-memo.m4a',
|
|
169
|
+
mimeType: 'audio/m4a',
|
|
170
|
+
detail: '0:42',
|
|
171
|
+
thumbnail: (
|
|
172
|
+
<AttachmentThumbnail
|
|
173
|
+
mimeType="audio/m4a"
|
|
174
|
+
thumbnailUrl={IMAGE_URL}
|
|
175
|
+
title="voice memo"
|
|
176
|
+
variant="dark"
|
|
177
|
+
/>
|
|
178
|
+
),
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
label: 'Untitled (dark)',
|
|
183
|
+
props: {
|
|
184
|
+
variant: 'dark',
|
|
185
|
+
mimeType: 'image/png',
|
|
186
|
+
detail: '1.1 MB',
|
|
187
|
+
thumbnail: (
|
|
188
|
+
<AttachmentThumbnail mimeType="image/png" variant="dark" />
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
label: 'With top badges',
|
|
194
|
+
props: {
|
|
195
|
+
variant: 'light',
|
|
196
|
+
title: 'receipt.pdf',
|
|
197
|
+
mimeType: 'application/pdf',
|
|
198
|
+
detail: 'Paid',
|
|
199
|
+
thumbnail: (
|
|
200
|
+
<AttachmentThumbnail mimeType="application/pdf" variant="light" />
|
|
201
|
+
),
|
|
202
|
+
topLeft: (
|
|
203
|
+
<span className="rounded-full bg-black/80 px-2 py-0.5 text-xs font-medium text-white">
|
|
204
|
+
Locked
|
|
205
|
+
</span>
|
|
206
|
+
),
|
|
207
|
+
topRight: (
|
|
208
|
+
<span className="rounded-full bg-white/90 px-2 py-0.5 text-xs font-medium text-black">
|
|
209
|
+
$5.00
|
|
210
|
+
</span>
|
|
211
|
+
),
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div className="grid grid-cols-2 gap-6 p-6">
|
|
218
|
+
{cards.map(({ label, props }) => (
|
|
219
|
+
<div key={label} className="flex flex-col gap-2">
|
|
220
|
+
<span className="text-xs text-stone">{label}</span>
|
|
221
|
+
<AttachmentCard {...props} />
|
|
222
|
+
</div>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
@@ -29,6 +29,11 @@ Default.args = {
|
|
|
29
29
|
image: 'https://i.pravatar.cc/150?img=1',
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// Per-size stories (SmallSize…ExtraLargeSize) are each a single cell of the
|
|
33
|
+
// VariousSizes grid; AIActive is the size=40 cell of AIActiveVariousSizes.
|
|
34
|
+
// Keep them in Storybook for browsing but skip in Chromatic.
|
|
35
|
+
const skipInChromatic = { chromatic: { disableSnapshot: true } }
|
|
36
|
+
|
|
32
37
|
export const AIActive: StoryFn<ComponentProps> = Template.bind({})
|
|
33
38
|
AIActive.args = {
|
|
34
39
|
id: 'ai-agent',
|
|
@@ -36,6 +41,7 @@ AIActive.args = {
|
|
|
36
41
|
image: 'https://i.pravatar.cc/150?img=12',
|
|
37
42
|
dmAgentEnabled: true,
|
|
38
43
|
}
|
|
44
|
+
AIActive.parameters = skipInChromatic
|
|
39
45
|
|
|
40
46
|
export const WithoutImage: StoryFn<ComponentProps> = Template.bind({})
|
|
41
47
|
WithoutImage.args = {
|
|
@@ -56,6 +62,7 @@ SmallSize.args = {
|
|
|
56
62
|
name: 'Carol Williams',
|
|
57
63
|
size: 20,
|
|
58
64
|
}
|
|
65
|
+
SmallSize.parameters = skipInChromatic
|
|
59
66
|
|
|
60
67
|
export const MediumSize: StoryFn<ComponentProps> = Template.bind({})
|
|
61
68
|
MediumSize.args = {
|
|
@@ -63,6 +70,7 @@ MediumSize.args = {
|
|
|
63
70
|
name: 'David Brown',
|
|
64
71
|
size: 32,
|
|
65
72
|
}
|
|
73
|
+
MediumSize.parameters = skipInChromatic
|
|
66
74
|
|
|
67
75
|
export const DefaultSize: StoryFn<ComponentProps> = Template.bind({})
|
|
68
76
|
DefaultSize.args = {
|
|
@@ -70,6 +78,7 @@ DefaultSize.args = {
|
|
|
70
78
|
name: 'Emma Davis',
|
|
71
79
|
size: 40,
|
|
72
80
|
}
|
|
81
|
+
DefaultSize.parameters = skipInChromatic
|
|
73
82
|
|
|
74
83
|
export const LargeSize: StoryFn<ComponentProps> = Template.bind({})
|
|
75
84
|
LargeSize.args = {
|
|
@@ -77,6 +86,7 @@ LargeSize.args = {
|
|
|
77
86
|
name: 'Frank Miller',
|
|
78
87
|
size: 56,
|
|
79
88
|
}
|
|
89
|
+
LargeSize.parameters = skipInChromatic
|
|
80
90
|
|
|
81
91
|
export const ExtraLargeSize: StoryFn<ComponentProps> = Template.bind({})
|
|
82
92
|
ExtraLargeSize.args = {
|
|
@@ -84,12 +94,17 @@ ExtraLargeSize.args = {
|
|
|
84
94
|
name: 'Grace Lee',
|
|
85
95
|
size: 80,
|
|
86
96
|
}
|
|
97
|
+
ExtraLargeSize.parameters = skipInChromatic
|
|
87
98
|
|
|
99
|
+
// Avatars don't render the participant name in the bubble — only the initial.
|
|
100
|
+
// LongName stays as a Storybook fixture for documentation but adds no visual
|
|
101
|
+
// regression value beyond what Default + WithoutImage already cover.
|
|
88
102
|
export const LongName: StoryFn<ComponentProps> = Template.bind({})
|
|
89
103
|
LongName.args = {
|
|
90
104
|
id: 'user-8',
|
|
91
105
|
name: 'Alexander Christopher Wellington-Montgomery III',
|
|
92
106
|
}
|
|
107
|
+
LongName.parameters = { chromatic: { disableSnapshot: true } }
|
|
93
108
|
|
|
94
109
|
export const DifferentColors: StoryFn = () => {
|
|
95
110
|
const users = [
|
|
@@ -117,6 +132,10 @@ export const DifferentColors: StoryFn = () => {
|
|
|
117
132
|
)
|
|
118
133
|
}
|
|
119
134
|
|
|
135
|
+
// Showcase grid that crosses image-present/absent with different users.
|
|
136
|
+
// Each individual case is already covered by `Default` (image) and
|
|
137
|
+
// `WithoutImage` (initial) — skip the showcase in Chromatic so a single
|
|
138
|
+
// pravatar avatar flake doesn't diff the whole grid.
|
|
120
139
|
export const MixedAvatars: StoryFn = () => {
|
|
121
140
|
const users = [
|
|
122
141
|
{ id: 'user-1', name: 'Alice Anderson', image: 'https://i.pravatar.cc/150?img=1' },
|
|
@@ -140,6 +159,7 @@ export const MixedAvatars: StoryFn = () => {
|
|
|
140
159
|
</div>
|
|
141
160
|
)
|
|
142
161
|
}
|
|
162
|
+
MixedAvatars.parameters = { chromatic: { disableSnapshot: true } }
|
|
143
163
|
|
|
144
164
|
export const VariousSizes: StoryFn = () => {
|
|
145
165
|
const sizes = [20, 32, 40, 56, 80]
|
|
@@ -19,15 +19,19 @@ const { getBlockedUsersMock, blockUserMock, unBlockUserMock } = vi.hoisted(
|
|
|
19
19
|
})
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
// Stable service + context references — production MessagingProvider keeps
|
|
23
|
+
// the service in useState and memoizes the context value, so the hook's
|
|
24
|
+
// service-keyed lookup invalidation only fires on real swaps. Returning a
|
|
25
|
+
// new object literal each call would re-trigger the lookup every render and
|
|
26
|
+
// loop forever.
|
|
27
|
+
const mockService = {
|
|
28
|
+
getBlockedUsers: getBlockedUsersMock,
|
|
29
|
+
blockUser: blockUserMock,
|
|
30
|
+
unBlockUser: unBlockUserMock,
|
|
31
|
+
}
|
|
32
|
+
const mockContext = { service: mockService, debug: false }
|
|
22
33
|
vi.mock('../../providers/MessagingProvider', () => ({
|
|
23
|
-
useMessagingContext: () =>
|
|
24
|
-
service: {
|
|
25
|
-
getBlockedUsers: getBlockedUsersMock,
|
|
26
|
-
blockUser: blockUserMock,
|
|
27
|
-
unBlockUser: unBlockUserMock,
|
|
28
|
-
},
|
|
29
|
-
debug: false,
|
|
30
|
-
}),
|
|
34
|
+
useMessagingContext: () => mockContext,
|
|
31
35
|
}))
|
|
32
36
|
|
|
33
37
|
vi.mock('../ActionButton', () => ({
|
|
@@ -229,6 +233,27 @@ describe('ChannelActionsMenu', () => {
|
|
|
229
233
|
})
|
|
230
234
|
})
|
|
231
235
|
|
|
236
|
+
it('recovers from a blocked-status lookup failure', async () => {
|
|
237
|
+
// Suppress the expected console.error for the rejected lookup so the
|
|
238
|
+
// test output stays clean.
|
|
239
|
+
const consoleErrorSpy = vi
|
|
240
|
+
.spyOn(console, 'error')
|
|
241
|
+
.mockImplementation(() => {})
|
|
242
|
+
getBlockedUsersMock.mockRejectedValueOnce(new Error('network down'))
|
|
243
|
+
|
|
244
|
+
renderWithProviders(<ChannelActionsMenu {...defaultProps()} />)
|
|
245
|
+
await openMenu()
|
|
246
|
+
|
|
247
|
+
// The lookup rejected, but the action recovers — Block becomes actionable
|
|
248
|
+
// rather than staying stuck in the disabled spinner state.
|
|
249
|
+
await waitFor(() => {
|
|
250
|
+
const block = screen.getByText('Block').closest('button')
|
|
251
|
+
expect(block).not.toBeDisabled()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
consoleErrorSpy.mockRestore()
|
|
255
|
+
})
|
|
256
|
+
|
|
232
257
|
it('calls onReportParticipantClick and opens the report page', async () => {
|
|
233
258
|
const onReportParticipantClick = vi.fn()
|
|
234
259
|
const windowOpenSpy = vi
|
|
@@ -8,11 +8,16 @@ import { ChannelList } from '.'
|
|
|
8
8
|
|
|
9
9
|
type ComponentProps = React.ComponentProps<typeof ChannelList>
|
|
10
10
|
|
|
11
|
+
// ChannelList renders a list of mocked channels driven by MockChatProvider —
|
|
12
|
+
// useful as a browsable Storybook fixture but the snapshot reflects the mock
|
|
13
|
+
// fixture rather than any reusable component contract. Skip the whole story
|
|
14
|
+
// file in Chromatic; per-channel-row visuals are covered by CustomChannelPreview.
|
|
11
15
|
const meta: Meta<ComponentProps> = {
|
|
12
16
|
title: 'ChannelList',
|
|
13
17
|
component: ChannelList,
|
|
14
18
|
parameters: {
|
|
15
19
|
layout: 'fullscreen',
|
|
20
|
+
chromatic: { disableSnapshot: true },
|
|
16
21
|
},
|
|
17
22
|
decorators: [
|
|
18
23
|
(Story) => (
|