@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "1.0.2",
3
+ "version": "1.1.0-rc-1760927977",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,8 @@
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
- }
13
+ },
14
+ "./styles.css": "./dist/assets/index.css"
14
15
  },
15
16
  "license": "UNLICENSED",
16
17
  "files": [
@@ -33,7 +34,7 @@
33
34
  },
34
35
  "dependencies": {
35
36
  "@linktr.ee/component-library": "*",
36
- "@linktr.ee/messaging-core": "*",
37
+ "@linktr.ee/messaging-core": "^1.0.1",
37
38
  "@phosphor-icons/react": "^2.1.9",
38
39
  "stream-chat-react": "^13.9.0"
39
40
  },
@@ -1,7 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import ActionButton from '.'
3
2
  import React from 'react'
4
3
 
4
+ import ActionButton from '.'
5
+
5
6
 
6
7
  type ComponentProps = React.ComponentProps<typeof ActionButton>
7
8
 
@@ -1,5 +1,7 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
+
2
3
  import { renderWithProviders, screen, userEvent } from '../../test/utils';
4
+
3
5
  import ActionButton from '.';
4
6
 
5
7
  describe('ActionButton', () => {
@@ -1,7 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import { Avatar } from './index'
3
2
  import React from 'react'
4
3
 
4
+ import { Avatar } from './index'
5
+
5
6
  type ComponentProps = React.ComponentProps<typeof Avatar>
6
7
 
7
8
  const meta: Meta<ComponentProps> = {
@@ -1,5 +1,6 @@
1
- import React from 'react';
2
1
  import classNames from 'classnames';
2
+ import React from 'react';
3
+
3
4
  import { getAvatarColors } from './avatarColors';
4
5
 
5
6
  export interface AvatarProps {
@@ -1,8 +1,10 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import { ChannelList } from '.'
2
+ import React from 'react'
3
+
3
4
  import { MockChatProvider } from '../../stories/mocks'
4
5
 
5
- import React from 'react'
6
+ import { ChannelList } from '.'
7
+
6
8
 
7
9
  type ComponentProps = React.ComponentProps<typeof ChannelList>
8
10
 
@@ -1,6 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import CustomChannelPreview from './CustomChannelPreview'
3
2
  import React from 'react'
3
+ import { Channel, LocalMessage, StreamChat } from 'stream-chat'
4
+
5
+ import CustomChannelPreview from './CustomChannelPreview'
4
6
 
5
7
  type ComponentProps = React.ComponentProps<typeof CustomChannelPreview>
6
8
 
@@ -8,13 +10,9 @@ const meta: Meta<ComponentProps> = {
8
10
  title: 'ChannelList/CustomChannelPreview',
9
11
  component: CustomChannelPreview,
10
12
  parameters: {
11
- layout: 'centered'
13
+ layout: 'centered',
12
14
  },
13
- decorators: [
14
- (Story) => (
15
- <Story />
16
- )
17
- ]
15
+ decorators: [(Story) => <Story />],
18
16
  }
19
17
  export default meta
20
18
 
@@ -34,7 +32,7 @@ const createMockChannel = (options: {
34
32
  lastMessageText?: string
35
33
  lastMessageTime?: Date
36
34
  unreadCount?: number
37
- }): any => {
35
+ }): Channel => {
38
36
  const {
39
37
  id,
40
38
  participantName,
@@ -50,7 +48,7 @@ const createMockChannel = (options: {
50
48
  cid: `messaging:${id}`,
51
49
  _client: {
52
50
  userID: mockUser.id,
53
- },
51
+ } as unknown as StreamChat,
54
52
  state: {
55
53
  members: {
56
54
  [mockUser.id]: {
@@ -66,20 +64,22 @@ const createMockChannel = (options: {
66
64
  user_id: participantId,
67
65
  },
68
66
  },
69
- messages: lastMessageText ? [
70
- {
71
- id: `msg-${id}-1`,
72
- text: lastMessageText,
73
- created_at: lastMessageTime.toISOString(),
74
- user: {
75
- id: participantId,
76
- name: participantName,
77
- },
78
- },
79
- ] : [],
67
+ messages: lastMessageText
68
+ ? ([
69
+ {
70
+ id: `msg-${id}-1`,
71
+ text: lastMessageText,
72
+ created_at: lastMessageTime.toISOString(),
73
+ user: {
74
+ id: participantId,
75
+ name: participantName,
76
+ },
77
+ },
78
+ ] as unknown as LocalMessage[])
79
+ : ([] as LocalMessage[]),
80
80
  unreadCount,
81
81
  },
82
- }
82
+ } as unknown as Channel
83
83
  }
84
84
 
85
85
  const Template: StoryFn<ComponentProps> = (args) => {
@@ -181,7 +181,8 @@ LongMessage.args = {
181
181
  participantName: 'Grace Lee',
182
182
  participantId: 'participant-7',
183
183
  participantImage: 'https://i.pravatar.cc/150?img=7',
184
- lastMessageText: 'This is a very long message that should be truncated because it contains way too much text to display in the preview. We want to make sure the component handles this gracefully.',
184
+ lastMessageText:
185
+ 'This is a very long message that should be truncated because it contains way too much text to display in the preview. We want to make sure the component handles this gracefully.',
185
186
  lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
186
187
  }),
187
188
  onChannelSelect: (channel) => console.log('Channel selected:', channel.id),
@@ -219,7 +220,9 @@ SelectedWithUnread.args = {
219
220
  }
220
221
 
221
222
  export const MultipleChannels: StoryFn = () => {
222
- const [selectedChannelId, setSelectedChannelId] = React.useState<string | null>('channel-2')
223
+ const [selectedChannelId, _setSelectedChannelId] = React.useState<
224
+ string | null
225
+ >('channel-2')
223
226
 
224
227
  const channels = [
225
228
  createMockChannel({
@@ -257,11 +260,12 @@ export const MultipleChannels: StoryFn = () => {
257
260
  }),
258
261
  ]
259
262
 
260
- const selectedChannel = channels.find(c => c.id === selectedChannelId) || null
263
+ const selectedChannel =
264
+ channels.find((c) => c.id === selectedChannelId) || null
261
265
 
262
266
  return (
263
267
  <div className="w-[360px] bg-chalk border border-sand rounded-lg overflow-hidden">
264
- {channels.map(channel => (
268
+ {channels.map((channel) => (
265
269
  <CustomChannelPreview
266
270
  key={channel.id}
267
271
  channel={channel}
@@ -295,9 +299,9 @@ WithVeryLongUrl.args = {
295
299
  participantName: 'Julia Martinez',
296
300
  participantId: 'participant-long-url',
297
301
  participantImage: 'https://i.pravatar.cc/150?img=11',
298
- lastMessageText: 'https://example.com/very/long/path/with/many/segments/and/query/parameters?param1=value1&param2=value2&param3=value3&param4=value4&param5=very-long-value-that-makes-the-url-extremely-long',
302
+ lastMessageText:
303
+ 'https://example.com/very/long/path/with/many/segments/and/query/parameters?param1=value1&param2=value2&param3=value3&param4=value4&param5=very-long-value-that-makes-the-url-extremely-long',
299
304
  lastMessageTime: new Date(Date.now() - 1000 * 60 * 20), // 20 minutes ago
300
305
  }),
301
306
  onChannelSelect: (channel) => console.log('Channel selected:', channel.id),
302
307
  }
303
-
@@ -1,7 +1,8 @@
1
+ import classNames from 'classnames'
1
2
  import React from 'react'
2
- import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
3
3
  import { Channel } from 'stream-chat'
4
- import classNames from 'classnames'
4
+ import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
5
+
5
6
  import { Avatar } from '../Avatar'
6
7
 
7
8
  /**
@@ -9,17 +10,11 @@ import { Avatar } from '../Avatar'
9
10
  */
10
11
  const CustomChannelPreview: React.FC<
11
12
  ChannelPreviewUIComponentProps & {
12
- selectedChannel?: Channel
13
+ selectedChannel?: Channel | null
13
14
  onChannelSelect: (channel: Channel) => void
14
15
  debug?: boolean
15
16
  }
16
- > = ({
17
- channel,
18
- selectedChannel,
19
- onChannelSelect,
20
- debug = false,
21
- ...props
22
- }) => {
17
+ > = ({ channel, selectedChannel, onChannelSelect, debug = false }) => {
23
18
  const isSelected = selectedChannel?.id === channel?.id
24
19
 
25
20
  const handleClick = () => {
@@ -35,7 +30,6 @@ const CustomChannelPreview: React.FC<
35
30
  )
36
31
  const participantName = participant?.user?.name || 'Conversation'
37
32
  const participantImage = participant?.user?.image
38
- const participantInitial = participantName.charAt(0).toUpperCase()
39
33
 
40
34
  // Get last message and format timestamp
41
35
  const lastMessage =
@@ -1,13 +1,16 @@
1
- import React from 'react';
2
- import { NotePencilIcon } from "@phosphor-icons/react/dist/csr/NotePencil";
1
+ import { NotePencilIcon } from '@phosphor-icons/react'
2
+ import classNames from 'classnames'
3
+ import React from 'react'
4
+ import {
5
+ ChannelList as StreamChannelList,
6
+ useChatContext,
7
+ } from 'stream-chat-react'
3
8
 
4
- import classNames from 'classnames';
5
- import { ChannelList as StreamChannelList, useChatContext } from 'stream-chat-react';
6
- import type { ChannelListProps } from '../../types';
7
- import { IconButton } from '../IconButton';
8
- import { useMessagingContext } from '../../providers/MessagingProvider';
9
- import CustomChannelPreview from './CustomChannelPreview';
9
+ import { useMessagingContext } from '../../providers/MessagingProvider'
10
+ import type { ChannelListProps } from '../../types'
11
+ import { IconButton } from '../IconButton'
10
12
 
13
+ import CustomChannelPreview from './CustomChannelPreview'
11
14
 
12
15
  /**
13
16
  * Channel list component with customizable header and actions
@@ -21,52 +24,57 @@ export const ChannelList: React.FC<ChannelListProps> = ({
21
24
  className,
22
25
  }) => {
23
26
  // Track renders
24
- const renderCountRef = React.useRef(0);
25
- renderCountRef.current++;
27
+ const renderCountRef = React.useRef(0)
28
+ renderCountRef.current++
26
29
 
27
30
  // Get debug flag from context
28
- const { debug = false } = useMessagingContext();
31
+ const { debug = false } = useMessagingContext()
29
32
 
30
33
  if (debug) {
31
34
  console.log('📺 [ChannelList] 🔄 RENDER START', {
32
35
  renderCount: renderCountRef.current,
33
36
  selectedChannelId: selectedChannel?.id,
34
37
  showStartConversation,
35
- participantLabel
36
- });
38
+ participantLabel,
39
+ })
37
40
  }
38
41
 
39
- const { client } = useChatContext();
42
+ const { client } = useChatContext()
40
43
 
41
44
  if (debug) {
42
45
  console.log('📺 [ChannelList] 📡 CHAT CONTEXT', {
43
46
  renderCount: renderCountRef.current,
44
47
  hasClient: !!client,
45
48
  clientUserId: client?.userID,
46
- clientConnected: client?.wsConnection?.isHealthy
47
- });
49
+ clientConnected: client?.wsConnection?.isHealthy,
50
+ })
48
51
  }
49
52
 
50
53
  // Filter for messaging channels
51
54
  const filters = React.useMemo(() => {
52
- const userId = client.userID;
55
+ const userId = client.userID
53
56
  const newFilters = userId
54
57
  ? { type: 'messaging', members: { $in: [userId] }, hidden: false }
55
- : { type: 'messaging' };
56
-
58
+ : { type: 'messaging' }
59
+
57
60
  if (debug) {
58
61
  console.log('📺 [ChannelList] 🔍 FILTERS MEMOIZED', {
59
62
  renderCount: renderCountRef.current,
60
63
  userId,
61
- filters: newFilters
62
- });
64
+ filters: newFilters,
65
+ })
63
66
  }
64
-
65
- return newFilters;
66
- }, [client.userID, debug]);
67
+
68
+ return newFilters
69
+ }, [client.userID, debug])
67
70
 
68
71
  return (
69
- <div className={classNames('h-full flex flex-col min-w-0 overflow-hidden', className)}>
72
+ <div
73
+ className={classNames(
74
+ 'h-full flex flex-col min-w-0 overflow-hidden',
75
+ className
76
+ )}
77
+ >
70
78
  {/* Header */}
71
79
  <div className="px-4 py-4 border-b border-sand bg-chalk">
72
80
  <div className="flex items-center justify-between gap-3 min-h-10 min-w-0">
@@ -93,10 +101,10 @@ export const ChannelList: React.FC<ChannelListProps> = ({
93
101
  renderCount: renderCountRef.current,
94
102
  filters,
95
103
  hasClient: !!client,
96
- clientUserId: client?.userID
97
- });
104
+ clientUserId: client?.userID,
105
+ })
98
106
  }
99
-
107
+
100
108
  return (
101
109
  <StreamChannelList
102
110
  filters={filters}
@@ -107,10 +115,10 @@ export const ChannelList: React.FC<ChannelListProps> = ({
107
115
  console.log('📺 [ChannelList] 📋 CHANNEL PREVIEW RENDER', {
108
116
  channelId: props.channel?.id,
109
117
  selectedChannelId: selectedChannel?.id,
110
- isSelected: selectedChannel?.id === props.channel?.id
111
- });
118
+ isSelected: selectedChannel?.id === props.channel?.id,
119
+ })
112
120
  }
113
-
121
+
114
122
  return (
115
123
  <CustomChannelPreview
116
124
  {...props}
@@ -118,12 +126,12 @@ export const ChannelList: React.FC<ChannelListProps> = ({
118
126
  onChannelSelect={onChannelSelect}
119
127
  debug={debug}
120
128
  />
121
- );
129
+ )
122
130
  }}
123
131
  />
124
- );
132
+ )
125
133
  })()}
126
134
  </div>
127
135
  </div>
128
- );
129
- };
136
+ )
137
+ }