@linktr.ee/messaging-react 1.11.4 → 1.11.5-rc-1765008275
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.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +652 -634
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/CustomChannelPreview.tsx +109 -106
- package/src/components/ChannelList/index.tsx +55 -66
- package/src/components/ChannelView.tsx +59 -59
- package/src/components/MessagingShell/EmptyState.tsx +4 -4
- package/src/components/MessagingShell/ErrorState.tsx +8 -5
- package/src/components/MessagingShell/LoadingState.tsx +5 -2
- package/src/components/MessagingShell/index.tsx +20 -6
package/package.json
CHANGED
|
@@ -6,123 +6,126 @@ import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
|
6
6
|
import { formatRelativeTime } from '../../utils/formatRelativeTime'
|
|
7
7
|
import { Avatar } from '../Avatar'
|
|
8
8
|
|
|
9
|
+
type CustomChannelPreviewProps = ChannelPreviewUIComponentProps & {
|
|
10
|
+
selectedChannel?: Channel | null
|
|
11
|
+
onChannelSelect: (channel: Channel) => void
|
|
12
|
+
debug?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* Custom channel preview that handles selection
|
|
11
17
|
*/
|
|
12
|
-
const CustomChannelPreview
|
|
13
|
-
|
|
14
|
-
selectedChannel
|
|
15
|
-
onChannelSelect: (channel: Channel) => void
|
|
16
|
-
debug?: boolean
|
|
17
|
-
}
|
|
18
|
-
> = ({ channel, selectedChannel, onChannelSelect, debug = false, unread }) => {
|
|
19
|
-
const isSelected = selectedChannel?.id === channel?.id
|
|
18
|
+
const CustomChannelPreview = React.memo<CustomChannelPreviewProps>(
|
|
19
|
+
({ channel, selectedChannel, onChannelSelect, debug = false, unread }) => {
|
|
20
|
+
const isSelected = selectedChannel?.id === channel?.id
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const handleClick = () => {
|
|
23
|
+
if (channel) {
|
|
24
|
+
onChannelSelect(channel)
|
|
25
|
+
}
|
|
24
26
|
}
|
|
25
|
-
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Get last message and format timestamp
|
|
36
|
-
const lastMessage =
|
|
37
|
-
channel?.state?.messages?.[channel.state.messages.length - 1]
|
|
38
|
-
|
|
39
|
-
// Fallback order: text -> attachment URL -> "No messages yet"
|
|
40
|
-
const getLastMessageText = () => {
|
|
41
|
-
if (lastMessage?.text) return lastMessage.text
|
|
42
|
-
|
|
43
|
-
const attachment = lastMessage?.attachments?.[0]
|
|
44
|
-
if (attachment?.asset_url) return attachment.asset_url
|
|
45
|
-
if (attachment?.image_url) return attachment.image_url
|
|
46
|
-
if (attachment?.og_scrape_url) return attachment.og_scrape_url
|
|
47
|
-
if (attachment?.thumb_url) return attachment.thumb_url
|
|
48
|
-
|
|
49
|
-
return 'No messages yet'
|
|
50
|
-
}
|
|
28
|
+
// Get participant info
|
|
29
|
+
const members = Object.values(channel?.state?.members || {})
|
|
30
|
+
const participant = members.find(
|
|
31
|
+
(member) => member.user?.id && member.user.id !== channel?._client?.userID
|
|
32
|
+
)
|
|
33
|
+
const participantName = participant?.user?.name || 'Conversation'
|
|
34
|
+
const participantImage = participant?.user?.image
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
36
|
+
// Get last message and format timestamp
|
|
37
|
+
const lastMessage =
|
|
38
|
+
channel?.state?.messages?.[channel.state.messages.length - 1]
|
|
39
|
+
|
|
40
|
+
// Fallback order: text -> attachment URL -> "No messages yet"
|
|
41
|
+
const getLastMessageText = () => {
|
|
42
|
+
if (lastMessage?.text) return lastMessage.text
|
|
43
|
+
|
|
44
|
+
const attachment = lastMessage?.attachments?.[0]
|
|
45
|
+
if (attachment?.asset_url) return attachment.asset_url
|
|
46
|
+
if (attachment?.image_url) return attachment.image_url
|
|
47
|
+
if (attachment?.og_scrape_url) return attachment.og_scrape_url
|
|
48
|
+
if (attachment?.thumb_url) return attachment.thumb_url
|
|
49
|
+
|
|
50
|
+
return 'No messages yet'
|
|
51
|
+
}
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
53
|
+
const lastMessageText = getLastMessageText()
|
|
54
|
+
const lastMessageTime = lastMessage?.created_at
|
|
55
|
+
? formatRelativeTime(new Date(lastMessage.created_at))
|
|
56
|
+
: ''
|
|
57
|
+
|
|
58
|
+
// Use the unread prop passed by Stream Chat (reactive and updates automatically)
|
|
59
|
+
const unreadCount = unread ?? 0
|
|
60
|
+
|
|
61
|
+
if (debug) {
|
|
62
|
+
console.log('📺 [ChannelList] 📋 CHANNEL PREVIEW RENDER', {
|
|
63
|
+
channelId: channel?.id,
|
|
64
|
+
isSelected,
|
|
65
|
+
participantName,
|
|
66
|
+
unreadCount,
|
|
67
|
+
hasTimestamp: !!lastMessageTime,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={handleClick}
|
|
75
|
+
className={classNames(
|
|
76
|
+
'group w-full px-4 py-3 transition-colors text-left max-w-full overflow-hidden focus-ring',
|
|
77
|
+
{
|
|
78
|
+
'bg-primary-alt/10 border-l-4 border-l-primary': isSelected,
|
|
79
|
+
'hover:bg-sand': !isSelected,
|
|
80
|
+
}
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
<div className="flex items-start gap-3">
|
|
84
|
+
{/* Avatar */}
|
|
85
|
+
<Avatar
|
|
86
|
+
id={participant?.user?.id || channel.id || 'unknown'}
|
|
87
|
+
name={participantName}
|
|
88
|
+
image={participantImage}
|
|
89
|
+
size={44}
|
|
90
|
+
className="[&_.avatar-fallback]:group-hover:bg-[#eeeeee]"
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
{/* Content column */}
|
|
94
|
+
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
|
95
|
+
{/* Name and timestamp row */}
|
|
96
|
+
<div className="flex items-center justify-between gap-2">
|
|
97
|
+
<h3
|
|
98
|
+
className={classNames(
|
|
99
|
+
'text-sm font-medium truncate',
|
|
100
|
+
isSelected ? 'text-primary' : 'text-charcoal'
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{participantName}
|
|
104
|
+
</h3>
|
|
105
|
+
{lastMessageTime && (
|
|
106
|
+
<span className="text-xs text-stone flex-shrink-0">
|
|
107
|
+
{lastMessageTime}
|
|
108
|
+
</span>
|
|
100
109
|
)}
|
|
101
|
-
>
|
|
102
|
-
{participantName}
|
|
103
|
-
</h3>
|
|
104
|
-
{lastMessageTime && (
|
|
105
|
-
<span className="text-xs text-stone flex-shrink-0">
|
|
106
|
-
{lastMessageTime}
|
|
107
|
-
</span>
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
+
</div>
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
{/* Message and unread badge row */}
|
|
113
|
+
<div className="flex items-center justify-between gap-2 min-w-0">
|
|
114
|
+
<p className="text-xs text-stone mr-2 flex-1 line-clamp-2">
|
|
115
|
+
{lastMessageText}
|
|
116
|
+
</p>
|
|
117
|
+
{unreadCount > 0 && (
|
|
118
|
+
<span className="bg-[#7f22fe] text-white text-xs px-2 py-0.5 rounded-full min-w-[20px] text-center flex-shrink-0">
|
|
119
|
+
{unreadCount > 99 ? '99+' : unreadCount}
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
121
123
|
</div>
|
|
122
124
|
</div>
|
|
123
|
-
</
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
</button>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
)
|
|
127
129
|
|
|
128
130
|
export default CustomChannelPreview
|
|
131
|
+
CustomChannelPreview.displayName = 'CustomChannelPreview'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import type { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
3
4
|
import { ChannelList as StreamChannelList } from 'stream-chat-react'
|
|
4
5
|
|
|
5
6
|
import { useMessagingContext } from '../../providers/MessagingProvider'
|
|
@@ -10,74 +11,62 @@ import CustomChannelPreview from './CustomChannelPreview'
|
|
|
10
11
|
/**
|
|
11
12
|
* Channel list component with customizable header and actions
|
|
12
13
|
*/
|
|
13
|
-
export const ChannelList
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
export const ChannelList = React.memo<ChannelListProps>(
|
|
15
|
+
({
|
|
16
|
+
onChannelSelect,
|
|
17
|
+
selectedChannel,
|
|
18
|
+
filters,
|
|
19
|
+
className,
|
|
20
|
+
customEmptyStateIndicator,
|
|
21
|
+
}) => {
|
|
22
|
+
// Track renders
|
|
23
|
+
const renderCountRef = React.useRef(0)
|
|
24
|
+
renderCountRef.current++
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
// Get debug flag from context
|
|
27
|
+
const { debug = false } = useMessagingContext()
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
className={classNames(
|
|
38
|
-
'messaging-channel-list h-full flex flex-col min-w-0 overflow-hidden',
|
|
39
|
-
className
|
|
40
|
-
)}
|
|
41
|
-
>
|
|
42
|
-
{/* Channel List */}
|
|
43
|
-
<div className="flex-1 overflow-hidden min-w-0">
|
|
44
|
-
{(() => {
|
|
45
|
-
if (debug) {
|
|
46
|
-
console.log('📺 [ChannelList] 🎬 RENDERING STREAM CHANNEL LIST', {
|
|
47
|
-
renderCount: renderCountRef.current,
|
|
48
|
-
filters,
|
|
49
|
-
})
|
|
50
|
-
}
|
|
29
|
+
if (debug) {
|
|
30
|
+
console.log('📺 [ChannelList] 🔄 RENDER START', {
|
|
31
|
+
renderCount: renderCountRef.current,
|
|
32
|
+
selectedChannelId: selectedChannel?.id,
|
|
33
|
+
filters,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
})
|
|
65
|
-
}
|
|
37
|
+
// Memoize Preview component to prevent re-renders
|
|
38
|
+
const PreviewComponent = React.useMemo(() => {
|
|
39
|
+
const Preview = (props: ChannelPreviewUIComponentProps) => (
|
|
40
|
+
<CustomChannelPreview
|
|
41
|
+
{...props}
|
|
42
|
+
selectedChannel={selectedChannel}
|
|
43
|
+
onChannelSelect={onChannelSelect}
|
|
44
|
+
debug={debug}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
return Preview
|
|
48
|
+
}, [selectedChannel, onChannelSelect, debug])
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={classNames(
|
|
53
|
+
'messaging-channel-list h-full flex flex-col min-w-0 overflow-hidden',
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
{/* Channel List */}
|
|
58
|
+
<div className="flex-1 overflow-hidden min-w-0">
|
|
59
|
+
<StreamChannelList
|
|
60
|
+
key={JSON.stringify(filters)}
|
|
61
|
+
filters={filters}
|
|
62
|
+
sort={{ last_message_at: -1 }}
|
|
63
|
+
options={{ limit: 30 }}
|
|
64
|
+
Preview={PreviewComponent}
|
|
65
|
+
EmptyStateIndicator={customEmptyStateIndicator}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
80
68
|
</div>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
ChannelList.displayName = 'ChannelList'
|
|
@@ -43,7 +43,7 @@ type BlockedUser = {
|
|
|
43
43
|
const CustomChannelHeader: React.FC<{
|
|
44
44
|
onBack?: () => void
|
|
45
45
|
showBackButton: boolean
|
|
46
|
-
onShowInfo
|
|
46
|
+
onShowInfo: () => void
|
|
47
47
|
canShowInfo: boolean
|
|
48
48
|
}> = ({ onBack, showBackButton, onShowInfo, canShowInfo }) => {
|
|
49
49
|
const { channel } = useChannelStateContext()
|
|
@@ -69,6 +69,8 @@ const CustomChannelHeader: React.FC<{
|
|
|
69
69
|
!showBackButton && 'invisible'
|
|
70
70
|
)}
|
|
71
71
|
onClick={onBack || (() => {})}
|
|
72
|
+
type="button"
|
|
73
|
+
aria-label="Back to conversations"
|
|
72
74
|
>
|
|
73
75
|
<ArrowLeftIcon className="size-5 text-black/90" />
|
|
74
76
|
</button>
|
|
@@ -87,7 +89,9 @@ const CustomChannelHeader: React.FC<{
|
|
|
87
89
|
className={classNames(
|
|
88
90
|
'size-10 rounded-full bg-[#F1F0EE] flex items-center justify-center'
|
|
89
91
|
)}
|
|
90
|
-
onClick={onShowInfo
|
|
92
|
+
onClick={onShowInfo}
|
|
93
|
+
type="button"
|
|
94
|
+
aria-label="Show info"
|
|
91
95
|
>
|
|
92
96
|
<DotsThreeIcon className="size-5 text-black/90" />
|
|
93
97
|
</button>
|
|
@@ -136,7 +140,7 @@ const CustomChannelHeader: React.FC<{
|
|
|
136
140
|
* Channel info dialog (matching original implementation)
|
|
137
141
|
*/
|
|
138
142
|
const ChannelInfoDialog: React.FC<{
|
|
139
|
-
|
|
143
|
+
dialogRef: React.RefObject<HTMLDialogElement>
|
|
140
144
|
onClose: () => void
|
|
141
145
|
participant: ChannelMemberResponse | undefined
|
|
142
146
|
channel: ChannelType
|
|
@@ -144,7 +148,7 @@ const ChannelInfoDialog: React.FC<{
|
|
|
144
148
|
onLeaveConversation?: (channel: ChannelType) => void
|
|
145
149
|
onBlockParticipant?: (participantId?: string) => void
|
|
146
150
|
}> = ({
|
|
147
|
-
|
|
151
|
+
dialogRef,
|
|
148
152
|
onClose,
|
|
149
153
|
participant,
|
|
150
154
|
channel,
|
|
@@ -153,24 +157,11 @@ const ChannelInfoDialog: React.FC<{
|
|
|
153
157
|
onBlockParticipant,
|
|
154
158
|
}) => {
|
|
155
159
|
const { service, debug } = useMessagingContext()
|
|
156
|
-
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
157
160
|
const [isParticipantBlocked, setIsParticipantBlocked] = useState(false)
|
|
158
161
|
const [isLeaving, setIsLeaving] = useState(false)
|
|
159
162
|
const [isUpdatingBlockStatus, setIsUpdatingBlockStatus] = useState(false)
|
|
160
163
|
|
|
161
|
-
//
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
const dialog = dialogRef.current
|
|
164
|
-
if (!dialog) return
|
|
165
|
-
|
|
166
|
-
if (isOpen) {
|
|
167
|
-
dialog.showModal()
|
|
168
|
-
} else {
|
|
169
|
-
dialog.close()
|
|
170
|
-
}
|
|
171
|
-
}, [isOpen])
|
|
172
|
-
|
|
173
|
-
// Check if participant is blocked
|
|
164
|
+
// Check if participant is blocked when participant changes
|
|
174
165
|
const checkIsParticipantBlocked = useCallback(async () => {
|
|
175
166
|
if (!service || !participant?.user?.id) return
|
|
176
167
|
|
|
@@ -189,10 +180,8 @@ const ChannelInfoDialog: React.FC<{
|
|
|
189
180
|
}, [service, participant?.user?.id])
|
|
190
181
|
|
|
191
182
|
useEffect(() => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
}, [isOpen, checkIsParticipantBlocked])
|
|
183
|
+
checkIsParticipantBlocked()
|
|
184
|
+
}, [checkIsParticipantBlocked])
|
|
196
185
|
|
|
197
186
|
const handleLeaveConversation = async () => {
|
|
198
187
|
if (isLeaving) return
|
|
@@ -426,7 +415,7 @@ const ChannelViewInner: React.FC<{
|
|
|
426
415
|
onBlockParticipant,
|
|
427
416
|
}) => {
|
|
428
417
|
const { channel } = useChannelStateContext()
|
|
429
|
-
const
|
|
418
|
+
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
430
419
|
|
|
431
420
|
// Get participant info for info dialog
|
|
432
421
|
const participant = React.useMemo(() => {
|
|
@@ -457,6 +446,14 @@ const ChannelViewInner: React.FC<{
|
|
|
457
446
|
return undefined
|
|
458
447
|
}, [channel.data])
|
|
459
448
|
|
|
449
|
+
const handleShowInfo = useCallback(() => {
|
|
450
|
+
infoDialogRef.current?.showModal()
|
|
451
|
+
}, [])
|
|
452
|
+
|
|
453
|
+
const handleCloseInfo = useCallback(() => {
|
|
454
|
+
infoDialogRef.current?.close()
|
|
455
|
+
}, [])
|
|
456
|
+
|
|
460
457
|
return (
|
|
461
458
|
<>
|
|
462
459
|
<Window>
|
|
@@ -465,7 +462,7 @@ const ChannelViewInner: React.FC<{
|
|
|
465
462
|
<CustomChannelHeader
|
|
466
463
|
onBack={onBack}
|
|
467
464
|
showBackButton={showBackButton}
|
|
468
|
-
onShowInfo={
|
|
465
|
+
onShowInfo={handleShowInfo}
|
|
469
466
|
canShowInfo={Boolean(participant)}
|
|
470
467
|
/>
|
|
471
468
|
</div>
|
|
@@ -487,8 +484,8 @@ const ChannelViewInner: React.FC<{
|
|
|
487
484
|
|
|
488
485
|
{/* Channel Info Dialog */}
|
|
489
486
|
<ChannelInfoDialog
|
|
490
|
-
|
|
491
|
-
onClose={
|
|
487
|
+
dialogRef={infoDialogRef}
|
|
488
|
+
onClose={handleCloseInfo}
|
|
492
489
|
participant={participant}
|
|
493
490
|
channel={channel}
|
|
494
491
|
followerStatusLabel={followerStatusLabel}
|
|
@@ -502,37 +499,40 @@ const ChannelViewInner: React.FC<{
|
|
|
502
499
|
/**
|
|
503
500
|
* Channel view component with message list and input
|
|
504
501
|
*/
|
|
505
|
-
export const ChannelView
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
<Channel
|
|
523
|
-
channel={channel}
|
|
524
|
-
MessageSystem={CustomSystemMessage}
|
|
525
|
-
EmptyStateIndicator={CustomChannelEmptyState}
|
|
502
|
+
export const ChannelView = React.memo<ChannelViewProps>(
|
|
503
|
+
({
|
|
504
|
+
channel,
|
|
505
|
+
onBack,
|
|
506
|
+
showBackButton = false,
|
|
507
|
+
renderMessageInputActions,
|
|
508
|
+
onLeaveConversation,
|
|
509
|
+
onBlockParticipant,
|
|
510
|
+
className,
|
|
511
|
+
CustomChannelEmptyState = ChannelEmptyState,
|
|
512
|
+
}) => {
|
|
513
|
+
return (
|
|
514
|
+
<div
|
|
515
|
+
className={classNames(
|
|
516
|
+
'messaging-channel-view h-full flex flex-col',
|
|
517
|
+
className
|
|
518
|
+
)}
|
|
526
519
|
>
|
|
527
|
-
<
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
520
|
+
<Channel
|
|
521
|
+
channel={channel}
|
|
522
|
+
MessageSystem={CustomSystemMessage}
|
|
523
|
+
EmptyStateIndicator={CustomChannelEmptyState}
|
|
524
|
+
>
|
|
525
|
+
<ChannelViewInner
|
|
526
|
+
onBack={onBack}
|
|
527
|
+
showBackButton={showBackButton}
|
|
528
|
+
renderMessageInputActions={renderMessageInputActions}
|
|
529
|
+
onLeaveConversation={onLeaveConversation}
|
|
530
|
+
onBlockParticipant={onBlockParticipant}
|
|
531
|
+
CustomChannelEmptyState={CustomChannelEmptyState}
|
|
532
|
+
/>
|
|
533
|
+
</Channel>
|
|
534
|
+
</div>
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
ChannelView.displayName = 'ChannelView'
|
|
@@ -96,9 +96,8 @@ const ChatBubblesIllustration = ({ className }: { className?: string }) => (
|
|
|
96
96
|
/**
|
|
97
97
|
* Empty state component shown when no channel is selected
|
|
98
98
|
*/
|
|
99
|
-
export const EmptyState
|
|
100
|
-
hasChannels
|
|
101
|
-
}> = ({ hasChannels }) => (
|
|
99
|
+
export const EmptyState = React.memo<{ hasChannels: boolean }>(
|
|
100
|
+
({ hasChannels }) => (
|
|
102
101
|
<div className="messaging-empty-state flex items-center justify-center h-full p-8 text-balance">
|
|
103
102
|
<div className="flex flex-col items-center max-w-sm text-center">
|
|
104
103
|
<ChatBubblesIllustration />
|
|
@@ -114,4 +113,5 @@ export const EmptyState: React.FC<{
|
|
|
114
113
|
)}
|
|
115
114
|
</div>
|
|
116
115
|
</div>
|
|
117
|
-
)
|
|
116
|
+
))
|
|
117
|
+
EmptyState.displayName = 'EmptyState'
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
+
type ErrorStateProps = {
|
|
4
|
+
message: string
|
|
5
|
+
onBack?: () => void
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Error state component shown when something goes wrong
|
|
5
10
|
*/
|
|
6
|
-
export const ErrorState
|
|
7
|
-
message: string
|
|
8
|
-
onBack?: () => void
|
|
9
|
-
}> = ({ message, onBack }) => (
|
|
11
|
+
export const ErrorState = React.memo<ErrorStateProps>(({ message, onBack }) => (
|
|
10
12
|
<div className="messaging-error-state flex items-center justify-center h-full p-8">
|
|
11
13
|
<div className="text-center max-w-sm">
|
|
12
14
|
<div className="w-24 h-24 bg-danger-alt/20 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
@@ -28,4 +30,5 @@ export const ErrorState: React.FC<{
|
|
|
28
30
|
)}
|
|
29
31
|
</div>
|
|
30
32
|
</div>
|
|
31
|
-
)
|
|
33
|
+
))
|
|
34
|
+
ErrorState.displayName = 'ErrorState'
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
1
3
|
import Loading from '../Loading'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Loading state component
|
|
5
7
|
*/
|
|
6
|
-
export const LoadingState = () => (
|
|
8
|
+
export const LoadingState = React.memo(() => (
|
|
7
9
|
<div className="messaging-loading-state flex items-center justify-center h-full">
|
|
8
10
|
<div className="flex items-center">
|
|
9
11
|
<Loading className="w-6 h-6" />
|
|
10
12
|
<span className="text-sm text-stone">Loading messages</span>
|
|
11
13
|
</div>
|
|
12
14
|
</div>
|
|
13
|
-
)
|
|
15
|
+
))
|
|
16
|
+
LoadingState.displayName = 'LoadingState'
|