@linktr.ee/messaging-react 1.0.2 → 1.1.0-rc-1760927977

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 (39) hide show
  1. package/dist/assets/index.css +1 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +836 -1079
  4. package/dist/index.js.map +1 -1
  5. package/package.json +4 -3
  6. package/src/components/ActionButton/ActionButton.stories.tsx +2 -1
  7. package/src/components/ActionButton/ActionButton.test.tsx +2 -0
  8. package/src/components/Avatar/Avatar.stories.tsx +2 -1
  9. package/src/components/Avatar/index.tsx +2 -1
  10. package/src/components/ChannelList/ChannelList.stories.tsx +4 -2
  11. package/src/components/ChannelList/CustomChannelPreview.stories.tsx +31 -27
  12. package/src/components/ChannelList/CustomChannelPreview.tsx +5 -11
  13. package/src/components/ChannelList/index.tsx +43 -35
  14. package/src/components/ChannelView.tsx +150 -127
  15. package/src/components/CloseButton/index.tsx +4 -5
  16. package/src/components/IconButton/IconButton.stories.tsx +3 -3
  17. package/src/components/Loading/Loading.stories.tsx +2 -1
  18. package/src/components/Loading/index.tsx +7 -9
  19. package/src/components/MessagingShell/EmptyState.stories.tsx +2 -1
  20. package/src/components/MessagingShell/ErrorState.stories.tsx +2 -1
  21. package/src/components/MessagingShell/LoadingState.stories.tsx +2 -1
  22. package/src/components/MessagingShell/LoadingState.tsx +3 -5
  23. package/src/components/MessagingShell/index.tsx +159 -135
  24. package/src/components/ParticipantPicker/ParticipantItem.stories.tsx +4 -2
  25. package/src/components/ParticipantPicker/ParticipantItem.tsx +25 -21
  26. package/src/components/ParticipantPicker/ParticipantPicker.stories.tsx +4 -2
  27. package/src/components/ParticipantPicker/ParticipantPicker.tsx +104 -76
  28. package/src/components/ParticipantPicker/index.tsx +93 -72
  29. package/src/components/SearchInput/SearchInput.stories.tsx +2 -1
  30. package/src/components/SearchInput/SearchInput.test.tsx +4 -2
  31. package/src/components/SearchInput/index.tsx +14 -15
  32. package/src/hooks/useParticipants.ts +1 -0
  33. package/src/index.ts +3 -0
  34. package/src/providers/MessagingProvider.tsx +213 -135
  35. package/src/stories/mocks.tsx +18 -19
  36. package/src/styles.css +75 -0
  37. package/src/test/setup.ts +11 -12
  38. package/src/test/utils.tsx +6 -7
  39. package/src/types.ts +1 -1
@@ -1,27 +1,34 @@
1
- import React, { createContext, useContext, useEffect, useState, useRef, useCallback } from 'react';
2
- import type { Channel } from 'stream-chat';
3
- import { Chat } from 'stream-chat-react';
4
- import { StreamChatService } from '@linktr.ee/messaging-core';
1
+ import { StreamChatService } from '@linktr.ee/messaging-core'
2
+ import React, {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useState,
7
+ useRef,
8
+ useCallback,
9
+ } from 'react'
10
+ import type { StreamChat } from 'stream-chat'
11
+ import { Chat } from 'stream-chat-react'
5
12
 
6
- import type {
7
- MessagingProviderProps,
13
+ import type {
14
+ MessagingProviderProps,
8
15
  MessagingCapabilities,
9
- MessagingCustomization
10
- } from '../types';
16
+ MessagingCustomization,
17
+ } from '../types'
11
18
 
12
19
  /**
13
20
  * Context value for messaging state and service
14
21
  */
15
22
  export interface MessagingContextValue {
16
- service: StreamChatService | null;
17
- client: any; // Stream Chat client
18
- isConnected: boolean;
19
- isLoading: boolean;
20
- error: string | null;
21
- capabilities: MessagingCapabilities;
22
- customization: MessagingCustomization;
23
- refreshConnection: () => Promise<void>;
24
- debug: boolean;
23
+ service: StreamChatService | null
24
+ client: StreamChat | null // Stream Chat client
25
+ isConnected: boolean
26
+ isLoading: boolean
27
+ error: string | null
28
+ capabilities: MessagingCapabilities
29
+ customization: MessagingCustomization
30
+ refreshConnection: () => Promise<void>
31
+ debug: boolean
25
32
  }
