@linktr.ee/messaging-react 3.18.0 → 3.19.0-rc-1785389440

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 (46) hide show
  1. package/dist/{Card-owYJxu9h.js → Card-B2Wwdd_f.js} +2 -2
  2. package/dist/Card-B2Wwdd_f.js.map +1 -0
  3. package/dist/{Card-DdJcz7Np.cjs → Card-BASTLo6d.cjs} +2 -2
  4. package/dist/Card-BASTLo6d.cjs.map +1 -0
  5. package/dist/assets/index.css +1 -1
  6. package/dist/index-BOwprCNq.cjs +2 -0
  7. package/dist/index-BOwprCNq.cjs.map +1 -0
  8. package/dist/{index-CsPg1TYf.js → index-CXGGi2Cy.js} +1347 -1297
  9. package/dist/index-CXGGi2Cy.js.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +14 -2
  12. package/dist/index.js +1 -1
  13. package/dist/testing.cjs.map +1 -1
  14. package/dist/testing.d.ts +3 -1
  15. package/dist/testing.js.map +1 -1
  16. package/package.json +4 -4
  17. package/src/components/ChannelView.stories.tsx +1 -2
  18. package/src/components/ChannelView.tsx +6 -1
  19. package/src/components/CustomMessage/CustomMessage.stories.tsx +40 -0
  20. package/src/components/CustomMessage/CustomMessage.test.tsx +141 -0
  21. package/src/components/CustomMessage/LockedAttachment/components/_shared/useReceivedSource.ts +3 -0
  22. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +76 -4
  23. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +75 -7
  24. package/src/components/CustomMessage/index.tsx +5 -2
  25. package/src/components/LinkAttachment/components/_shared/useChinPalette.ts +3 -0
  26. package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +0 -1
  27. package/src/components/MessageAttachment/File/FileAttachment.stories.tsx +0 -1
  28. package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +0 -1
  29. package/src/components/MessageAttachment/Pdf/PdfAttachment.stories.tsx +0 -1
  30. package/src/components/MessageAttachment/Video/VideoAttachment.stories.tsx +0 -1
  31. package/src/components/MessageAttachment/_shared/VideoViewer.tsx +0 -1
  32. package/src/components/MessageAttachment/_shared/useCarousel.ts +3 -0
  33. package/src/components/MessagingShell/index.tsx +5 -0
  34. package/src/hooks/useChannelModerationActions.ts +3 -0
  35. package/src/hooks/useChannelStar.ts +3 -0
  36. package/src/index.ts +1 -0
  37. package/src/providers/MessagingProvider.tsx +3 -0
  38. package/src/stream-custom-data.ts +3 -0
  39. package/src/styles.css +48 -5
  40. package/src/testing/createMockMessagingClient.ts +0 -1
  41. package/src/types.ts +12 -0
  42. package/dist/Card-DdJcz7Np.cjs.map +0 -1
  43. package/dist/Card-owYJxu9h.js.map +0 -1
  44. package/dist/index-BQ4mQDo2.cjs +0 -2
  45. package/dist/index-BQ4mQDo2.cjs.map +0 -1
  46. package/dist/index-CsPg1TYf.js.map +0 -1
@@ -1,8 +1,5 @@
1
1
  import React from 'react'
2
- import {
3
- useChannelStateContext,
4
- useMessageContext,
5
- } from 'stream-chat-react'
2
+ import { useChannelStateContext, useMessageContext } from 'stream-chat-react'
6
3
  import { beforeEach, describe, expect, it, vi } from 'vitest'
7
4
 
8
5
  import { renderWithProviders, screen, within } from '../../test/utils'
