@linktr.ee/messaging-react 1.0.1 → 1.0.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"react-dom": "^18.3.1",
|
|
62
62
|
"storybook": "^8.5.0",
|
|
63
63
|
"stream-chat": "^9.22.1",
|
|
64
|
-
"tailwindcss": "3.
|
|
64
|
+
"tailwindcss": "^3.4.17",
|
|
65
65
|
"tailwindcss-animate": "^1.0.7",
|
|
66
66
|
"typescript": "5.9.2",
|
|
67
67
|
"vite": "^6.0.7",
|
|
@@ -75,4 +75,4 @@
|
|
|
75
75
|
"stream-chat": "^9.22.1",
|
|
76
76
|
"stream-chat-react": "^13.9.0"
|
|
77
77
|
}
|
|
78
|
-
}
|
|
78
|
+
}
|
|
@@ -3,34 +3,33 @@
|
|
|
3
3
|
* Uses the ID to deterministically select from design system colors
|
|
4
4
|
*/
|
|
5
5
|
export function getAvatarColors(id: string): {
|
|
6
|
-
bgColor: string
|
|
7
|
-
textColor: string
|
|
6
|
+
bgColor: string
|
|
7
|
+
textColor: string
|
|
8
8
|
} {
|
|
9
9
|
// Map of color combinations from Linktree design system
|
|
10
10
|
// Selected for good contrast at 20% opacity
|
|
11
11
|
const colorPairs = [
|
|
12
|
-
{ bgColor:
|
|
13
|
-
{ bgColor:
|
|
14
|
-
{ bgColor:
|
|
15
|
-
{ bgColor:
|
|
16
|
-
{ bgColor:
|
|
17
|
-
{ bgColor:
|
|
18
|
-
{ bgColor:
|
|
19
|
-
{ bgColor:
|
|
20
|
-
{ bgColor:
|
|
21
|
-
{ bgColor:
|
|
22
|
-
{ bgColor:
|
|
23
|
-
{ bgColor:
|
|
24
|
-
]
|
|
12
|
+
{ bgColor: 'bg-primary/20', textColor: 'text-primary' }, // #8129D9 - purple
|
|
13
|
+
{ bgColor: 'bg-forest/20', textColor: 'text-forest' }, // #254f1a - dark green
|
|
14
|
+
{ bgColor: 'bg-iris/20', textColor: 'text-iris' }, // #061492 - dark blue
|
|
15
|
+
{ bgColor: 'bg-shade/20', textColor: 'text-shade' }, // #1e2330 - dark blue-gray
|
|
16
|
+
{ bgColor: 'bg-dahlia/20', textColor: 'text-dahlia' }, // #502274 - dark purple
|
|
17
|
+
{ bgColor: 'bg-orchid/20', textColor: 'text-orchid' }, // #d717e7 - magenta
|
|
18
|
+
{ bgColor: 'bg-currant/20', textColor: 'text-currant' }, // #780016 - dark red
|
|
19
|
+
{ bgColor: 'bg-apple/20', textColor: 'text-apple' }, // #c41500 - red
|
|
20
|
+
{ bgColor: 'bg-rose/20', textColor: 'text-rose' }, // #fc3e4b - pink
|
|
21
|
+
{ bgColor: 'bg-root/20', textColor: 'text-root' }, // #4c2e05 - brown
|
|
22
|
+
{ bgColor: 'bg-poppy/20', textColor: 'text-poppy' }, // #ff6c02 - orange
|
|
23
|
+
{ bgColor: 'bg-moss/20', textColor: 'text-moss' }, // #70764d - olive green
|
|
24
|
+
]
|
|
25
25
|
|
|
26
26
|
// Simple hash function to get consistent index
|
|
27
|
-
let hash = 0
|
|
27
|
+
let hash = 0
|
|
28
28
|
for (let i = 0; i < id.length; i++) {
|
|
29
|
-
hash = (
|
|
30
|
-
hash = hash & hash
|
|
29
|
+
hash = (hash << 5) - hash + id.charCodeAt(i)
|
|
30
|
+
hash = hash & hash // Convert to 32bit integer
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
const index = Math.abs(hash) % colorPairs.length;
|
|
34
|
-
return colorPairs[index];
|
|
35
|
-
}
|
|
36
32
|
|
|
33
|
+
const index = Math.abs(hash) % colorPairs.length
|
|
34
|
+
return colorPairs[index]
|
|
35
|
+
}
|
|
@@ -1,48 +1,55 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
3
|
-
import { Channel } from 'stream-chat'
|
|
4
|
-
import classNames from 'classnames'
|
|
5
|
-
import { Avatar } from '../Avatar'
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
3
|
+
import { Channel } from 'stream-chat'
|
|
4
|
+
import classNames from 'classnames'
|
|
5
|
+
import { Avatar } from '../Avatar'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Custom channel preview that handles selection
|
|
9
9
|
*/
|
|
10
10
|
const CustomChannelPreview: React.FC<
|
|
11
11
|
ChannelPreviewUIComponentProps & {
|
|
12
|
-
selectedChannel?: Channel
|
|
13
|
-
onChannelSelect: (channel: Channel) => void
|
|
14
|
-
debug?: boolean
|
|
12
|
+
selectedChannel?: Channel
|
|
13
|
+
onChannelSelect: (channel: Channel) => void
|
|
14
|
+
debug?: boolean
|
|
15
15
|
}
|
|
16
|
-
> = ({
|
|
17
|
-
|
|
16
|
+
> = ({
|
|
17
|
+
channel,
|
|
18
|
+
selectedChannel,
|
|
19
|
+
onChannelSelect,
|
|
20
|
+
debug = false,
|
|
21
|
+
...props
|
|
22
|
+
}) => {
|
|
23
|
+
const isSelected = selectedChannel?.id === channel?.id
|
|
18
24
|
|
|
19
25
|
const handleClick = () => {
|
|
20
26
|
if (channel) {
|
|
21
|
-
onChannelSelect(channel)
|
|
27
|
+
onChannelSelect(channel)
|
|
22
28
|
}
|
|
23
|
-
}
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
// Get participant info
|
|
26
|
-
const members = Object.values(channel?.state?.members || {})
|
|
27
|
-
const participant = members.find(
|
|
28
|
-
member.user?.id && member.user.id !== channel?._client?.userID
|
|
29
|
-
)
|
|
30
|
-
const participantName = participant?.user?.name || 'Conversation'
|
|
31
|
-
const participantImage = participant?.user?.image
|
|
32
|
-
const participantInitial = participantName.charAt(0).toUpperCase()
|
|
32
|
+
const members = Object.values(channel?.state?.members || {})
|
|
33
|
+
const participant = members.find(
|
|
34
|
+
(member) => member.user?.id && member.user.id !== channel?._client?.userID
|
|
35
|
+
)
|
|
36
|
+
const participantName = participant?.user?.name || 'Conversation'
|
|
37
|
+
const participantImage = participant?.user?.image
|
|
38
|
+
const participantInitial = participantName.charAt(0).toUpperCase()
|
|
33
39
|
|
|
34
40
|
// Get last message and format timestamp
|
|
35
|
-
const lastMessage =
|
|
36
|
-
|
|
41
|
+
const lastMessage =
|
|
42
|
+
channel?.state?.messages?.[channel.state.messages.length - 1]
|
|
43
|
+
const lastMessageText = lastMessage?.text || 'No messages yet'
|
|
37
44
|
const lastMessageTime = lastMessage?.created_at
|
|
38
45
|
? new Date(lastMessage.created_at).toLocaleTimeString([], {
|
|
39
46
|
hour: '2-digit',
|
|
40
47
|
minute: '2-digit',
|
|
41
48
|
})
|
|
42
|
-
: ''
|
|
49
|
+
: ''
|
|
43
50
|
|
|
44
51
|
// Get unread count from channel state
|
|
45
|
-
const unread = channel?.state?.unreadCount || 0
|
|
52
|
+
const unread = channel?.state?.unreadCount || 0
|
|
46
53
|
|
|
47
54
|
if (debug) {
|
|
48
55
|
console.log('📺 [ChannelList] 📋 CHANNEL PREVIEW RENDER', {
|
|
@@ -51,7 +58,7 @@ const CustomChannelPreview: React.FC<
|
|
|
51
58
|
participantName,
|
|
52
59
|
unread,
|
|
53
60
|
hasTimestamp: !!lastMessageTime,
|
|
54
|
-
})
|
|
61
|
+
})
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
return (
|
|
@@ -61,7 +68,7 @@ const CustomChannelPreview: React.FC<
|
|
|
61
68
|
className={classNames(
|
|
62
69
|
'w-full px-4 py-3 transition-colors border-b border-sand text-left max-w-full overflow-hidden focus-ring',
|
|
63
70
|
{
|
|
64
|
-
'bg-primary-alt
|
|
71
|
+
'bg-primary-alt/10 border-l-4 border-l-primary': isSelected,
|
|
65
72
|
'hover:bg-sand': !isSelected,
|
|
66
73
|
}
|
|
67
74
|
)}
|
|
@@ -108,7 +115,7 @@ const CustomChannelPreview: React.FC<
|
|
|
108
115
|
</div>
|
|
109
116
|
</div>
|
|
110
117
|
</button>
|
|
111
|
-
)
|
|
112
|
-
}
|
|
118
|
+
)
|
|
119
|
+
}
|
|
113
120
|
|
|
114
|
-
export default CustomChannelPreview
|
|
121
|
+
export default CustomChannelPreview
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Empty state component shown when no channel is selected
|
|
5
5
|
*/
|
|
6
6
|
export const EmptyState: React.FC<{
|
|
7
|
-
hasChannels: boolean
|
|
8
|
-
onStartConversation?: () => void
|
|
9
|
-
participantLabel: string
|
|
7
|
+
hasChannels: boolean
|
|
8
|
+
onStartConversation?: () => void
|
|
9
|
+
participantLabel: string
|
|
10
10
|
}> = ({ hasChannels, onStartConversation, participantLabel }) => (
|
|
11
11
|
<div className="flex items-center justify-center h-full p-8 text-balance">
|
|
12
12
|
<div className="text-center max-w-sm">
|
|
13
|
-
<div className="w-24 h-24 bg-primary-alt
|
|
13
|
+
<div className="w-24 h-24 bg-primary-alt/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
14
14
|
<span className="text-4xl">💬</span>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
-
<h2 className="font-semibold text-charcoal">
|
|
18
|
-
Welcome to Messages
|
|
19
|
-
</h2>
|
|
17
|
+
<h2 className="font-semibold text-charcoal">Welcome to Messages</h2>
|
|
20
18
|
|
|
21
19
|
<p className="text-stone text-sm mb-6">
|
|
22
20
|
{hasChannels ? (
|
|
@@ -31,25 +29,30 @@ export const EmptyState: React.FC<{
|
|
|
31
29
|
) : (
|
|
32
30
|
onStartConversation && (
|
|
33
31
|
<>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
<TextButton onClick={onStartConversation}>
|
|
33
|
+
Start a new conversation with one of your {participantLabel}
|
|
34
|
+
</TextButton>{' '}
|
|
35
|
+
to begin messaging.
|
|
36
|
+
</>
|
|
39
37
|
)
|
|
40
38
|
)}
|
|
41
39
|
</p>
|
|
42
40
|
</div>
|
|
43
41
|
</div>
|
|
44
|
-
)
|
|
45
|
-
|
|
42
|
+
)
|
|
46
43
|
|
|
47
|
-
const TextButton = ({
|
|
44
|
+
const TextButton = ({
|
|
45
|
+
onClick,
|
|
46
|
+
children,
|
|
47
|
+
}: {
|
|
48
|
+
onClick: () => void
|
|
49
|
+
children: React.ReactNode
|
|
50
|
+
}) => (
|
|
48
51
|
<button
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={onClick}
|
|
54
|
+
className="inline-flex items-center gap-1 text-sm font-medium text-primary hover:text-primary-alt focus:outline-none focus:ring-2 focus:ring-primary"
|
|
52
55
|
>
|
|
53
56
|
{children}
|
|
54
57
|
</button>
|
|
55
|
-
)
|
|
58
|
+
)
|