@linktr.ee/messaging-react 4.2.0-rc-1787083791 → 4.2.0-rc-1787181113

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 (71) hide show
  1. package/dist/{Card-B4lMv-0o.js → Card-BOudQBd6.js} +2 -2
  2. package/dist/{Card-B4lMv-0o.js.map → Card-BOudQBd6.js.map} +1 -1
  3. package/dist/{Card-DWL5I62S.cjs → Card-k2959zNX.cjs} +2 -2
  4. package/dist/{Card-DWL5I62S.cjs.map → Card-k2959zNX.cjs.map} +1 -1
  5. package/dist/assets/index.css +1 -1
  6. package/dist/{index-C93LTGLc.js → index-6RdyZh6F.js} +1809 -1912
  7. package/dist/index-6RdyZh6F.js.map +1 -0
  8. package/dist/index-BcU18EjV.cjs +3 -0
  9. package/dist/index-BcU18EjV.cjs.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +78 -59
  12. package/dist/index.js +30 -26
  13. package/dist/testing.cjs +1 -1
  14. package/dist/testing.cjs.map +1 -1
  15. package/dist/testing.js +2 -2
  16. package/dist/testing.js.map +1 -1
  17. package/package.json +2 -2
  18. package/src/components/AttachmentCard/MediaPlayer.test.tsx +346 -0
  19. package/src/components/AttachmentCard/Thumbnail.test.tsx +215 -0
  20. package/src/components/AttachmentCard/index.test.tsx +160 -0
  21. package/src/components/Avatar/Avatar.test.tsx +126 -0
  22. package/src/components/Avatar/getAvatarEmoji.test.ts +39 -0
  23. package/src/components/ChannelList/ChannelListContext.test.tsx +87 -0
  24. package/src/components/ChannelList/CustomChannelPreview.test.tsx +410 -3
  25. package/src/components/ChannelList/index.test.tsx +132 -1
  26. package/src/components/CustomMessage/CustomMessage.stories.tsx +6 -314
  27. package/src/components/CustomMessage/CustomMessage.test.tsx +56 -2
  28. package/src/components/CustomMessage/Locked.stories.tsx +108 -0
  29. package/src/components/CustomMessage/MessageTag.stories.tsx +17 -53
  30. package/src/components/CustomMessage/MessageTag.test.tsx +7 -40
  31. package/src/components/CustomMessage/MessageTag.tsx +7 -39
  32. package/src/components/CustomMessage/PaidMessage.stories.tsx +42 -0
  33. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +8 -7
  34. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +2 -1
  35. package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +0 -33
  36. package/src/components/CustomMessage/StreamAttachmentMessage.tsx +0 -6
  37. package/src/components/CustomMessage/Tip.stories.tsx +48 -0
  38. package/src/components/CustomMessage/TipMessage/TipMessage.stories.tsx +51 -0
  39. package/src/components/CustomMessage/TipMessage/TipMessage.test.tsx +65 -0
  40. package/src/components/CustomMessage/TipMessage/index.tsx +27 -0
  41. package/src/components/CustomMessage/index.tsx +22 -19
  42. package/src/components/FaqList/FaqList.test.tsx +204 -0
  43. package/src/components/LinkAttachment/LinkAttachment.test.tsx +327 -0
  44. package/src/components/LinkAttachment/components/_shared/BubbleTail.test.tsx +76 -0
  45. package/src/components/LinkAttachment/components/_shared/CardBody.test.tsx +248 -0
  46. package/src/components/LinkAttachment/components/_shared/CardCta.test.tsx +123 -0
  47. package/src/components/LinkAttachment/components/_shared/CardShell.test.tsx +193 -0
  48. package/src/components/LinkAttachment/components/_shared/CardThumbnail.test.tsx +242 -0
  49. package/src/components/LinkAttachment/components/_shared/hex.test.ts +37 -0
  50. package/src/components/LinkAttachment/components/_shared/normalizeExternalHref.test.ts +72 -0
  51. package/src/components/LinkAttachment/components/_shared/useChinPalette.test.ts +162 -0
  52. package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +58 -165
  53. package/src/components/MessageAttachment/Audio/index.tsx +77 -71
  54. package/src/components/MessageAttachment/MessageAttachment.test.tsx +74 -176
  55. package/src/components/MessageAttachment/index.tsx +9 -25
  56. package/src/components/MessageAttachment/types.ts +9 -19
  57. package/src/components/MessagingShell/MessagingShell.test.tsx +20 -0
  58. package/src/components/MessagingShell/index.tsx +2 -0
  59. package/src/index.ts +4 -0
  60. package/src/logging/index.test.tsx +136 -0
  61. package/src/logging/index.tsx +48 -0
  62. package/src/providers/MessagingProvider.test.tsx +113 -1
  63. package/src/providers/MessagingProvider.tsx +25 -15
  64. package/src/stories/customMessageStoryTemplate.tsx +199 -0
  65. package/src/styles.css +10 -121
  66. package/src/testing/createMockMessagingClient.ts +2 -2
  67. package/src/types.ts +9 -0
  68. package/dist/index-B-9LkWep.cjs +0 -3
  69. package/dist/index-B-9LkWep.cjs.map +0 -1
  70. package/dist/index-C93LTGLc.js.map +0 -1
  71. package/src/components/MessageAttachment/Audio/WaveformAudioRow.tsx +0 -335
