@linktr.ee/messaging-react 3.17.0-rc-1785345258 → 3.17.0

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.
Files changed (34) hide show
  1. package/dist/{Card-Cyp6T66U.cjs → Card-D7OY-zlB.cjs} +2 -2
  2. package/dist/{Card-Cyp6T66U.cjs.map → Card-D7OY-zlB.cjs.map} +1 -1
  3. package/dist/{Card-DDZI9ZIj.js → Card-DkW8wedR.js} +2 -2
  4. package/dist/{Card-DDZI9ZIj.js.map → Card-DkW8wedR.js.map} +1 -1
  5. package/dist/assets/index.css +1 -1
  6. package/dist/index-BFZ-G_jg.cjs +2 -0
  7. package/dist/index-BFZ-G_jg.cjs.map +1 -0
  8. package/dist/{index-DskHxSVN.js → index-CxGxAIhL.js} +1568 -1527
  9. package/dist/index-CxGxAIhL.js.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +2 -14
  12. package/dist/index.js +1 -1
  13. package/dist/testing.d.ts +1 -3
  14. package/package.json +2 -2
  15. package/src/components/ChannelView.tsx +1 -6
  16. package/src/components/CustomMessage/CustomMessage.stories.tsx +0 -40
  17. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +4 -76
  18. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +7 -75
  19. package/src/components/CustomMessage/index.tsx +1 -4
  20. package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +77 -0
  21. package/src/components/MessageAttachment/Image/index.tsx +117 -8
  22. package/src/components/MessageAttachment/MessageAttachment.test.tsx +177 -0
  23. package/src/components/MessageAttachment/Video/index.tsx +1 -5
  24. package/src/components/MessageAttachment/_shared/Bubble.tsx +40 -7
  25. package/src/components/MessageAttachment/_shared/ImageViewer.tsx +25 -16
  26. package/src/components/MessageAttachment/_shared/MediaStackGrid.tsx +105 -7
  27. package/src/components/MessagingShell/index.tsx +0 -2
  28. package/src/index.ts +0 -1
  29. package/src/stream-custom-data.ts +0 -3
  30. package/src/styles.css +5 -48
  31. package/src/types.ts +0 -12
  32. package/dist/index-DBjALsli.cjs +0 -2
  33. package/dist/index-DBjALsli.cjs.map +0 -1
  34. package/dist/index-DskHxSVN.js.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-DBjALsli.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.MessageBubble=e.MessageBubble;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.optimizeMessagingAttachmentUrl=e.optimizeMessagingAttachmentUrl;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BFZ-G_jg.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.MessageBubble=e.MessageBubble;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.optimizeMessagingAttachmentUrl=e.optimizeMessagingAttachmentUrl;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -83,11 +83,6 @@ export declare interface AvatarProps {
83
83
  dmAgentEnabled?: boolean;
84
84
  }
85
85
 
86
- export declare type BroadcastClickHandler = (broadcast: {
87
- id: string;
88
- name?: string;
89
- }) => void;
90
-
91
86
  /**
92
87
  * Build the meta line shown under the title in compact document / file
93
88
  * attachments — `EXT · SIZE` (`PDF · 379.5 KB`). Either part is dropped
@@ -211,7 +206,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
211
206
  * Props that MessagingShell passes through to ChannelView.
212
207
  * ChannelViewProps is the source of truth for these props.
213
208
  */
214
- declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'onBroadcastClick' | 'showChannelInfo' | 'composerInput'>;
209
+ declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'showChannelInfo' | 'composerInput'>;
215
210
 
216
211
  /**
217
212
  * ChannelView component props
@@ -397,11 +392,6 @@ export declare interface ChannelViewProps {
397
392
  * onMessageLinkClick={(url) => trackChannelLinkClicked(url)}
398
393
  */
399
394
  onMessageLinkClick?: MessageLinkClickHandler;
400
- /**
401
- * Fired when a broadcast label is clicked on an outgoing broadcast message.
402
- * Hosts own navigation so this shared package never assumes an admin URL.
403
- */
404
- onBroadcastClick?: BroadcastClickHandler;
405
395
  /**
406
396
  * Passed to Stream `Channel` as `SendButton`. Required for hosts that replace
407
397
  * the send control: `Channel` merges this into `ComponentContext` and an
@@ -1299,7 +1289,7 @@ export declare interface MessageBubbleBaseProps {
1299
1289
  */
1300
1290
  export declare type MessageBubbleVariant = 'sent' | 'received';
1301
1291
 
