@linktr.ee/messaging-react 1.30.0 → 1.31.0-rc-1776677746

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.
@@ -21,6 +21,7 @@ export interface CreatorCardProps extends LockedAttachmentBaseProps {
21
21
  isPreview?: boolean
22
22
  placeholderTitle?: string
23
23
  placeholderAmountText?: string
24
+ sourceUrl?: string
24
25
  onDismiss?: () => void
25
26
  }
26
27
 
@@ -43,7 +44,7 @@ const CloseButton: React.FC<CloseButtonProps> = (props) => {
43
44
  }
44
45
 
45
46
  interface CollapsedThumbnailProps {
46
- thumbnail?: string
47
+ thumbnailUrl?: string
47
48
  mimeType: string
48
49
  overlayIcon?: React.ElementType
49
50
  darkOverlay?: boolean
@@ -51,7 +52,7 @@ interface CollapsedThumbnailProps {
51
52
  }
52
53
 
53
54
  const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {
54
- const { thumbnail, mimeType, overlayIcon: OverlayIcon, darkOverlay, onClick } = props
55
+ const { thumbnailUrl, mimeType, overlayIcon: OverlayIcon, darkOverlay, onClick } = props
55
56
  return (
56
57
  <button
57
58
  type="button"
@@ -63,9 +64,9 @@ const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {
63
64
  onClick={onClick}
64
65
  aria-label={OverlayIcon ? 'Toggle preview' : undefined}
65
66
  >
66
- {thumbnail ? (
67
+ {thumbnailUrl ? (
67
68
  <img
68
- src={thumbnail}
69
+ src={thumbnailUrl}
69
70
  alt=""
70
71
  className="absolute inset-0 h-full w-full object-cover"
71
72
  />
@@ -91,13 +92,13 @@ const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {
91
92
 
92
93
 
93
94
  interface AudioPreviewProps {
94
- source?: string
95
- thumbnail?: string
95
+ sourceUrl?: string
96
+ thumbnailUrl?: string
96
97
  mimeType: string
97
98
  }
98
99
 
99
100
  const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
100
- const { source, thumbnail, mimeType } = props
101
+ const { sourceUrl, thumbnailUrl, mimeType } = props
101
102
  const [playing, setPlaying] = useState(false)
102
103
  const [played, setPlayed] = useState(0)
103
104
  const [seeking, setSeeking] = useState(false)
@@ -133,12 +134,6 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
133
134
 
134
135
  const [audioReady, setAudioReady] = useState(false)
135
136
 
136
- useEffect(() => {
137
- setPlaying(false)
138
- setPlayed(0)
139
- setAudioReady(false)
140
- }, [source])
141
-
142
137
  const getFraction = useCallback(
143
138
  (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {
144
139
  const track = trackRef.current
@@ -185,10 +180,10 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
185
180
 
186
181
  return (
187
182
  <div className="relative">
188
- {source && (
183
+ {sourceUrl && (
189
184
  <audio
190
185
  ref={audioRef}
191
- src={source}
186
+ src={sourceUrl}
192
187
  loop
193
188
  onCanPlay={() => setAudioReady(true)}
194
189
  onEnded={() => {
@@ -200,12 +195,12 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
200
195
  </audio>
201
196
  )}
202
197
  <CollapsedThumbnail
203
- thumbnail={thumbnail}
198
+ thumbnailUrl={thumbnailUrl}
204
199
  mimeType={mimeType}
205
- overlayIcon={source && audioReady ? (playing ? PauseIcon : PlayIcon) : undefined}
206
- onClick={source && audioReady ? toggle : undefined}
200
+ overlayIcon={sourceUrl && audioReady ? (playing ? PauseIcon : PlayIcon) : undefined}
201
+ onClick={sourceUrl && audioReady ? toggle : undefined}
207
202
  />
208
- {source && audioReady && (
203
+ {sourceUrl && audioReady && (
209
204
  <div className="absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent">
210
205
  <div
211
206
  ref={trackRef}
@@ -250,22 +245,18 @@ const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
250
245
  }
251
246
 
252
247
  interface VideoPreviewProps {
253
- source?: string
254
- thumbnail?: string
248
+ sourceUrl?: string
249
+ thumbnailUrl?: string
255
250
  mimeType: string
256
251
  }
257
252
 
258
253
  const VideoPreview: React.FC<VideoPreviewProps> = (props) => {
259
- const { source, thumbnail, mimeType } = props
254
+ const { sourceUrl, thumbnailUrl, mimeType } = props
260
255
  const [expanded, setExpanded] = useState(false)
261
256
  const collapse = () => setExpanded(false)
262
257
 
263
- useEffect(() => {
264
- setExpanded(false)
265
- }, [source])
266
-
267
- if (!source) {
268
- return <CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} />
258
+ if (!sourceUrl) {
259
+ return <CollapsedThumbnail thumbnailUrl={thumbnailUrl} mimeType={mimeType} />
269
260
  }
270
261
 
271
262
  return (
@@ -275,15 +266,15 @@ const VideoPreview: React.FC<VideoPreviewProps> = (props) => {
275
266
  })}
276
267
  >
277
268
  <MediaPlayer
278
- source={source}
269
+ source={sourceUrl}
279
270
  mimeType={mimeType}
280
- poster={thumbnail}
271
+ poster={thumbnailUrl}
281
272
  playing={expanded}
282
- loop
273
+ loop={true}
283
274
  controls={false}
284
- showProgress
275
+ muted={true}
276
+ showProgress={true}
285
277
  onContainerClick={collapse}
286
- muted
287
278
  />
288
279
  {!expanded && (
289
280
  <button
@@ -292,9 +283,9 @@ const VideoPreview: React.FC<VideoPreviewProps> = (props) => {
292
283
  onClick={() => setExpanded(true)}
293
284
  aria-label="Expand video preview"
294
285
  >
295
- {thumbnail ? (
286
+ {thumbnailUrl ? (
296
287
  <img
297
- src={thumbnail}
288
+ src={thumbnailUrl}
298
289
  alt=""
299
290
  className="absolute inset-0 h-full w-full object-cover"
300
291
  />
@@ -317,43 +308,38 @@ const VideoPreview: React.FC<VideoPreviewProps> = (props) => {
317
308
  }
318
309
 
319
310
  interface ImagePreviewProps {
320
- source?: string
321
- thumbnail?: string
311
+ sourceUrl?: string
312
+ thumbnailUrl?: string
322
313
  mimeType: string
323
314
  title?: string
324
315
  }
325
316
 
326
317
  const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
327
- const { source, thumbnail, mimeType, title } = props
318
+ const { sourceUrl, thumbnailUrl, mimeType, title } = props
328
319
  const [expanded, setExpanded] = useState(false)
329
- const collapse = () => setExpanded(false)
330
-
331
- useEffect(() => {
332
- setExpanded(false)
333
- }, [source])
334
320
 
335
- if (expanded && source) {
321
+ if (expanded && sourceUrl) {
336
322
  return (
337
323
  <div className="relative">
338
324
  <button
339
325
  type="button"
340
326
  className="block w-full cursor-pointer border-0 p-0 text-left appearance-none"
341
- onClick={collapse}
327
+ onClick={() => setExpanded(false)}
342
328
  aria-label="Close preview"
343
329
  >
344
- <img src={source} alt={title ?? ''} className="block w-full" />
330
+ <img src={sourceUrl} alt={title ?? ''} className="block w-full" />
345
331
  </button>
346
- <CloseButton onClose={collapse} />
332
+ <CloseButton onClose={() => setExpanded(false)} />
347
333
  </div>
348
334
  )
349
335
  }
350
336
 
351
337
  return (
352
338
  <CollapsedThumbnail
353
- thumbnail={thumbnail}
339
+ thumbnailUrl={thumbnailUrl}
354
340
  mimeType={mimeType}
355
- overlayIcon={source ? EyeSlashIcon : undefined}
356
- onClick={source ? () => setExpanded(true) : undefined}
341
+ overlayIcon={sourceUrl ? EyeSlashIcon : undefined}
342
+ onClick={sourceUrl ? () => setExpanded(true) : undefined}
357
343
  />
358
344
  )
359
345
  }
@@ -363,8 +349,8 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
363
349
  const {
364
350
  title,
365
351
  mimeType = 'application/octet-stream',
366
- thumbnail,
367
- source,
352
+ thumbnailUrl,
353
+ sourceUrl,
368
354
  detail,
369
355
  amountText,
370
356
  placeholderTitle = 'Attachment title',
@@ -380,17 +366,18 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
380
366
  let mediaPreview: React.ReactNode
381
367
  if (isPreview && sourceType === 'audio') {
382
368
  mediaPreview = (
383
- <AudioPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />
369
+ <AudioPreview key={sourceUrl} sourceUrl={sourceUrl} thumbnailUrl={thumbnailUrl} mimeType={mimeType} />
384
370
  )
385
371
  } else if (isPreview && sourceType === 'video') {
386
372
  mediaPreview = (
387
- <VideoPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />
373
+ <VideoPreview key={sourceUrl} sourceUrl={sourceUrl} thumbnailUrl={thumbnailUrl} mimeType={mimeType} />
388
374
  )
389
375
  } else if (isPreview && sourceType === 'image') {
390
376
  mediaPreview = (
391
377
  <ImagePreview
392
- source={source}
393
- thumbnail={thumbnail}
378
+ key={sourceUrl}
379
+ sourceUrl={sourceUrl}
380
+ thumbnailUrl={thumbnailUrl}
394
381
  mimeType={mimeType}
395
382
  title={title}
396
383
  />
@@ -403,7 +390,7 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
403
390
  : LockIcon
404
391
  mediaPreview = (
405
392
  <CollapsedThumbnail
406
- thumbnail={thumbnail}
393
+ thumbnailUrl={thumbnailUrl}
407
394
  mimeType={mimeType}
408
395
  overlayIcon={lockedOverlayIcon}
409
396
  darkOverlay
@@ -412,7 +399,7 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
412
399
  }
413
400
 
414
401
  return (
415
- <div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-card">
402
+ <div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]">
416
403
  {onDismiss && (
417
404
  <button
418
405
  type="button"
@@ -5,12 +5,11 @@ import { isDevBuild } from '../../../utils/isDevBuild'
5
5
  import { renderTypeIcon } from '../utils/icons'
6
6
  import { getSourceType, type AttachmentSourceType } from '../utils/mimeType'
7
7
 
8
- const getPlayerBg = (sourceType: AttachmentSourceType, poster?: string) =>
9
- sourceType === 'audio' && !poster ? 'bg-black/5' : 'bg-black'
8
+ const getPlayerBg = (sourceType: AttachmentSourceType, poster?: string) => {
9
+ return sourceType === 'audio' && !poster ? 'bg-black/5' : 'bg-black'
10
+ }
10
11
 
11
- function getClientXFromEvent(
12
- e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent
13
- ): number {
12
+ const getClientXFromEvent = ( e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent ): number => {
14
13
  if ('touches' in e) {
15
14
  return e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0
16
15
  }
@@ -71,10 +70,6 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
71
70
  const trackRef = useRef<HTMLDivElement>(null)
72
71
  const rafRef = useRef<number | null>(null)
73
72
 
74
- useEffect(() => {
75
- setManualPlayRequired(false)
76
- setInitialLoad(true)
77
- }, [source])
78
73
 
79
74
  useEffect(() => {
80
75
  if (!playing) {
@@ -197,14 +192,15 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
197
192
  if (controls) setPlaying((p) => !p)
198
193
  }}
199
194
  >
200
- {poster && (
195
+ {/* For audio, poster persists as a visual background. For video, hide once loaded. */}
196
+ {poster && (sourceType === 'audio' || initialLoad) && (
201
197
  <img
202
198
  src={poster}
203
199
  alt=""
204
200
  className="absolute inset-0 h-full w-full object-cover"
205
201
  />
206
202
  )}
207
- {!poster && (
203
+ {!poster && (sourceType === 'audio' || initialLoad) && (
208
204
  <div className="absolute inset-0 flex items-center justify-center">
209
205
  {renderTypeIcon(mimeType, {
210
206
  className: 'size-12 text-black/20',
@@ -212,11 +208,6 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
212
208
  })}
213
209
  </div>
214
210
  )}
215
- {initialLoad && poster && (
216
- <div className="absolute inset-0 z-20">
217
- <img src={poster} alt="" className="h-full w-full object-cover" />
218
- </div>
219
- )}
220
211
  <div className="absolute inset-0">
221
212
  {sourceType === 'audio' ? (
222
213
  <audio
@@ -242,7 +233,6 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
242
233
  <video
243
234
  ref={playerRef as React.RefObject<HTMLVideoElement>}
244
235
  src={source}
245
- poster={poster}
246
236
  loop={loop}
247
237
  muted={muted}
248
238
  playsInline