26
33
 
27
34
  const MessagingContext = createContext<MessagingContextValue>({
@@ -34,12 +41,12 @@ const MessagingContext = createContext<MessagingContextValue>({
34
41
  customization: {},
35
42
  refreshConnection: async () => {},
36
43
  debug: false,
37
- });
44
+ })
38
45
 
39
46
  /**
40
47
  * Hook to access messaging context
41
48
  */
42
- export const useMessagingContext = () => useContext(MessagingContext);
49
+ export const useMessagingContext = () => useContext(MessagingContext)
43
50
 
44
51
  /**
45
52
  * Provider component that wraps messaging-core with React state management
@@ -54,91 +61,122 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
54
61
  debug = false,
55
62
  }) => {
56
63
  // Create debug logger that respects the debug prop
57
- const debugLog = (message: string, ...args: any[]) => {
58
- if (debug) {
59
- console.log(`🔥 [MessagingProvider] ${message}`, ...args);
60
- }
61
- };
64
+ const debugLog = useCallback(
65
+ (message: string, ...args: unknown[]) => {
66
+ if (debug) {
67
+ console.log(`🔥 [MessagingProvider] ${message}`, ...args)
68
+ }
69
+ },
70
+ [debug]
71
+ )
62
72
 
63
73
  debugLog('🔄 RENDER START', {
64
74
  userId: user?.id,
65
75
  apiKey: apiKey?.substring(0, 8) + '...',
66
76
  serviceConfig: !!serviceConfig,
67
77
  capabilities: Object.keys(capabilities),
68
- customization: Object.keys(customization)
69
- });
70
-
71
- const [service, setService] = useState<StreamChatService | null>(null);
72
- const [client, setClient] = useState<any>(null);
73
- const [isConnected, setIsConnected] = useState(false);
74
- const [isLoading, setIsLoading] = useState(false);
75
- const [error, setError] = useState<string | null>(null);
76
-
78
+ customization: Object.keys(customization),
79
+ })
80
+
81
+ const [service, setService] = useState<StreamChatService | null>(null)
82
+ const [client, setClient] = useState<StreamChat | null>(null)
83
+ const [isConnected, setIsConnected] = useState(false)
84
+ const [isLoading, setIsLoading] = useState(false)
85
+ const [error, setError] = useState<string | null>(null)
86
+
77
87
  // Prevent multiple concurrent connection attempts
78
- const connectingRef = useRef(false);
88
+ const connectingRef = useRef(false)
79
89
 
80
90
  // Track renders and prop changes
81
- const prevPropsRef = useRef({ userId: user?.id, apiKey, serviceConfig, capabilities, customization });
82
- const renderCountRef = useRef(0);
83
- renderCountRef.current++;
84
-
91
+ const prevPropsRef = useRef({
92
+ userId: user?.id,
93
+ apiKey,
94
+ serviceConfig,
95
+ capabilities,
96
+ customization,
97
+ })
98
+ const renderCountRef = useRef(0)
99
+ renderCountRef.current++
100
+
85
101
  debugLog('📊 RENDER INFO', {
86
102
  renderCount: renderCountRef.current,
87
103
  currentProps: { userId: user?.id, apiKey: apiKey?.substring(0, 8) + '...' },
88
104
  propChanges: {
89
105
  userChanged: prevPropsRef.current.userId !== user?.id,
90
106
  apiKeyChanged: prevPropsRef.current.apiKey !== apiKey,
91
- serviceConfigChanged: prevPropsRef.current.serviceConfig !== serviceConfig,
107
+ serviceConfigChanged:
108
+ prevPropsRef.current.serviceConfig !== serviceConfig,
92
109
  capabilitiesChanged: prevPropsRef.current.capabilities !== capabilities,
93
- customizationChanged: prevPropsRef.current.customization !== customization
94
- }
95
- });
96
-
97
- prevPropsRef.current = { userId: user?.id, apiKey, serviceConfig, capabilities, customization };
110
+ customizationChanged:
111
+ prevPropsRef.current.customization !== customization,
112
+ },
113
+ })
114
+
115
+ prevPropsRef.current = {
116
+ userId: user?.id,
117
+ apiKey,
118
+ serviceConfig,
119
+ capabilities,
120
+ customization,
121
+ }
98
122
 
99
123
  // Initialize service when config changes
100
124
  useEffect(() => {
101
- const currentRender = renderCountRef.current;
125
+ const currentRender = renderCountRef.current
102
126
  debugLog('🔧 SERVICE INIT EFFECT TRIGGERED', {
103
127
  renderCount: currentRender,
104
128
  apiKey: !!apiKey,
105
129
  serviceConfig: !!serviceConfig,
106
- dependencies: {
107
- apiKey: apiKey?.substring(0, 8) + '...',
130
+ dependencies: {
131
+ apiKey: apiKey?.substring(0, 8) + '...',
108
132
  serviceConfigRef: serviceConfig,
109
- serviceConfigStable: prevPropsRef.current.serviceConfig === serviceConfig,
110
- apiKeyStable: prevPropsRef.current.apiKey === apiKey
111
- }
112
- });
133
+ serviceConfigStable:
134
+ prevPropsRef.current.serviceConfig === serviceConfig,
135
+ apiKeyStable: prevPropsRef.current.apiKey === apiKey,
136
+ },
137
+ })
113
138
 
114
139
  if (!apiKey || !serviceConfig) {
115
- debugLog('⚠️ SERVICE INIT SKIPPED', { renderCount: currentRender, reason: 'Missing apiKey or serviceConfig' });
116
- return;
140
+ debugLog('⚠️ SERVICE INIT SKIPPED', {
141
+ renderCount: currentRender,
142
+ reason: 'Missing apiKey or serviceConfig',
143
+ })
144
+ return
117
145
  }
118
146
 
119
- debugLog('🚀 CREATING NEW SERVICE', {
147
+ debugLog('🚀 CREATING NEW SERVICE', {
120
148
  renderCount: currentRender,
121
149
  apiKey: apiKey?.substring(0, 8) + '...',
122
- serviceConfigChanged: prevPropsRef.current.serviceConfig !== serviceConfig
123
- });
124
-
150
+ serviceConfigChanged:
151
+ prevPropsRef.current.serviceConfig !== serviceConfig,
152
+ })
153
+
125
154
  const newService = new StreamChatService({
126
155
  ...serviceConfig,
127
156
  apiKey,
128
157
  debug,
129
- });
158
+ })
159
+
160
+ setService(newService)
161
+ debugLog('✅ SERVICE SET', {
162
+ renderCount: currentRender,
163
+ serviceInstance: !!newService,
164
+ })
130
165
 
131
- setService(newService);
132
- debugLog('✅ SERVICE SET', { renderCount: currentRender, serviceInstance: !!newService });
133
-
134
166
  return () => {
135
- debugLog('🧹 SERVICE CLEANUP', { renderCount: currentRender, reason: 'Effect cleanup' });
136
- newService.disconnectUser().catch(console.error);
137
- };
138
- }, [apiKey, serviceConfig]); // Use serviceConfig object directly, not individual properties
167
+ debugLog('🧹 SERVICE CLEANUP', {
168
+ renderCount: currentRender,
169
+ reason: 'Effect cleanup',
170
+ })
171
+ newService.disconnectUser().catch(console.error)
172
+ }
173
+ }, [apiKey, serviceConfig, debug, debugLog]) // Use serviceConfig object directly, not individual properties
139
174
 
140
175
  // Track if we've already connected this user with this service to prevent duplicate connections
141
- const connectedUserRef = useRef<{serviceId: any, userId: string} | null>(null);
176
+ const connectedUserRef = useRef<{
177
+ serviceId: StreamChatService
178
+ userId: string
179
+ } | null>(null)
142
180
 
143
181
  // Connect user when service and user are available
144
182
  useEffect(() => {
@@ -148,94 +186,125 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
148
186
  userId: user?.id,
149
187
  isConnecting: connectingRef.current,
150
188
  isConnected: isConnected,
151
- dependencies: { service: !!service, userId: user?.id }
152
- });
189
+ dependencies: { service: !!service, userId: user?.id },
190
+ })
153
191
 
154
192
  if (!service || !user) {
155
- debugLog('⚠️ USER CONNECTION SKIPPED', 'Missing service or user');
156
- return;
193
+ debugLog('⚠️ USER CONNECTION SKIPPED', 'Missing service or user')
194
+ return
157
195
  }
158
-
196
+
159
197
  if (connectingRef.current) {
160
- debugLog('⚠️ USER CONNECTION SKIPPED', 'Already connecting');
161
- return;
198
+ debugLog('⚠️ USER CONNECTION SKIPPED', 'Already connecting')
199
+ return
162
200
  }
163
-
201
+
164
202
  // Check if we've already connected this exact user with this exact service instance
165
- if (connectedUserRef.current?.serviceId === service && connectedUserRef.current?.userId === user.id) {
166
- debugLog('⚠️ USER CONNECTION SKIPPED', 'Already connected this user with this service');
167
- return;
203
+ if (
204
+ connectedUserRef.current?.serviceId === service &&
205
+ connectedUserRef.current?.userId === user.id
206
+ ) {
207
+ debugLog(
208
+ '⚠️ USER CONNECTION SKIPPED',
209
+ 'Already connected this user with this service'
210
+ )
211
+ return
168
212
  }
169
213
 
170
214
  const connectUser = async () => {
171
- debugLog('🚀 STARTING USER CONNECTION', { userId: user.id });
172
- connectingRef.current = true;
173
- setIsLoading(true);
174
- setError(null);
215
+ debugLog('🚀 STARTING USER CONNECTION', { userId: user.id })
216
+ connectingRef.current = true
217
+ setIsLoading(true)
218
+ setError(null)
175
219
 
176
220
  try {
177
- debugLog('📞 CALLING SERVICE.CONNECTUSER', { userId: user.id });
178
- const streamClient = await service.connectUser(user);
179
- setClient(streamClient);
180
- setIsConnected(true);
181
- connectedUserRef.current = { serviceId: service, userId: user.id }; // Mark as connected
182
- debugLog('✅ USER CONNECTION SUCCESS', { userId: user.id, clientId: streamClient.userID });
221
+ debugLog('📞 CALLING SERVICE.CONNECTUSER', { userId: user.id })
222
+ const streamClient = await service.connectUser(user)
223
+ setClient(streamClient)
224
+ setIsConnected(true)
225
+ connectedUserRef.current = { serviceId: service, userId: user.id } // Mark as connected
226
+ debugLog('✅ USER CONNECTION SUCCESS', {
227
+ userId: user.id,
228
+ clientId: streamClient.userID,
229
+ })
183
230
  } catch (err) {
184
- const errorMessage = err instanceof Error ? err.message : 'Connection failed';
185
- setError(errorMessage);
186
- debugLog('❌ USER CONNECTION ERROR', { userId: user.id, error: errorMessage });
231
+ const errorMessage =
232
+ err instanceof Error ? err.message : 'Connection failed'
233
+ setError(errorMessage)
234
+ debugLog('❌ USER CONNECTION ERROR', {
235
+ userId: user.id,
236
+ error: errorMessage,
237
+ })
187
238
  } finally {
188
- setIsLoading(false);
189
- connectingRef.current = false;
190
- debugLog('🔄 USER CONNECTION FINISHED', { userId: user.id, isConnected });
239
+ setIsLoading(false)
240
+ connectingRef.current = false
241
+ debugLog('🔄 USER CONNECTION FINISHED', {
242
+ userId: user.id,
243
+ isConnected,
244
+ })
191
245
  }
192
- };
246
+ }
193
247
 
194
- connectUser();
195
- }, [service, user]); // Remove isConnected to prevent circular dependency
248
+ connectUser()
249
+ }, [service, user, debugLog, isConnected]) // Remove isConnected to prevent circular dependency
196
250
 
197
251
  // Disconnect when user is removed (cleanup effect)
198
252
  useEffect(() => {
199
- debugLog('🔌 CLEANUP EFFECT REGISTERED', { hasService: !!service, isConnected });
253
+ debugLog('🔌 CLEANUP EFFECT REGISTERED', {
254
+ hasService: !!service,
255
+ isConnected,
256
+ })
200
257
  return () => {
201
258
  if (service && isConnected) {
202
- debugLog('🧹 CLEANUP EFFECT TRIGGERED', 'Cleaning up connection on unmount');
203
- connectedUserRef.current = null; // Reset connection tracking
204
- service.disconnectUser().catch(console.error);
259
+ debugLog(
260
+ '🧹 CLEANUP EFFECT TRIGGERED',
261
+ 'Cleaning up connection on unmount'
262
+ )
263
+ connectedUserRef.current = null // Reset connection tracking
264
+ service.disconnectUser().catch(console.error)
205
265
  } else {
206
- debugLog('🔇 CLEANUP EFFECT SKIPPED', { hasService: !!service, isConnected });
266
+ debugLog('🔇 CLEANUP EFFECT SKIPPED', {
267
+ hasService: !!service,
268
+ isConnected,
269
+ })
207
270
  }
208
- };
209
- }, [service, isConnected]);
271
+ }
272
+ }, [service, isConnected, debugLog])
210
273
 
211
274
  const refreshConnection = useCallback(async () => {
212
- debugLog('🔄 REFRESH CONNECTION CALLED', { hasService: !!service, hasUser: !!user });
213
-
275
+ debugLog('🔄 REFRESH CONNECTION CALLED', {
276
+ hasService: !!service,
277
+ hasUser: !!user,
278
+ })
279
+
214
280
  if (!service || !user) {
215
- debugLog('⚠️ REFRESH CONNECTION SKIPPED', 'Missing service or user');
216
- return;
281
+ debugLog('⚠️ REFRESH CONNECTION SKIPPED', 'Missing service or user')
282
+ return
217
283
  }
218
284
 
219
- debugLog('🚀 STARTING CONNECTION REFRESH', { userId: user.id });
220
- setIsLoading(true);
285
+ debugLog('🚀 STARTING CONNECTION REFRESH', { userId: user.id })
286
+ setIsLoading(true)
221
287
  try {
222
- debugLog('🔌 DISCONNECTING FOR REFRESH');
223
- await service.disconnectUser();
224
- debugLog('📞 RECONNECTING FOR REFRESH');
225
- const streamClient = await service.connectUser(user);
226
- setClient(streamClient);
227
- setIsConnected(true);
228
- setError(null);
229
- debugLog('✅ CONNECTION REFRESH SUCCESS', { userId: user.id });
288
+ debugLog('🔌 DISCONNECTING FOR REFRESH')
289
+ await service.disconnectUser()
290
+ debugLog('📞 RECONNECTING FOR REFRESH')
291
+ const streamClient = await service.connectUser(user)
292
+ setClient(streamClient)
293
+ setIsConnected(true)
294
+ setError(null)
295
+ debugLog('✅ CONNECTION REFRESH SUCCESS', { userId: user.id })
230
296
  } catch (err) {
231
- const errorMessage = err instanceof Error ? err.message : 'Refresh failed';
232
- setError(errorMessage);
233
- debugLog('❌ CONNECTION REFRESH ERROR', { userId: user.id, error: errorMessage });
297
+ const errorMessage = err instanceof Error ? err.message : 'Refresh failed'
298
+ setError(errorMessage)
299
+ debugLog('❌ CONNECTION REFRESH ERROR', {
300
+ userId: user.id,
301
+ error: errorMessage,
302
+ })
234
303
  } finally {
235
- setIsLoading(false);
236
- debugLog('🔄 CONNECTION REFRESH FINISHED', { userId: user.id });
304
+ setIsLoading(false)
305
+ debugLog('🔄 CONNECTION REFRESH FINISHED', { userId: user.id })
237
306
  }
238
- }, [service, user]);
307
+ }, [service, user, debugLog])
239
308
 
240
309
  // Memoize context value to prevent unnecessary re-renders
241
310
  const contextValue: MessagingContextValue = React.useMemo(() => {
@@ -246,9 +315,9 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
246
315
  isLoading,
247
316
  hasError: !!error,
248
317
  capabilitiesKeys: Object.keys(capabilities),
249
- customizationKeys: Object.keys(customization)
250
- });
251
-
318
+ customizationKeys: Object.keys(customization),
319
+ })
320
+
252
321
  return {
253
322
  service,
254
323
  client,
@@ -259,24 +328,33 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
259
328
  customization,
260
329
  refreshConnection,
261
330
  debug,
262
- };
263
- }, [service, client, isConnected, isLoading, error, capabilities, customization, refreshConnection, debug]);
331
+ }
332
+ }, [
333
+ service,
334
+ client,
335
+ isConnected,
336
+ isLoading,
337
+ error,
338
+ capabilities,
339
+ customization,
340
+ refreshConnection,
341
+ debug,
342
+ debugLog,
343
+ ])
264
344
 
265
345
  debugLog('🔄 RENDER END', {
266
346
  renderCount: renderCountRef.current,
267
347
  willRenderChat: !!(client && isConnected),
268
- contextValueReady: !!contextValue
269
- });
348
+ contextValueReady: !!contextValue,
349
+ })
270
350
 
271
351
  return (
272
352
  <MessagingContext.Provider value={contextValue}>
273
353
  {client && isConnected ? (
274
- <Chat client={client}>
275
- {children}
276
- </Chat>
354
+ <Chat client={client}>{children}</Chat>
277
355
  ) : (
278
356
  children
279
357
  )}
280
358
  </MessagingContext.Provider>
281
- );
282
- };
359
+ )
360
+ }
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { StreamChat } from 'stream-chat'
2
+ import { ConnectionOpen, StreamChat } from 'stream-chat'
3
3
  import { Chat } from 'stream-chat-react'
4
4
 
5
5
  // Mock Stream Chat client for Storybook
@@ -13,25 +13,25 @@ const createMockClient = () => {
13
13
  const client = new StreamChat('mock-api-key', {
14
14
  allowServerSideConnect: true,
15
15
  })
16
-
16
+
17
17
  // Mock the client methods
18
18
  client.connectUser = async () => {
19
- return { me: mockUser } as any
19
+ return { me: mockUser } as unknown as ConnectionOpen
20
20
  }
21
-
21
+
22
22
  client.disconnectUser = async () => {
23
23
  return Promise.resolve()
24
24
  }
25
25
 
26
- // @ts-ignore
27
26
  client.userID = mockUser.id
28
- // @ts-ignore
29
27
  client.user = mockUser
30
-
28
+
31
29
  return client
32
30
  }
33
31
 
34
- export const MockChatProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
32
+ export const MockChatProvider: React.FC<{ children: React.ReactNode }> = ({
33
+ children,
34
+ }) => {
35
35
  const [client] = React.useState(() => createMockClient())
36
36
 
37
37
  React.useEffect(() => {
@@ -44,17 +44,17 @@ export const MockChatProvider: React.FC<{ children: React.ReactNode }> = ({ chil
44
44
  }
45
45
 
46
46
  // Create a mock chat provider with pre-populated channels
47
- export const MockChatProviderWithChannels: React.FC<{
48
- children: React.ReactNode;
49
- channelCount?: number;
47
+ export const MockChatProviderWithChannels: React.FC<{
48
+ children: React.ReactNode
49
+ channelCount?: number
50
50
  }> = ({ children, channelCount = 3 }) => {
51
51
  const [client] = React.useState(() => {
52
52
  const mockClient = createMockClient()
53
-
53
+
54
54
  // Create mock channels
55
55
  const mockChannels = Array.from({ length: channelCount }, (_, i) => {
56
56
  const participant = mockParticipants[i % mockParticipants.length]
57
-
57
+
58
58
  const mockChannel = {
59
59
  id: `channel-${i + 1}`,
60
60
  cid: `messaging:channel-${i + 1}`,
@@ -62,11 +62,11 @@ export const MockChatProviderWithChannels: React.FC<{
62
62
  _client: mockClient,
63
63
  state: {
64
64
  members: {
65
- [mockUser.id]: {
65
+ [mockUser.id]: {
66
66
  user: mockUser,
67
67
  user_id: mockUser.id,
68
68
  },
69
- [participant.id]: {
69
+ [participant.id]: {
70
70
  user: participant,
71
71
  user_id: participant.id,
72
72
  },
@@ -86,13 +86,13 @@ export const MockChatProviderWithChannels: React.FC<{
86
86
  query: async () => ({ messages: [] }),
87
87
  watch: async () => ({ messages: [] }),
88
88
  }
89
-
89
+
90
90
  return mockChannel
91
91
  })
92
92
 
93
- // @ts-ignore - Mock queryChannels method
93
+ // @ts-expect-error - Mock queryChannels method
94
94
  mockClient.queryChannels = async () => mockChannels
95
-
95
+
96
96
  return mockClient
97
97
  })
98
98
 
@@ -154,4 +154,3 @@ export const mockParticipantSource = {
154
154
  }
155
155
  },
156
156
  }
157
-
package/src/styles.css ADDED
@@ -0,0 +1,75 @@
1
+ /* Stream Chat base styles */
2
+ @import 'stream-chat-react/dist/css/v2/index.css';
3
+
4
+ /* Dialog component styles - used by messaging components */
5
+ /* Note: Dialogs get moved to the top layer when opened with .showModal() */
6
+ .mes-dialog {
7
+ --transition-duration: 0.2s;
8
+
9
+ border: none;
10
+ padding: 0;
11
+ margin: 0;
12
+ background: transparent;
13
+ max-height: 100vh;
14
+ width: 100%;
15
+ height: 100vh;
16
+ position: fixed;
17
+ top: 0;
18
+ right: 0;
19
+ bottom: 0;
20
+ left: auto;
21
+
22
+ /* Mobile: full width */
23
+ max-width: 100%;
24
+
25
+ /* Desktop (md and up): constrained width respecting sidebar */
26
+ @media (min-width: 767px) {
27
+ max-width: min(500px, calc(100% - var(--sidebar-width, 0px)));
28
+ }
29
+
30
+ /* Transitions for slide-in effect */
31
+ transition:
32
+ translate var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),
33
+ overlay var(--transition-duration) allow-discrete,
34
+ display var(--transition-duration) allow-discrete;
35
+
36
+ /* Start off-screen to the right */
37
+ translate: 100% 0;
38
+ }
39
+
40
+ .mes-dialog[open] {
41
+ translate: 0 0;
42
+ }
43
+
44
+ .mes-dialog::backdrop {
45
+ transition:
46
+ display var(--transition-duration) allow-discrete,
47
+ overlay var(--transition-duration) allow-discrete,
48
+ background-color var(--transition-duration) linear;
49
+ background-color: rgba(0, 0, 0, 0);
50
+ }
51
+
52
+ .mes-dialog[open]::backdrop {
53
+ background-color: rgba(0, 0, 0, 0.5);
54
+ }
55
+
56
+ /* Starting style for smooth entry animation */
57
+ @starting-style {
58
+ .mes-dialog[open] {
59
+ translate: 100% 0;
60
+
61
+ &::backdrop {
62
+ background-color: rgba(0, 0, 0, 0);
63
+ }
64
+ }
65
+ }
66
+
67
+ /* Textarea composer focus styles */
68
+ .mes-textarea-composer-container:has(.mes-textarea-composer:focus) {
69
+ outline: 2px solid black;
70
+
71
+ .mes-textarea-composer {
72
+ outline: none;
73
+ }
74
+ }
75
+
package/src/test/setup.ts CHANGED
@@ -1,18 +1,18 @@
1
- import '@testing-library/jest-dom';
2
- import { cleanup } from '@testing-library/react';
3
- import { afterEach } from 'vitest';
1
+ import '@testing-library/jest-dom/vitest'
2
+ import { cleanup } from '@testing-library/react'
3
+ import { afterEach } from 'vitest'
4
4
 
5
5
  // Cleanup after each test
6
6
  afterEach(() => {
7
- cleanup();
8
- });
7
+ cleanup()
8
+ })
9
9
 
10
10
  // Mock Stream Chat if needed
11
11
  global.ResizeObserver = class ResizeObserver {
12
12
  observe() {}
13
13
  unobserve() {}
14
14
  disconnect() {}
15
- };
15
+ }
16
16
 
17
17
  // Mock IntersectionObserver for virtualization
18
18
  global.IntersectionObserver = class IntersectionObserver {
@@ -21,10 +21,9 @@ global.IntersectionObserver = class IntersectionObserver {
21
21
  unobserve() {}
22
22
  disconnect() {}
23
23
  takeRecords() {
24
- return [];
24
+ return []
25
25
  }
26
- root = null;
27
- rootMargin = '';
28
- thresholds = [];
29
- };
30
-
26
+ root = null
27
+ rootMargin = ''
28
+ thresholds = []
29
+ }