@@ -1,10 +1,16 @@
1
1
  import { StreamChatService } from '@linktr.ee/messaging-core'
2
- import type { MessagingUser } from '@linktr.ee/messaging-core'
2
+ import type {
3
+ Logger,
4
+ MessagingUser,
5
+ StreamChatServiceConfig,
6
+ } from '@linktr.ee/messaging-core'
3
7
  import { render, waitFor, act } from '@testing-library/react'
4
8
  import React from 'react'
5
9
  import type { StreamChat } from 'stream-chat'
6
10
  import { describe, it, expect, vi, beforeEach } from 'vitest'
7
11
 
12
+ import { useMessagingLogger } from '../logging'
13
+
8
14
  import { MessagingProvider, useMessagingContext } from './MessagingProvider'
9
15
 
10
16
  // Stub stream-chat-react's <Chat /> so we don't need a real, fully-wired
@@ -486,4 +492,110 @@ describe('MessagingProvider', () => {
486
492
  expect(last.hasClient).toBe(false)
487
493
  expect(last.error).toBeNull()
488
494
  })
495
+
496
+ describe('logger seam', () => {
497
+ const ServiceProbe: React.FC<{
498
+ onService: (service: StreamChatService | null) => void
499
+ }> = ({ onService }) => {
500
+ const { service } = useMessagingContext()
501
+ React.useEffect(() => {
502
+ onService(service)
503
+ }, [service, onService])
504
+ return null
505
+ }
506
+
507
+ const LoggerProbe: React.FC<{ onLogger: (logger: Logger) => void }> = ({
508
+ onLogger,
509
+ }) => {
510
+ const logger = useMessagingLogger()
511
+ React.useEffect(() => {
512
+ onLogger(logger)
513
+ }, [logger, onLogger])
514
+ return null
515
+ }
516
+
517
+ // The service stores its resolved sink privately; read it to prove which
518
+ // logger the provider handed to the constructor.
519
+ const serviceLogger = (service: StreamChatService) =>
520
+ (service as unknown as { logger: Logger }).logger
521
+
522
+ const renderWithLogger = async (
523
+ props: Partial<React.ComponentProps<typeof MessagingProvider>> & {
524
+ serviceConfig: Omit<StreamChatServiceConfig, 'apiKey'>
525
+ }
526
+ ) => {
527
+ setupServiceMock()
528
+ const services: (StreamChatService | null)[] = []
529
+ const loggers: Logger[] = []
530
+
531
+ render(
532
+ <MessagingProvider
533
+ apiKey="mock-api-key"
534
+ user={
535
+ { type: 'guest', id: 'guest-1', name: 'Guest' } as MessagingUser
536
+ }
537
+ {...props}
538
+ >
539
+ <ServiceProbe onService={(s) => services.push(s)} />
540
+ <LoggerProbe onLogger={(l) => loggers.push(l)} />
541
+ </MessagingProvider>
542
+ )
543
+
544
+ await waitFor(() => expect(services.filter(Boolean).length).toBe(1))
545
+ return {
546
+ service: services.filter(Boolean).pop() as StreamChatService,
547
+ logger: loggers[loggers.length - 1],
548
+ }
549
+ }
550
+
551
+ const baseServiceConfig: Omit<StreamChatServiceConfig, 'apiKey'> = {
552
+ fetchToken: async () => 'token',
553
+ createChannel: async () => ({ channelId: 'ch-1' }),
554
+ }
555
+
556
+ it('forwards the logger prop into the service and to the subtree', async () => {
557
+ const sink: Logger = { error: vi.fn() }
558
+ const { service, logger } = await renderWithLogger({
559
+ serviceConfig: baseServiceConfig,
560
+ logger: sink,
561
+ })
562
+
563
+ expect(serviceLogger(service)).toBe(sink)
564
+
565
+ const error = new Error('boom')
566
+ logger.error?.('[MessagingShell] Failed:', error)
567
+ expect(sink.error).toHaveBeenCalledWith('[MessagingShell] Failed:', error)
568
+ })
569
+
570
+ it('lets an explicit serviceConfig.logger win over the logger prop', async () => {
571
+ const configSink: Logger = { error: vi.fn() }
572
+ const propSink: Logger = { error: vi.fn() }
573
+ const { service } = await renderWithLogger({
574
+ serviceConfig: { ...baseServiceConfig, logger: configSink },
575
+ logger: propSink,
576
+ })
577
+
578
+ expect(serviceLogger(service)).toBe(configSink)
579
+ })
580
+
581
+ it('leaves the service on its console defaults when no logger is given', async () => {
582
+ const consoleError = vi
583
+ .spyOn(console, 'error')
584
+ .mockImplementation(() => {})
585
+ const { service, logger } = await renderWithLogger({
586
+ serviceConfig: baseServiceConfig,
587
+ })
588
+
589
+ // Not the react seam object: the service fell back to its own defaults.
590
+ expect(serviceLogger(service)).not.toBe(logger)
591
+
592
+ const error = new Error('boom')
593
+ logger.error?.('[MessagingShell] Failed:', error)
594
+ serviceLogger(service).error?.('[StreamChatService] Failed:', error)
595
+ expect(consoleError.mock.calls).toEqual([
596
+ ['[MessagingShell] Failed:', error],
597
+ ['[StreamChatService] Failed:', error],
598
+ ])
599
+ })
600
+ })
489
601
  })
