@linktr.ee/messaging-react 1.9.1 → 1.9.3
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 +0 -11
- package/dist/index.js +682 -671
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CustomLinkPreviewList/index.tsx +97 -0
- package/src/components/CustomMessageInput/index.tsx +4 -3
- package/src/components/MessagingShell/EmptyState.stories.tsx +0 -4
- package/src/components/MessagingShell/EmptyState.tsx +4 -38
- package/src/components/MessagingShell/index.tsx +3 -21
- package/src/providers/MessagingProvider.tsx +1 -16
- package/src/types.ts +0 -10
package/package.json
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { XIcon } from '@phosphor-icons/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import {
|
|
4
|
+
LinkPreview,
|
|
5
|
+
LinkPreviewsManager,
|
|
6
|
+
LinkPreviewsManagerState,
|
|
7
|
+
} from 'stream-chat'
|
|
8
|
+
import { useMessageComposer, useStateStore } from 'stream-chat-react'
|
|
9
|
+
|
|
10
|
+
const linkPreviewsManagerStateSelector = (state: LinkPreviewsManagerState) => ({
|
|
11
|
+
linkPreviews: Array.from(state.previews.values()).filter(
|
|
12
|
+
(preview) =>
|
|
13
|
+
LinkPreviewsManager.previewIsLoaded(preview) ||
|
|
14
|
+
LinkPreviewsManager.previewIsLoading(preview)
|
|
15
|
+
),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
interface CustomLinkPreviewCardProps {
|
|
19
|
+
link: LinkPreview
|
|
20
|
+
onDismiss: (url: string) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CustomLinkPreviewCard: React.FC<CustomLinkPreviewCardProps> = ({
|
|
24
|
+
link,
|
|
25
|
+
onDismiss,
|
|
26
|
+
}) => {
|
|
27
|
+
const { og_scrape_url, title, image_url } = link
|
|
28
|
+
|
|
29
|
+
const handleDismissLink = (e: React.MouseEvent) => {
|
|
30
|
+
e.preventDefault()
|
|
31
|
+
onDismiss(og_scrape_url)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<a
|
|
36
|
+
href={og_scrape_url}
|
|
37
|
+
target="_blank"
|
|
38
|
+
rel="noopener noreferrer"
|
|
39
|
+
className="relative w-full block rounded-[24px] bg-[#121110] p-2 no-underline transition-opacity hover:opacity-90"
|
|
40
|
+
>
|
|
41
|
+
{image_url && (
|
|
42
|
+
<img
|
|
43
|
+
src={image_url}
|
|
44
|
+
alt={title || ''}
|
|
45
|
+
className="h-[148px] w-full rounded-[20px] object-cover"
|
|
46
|
+
/>
|
|
47
|
+
)}
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={handleDismissLink}
|
|
51
|
+
className="absolute right-4 top-4 flex size-6 items-center justify-center rounded-full border border-white/40 bg-white/70 backdrop-blur-2xl"
|
|
52
|
+
aria-label="Close link preview"
|
|
53
|
+
>
|
|
54
|
+
<XIcon className="size-4 text-black/90" />
|
|
55
|
+
</button>
|
|
56
|
+
<div className="p-2">
|
|
57
|
+
{title && (
|
|
58
|
+
<div className="text-[14px] font-medium leading-5 text-white">
|
|
59
|
+
{title}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
<div className="text-[12px] leading-4 text-white/55">
|
|
63
|
+
{og_scrape_url}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</a>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const CustomLinkPreviewList = () => {
|
|
71
|
+
const { linkPreviewsManager } = useMessageComposer()
|
|
72
|
+
|
|
73
|
+
const { linkPreviews: stateLinkPreviews } = useStateStore(
|
|
74
|
+
linkPreviewsManager.state,
|
|
75
|
+
linkPreviewsManagerStateSelector
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const handleDismiss = (url: string) => {
|
|
79
|
+
linkPreviewsManager.dismissPreview(url)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const showLinkPreviews = stateLinkPreviews.length > 0
|
|
83
|
+
|
|
84
|
+
if (!showLinkPreviews) return null
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="str-chat__link-preview-list p-0 gap-2 mb-4">
|
|
88
|
+
{stateLinkPreviews.map((linkPreview) => (
|
|
89
|
+
<CustomLinkPreviewCard
|
|
90
|
+
key={linkPreview.og_scrape_url}
|
|
91
|
+
link={linkPreview}
|
|
92
|
+
onDismiss={handleDismiss}
|
|
93
|
+
/>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -2,7 +2,6 @@ import { ArrowUpIcon } from '@phosphor-icons/react'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import {
|
|
4
4
|
AttachmentPreviewList,
|
|
5
|
-
LinkPreviewList,
|
|
6
5
|
MessageInput,
|
|
7
6
|
QuotedMessagePreview,
|
|
8
7
|
SimpleAttachmentSelector,
|
|
@@ -11,6 +10,8 @@ import {
|
|
|
11
10
|
useMessageInputContext,
|
|
12
11
|
} from 'stream-chat-react'
|
|
13
12
|
|
|
13
|
+
import { CustomLinkPreviewList } from '../CustomLinkPreviewList'
|
|
14
|
+
|
|
14
15
|
const CustomMessageInputInner: React.FC = () => {
|
|
15
16
|
const { handleSubmit } = useMessageInputContext()
|
|
16
17
|
const hasSendableData = useMessageComposerHasSendableData()
|
|
@@ -20,9 +21,9 @@ const CustomMessageInputInner: React.FC = () => {
|
|
|
20
21
|
<div className="left-container">
|
|
21
22
|
<SimpleAttachmentSelector />
|
|
22
23
|
</div>
|
|
23
|
-
<div className="central-container w-full p-2 bg-white rounded-lg shadow-[0_4px_16px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.04)]">
|
|
24
|
+
<div className="central-container min-w-0 w-full p-2 bg-white rounded-lg shadow-[0_4px_16px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.04)]">
|
|
24
25
|
<QuotedMessagePreview />
|
|
25
|
-
<
|
|
26
|
+
<CustomLinkPreviewList />
|
|
26
27
|
<AttachmentPreviewList />
|
|
27
28
|
<div className="flex">
|
|
28
29
|
<div className="w-full ml-2 mr-4">
|
|
@@ -27,13 +27,9 @@ const Template: StoryFn<ComponentProps> = (args) => {
|
|
|
27
27
|
export const NoChannels: StoryFn<ComponentProps> = Template.bind({})
|
|
28
28
|
NoChannels.args = {
|
|
29
29
|
hasChannels: false,
|
|
30
|
-
participantLabel: 'followers',
|
|
31
|
-
onStartConversation: () => console.log('Start conversation clicked'),
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
export const WithChannels: StoryFn<ComponentProps> = Template.bind({})
|
|
35
33
|
WithChannels.args = {
|
|
36
34
|
hasChannels: true,
|
|
37
|
-
participantLabel: 'participants',
|
|
38
|
-
onStartConversation: () => console.log('Start conversation clicked'),
|
|
39
35
|
}
|
|
@@ -5,9 +5,7 @@ import React from 'react'
|
|
|
5
5
|
*/
|
|
6
6
|
export const EmptyState: React.FC<{
|
|
7
7
|
hasChannels: boolean
|
|
8
|
-
|
|
9
|
-
participantLabel: string
|
|
10
|
-
}> = ({ hasChannels, onStartConversation, participantLabel }) => (
|
|
8
|
+
}> = ({ hasChannels }) => (
|
|
11
9
|
<div className="messaging-empty-state flex items-center justify-center h-full p-8 text-balance">
|
|
12
10
|
<div className="text-center max-w-sm">
|
|
13
11
|
<div className="w-24 h-24 bg-primary-alt/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
@@ -17,42 +15,10 @@ export const EmptyState: React.FC<{
|
|
|
17
15
|
<h2 className="font-semibold text-charcoal">Welcome to Messages</h2>
|
|
18
16
|
|
|
19
17
|
<p className="text-stone text-sm mb-6">
|
|
20
|
-
{hasChannels
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{onStartConversation && (
|
|
24
|
-
<TextButton onClick={onStartConversation}>
|
|
25
|
-
start a new conversation with a {participantLabel.slice(0, -1)}.
|
|
26
|
-
</TextButton>
|
|
27
|
-
)}
|
|
28
|
-
</>
|
|
29
|
-
) : (
|
|
30
|
-
onStartConversation && (
|
|
31
|
-
<>
|
|
32
|
-
<TextButton onClick={onStartConversation}>
|
|
33
|
-
Start a new conversation with one of your {participantLabel}
|
|
34
|
-
</TextButton>{' '}
|
|
35
|
-
to begin messaging.
|
|
36
|
-
</>
|
|
37
|
-
)
|
|
38
|
-
)}
|
|
18
|
+
{hasChannels
|
|
19
|
+
? 'Choose a conversation from the list.'
|
|
20
|
+
: 'No conversations yet.'}
|
|
39
21
|
</p>
|
|
40
22
|
</div>
|
|
41
23
|
</div>
|
|
42
24
|
)
|
|
43
|
-
|
|
44
|
-
const TextButton = ({
|
|
45
|
-
onClick,
|
|
46
|
-
children,
|
|
47
|
-
}: {
|
|
48
|
-
onClick: () => void
|
|
49
|
-
children: React.ReactNode
|
|
50
|
-
}) => (
|
|
51
|
-
<button
|
|
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"
|
|
55
|
-
>
|
|
56
|
-
{children}
|
|
57
|
-
</button>
|
|
58
|
-
)
|
|
@@ -44,7 +44,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
44
44
|
const [existingParticipantIds, setExistingParticipantIds] = useState<
|
|
45
45
|
Set<string>
|
|
46
46
|
>(new Set())
|
|
47
|
-
const [pickerKey,
|
|
47
|
+
const [pickerKey, _setPickerKey] = useState(0) // Key to force remount of ParticipantPicker
|
|
48
48
|
const [directConversationMode, setDirectConversationMode] = useState(false)
|
|
49
49
|
const [directConversationError, setDirectConversationError] = useState<
|
|
50
50
|
string | null
|
|
@@ -52,11 +52,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
52
52
|
|
|
53
53
|
const participantPickerRef = useRef<HTMLDialogElement>(null)
|
|
54
54
|
|
|
55
|
-
const {
|
|
56
|
-
showStartConversation = false,
|
|
57
|
-
participantSource,
|
|
58
|
-
participantLabel = 'participants',
|
|
59
|
-
} = capabilities
|
|
55
|
+
const { participantSource, participantLabel = 'participants' } = capabilities
|
|
60
56
|
|
|
61
57
|
// Create default filters and merge with provided filters
|
|
62
58
|
const channelFilters = React.useMemo(() => {
|
|
@@ -276,14 +272,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
276
272
|
setSelectedChannel(null)
|
|
277
273
|
}, [directConversationMode])
|
|
278
274
|
|
|
279
|
-
const handleStartConversation = useCallback(() => {
|
|
280
|
-
if (participantSource) {
|
|
281
|
-
setPickerKey((prev) => prev + 1) // Increment key to force remount
|
|
282
|
-
setShowParticipantPicker(true)
|
|
283
|
-
participantPickerRef.current?.showModal()
|
|
284
|
-
}
|
|
285
|
-
}, [participantSource])
|
|
286
|
-
|
|
287
275
|
const handleSelectParticipant = useCallback(
|
|
288
276
|
async (participant: Participant) => {
|
|
289
277
|
if (!service) return
|
|
@@ -460,13 +448,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
460
448
|
/>
|
|
461
449
|
</div>
|
|
462
450
|
) : (
|
|
463
|
-
<EmptyState
|
|
464
|
-
hasChannels={hasChannels}
|
|
465
|
-
onStartConversation={
|
|
466
|
-
showStartConversation ? handleStartConversation : undefined
|
|
467
|
-
}
|
|
468
|
-
participantLabel={participantLabel}
|
|
469
|
-
/>
|
|
451
|
+
<EmptyState hasChannels={hasChannels} />
|
|
470
452
|
)}
|
|
471
453
|
</div>
|
|
472
454
|
</div>
|
|
@@ -10,11 +10,7 @@ import React, {
|
|
|
10
10
|
import type { StreamChat } from 'stream-chat'
|
|
11
11
|
import { Chat } from 'stream-chat-react'
|
|
12
12
|
|
|
13
|
-
import type {
|
|
14
|
-
MessagingProviderProps,
|
|
15
|
-
MessagingCapabilities,
|
|
16
|
-
MessagingCustomization,
|
|
17
|
-
} from '../types'
|
|
13
|
+
import type { MessagingProviderProps, MessagingCapabilities } from '../types'
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
16
|
* Context value for messaging state and service
|
|
@@ -26,7 +22,6 @@ export interface MessagingContextValue {
|
|
|
26
22
|
isLoading: boolean
|
|
27
23
|
error: string | null
|
|
28
24
|
capabilities: MessagingCapabilities
|
|
29
|
-
customization: MessagingCustomization
|
|
30
25
|
refreshConnection: () => Promise<void>
|
|
31
26
|
debug: boolean
|
|
32
27
|
}
|
|
@@ -38,7 +33,6 @@ const MessagingContext = createContext<MessagingContextValue>({
|
|
|
38
33
|
isLoading: false,
|
|
39
34
|
error: null,
|
|
40
35
|
capabilities: {},
|
|
41
|
-
customization: {},
|
|
42
36
|
refreshConnection: async () => {},
|
|
43
37
|
debug: false,
|
|
44
38
|
})
|
|
@@ -57,7 +51,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
57
51
|
serviceConfig,
|
|
58
52
|
apiKey,
|
|
59
53
|
capabilities = {},
|
|
60
|
-
customization = {},
|
|
61
54
|
debug = false,
|
|
62
55
|
}) => {
|
|
63
56
|
// Create debug logger that respects the debug prop
|
|
@@ -75,7 +68,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
75
68
|
apiKey: apiKey?.substring(0, 8) + '...',
|
|
76
69
|
serviceConfig: !!serviceConfig,
|
|
77
70
|
capabilities: Object.keys(capabilities),
|
|
78
|
-
customization: Object.keys(customization),
|
|
79
71
|
})
|
|
80
72
|
|
|
81
73
|
const [service, setService] = useState<StreamChatService | null>(null)
|
|
@@ -93,7 +85,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
93
85
|
apiKey,
|
|
94
86
|
serviceConfig,
|
|
95
87
|
capabilities,
|
|
96
|
-
customization,
|
|
97
88
|
})
|
|
98
89
|
const renderCountRef = useRef(0)
|
|
99
90
|
renderCountRef.current++
|
|
@@ -107,8 +98,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
107
98
|
serviceConfigChanged:
|
|
108
99
|
prevPropsRef.current.serviceConfig !== serviceConfig,
|
|
109
100
|
capabilitiesChanged: prevPropsRef.current.capabilities !== capabilities,
|
|
110
|
-
customizationChanged:
|
|
111
|
-
prevPropsRef.current.customization !== customization,
|
|
112
101
|
},
|
|
113
102
|
})
|
|
114
103
|
|
|
@@ -117,7 +106,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
117
106
|
apiKey,
|
|
118
107
|
serviceConfig,
|
|
119
108
|
capabilities,
|
|
120
|
-
customization,
|
|
121
109
|
}
|
|
122
110
|
|
|
123
111
|
// Initialize service when config changes
|
|
@@ -315,7 +303,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
315
303
|
isLoading,
|
|
316
304
|
hasError: !!error,
|
|
317
305
|
capabilitiesKeys: Object.keys(capabilities),
|
|
318
|
-
customizationKeys: Object.keys(customization),
|
|
319
306
|
})
|
|
320
307
|
|
|
321
308
|
return {
|
|
@@ -325,7 +312,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
325
312
|
isLoading,
|
|
326
313
|
error,
|
|
327
314
|
capabilities,
|
|
328
|
-
customization,
|
|
329
315
|
refreshConnection,
|
|
330
316
|
debug,
|
|
331
317
|
}
|
|
@@ -336,7 +322,6 @@ export const MessagingProvider: React.FC<MessagingProviderProps> = ({
|
|
|
336
322
|
isLoading,
|
|
337
323
|
error,
|
|
338
324
|
capabilities,
|
|
339
|
-
customization,
|
|
340
325
|
refreshConnection,
|
|
341
326
|
debug,
|
|
342
327
|
debugLog,
|
package/src/types.ts
CHANGED
|
@@ -44,20 +44,11 @@ export interface MessagingCapabilities {
|
|
|
44
44
|
participantLabel?: string // e.g., "followers", "team members"
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
* Customization options
|
|
49
|
-
*/
|
|
50
|
-
export interface MessagingCustomization {
|
|
51
|
-
theme?: 'light' | 'dark' | 'auto'
|
|
52
|
-
accentColor?: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
47
|
/**
|
|
56
48
|
* Main MessagingShell component props
|
|
57
49
|
*/
|
|
58
50
|
export interface MessagingShellProps {
|
|
59
51
|
capabilities?: MessagingCapabilities
|
|
60
|
-
customization?: MessagingCustomization
|
|
61
52
|
className?: string
|
|
62
53
|
renderMessageInputActions?: (channel: Channel) => React.ReactNode
|
|
63
54
|
onChannelSelect?: (channel: Channel) => void
|
|
@@ -157,6 +148,5 @@ export interface MessagingProviderProps {
|
|
|
157
148
|
serviceConfig: Omit<StreamChatServiceConfig, 'apiKey'>
|
|
158
149
|
apiKey: string
|
|
159
150
|
capabilities?: MessagingCapabilities
|
|
160
|
-
customization?: MessagingCustomization
|
|
161
151
|
debug?: boolean
|
|
162
152
|
}
|