@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
- <LinkPreviewList />
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
- onStartConversation?: () => void
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
- Choose a conversation from the list or{' '}
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, setPickerKey] = useState(0) // Key to force remount of ParticipantPicker
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
  }