@@ -69,6 +66,7 @@ describe('SentMessageDeliveryStatus', () => {
69
66
  // avatars.
70
67
  mockedUseMessageContext.mockReturnValue({
71
68
  message: { id: 'own-2' },
69
+ isMyMessage: () => true,
72
70
  } as never)
73
71
 
74
72
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -83,6 +81,7 @@ describe('SentMessageDeliveryStatus', () => {
83
81
  it('renders nothing for an earlier own message once a later message exists', () => {
84
82
  mockedUseMessageContext.mockReturnValue({
85
83
  message: { id: 'own-1' },
84
+ isMyMessage: () => true,
86
85
  } as never)
87
86
 
88
87
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -104,6 +103,7 @@ describe('SentMessageDeliveryStatus', () => {
104
103
  } as never)
105
104
  mockedUseMessageContext.mockReturnValue({
106
105
  message: { id: 'own-2' },
106
+ isMyMessage: () => true,
107
107
  } as never)
108
108
 
109
109
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -124,6 +124,7 @@ describe('SentMessageDeliveryStatus', () => {
124
124
  } as never)
125
125
  mockedUseMessageContext.mockReturnValue({
126
126
  message: { id: 'own-2' },
127
+ isMyMessage: () => true,
127
128
  } as never)
128
129
 
129
130
  renderWithProviders(<SentMessageDeliveryStatus />)
@@ -138,6 +139,7 @@ describe('SentMessageDeliveryStatus', () => {
138
139
  it('renders "Sending" in the sending slot for the latest message', () => {
139
140
  mockedUseMessageContext.mockReturnValue({
140
141
  message: { id: 'own-2', status: 'sending' },
142
+ isMyMessage: () => true,
141
143
  threadList: false,
142
144
  } as never)
143
145
 
@@ -149,6 +151,7 @@ describe('SentMessageDeliveryStatus', () => {
149
151
  it('does not render "Sending" for an earlier message', () => {
150
152
  mockedUseMessageContext.mockReturnValue({
151
153
  message: { id: 'own-1', status: 'sending' },
154
+ isMyMessage: () => true,
152
155
  threadList: false,
153
156
  } as never)
154
157
 
@@ -162,6 +165,7 @@ describe('SentMessageDeliveryStatus', () => {
162
165
  // threadList is true; we suppress the stamp ourselves.
163
166
  mockedUseMessageContext.mockReturnValue({
164
167
  message: { id: 'own-2', status: 'sending' },
168
+ isMyMessage: () => true,
165
169
  threadList: true,
166
170
  } as never)
167
171
 
@@ -173,6 +177,74 @@ describe('SentMessageDeliveryStatus', () => {
173
177
  ).not.toBeInTheDocument()
174
178
  })
175
179
 
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
+
176
248
  it('renders "Failed to send" for every own failed message', () => {
177
249
  mockedUseMessageContext.mockReturnValue({
178
250
  message: { id: 'own-1', status: 'failed', type: 'regular' },
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ChecksIcon,
3
3
  CircleNotchIcon,
4
+ MegaphoneIcon,
4
5
  WarningCircleIcon,
5
6
  } from '@phosphor-icons/react'
6
7
  import {
@@ -93,6 +94,44 @@ const SendingStatus = () => {
93
94
  )
94
95
  }
95
96
 
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
+
96
135
  const FailedStatus = () => (
97
136
  <span className="str-chat__message-status sent-message-status--failed">
98
137
  <WarningCircleIcon weight="bold" aria-hidden="true" />
@@ -114,10 +153,15 @@ const FailedStatus = () => (
114
153
  * because it has no failed slot and are shown on every own, non-thread
115
154
  * failed message.
116
155
  */
117
- export const SentMessageDeliveryStatus = () => {
156
+ export const SentMessageDeliveryStatus = ({
157
+ onBroadcastClick,
158
+ }: {
159
+ onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
160
+ }) => {
118
161
  const { isMyMessage, message, threadList } = useMessageContext(
119
162
  'SentMessageDeliveryStatus'
120
163
  )
164
+ const isOwnMessage = isMyMessage()
121
165
 
122
166
  // Stream's MessageStatus still mounts MessageSendingStatus when
123
167
  // threadList is true; keep all of our stamps out of thread replies.
@@ -125,15 +169,28 @@ export const SentMessageDeliveryStatus = () => {
125
169
  return null
126
170
  }
127
171
 
128
- if (
129
- message.status === 'failed' &&
130
- isMyMessage() &&
131
- message.type !== 'error'
132
- ) {
172
+ if (message.status === 'failed' && isOwnMessage && message.type !== 'error') {
133
173
  return <FailedStatus />
134
174
  }
135
175
 
136
- return (
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 = (
137
194
  <MessageStatus
138
195
  MessageSentStatus={DeliveredStatus}
139
196
  MessageDeliveredStatus={DeliveredStatus}
@@ -141,4 +198,15 @@ export const SentMessageDeliveryStatus = () => {
141
198
  MessageSendingStatus={SendingStatus}
142
199
  />
143
200
  )
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
+ )
144
212
  }
@@ -54,11 +54,13 @@ import StreamAttachmentMessage, {
54
54
  type CustomMessageUIComponentProps = MessageUIComponentProps & {
55
55
  chatbotVotingEnabled?: boolean
56
56
  viewerLanguage?: string
57
+ onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
57
58
  }
58
59
 
59
60
  type CustomMessageWithContextProps = MessageContextValue & {
60
61
  chatbotVotingEnabled?: boolean
61
62
  viewerLanguage?: string
63
+ onBroadcastClick?: (broadcast: { id: string; name?: string }) => void
62
64
  }
63
65
 
64
66
  const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
@@ -79,6 +81,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
79
81
  renderText,
80
82
  threadList,
81
83
  viewerLanguage,
84
+ onBroadcastClick,
82
85
  } = props
83
86
 
84
87
  const { client } = useChatContext('CustomMessage')
@@ -278,7 +281,6 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
278
281
  dmAgentEnabled={isChatbot}
279
282
  />
280
283
  )}
281
- {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
282
284
  <div
283
285
  className={classNames('str-chat__message-inner', {
284
286
  'str-chat__simple-message--error-failed': allowRetry || isBounced,
@@ -424,7 +426,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
424
426
  )}
425
427
  </div>
426
428
  )}
427
- <SentMessageDeliveryStatus />
429
+ <SentMessageDeliveryStatus onBroadcastClick={onBroadcastClick} />
428
430
  {showReplyCountButton && (
429
431
  <MessageRepliesCountButton
430
432
  onClick={handleOpenThread}
@@ -444,6 +446,7 @@ const MemoizedCustomMessage = React.memo(
444
446
  (prev, next) => {
445
447
  if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false
446
448
  if (prev.viewerLanguage !== next.viewerLanguage) return false
449
+ if (prev.onBroadcastClick !== next.onBroadcastClick) return false
447
450
  return areMessageUIPropsEqual(prev, next)
448
451
  }
449
452
  ) as typeof CustomMessageWithContext
@@ -1,3 +1,6 @@
1
+ /* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React
2
+ Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
3
+ out of scope for the lint toolchain upgrade. */
1
4
  import React from 'react'
2
5
 
3
6
  import type { LinkAttachmentLayout, LinkAttachmentStatus } from '../../types'
@@ -39,7 +39,6 @@ const meta: Meta = {
39
39
  export default meta
40
40
 
41
41
  const handleDismiss = () => {
42
- // eslint-disable-next-line no-console
43
42
  console.log('Dismissed audio attachment')
44
43
  }
45
44
 
@@ -49,7 +49,6 @@ const meta: Meta = {
49
49
  export default meta
50
50
 
51
51
  const handleDismiss = () => {
52
- // eslint-disable-next-line no-console
53
52
  console.log('Dismissed file attachment')
54
53
  }
55
54
 
@@ -74,7 +74,6 @@ const meta: Meta = {
74
74
  export default meta
75
75
 
76
76
  const handleDismiss = () => {
77
- // eslint-disable-next-line no-console
78
77
  console.log('Dismissed image attachment')
79
78
  }
80
79
 
@@ -27,7 +27,6 @@ const meta: Meta = {
27
27
  export default meta
28
28
 
29
29
  const handleDismiss = () => {
30
- // eslint-disable-next-line no-console
31
30
  console.log('Dismissed PDF attachment')
32
31
  }
33
32
 
@@ -46,7 +46,6 @@ const meta: Meta = {
46
46
  export default meta
47
47
 
48
48
  const handleDismiss = () => {
49
- // eslint-disable-next-line no-console
50
49
  console.log('Dismissed video attachment')
51
50
  }
52
51
 
@@ -74,7 +74,6 @@ const VideoViewer: React.FC<VideoViewerProps> = ({
74
74
  for chat attachments, and an empty `<track kind="captions" />`
75
75
  emits a runtime warning. Re-add a real track (with `src`,
76
76
  `srcLang`, and `default`) once caption support ships. */}
77
- {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
78
77
  <video
79
78
  // Forcing a key swap on item change ensures the new source
80
79
  // mounts a fresh element instead of reusing the previous
@@ -1,3 +1,6 @@
1
+ /* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React
2
+ Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
3
+ out of scope for the lint toolchain upgrade. */
1
4
  import { useCallback, useEffect, useState } from 'react'
2
5
 
3
6
  const clamp = (value: number, min: number, max: number) =>
@@ -1,3 +1,6 @@
1
+ /* eslint-disable react-hooks/refs -- pre-existing pattern flagged by the React
2
+ Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
3
+ out of scope for the lint toolchain upgrade. */
1
4
  import React, { useCallback, useEffect, useRef, useState } from 'react'
2
5
  import type { Channel } from 'stream-chat'
3
6
 
@@ -35,6 +38,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
35
38
  customChannelActions,
36
39
  renderChannelActions,
37
40
  onParticipantNameClick,
41
+ onBroadcastClick,
38
42
  renderMessage,
39
43
  onMessageLinkClick,
40
44
  showChannelInfo,
@@ -260,6 +264,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
260
264
  customChannelActions={customChannelActions}
261
265
  renderChannelActions={renderChannelActions}
262
266
  onParticipantNameClick={onParticipantNameClick}
267
+ onBroadcastClick={onBroadcastClick}
263
268
  renderMessage={renderMessage}
264
269
  onMessageLinkClick={onMessageLinkClick}
265
270
  showChannelInfo={showChannelInfo}
@@ -1,3 +1,6 @@
1
+ /* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React
2
+ Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
3
+ out of scope for the lint toolchain upgrade. */
1
4
  import { useEffect, useState } from 'react'
2
5
  import type { Channel as ChannelType, ChannelMemberResponse } from 'stream-chat'
3
6
 
@@ -1,3 +1,6 @@
1
+ /* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React
2
+ Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
3
+ out of scope for the lint toolchain upgrade. */
1
4
  import { useEffect, useState } from 'react'
2
5
  import { Channel, Event } from 'stream-chat'
3
6
 
package/src/index.ts CHANGED
@@ -60,6 +60,7 @@ export type {
60
60
  MessagingProviderProps,
61
61
  MessagingCapabilities,
62
62
  MessageLinkClickHandler,
63
+ BroadcastClickHandler,
63
64
  Participant,
64
65
  LockedAttachmentSource,
65
66
  } from './types'
@@ -1,3 +1,6 @@
1
+ /* eslint-disable react-hooks/refs, react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React
2
+ Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
3
+ out of scope for the lint toolchain upgrade. */
1
4
  import { StreamChatService } from '@linktr.ee/messaging-core'
2
5
  import React, {
3
6
  createContext,
@@ -27,6 +27,7 @@ export type MessageCustomType =
27
27
  | 'MESSAGE_TIP'
28
28
  | 'MESSAGE_PAID'
29
29
  | 'MESSAGE_CHATBOT'
30
+ | 'MESSAGE_OUTBOUND'
30
31
  | 'MESSAGE_ATTACHMENT'
31
32
  | AgeSafetySystemType
32
33
  | EscalationSystemType
@@ -57,6 +58,8 @@ export interface MessageMetadata {
57
58
  * `custom_type` — it identifies the send context, not the message kind. (MES-1266)
58
59
  */
59
60
  sent_from?: 'stack_detail'
61
+ broadcast_id?: string
62
+ broadcast_name?: string
60
63
  }
61
64
 
62
65
  /**
package/src/styles.css CHANGED
@@ -480,6 +480,19 @@
480
480
  position: relative;
481
481
  }
482
482
 
483
+ .sent-message-broadcast-link {
484
+ display: inline-flex;
485
+ align-items: center;
486
+ gap: 0.25rem;
487
+ background: none;
488
+ border: 0;
489
+ padding: 0;
490
+ font: inherit;
491
+ color: inherit;
492
+ cursor: pointer;
493
+ text-decoration: underline;
494
+ }
495
+
483
496
  .str-chat__li .str-chat__message-inner {
484
497
  grid-column-gap: 4px;
485
498
  }
@@ -581,11 +594,12 @@
581
594
  }
582
595
 
583
596
  /* Message delivery status under sent messages (MES-1036/MES-1296).
584
- `SentMessageDeliveryStatus` renders it through stream's `MessageStatus`,
585
- whose `.str-chat__message-status` span is the grid item here (right-aligned
586
- via the outer grid's `justify-items: end`). Stream emits that span for every
587
- own message and leaves it empty when no state is active (i.e. not the last
588
- sent message), so `:empty` collapses those away. */
597
+ `SentMessageDeliveryStatus` renders stream's `.str-chat__message-status`
598
+ directly when there is no broadcast label. When a broadcast label is
599
+ present, `.sent-message-status-row` is the grid item and contains the
600
+ stream status span. Both cases are right-aligned via the outer grid's
601
+ `justify-items: end`; stream's empty status span collapses when no state is
602
+ active (i.e. not the last sent message). */
589
603
  .str-chat__li .str-chat__message--me .str-chat__message-status {
590
604
  grid-area: delivery-status;
591
605
  justify-content: flex-end;
@@ -596,6 +610,28 @@
596
610
  line-height: 1.4;
597
611
  color: var(--str-chat__text-low-emphasis-color, rgba(0, 0, 0, 0.5));
598
612
  }
613
+ .str-chat__li .str-chat__message--me .sent-message-status-row {
614
+ display: inline-flex;
615
+ grid-area: delivery-status;
616
+ align-items: center;
617
+ justify-content: flex-end;
618
+ gap: 4px;
619
+ padding-block-start: 4px;
620
+ padding-inline-end: 16px;
621
+ font-size: 10px;
622
+ line-height: 1.4;
623
+ color: var(--str-chat__text-low-emphasis-color, rgba(0, 0, 0, 0.5));
624
+ }
625
+ .str-chat__li .str-chat__message--me .sent-message-status-row
626
+ .str-chat__message-status {
627
+ grid-area: auto;
628
+ justify-content: inherit;
629
+ gap: inherit;
630
+ padding: 0;
631
+ font: inherit;
632
+ line-height: inherit;
633
+ color: inherit;
634
+ }
599
635
  .str-chat__li .str-chat__message-status:empty {
600
636
  display: none;
601
637
  }
@@ -608,6 +644,13 @@
608
644
  .str-chat__li .str-chat__message--me .str-chat__message-status svg path {
609
645
  fill: currentColor;
610
646
  }
647
+ .str-chat__li .str-chat__message--me .sent-message-status-row svg {
648
+ width: 14px;
649
+ height: 14px;
650
+ }
651
+ .str-chat__li .str-chat__message--me .sent-message-status-row svg path {
652
+ fill: currentColor;
653
+ }
611
654
  .str-chat__li .str-chat__message--me .sent-message-status--failed {
612
655
  color: var(--str-chat__message-error-color, #d92d20);
613
656
  }
@@ -145,7 +145,6 @@ export function createMockMessagingClient({
145
145
  })
146
146
  // Read/unread bookkeeping consults channel mute status; without a live
147
147
  // connection the real method throws, so report "not muted".
148
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
148
  channel.muteStatus = (() => ({
150
149
  muted: false,
151
150
  createdAt: null,
package/src/types.ts CHANGED
@@ -36,6 +36,11 @@ export type MessageLinkClickHandler = (
36
36
  message: LocalMessage | undefined
37
37
  ) => void
38
38
 
39
+ export type BroadcastClickHandler = (broadcast: {
40
+ id: string
41
+ name?: string
42
+ }) => void
43
+
39
44
  export type ChannelPreviewProps = ChannelPreviewUIComponentProps & {
40
45
  selectedChannel?: Channel | null
41
46
  onChannelSelect: (channel: Channel) => void
@@ -362,6 +367,12 @@ export interface ChannelViewProps {
362
367
  */
363
368
  onMessageLinkClick?: MessageLinkClickHandler
364
369
 
370
+ /**
371
+ * Fired when a broadcast label is clicked on an outgoing broadcast message.
372
+ * Hosts own navigation so this shared package never assumes an admin URL.
373
+ */
374
+ onBroadcastClick?: BroadcastClickHandler
375
+
365
376
  /**
366
377
  * Passed to Stream `Channel` as `SendButton`. Required for hosts that replace
367
378
  * the send control: `Channel` merges this into `ComponentContext` and an
@@ -430,6 +441,7 @@ export type ChannelViewPassthroughProps = Pick<
430
441
  | 'onParticipantNameClick'
431
442
  | 'renderMessage'
432
443
  | 'onMessageLinkClick'
444
+ | 'onBroadcastClick'
433
445
  | 'showChannelInfo'
434
446
  | 'composerInput'
435
447
  >