1302
- declare type MessageCustomType = 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | 'MESSAGE_OUTBOUND' | 'MESSAGE_ATTACHMENT' | AgeSafetySystemType | EscalationSystemType | DmAgentSystemType;
1292
+ declare type MessageCustomType = 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | 'MESSAGE_ATTACHMENT' | AgeSafetySystemType | EscalationSystemType | DmAgentSystemType;
1303
1293
 
1304
1294
  /**
1305
1295
  * Callback invoked when a user clicks a link inside the channel view.
@@ -1330,8 +1320,6 @@ export declare interface MessageMetadata {
1330
1320
  * `custom_type` — it identifies the send context, not the message kind. (MES-1266)
1331
1321
  */
1332
1322
  sent_from?: 'stack_detail';
1333
- broadcast_id?: string;
1334
- broadcast_name?: string;
1335
1323
  }
1336
1324
 
1337
1325
  export declare const MessageVoteButtons: default_2.FC<MessageVoteButtonsProps>;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as p, m as d, n as v, p as C, q as A, s as k, t as F, u as b, v as P, w as f, x, o as y, y as z, z as B, B as S, D as q, E as D, G as E, H as V, I as w } from "./index-DskHxSVN.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as p, m as d, n as v, p as C, q as A, s as k, t as F, u as b, v as P, w as f, x, o as y, y as z, z as B, B as S, D as q, E as D, G as E, H as V, I as w } from "./index-CxGxAIhL.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/dist/testing.d.ts CHANGED
@@ -41,7 +41,7 @@ declare type DmAgentSystemType = 'SYSTEM_DM_AGENT_PAUSED' | 'SYSTEM_DM_AGENT_RES
41
41
 
42
42
  declare type EscalationSystemType = 'SYSTEM_ESCALATION';
43
43
 
44
- declare type MessageCustomType = 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | 'MESSAGE_OUTBOUND' | 'MESSAGE_ATTACHMENT' | AgeSafetySystemType | EscalationSystemType | DmAgentSystemType;
44
+ declare type MessageCustomType = 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | 'MESSAGE_ATTACHMENT' | AgeSafetySystemType | EscalationSystemType | DmAgentSystemType;
45
45
 
46
46
  export declare interface MessageMetadata {
47
47
  custom_type?: MessageCustomType;
@@ -62,8 +62,6 @@ export declare interface MessageMetadata {
62
62
  * `custom_type` — it identifies the send context, not the message kind. (MES-1266)
63
63
  */
64
64
  sent_from?: 'stack_detail';
65
- broadcast_id?: string;
66
- broadcast_name?: string;
67
65
  }
68
66
 
69
67
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.17.0-rc-1785345258",
3
+ "version": "3.17.0",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@linktr.ee/component-library": "11.8.6",
53
- "@linktr.ee/messaging-core": "2.3.0-rc-1785345258",
53
+ "@linktr.ee/messaging-core": "^2.2.0",
54
54
  "@phosphor-icons/react": "^2.1.10"
55
55
  },