@@ -13,6 +13,7 @@ import React, {
13
13
  import { StreamChat } from 'stream-chat'
14
14
  import { Chat } from 'stream-chat-react'
15
15
 
16
+ import { MessagingLoggerProvider } from '../logging'
16
17
  import type { MessagingProviderProps, MessagingCapabilities } from '../types'
17
18
 
18
19
  /**
@@ -56,6 +57,7 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
56
57
  capabilities = {},
57
58
  debug = false,
58
59
  client: injectedClient,
60
+ logger,
59
61
  }) => {
60
62
  // Create debug logger that respects the debug prop
61
63
  const debugLog = useCallback(
@@ -94,6 +96,8 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
94
96
  serviceConfigRef.current = serviceConfig
95
97
  const debugRef = useRef(debug)
96
98
  debugRef.current = debug
99
+ const loggerRef = useRef(logger)
100
+ loggerRef.current = logger
97
101
 
98
102
  // Track renders and prop changes
99
103
  const prevPropsRef = useRef({
@@ -199,6 +203,10 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
199
203
  apiKey,
200
204
  debug: debugRef.current,
201
205
  client: chatClient,
206
+ // An explicit serviceConfig.logger wins — existing injectors keep working.
207
+ ...(currentConfig.logger || !loggerRef.current
208
+ ? {}
209
+ : { logger: loggerRef.current }),
202
210
  })
203
211
 
204
212
  serviceRef.current = newService
@@ -404,20 +412,22 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
404
412
  })
405
413
 
406
414
  return (
407
- <MessagingContext.Provider value={contextValue}>
408
- {chatClient ? (
409
- <Chat
410
- client={chatClient}
411
- customClasses={{
412
- channelList:
413
- 'str-chat__channel-list str-chat__channel-list-react bg-transparent lg:border-r-2 border-r-0 border-[#0000000A]',
414
- }}
415
- >
416
- {children}
417
- </Chat>
418
- ) : (
419
- children
420
- )}
421
- </MessagingContext.Provider>
415
+ <MessagingLoggerProvider logger={logger}>
416
+ <MessagingContext.Provider value={contextValue}>
417
+ {chatClient ? (
418
+ <Chat
419
+ client={chatClient}
420
+ customClasses={{
421
+ channelList:
422
+ 'str-chat__channel-list str-chat__channel-list-react bg-transparent lg:border-r-2 border-r-0 border-[#0000000A]',
423
+ }}
424
+ >
425
+ {children}
426
+ </Chat>
427
+ ) : (
428
+ children
429
+ )}
430
+ </MessagingContext.Provider>
431
+ </MessagingLoggerProvider>
422
432
  )
423
433
  }
@@ -0,0 +1,199 @@
1
+ import '../stream-custom-data'
2
+
3
+ import type { StoryFn } from '@storybook/react'
4
+ import React, { useEffect } from 'react'
5
+ import {
6
+ Channel as ChannelType,
7
+ QueryChannelAPIResponse,
8
+ StreamChat,
9
+ } from 'stream-chat'
10
+ import {
11
+ Channel,
12
+ Chat,
13
+ MessageList,
14
+ MessageUIComponentProps,
15
+ Window,
16
+ } from 'stream-chat-react'
17
+
18
+ import { CustomMessage } from '../components/CustomMessage'
19
+ import { OutboundContext, buildOutboundsByRecordId } from '../components/CustomMessage/OutboundContext'
20
+ import CustomTypingIndicator from '../components/CustomTypingIndicator'
21
+ import { createMockStreamChatClient } from '../testing/createMockStreamChatClient'
22
+ import type { OutboundResolver } from '../types'
23
+
24
+ import { minutesAgo } from './decorators/storyTime'
25
+ import { StoryUser, storyUsers } from './decorators/storyUser'
26
+
27
+ export const createMockChannel = async (
28
+ client: StreamChat,
29
+ messages: TemplateProps['messages']
30
+ ) => {
31
+ const mockMessages = messages.map((msg, index) => ({
32
+ ...msg,
33
+ type: msg.type ?? ('regular' as const),
34
+ created_at: minutesAgo(messages.length - index),
35
+ updated_at: minutesAgo(messages.length - index),
36
+ attachments: msg.attachments ?? [],
37
+ latest_reactions: [],
38
+ own_reactions: [],
39
+ reaction_counts: {},
40
+ reaction_scores: {},
41
+ reply_count: 0,
42
+ status: 'received',
43
+ cid: 'messaging:storybook-channel-1',
44
+ mentioned_users: msg.mentioned_users ?? [],
45
+ }))
46
+
47
+ const channelData = {
48
+ members: [storyUsers.creator.id, storyUsers.visitor.id],
49
+ // Required for useUserRole: canDelete needs delete-own-message, canFlag needs flag-message
50
+ own_capabilities: ['delete-own-message', 'flag-message'],
51
+ }
52
+
53
+ const channel = client.channel(
54
+ 'messaging',
55
+ 'storybook-channel-1',
56
+ channelData
57
+ )
58
+
59
+ channel.watch = async () => {
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ channel.state.messages = mockMessages as unknown as any[]
62
+ channel.state.members = {
63
+ [storyUsers.creator.id]: {
64
+ user: storyUsers.creator,
65
+ user_id: storyUsers.creator.id,
66
+ },
67
+ [storyUsers.visitor.id]: {
68
+ user: storyUsers.visitor,
69
+ user_id: storyUsers.visitor.id,
70
+ },
71
+ }
72
+ return {
73
+ channel: channelData,
74
+ members: [],
75
+ messages: mockMessages,
76
+ watchers: [],
77
+ pinned_messages: [],
78
+ duration: '0ms',
79
+ } as unknown as QueryChannelAPIResponse
80
+ }
81
+
82
+ try {
83
+ await channel.watch()
84
+ } catch {
85
+ // Ignore errors in mock mode
86
+ }
87
+
88
+ return channel
89
+ }
90
+
91
+ export interface TemplateProps {
92
+ currentUser: StoryUser
93
+ typingUser?: StoryUser
94
+ resolveOutbound?: OutboundResolver
95
+ messages: Array<{
96
+ id: string
97
+ text: string
98
+ user: StoryUser
99
+ mentioned_users?: StoryUser[]
100
+ type?: 'regular' | 'system'
101
+ attachments?: Array<Record<string, unknown>>
102
+ metadata?: {
103
+ custom_type?:
104
+ | 'MESSAGE_TIP'
105
+ | 'MESSAGE_PAID'
106
+ | 'MESSAGE_CHATBOT'
107
+ | 'MESSAGE_ATTACHMENT'
108
+ | 'MESSAGE_OUTBOUND'
109
+ payment_status?: 'pending' | 'paid' | 'failed' | 'refunded'
110
+ amount_text?: string
111
+ attachment_title?: string
112
+ attachment_mime_type?: string
113
+ attachment_thumbnail?: string
114
+ attachment_detail?: string
115
+ attachment_content_type?: 'text' | 'media'
116
+ outbound_id?: string
117
+ }
118
+ }>
119
+ }
120
+
121
+ const TemplateInner: React.FC<{
122
+ currentUser: StoryUser
123
+ typingUser?: StoryUser
124
+ messages: TemplateProps['messages']
125
+ resolveOutbound?: OutboundResolver
126
+ }> = ({ currentUser, typingUser, messages, resolveOutbound }) => {
127
+ const [client] = React.useState(() => createMockStreamChatClient(currentUser))
128
+
129
+ const [channel, setChannel] = React.useState<ChannelType | null>(null)
130
+
131
+ useEffect(() => {
132
+ createMockChannel(client, messages).then(setChannel)
133
+ }, [client, messages])
134
+
135
+ useEffect(() => {
136
+ if (!channel || !typingUser) {
137
+ return
138
+ }
139
+
140
+ // Stream Channel UI only updates typing context from typing.start/typing.stop events.
141
+ const timer = setTimeout(() => {
142
+ client.dispatchEvent({
143
+ type: 'typing.start',
144
+ cid: channel.cid,
145
+ user: typingUser,
146
+ })
147
+ }, 0)
148
+
149
+ return () => clearTimeout(timer)
150
+ }, [channel, client, typingUser])
151
+
152
+ const outboundsByRecordId = React.useMemo(
153
+ () => buildOutboundsByRecordId(messages, resolveOutbound),
154
+ [messages, resolveOutbound]
155
+ )
156
+
157
+ const MessageComponent = React.useMemo(() => {
158
+ return function CustomMessageComponent(props: MessageUIComponentProps) {
159
+ return <CustomMessage {...props} onOutboundClick={() => undefined} />
160
+ }
161
+ }, [])
162
+
163
+ if (!channel) {
164
+ return <div className="p-4">Loading...</div>
165
+ }
166
+
167
+ return (
168
+ <OutboundContext.Provider value={outboundsByRecordId}>
169
+ <Chat client={client}>
170
+ <div className="h-screen w-full bg-white">
171
+ <Channel
172
+ channel={channel}
173
+ Message={MessageComponent}
174
+ TypingIndicator={CustomTypingIndicator}
175
+ >
176
+ <Window>
177
+ <MessageList />
178
+ </Window>
179
+ </Channel>
180
+ </div>
181
+ </Chat>
182
+ </OutboundContext.Provider>
183
+ )
184
+ }
185
+
186
+ export const Template: StoryFn<TemplateProps> = ({
187
+ currentUser = storyUsers.creator,
188
+ typingUser,
189
+ messages,
190
+ resolveOutbound,
191
+ }) => (
192
+ <TemplateInner
193
+ key={currentUser.id}
194
+ currentUser={currentUser}
195
+ typingUser={typingUser}
196
+ messages={messages}
197
+ resolveOutbound={resolveOutbound}
198
+ />
199
+ )
package/src/styles.css CHANGED
@@ -924,12 +924,21 @@
924
924
  align-items: flex-start;
925
925
  }
926
926
 
927
- /* Gap between locked attachment card and accompanying text bubble */
927
+ /* Gap between a wrapper's stacked bubbles (e.g. StreamAttachmentMessage's
928
+ media + caption, MediaMessage's card + text) */
928
929
  .str-chat__message-bubble-wrapper
929
930
  > .str-chat__message-bubble:not(:first-child) {
930
931
  margin-top: 4px;
931
932
  }
932
933
 
934
+ /* Tighter gap for the locked-attachment card + text bubble and the tip
935
+ pill + text bubble — same margin-top mechanism, smaller value. Must come
936
+ after the rule above: equal specificity, so source order decides. */
937
+ .message-locked-attachment-wrapper > .str-chat__message-bubble:not(:first-child),
938
+ .message-tip-wrapper > .str-chat__message-bubble:not(:first-child) {
939
+ margin-top: 2px;
940
+ }
941
+
933
942
  /** Remove padding from the text inside the locked attachment text shell,
934
943
  and inherit TextShell's explicit colour — stream-chat-react's own
935
944
  `MessageText` sets its own (black) colour that only gets overridden by
@@ -941,123 +950,3 @@
941
950
  .message-locked-attachment-text .str-chat__message-text-inner {
942
951
  color: inherit;
943
952
  }
944
-
945
- /* Audio attachment waveform — `MessageAttachment.Audio` (MES-1360).
946
-
947
- Plain CSS rather than Tailwind utilities for two reasons: the per-bar
948
- height is a floored custom property that has to be authored once
949
- rather than repeated on 35 elements, and the played / unplayed fills
950
- are the one design decision Figma left open (it draws the idle state
951
- only), so keeping them in a single place is what makes them reviewable.
952
- Follows the existing `.mes-media-viewer*` / `.mes-dialog` convention:
953
- the package's shipped stylesheet is enough, with no Tailwind
954
- dependency in the consumer.
955
-
956
- Geometry is Figma `2754:17710` → `sound-wave` (`…;2754:8065`): a
957
- 138×30 track holding 35 bars at 2px with 2px gaps
958
- (35 x 2 + 34 x 2 = 138). Bars flex from a zero basis, so the exact
959
- 2px falls out of the 138px track and a narrower container degrades
960
- proportionally instead of clipping. */
961
- .mes-audio-wave {
962
- position: relative;
963
- display: flex;
964
- flex: 1 1 0;
965
- min-width: 0;
966
- align-items: center;
967
- height: 30px;
968
- }
969
-
970
- .mes-audio-wave__track {
971
- display: flex;
972
- width: 100%;
973
- height: 100%;
974
- align-items: center;
975
- gap: 2px;
976
- }
977
-
978
- /* Figma's bars run 4px..30px on a 30px track, so the amplitude
979
- percentage is floored at that 4px minimum: a silent sample — or the
980
- all-zero fallback used when no waveform could be produced — still
981
- draws the shortest bar the design already contains, keeping the
982
- card's silhouette identical instead of collapsing it. */
983
- .mes-audio-wave__bar {
984
- flex: 1 1 0;
985
- min-width: 0;
986
- height: max(4px, var(--mes-audio-wave-bar, 0%));
987
- border-radius: 9999px;
988
- background: rgb(255 255 255 / 0.4);
989
- }
990
-
991
- .mes-audio-wave__bar--played {
992
- background: #ffffff;
993
- }
994
-
995
- .mes-audio-wave--light .mes-audio-wave__bar {
996
- background: rgb(4 4 3 / 0.5);
997
- }
998
-
999
- .mes-audio-wave--light .mes-audio-wave__bar--played {
1000
- background: #040403;
1001
- }
1002
-
1003
- /* Seek is a real `<input type="range">` stretched over the bars, which
1004
- supplies click, drag, arrow keys, Home / End and a screen-reader
1005
- slider role natively. The bars are the visible track, so the input
1006
- itself must not paint — but it must stay focusable, which is why the
1007
- thumb and track are cleared individually rather than the whole input
1008
- being given `opacity: 0`. */
1009
- .mes-audio-wave__seek {
1010
- position: absolute;
1011
- inset: 0;
1012
- width: 100%;
1013
- height: 100%;
1014
- margin: 0;
1015
- padding: 0;
1016
- cursor: pointer;
1017
- background: transparent;
1018
- -webkit-appearance: none;
1019
- appearance: none;
1020
- }
1021
-
1022
- .mes-audio-wave__seek:disabled {
1023
- cursor: default;
1024
- }
1025
-
1026
- .mes-audio-wave__seek::-webkit-slider-runnable-track {
1027
- background: transparent;
1028
- }
1029
-
1030
- .mes-audio-wave__seek::-webkit-slider-thumb {
1031
- -webkit-appearance: none;
1032
- appearance: none;
1033
- width: 1px;
1034
- height: 100%;
1035
- opacity: 0;
1036
- }
1037
-
1038
- .mes-audio-wave__seek::-moz-range-track {
1039
- background: transparent;
1040
- }
1041
-
1042
- .mes-audio-wave__seek::-moz-range-thumb {
1043
- width: 1px;
1044
- height: 100%;
1045
- border: 0;
1046
- opacity: 0;
1047
- }
1048
-
1049
- .mes-audio-wave__seek:focus {
1050
- outline: none;
1051
- }
1052
-
1053
- /* The input is transparent, so the focus ring has to render on the
1054
- wrapper that actually has pixels. */
1055
- .mes-audio-wave:has(.mes-audio-wave__seek:focus-visible) {
1056
- outline: 2px solid rgb(255 255 255 / 0.6);
1057
- outline-offset: 4px;
1058
- border-radius: 4px;
1059
- }
1060
-
1061
- .mes-audio-wave--light:has(.mes-audio-wave__seek:focus-visible) {
1062
- outline-color: rgb(4 4 3 / 0.6);
1063
- }
@@ -141,11 +141,11 @@ export function createMockMessagingClient({
141
141
  })
142
142
  // Read/unread bookkeeping consults channel mute status; without a live
143
143
  // connection the real method throws, so report "not muted".
144
- channel.muteStatus = (() => ({
144
+ channel.muteStatus = () => ({
145
145
  muted: false,
146
146
  createdAt: null,
147
147
  expiresAt: null,
148
- })) as any
148
+ })
149
149
 
150
150
  // Neutralise the rest of the connection-dependent channel API that
151
151
  // stream-chat-react calls during the chat lifecycle. Each of these POSTs to
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ Logger,
2
3
  MessagingUser,
3
4
  StreamChatServiceConfig,
4
5
  } from '@linktr.ee/messaging-core'
@@ -453,6 +454,7 @@ export type ChannelViewPassthroughProps = Pick<
453
454
  | 'resolveOutbound'
454
455
  | 'showChannelInfo'
455
456
  | 'composerInput'
457
+ | 'attachmentPreviewList'
456
458
  >
457
459
 
458
460
  /**
@@ -500,6 +502,13 @@ interface MessagingProviderBaseProps {
500
502
  user: MessagingUser | null
501
503
  capabilities?: MessagingCapabilities
502
504
  debug?: boolean
505
+ /**
506
+ * Optional sink for the package's own log output. Levels left undefined fall
507
+ * back to console, which is also the default when the prop is omitted. In
508
+ * apiKey mode it is forwarded to the underlying StreamChatService unless
509
+ * `serviceConfig.logger` is set, which wins.
510
+ */
511
+ logger?: Logger
503
512
  }
504
513
 
505
514
  /**