56
56
  "devDependencies": {
@@ -56,7 +56,6 @@ const ChannelViewInner: React.FC<{
56
56
  viewerLanguage?: string
57
57
  showChannelInfo?: boolean
58
58
  onParticipantNameClick?: () => void
59
- onBroadcastClick?: ChannelViewProps['onBroadcastClick']
60
59
  composerInput?: ChannelViewProps['composerInput']
61
60
  }> = ({
62
61
  onBack,
@@ -83,7 +82,6 @@ const ChannelViewInner: React.FC<{
83
82
  viewerLanguage,
84
83
  showChannelInfo = true,
85
84
  onParticipantNameClick,
86
- onBroadcastClick,
87
85
  composerInput,
88
86
  }) => {
89
87
  const { channel } = useChannelStateContext()
@@ -127,7 +125,6 @@ const ChannelViewInner: React.FC<{
127
125
  {...props}
128
126
  chatbotVotingEnabled={chatbotVotingEnabled}
129
127
  viewerLanguage={viewerLanguage}
130
- onBroadcastClick={onBroadcastClick}
131
128
  />
132
129
  )
133
130
 
@@ -137,7 +134,7 @@ const ChannelViewInner: React.FC<{
137
134
 
138
135
  return renderMessage(messageNode, message)
139
136
  },
140
- [chatbotVotingEnabled, onBroadcastClick, renderMessage, viewerLanguage]
137
+ [chatbotVotingEnabled, renderMessage, viewerLanguage]
141
138
  )
142
139
 
143
140
  return (
@@ -295,7 +292,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
295
292
  viewerLanguage,
296
293
  showChannelInfo = true,
297
294
  onParticipantNameClick,
298
- onBroadcastClick,
299
295
  composerInput,
300
296
  }) => {
301
297
  // Custom send message handler that:
@@ -409,7 +405,6 @@ export const ChannelView = React.memo<ChannelViewProps>(
409
405
  viewerLanguage={viewerLanguage}
410
406
  showChannelInfo={showChannelInfo}
411
407
  onParticipantNameClick={onParticipantNameClick}
412
- onBroadcastClick={onBroadcastClick}
413
408
  composerInput={composerInput}
414
409
  />
415
410
  </Channel>
@@ -120,7 +120,6 @@ interface TemplateProps {
120
120
  | 'MESSAGE_PAID'
121
121
  | 'MESSAGE_CHATBOT'
122
122
  | 'MESSAGE_ATTACHMENT'
123
- | 'MESSAGE_OUTBOUND'
124
123
  payment_status?: 'pending' | 'paid' | 'failed' | 'refunded'
125
124
  amount_text?: string
126
125
  attachment_title?: string
@@ -128,8 +127,6 @@ interface TemplateProps {
128
127
  attachment_thumbnail?: string
129
128
  attachment_detail?: string
130
129
  attachment_content_type?: 'text' | 'media'
131
- broadcast_id?: string
132
- broadcast_name?: string
133
130
  }
134
131
  }>
135
132
  }
@@ -260,43 +257,6 @@ DeliveredStatus.args = {
260
257
  ],
261
258
  }
262
259
 
263
- export const BroadcastStatus: StoryFn<TemplateProps> = Template.bind({})
264
- BroadcastStatus.args = {
265
- messages: [
266
- {
267
- id: 'msg-1',
268
- text: 'July class details',
269
- user: storyUsers.creator,
270
- metadata: {
271
- custom_type: 'MESSAGE_OUTBOUND',
272
- broadcast_id: 'broadcast-1',
273
- broadcast_name: 'July 9th Class attendees',
274
- },
275
- },
276
- ],
277
- }
278
-
279
- export const BroadcastStatusNonLatest: StoryFn<TemplateProps> = Template.bind({})
280
- BroadcastStatusNonLatest.args = {
281
- messages: [
282
- {
283
- id: 'msg-1',
284
- text: 'July class details',
285
- user: storyUsers.creator,
286
- metadata: {
287
- custom_type: 'MESSAGE_OUTBOUND',
288
- broadcast_id: 'broadcast-1',
289
- broadcast_name: 'July 9th Class attendees',
290
- },
291
- },
292
- {
293
- id: 'msg-2',
294
- text: 'Let me know if you have any questions.',
295
- user: storyUsers.creator,
296
- },
297
- ],
298
- }
299
-
300
260
  export const WithTipTag: StoryFn<TemplateProps> = Template.bind({})
301
261
  WithTipTag.args = {
302
262
  messages: [
@@ -1,5 +1,8 @@
1
1
  import React from 'react'
2
- import { useChannelStateContext, useMessageContext } from 'stream-chat-react'
2
+ import {
3
+ useChannelStateContext,
4
+ useMessageContext,
5
+ } from 'stream-chat-react'
3
6
  import { beforeEach, describe, expect, it, vi } from 'vitest'
4
7
 
5
8
  import { renderWithProviders, screen, within } from '../../test/utils'
@@ -66,7 +69,6 @@ describe('SentMessageDeliveryStatus', () => {
66
69
  // avatars.
67
70
  mockedUseMessageContext.mockReturnValue({
68
71
  message: { id: 'own-2' },
69
- isMyMessage: () => true,
70
72
  } as never)
71
73
 
72
74
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -81,7 +83,6 @@ describe('SentMessageDeliveryStatus', () => {
81
83
  it('renders nothing for an earlier own message once a later message exists', () => {
82
84
  mockedUseMessageContext.mockReturnValue({
83
85
  message: { id: 'own-1' },
84
- isMyMessage: () => true,
85
86
  } as never)
86
87
 
87
88
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -103,7 +104,6 @@ describe('SentMessageDeliveryStatus', () => {
103
104
  } as never)
104
105
  mockedUseMessageContext.mockReturnValue({
105
106
  message: { id: 'own-2' },
106
- isMyMessage: () => true,
107
107
  } as never)
108
108
 
109
109
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -124,7 +124,6 @@ describe('SentMessageDeliveryStatus', () => {
124
124
  } as never)
125
125
  mockedUseMessageContext.mockReturnValue({
126
126
  message: { id: 'own-2' },
127
- isMyMessage: () => true,
128
127
  } as never)
129
128
 
130
129
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -139,7 +138,6 @@ describe('SentMessageDeliveryStatus', () => {
139
138
  it('renders "Sending" in the sending slot for the latest message', () => {
140
139
  mockedUseMessageContext.mockReturnValue({
141
140
  message: { id: 'own-2', status: 'sending' },
142
- isMyMessage: () => true,
143
141
  threadList: false,
144
142
  } as never)
145
143
 
@@ -151,7 +149,6 @@ describe('SentMessageDeliveryStatus', () => {
151
149
  it('does not render "Sending" for an earlier message', () => {
152
150
  mockedUseMessageContext.mockReturnValue({
153
151
  message: { id: 'own-1', status: 'sending' },
154
- isMyMessage: () => true,
155
152
  threadList: false,
156
153
  } as never)
157
154
 
@@ -165,7 +162,6 @@ describe('SentMessageDeliveryStatus', () => {
165
162
  // threadList is true; we suppress the stamp ourselves.
166
163
  mockedUseMessageContext.mockReturnValue({
167
164
  message: { id: 'own-2', status: 'sending' },
168
- isMyMessage: () => true,
169
165
  threadList: true,
170
166
  } as never)
171
167
 
@@ -177,74 +173,6 @@ describe('SentMessageDeliveryStatus', () => {
177
173
  ).not.toBeInTheDocument()
178
174
  })
179
175
 
180
- it('renders a broadcast label for every own broadcast message, including non-latest messages', () => {
181
- mockedUseMessageContext.mockReturnValue({
182
- message: {
183
- id: 'own-1',
184
- metadata: {
185
- broadcast_id: 'broadcast-1',
186
- broadcast_name: 'July 9th Class attendees',
187
- },
188
- },
189
- isMyMessage: () => true,
190
- threadList: false,
191
- } as never)
192
-
193
- renderWithProviders(<SentMessageDeliveryStatus />)
194
-
195
- expect(
196
- screen.getByTestId('sent-message-broadcast-status')
197
- ).toHaveTextContent('July 9th Class attendees')
198
- expect(
199
- screen.queryByTestId('sent-message-delivery-status')
200
- ).not.toBeInTheDocument()
201
- expect(screen.queryByText('•')).not.toBeInTheDocument()
202
- })
203
-
204
- it('uses a generic label and invokes the broadcast callback when the name is missing', () => {
205
- const onBroadcastClick = vi.fn()
206
- mockedUseMessageContext.mockReturnValue({
207
- message: { id: 'own-2', metadata: { broadcast_id: 'broadcast-1' } },
208
- isMyMessage: () => true,
209
- threadList: false,
210
- } as never)
211
-
212
- renderWithProviders(
213
- <SentMessageDeliveryStatus onBroadcastClick={onBroadcastClick} />
214
- )
215
-
216
- expect(
217
- screen.getByTestId('sent-message-broadcast-status')
218
- ).toHaveTextContent('Broadcast')
219
- expect(
220
- screen.getByRole('button', { name: 'View broadcast Broadcast' })
221
- ).toBeInTheDocument()
222
- screen.getByRole('button', { name: 'View broadcast Broadcast' }).click()
223
- expect(onBroadcastClick).toHaveBeenCalledWith({ id: 'broadcast-1' })
224
- })
225
-
226
- it('does not render a broadcast label for a visitor viewing a broadcast message', () => {
227
- mockedUseMessageContext.mockReturnValue({
228
- message: {
229
- id: 'broadcast-1',
230
- metadata: {
231
- broadcast_id: 'broadcast-1',
232
- broadcast_name: 'July 9th Class attendees',
233
- },
234
- },
235
- isMyMessage: () => false,
236
- threadList: false,
237
- } as never)
238
-
239
- renderWithProviders(
240
- <SentMessageDeliveryStatus onBroadcastClick={vi.fn()} />
241
- )
242
-
243
- expect(
244
- screen.queryByTestId('sent-message-broadcast-status')
245
- ).not.toBeInTheDocument()
246
- })
247
-
248
176
  it('renders "Failed to send" for every own failed message', () => {
249
177
  mockedUseMessageContext.mockReturnValue({
250
178
  message: { id: 'own-1', status: 'failed', type: 'regular' },
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  ChecksIcon,
3
3
  CircleNotchIcon,
4
- MegaphoneIcon,
5
4
  WarningCircleIcon,
6
5
  } from '@phosphor-icons/react'
7
6
  import {
@@ -94,44 +93,6 @@ const SendingStatus = () => {
94
93
  )
95
94
  }
96
95
 
97
- const BroadcastStatus = ({
98
- broadcastId,
99
- broadcastName,
100
- onBroadcastClick,
101
- }: {
102
- broadcastId: string
103
- broadcastName?: string
104
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
105
- }) => {
106
- const name = broadcastName || 'Broadcast'
107
- const isLatestRealMessage = useIsLatestRealMessage()
108
- const content = (
109
- <>
110
- {isLatestRealMessage && <span aria-hidden="true">•</span>}
111
- <MegaphoneIcon weight="bold" aria-hidden="true" />
112
- <span data-testid="sent-message-broadcast-status">{name}</span>
113
- </>
114
- )
115
-
116
- return onBroadcastClick ? (
117
- <button
118
- type="button"
119
- className="sent-message-broadcast-link"
120
- aria-label={`View broadcast ${name}`}
121
- onClick={() =>
122
- onBroadcastClick({
123
- id: broadcastId,
124
- ...(broadcastName ? { name: broadcastName } : {}),
125
- })
126
- }
127
- >
128
- {content}
129
- </button>
130
- ) : (
131
- content
132
- )
133
- }
134
-
135
96
  const FailedStatus = () => (
136
97
  <span className="str-chat__message-status sent-message-status--failed">
137
98
  <WarningCircleIcon weight="bold" aria-hidden="true" />
@@ -153,15 +114,10 @@ const FailedStatus = () => (
153
114
  * because it has no failed slot and are shown on every own, non-thread
154
115
  * failed message.
155
116
  */
156
- export const SentMessageDeliveryStatus = ({
157
- onBroadcastClick,
158
- }: {
159
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
160
- }) => {
117
+ export const SentMessageDeliveryStatus = () => {
161
118
  const { isMyMessage, message, threadList } = useMessageContext(
162
119
  'SentMessageDeliveryStatus'
163
120
  )
164
- const isOwnMessage = isMyMessage()
165
121
 
166
122
  // Stream's MessageStatus still mounts MessageSendingStatus when
167
123
  // threadList is true; keep all of our stamps out of thread replies.
@@ -169,28 +125,15 @@ export const SentMessageDeliveryStatus = ({
169
125
  return null
170
126
  }
171
127
 
172
- if (message.status === 'failed' && isOwnMessage && message.type !== 'error') {
128
+ if (
129
+ message.status === 'failed' &&
130
+ isMyMessage() &&
131
+ message.type !== 'error'
132
+ ) {
173
133
  return <FailedStatus />
174
134
  }
175
135
 
176
- if (!isOwnMessage) {
177
- return null
178
- }
179
-
180
- const broadcastId = message.metadata?.broadcast_id
181
- const broadcastName = message.metadata?.broadcast_name
182
- const broadcastStatus =
183
- message.status !== 'sending' &&
184
- message.status !== 'failed' &&
185
- broadcastId ? (
186
- <BroadcastStatus
187
- broadcastId={broadcastId}
188
- broadcastName={broadcastName}
189
- onBroadcastClick={onBroadcastClick}
190
- />
191
- ) : null
192
-
193
- const messageStatus = (
136
+ return (
194
137
  <MessageStatus
195
138
  MessageSentStatus={DeliveredStatus}
196
139
  MessageDeliveredStatus={DeliveredStatus}
@@ -198,15 +141,4 @@ export const SentMessageDeliveryStatus = ({
198
141
  MessageSendingStatus={SendingStatus}
199
142
  />
200
143
  )
201
-
202
- if (!broadcastStatus) {
203
- return messageStatus
204
- }
205
-
206
- return (
207
- <span className="sent-message-status-row">
208
- {messageStatus}
209
- {broadcastStatus}
210
- </span>
211
- )
212
144
  }
@@ -54,13 +54,11 @@ import StreamAttachmentMessage, {
54
54
  type CustomMessageUIComponentProps = MessageUIComponentProps & {
55
55
  chatbotVotingEnabled?: boolean
56
56
  viewerLanguage?: string
57
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
58
57
  }
59
58
 
60
59
  type CustomMessageWithContextProps = MessageContextValue & {
61
60
  chatbotVotingEnabled?: boolean
62
61
  viewerLanguage?: string
63
- onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
64
62
  }
65
63
 
66
64
  const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
@@ -81,7 +79,6 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
81
79
  renderText,
82
80
  threadList,
83
81
  viewerLanguage,
84
- onBroadcastClick,
85
82
  } = props
86
83
 
87
84
  const { client } = useChatContext('CustomMessage')
@@ -427,7 +424,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
427
424
  )}
428
425
  </div>
429
426
  )}
430
- <SentMessageDeliveryStatus onBroadcastClick={onBroadcastClick} />
427
+ <SentMessageDeliveryStatus />
431
428
  {showReplyCountButton && (
432
429
  <MessageRepliesCountButton
433
430
  onClick={handleOpenThread}
@@ -21,6 +21,52 @@ const STACK_IMAGES: ImageItem[] = [
21
21
  { src: 'https://picsum.photos/seed/img-6/720/720', alt: 'Photo 6' },
22
22
  ]
23
23
 
24
+ /**
25
+ * One source per aspect bucket, with intrinsic dimensions supplied so
26
+ * the card resolves at first paint instead of waiting on a network
27
+ * decode. Expected web cards (326×436 content box inside a 2px
28
+ * frame): portrait 249×440, landscape 330×187, square 330×330,
29
+ * panorama 330×69.
30
+ */
31
+ const ASPECT_BUCKETS: { label: string; item: ImageItem }[] = [
32
+ {
33
+ label: 'Portrait 9:16',
34
+ item: {
35
+ src: 'https://picsum.photos/seed/ratio-portrait/1080/1920',
36
+ alt: 'Portrait photo',
37
+ width: 1080,
38
+ height: 1920,
39
+ },
40
+ },
41
+ {
42
+ label: 'Landscape 16:9',
43
+ item: {
44
+ src: 'https://picsum.photos/seed/ratio-landscape/1920/1080',
45
+ alt: 'Landscape photo',
46
+ width: 1920,
47
+ height: 1080,
48
+ },
49
+ },
50
+ {
51
+ label: 'Square 1:1',
52
+ item: {
53
+ src: 'https://picsum.photos/seed/ratio-square/1200/1200',
54
+ alt: 'Square photo',
55
+ width: 1200,
56
+ height: 1200,
57
+ },
58
+ },
59
+ {
60
+ label: 'Panorama 5:1',
61
+ item: {
62
+ src: 'https://picsum.photos/seed/ratio-panorama/3000/600',
63
+ alt: 'Panoramic photo',
64
+ width: 3000,
65
+ height: 600,
66
+ },
67
+ },
68
+ ]
69
+
24
70
  const meta: Meta = {
25
71
  title: 'MessageAttachment/Image',
26
72
  parameters: { layout: 'fullscreen' },
@@ -75,6 +121,37 @@ export const Single: StoryFn = () => (
75
121
  </StoryPage>
76
122
  )
77
123
 
124
+ /**
125
+ * Single image, one row per aspect bucket. The image keeps its own
126
+ * ratio, fitted inside a max 326×436 content box on web (309×436
127
+ * below the `sm` breakpoint) — so `object-cover` never crops. Portrait
128
+ * resolves against the 440px height cap; landscape, square and
129
+ * panorama against the 330px width cap.
130
+ *
131
+ * Sent + Received only: the composer thumbnail is a fixed square and
132
+ * is unaffected by this treatment, so the Composer column is blank.
133
+ */
134
+ export const SingleAspectRatios: StoryFn = () => (
135
+ <StoryPage>
136
+ <StoryHeading
137
+ title="Single image — aspect ratios"
138
+ description="Pascal's original defect was every single image being forced to 1:1. Each row here is one source ratio; the card adopts the fitted box rather than cropping to a square. Composer column is intentionally blank — the composer thumbnail is unchanged."
139
+ />
140
+ <StoryGrid>
141
+ {ASPECT_BUCKETS.map(({ label, item }) => (
142
+ <StoryRow
143
+ key={label}
144
+ label={label}
145
+ sent={<ImageAttachment.Sent items={[item]} filename="photo.jpg" />}
146
+ received={
147
+ <ImageAttachment.Received items={[item]} filename="photo.jpg" />
148
+ }
149
+ />
150
+ ))}
151
+ </StoryGrid>
152
+ </StoryPage>
153
+ )
154
+
78
155
  /** Single image with an accompanying caption rendered inside the bubble.
79
156
  * Sent + Received only — captions are not rendered on the composer
80
157
  * surface (the user types them in the message input, not on the