@linktr.ee/messaging-react 1.27.0 → 1.28.0-rc-1776231821
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/Creator-BFpHsh2u.js +318 -0
- package/dist/Creator-BFpHsh2u.js.map +1 -0
- package/dist/MediaPlayer-DXz4IBLx.js +313 -0
- package/dist/MediaPlayer-DXz4IBLx.js.map +1 -0
- package/dist/Visitor-C1Fcrgd6.js +199 -0
- package/dist/Visitor-C1Fcrgd6.js.map +1 -0
- package/dist/assets/index.css +1 -1
- package/dist/index.d.ts +13 -16
- package/dist/index.js +27 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/components/CustomMessage/CustomMessage.stories.tsx +1 -1
- package/src/components/CustomMessage/index.tsx +0 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +143 -49
- package/src/components/LockedAttachment/components/Creator.tsx +406 -114
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +162 -80
- package/src/components/LockedAttachment/components/Visitor.tsx +205 -145
- package/src/components/LockedAttachment/index.tsx +7 -7
- package/src/components/LockedAttachment/types.ts +1 -5
- package/src/components/LockedAttachment/utils/icons.ts +2 -1
- package/src/components/LockedAttachment/utils/mimeType.test.ts +29 -7
- package/src/components/LockedAttachment/utils/mimeType.ts +3 -1
- package/src/types.ts +0 -1
- package/dist/Creator-B6M8dB0U.js +0 -87
- package/dist/Creator-B6M8dB0U.js.map +0 -1
- package/dist/MediaPlayer-DsjlYGGH.js +0 -539
- package/dist/MediaPlayer-DsjlYGGH.js.map +0 -1
- package/dist/Preview-DqAv16NS.js +0 -87
- package/dist/Preview-DqAv16NS.js.map +0 -1
- package/dist/Visitor-CpmFZRGO.js +0 -175
- package/dist/Visitor-CpmFZRGO.js.map +0 -1
- package/dist/dash.all.min-Duv4lvGS.js +0 -18858
- package/dist/dash.all.min-Duv4lvGS.js.map +0 -1
- package/dist/hls-Bogc7CBn.js +0 -21710
- package/dist/hls-Bogc7CBn.js.map +0 -1
- package/dist/index-Da-xN4Yq.js +0 -16142
- package/dist/index-Da-xN4Yq.js.map +0 -1
- package/dist/index-Dj9rqWcU.js +0 -69
- package/dist/index-Dj9rqWcU.js.map +0 -1
- package/dist/mixin-B6jYfIcp.js +0 -808
- package/dist/mixin-B6jYfIcp.js.map +0 -1
- package/dist/react-BxlQMOfz.js +0 -419
- package/dist/react-BxlQMOfz.js.map +0 -1
- package/dist/react-COAP-MIW.js +0 -377
- package/dist/react-COAP-MIW.js.map +0 -1
- package/dist/react-Cn4WlMcl.js +0 -3108
- package/dist/react-Cn4WlMcl.js.map +0 -1
- package/dist/react-CwTJArKY.js +0 -459
- package/dist/react-CwTJArKY.js.map +0 -1
- package/dist/react-DkfS_atT.js +0 -373
- package/dist/react-DkfS_atT.js.map +0 -1
- package/dist/react-Pea5fum1.js +0 -286
- package/dist/react-Pea5fum1.js.map +0 -1
- package/dist/react-RiBbsUDd.js +0 -534
- package/dist/react-RiBbsUDd.js.map +0 -1
- package/dist/react-dS1WBxxz.js +0 -238
- package/dist/react-dS1WBxxz.js.map +0 -1
|
@@ -1,169 +1,461 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CheckCircleIcon,
|
|
3
|
+
EyeIcon,
|
|
4
|
+
EyeSlashIcon,
|
|
5
|
+
PauseIcon,
|
|
6
|
+
PlayIcon,
|
|
7
|
+
XIcon,
|
|
8
|
+
} from '@phosphor-icons/react'
|
|
2
9
|
import classNames from 'classnames'
|
|
3
|
-
import React, { useState } from 'react'
|
|
10
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
4
11
|
|
|
5
|
-
import type { LockedAttachmentBaseProps
|
|
12
|
+
import type { LockedAttachmentBaseProps } from '../types'
|
|
6
13
|
import { renderTypeIcon } from '../utils/icons'
|
|
7
|
-
import { getSourceType
|
|
14
|
+
import { getSourceType } from '../utils/mimeType'
|
|
8
15
|
|
|
9
16
|
import MediaPlayer from './MediaPlayer'
|
|
10
17
|
|
|
11
18
|
export interface CreatorCardProps extends LockedAttachmentBaseProps {
|
|
12
|
-
|
|
19
|
+
isPreview?: boolean
|
|
13
20
|
placeholderTitle?: string
|
|
14
21
|
placeholderAmountText?: string
|
|
22
|
+
onDismiss?: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CloseButtonProps {
|
|
26
|
+
onClose: () => void
|
|
15
27
|
}
|
|
16
28
|
|
|
17
|
-
const CloseButton
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
const CloseButton: React.FC<CloseButtonProps> = (props) => {
|
|
30
|
+
const { onClose } = props
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
onClick={onClose}
|
|
35
|
+
className="absolute left-3 top-3 z-40 flex size-8 items-center justify-center rounded-full bg-black/60 text-white"
|
|
36
|
+
aria-label="Close preview"
|
|
37
|
+
>
|
|
38
|
+
<EyeIcon className="size-4" weight="fill" />
|
|
39
|
+
</button>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
27
42
|
|
|
28
43
|
interface CollapsedThumbnailProps {
|
|
29
44
|
thumbnail?: string
|
|
30
45
|
mimeType: string
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
overlayIcon?: React.ElementType
|
|
47
|
+
onClick?: () => void
|
|
33
48
|
}
|
|
34
49
|
|
|
35
|
-
const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (
|
|
50
|
+
const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = (props) => {
|
|
51
|
+
const { thumbnail, mimeType, overlayIcon: OverlayIcon, onClick } = props
|
|
36
52
|
return (
|
|
37
53
|
<button
|
|
38
54
|
type="button"
|
|
39
|
-
disabled={!
|
|
40
|
-
className={classNames(
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
disabled={!onClick}
|
|
56
|
+
className={classNames(
|
|
57
|
+
'relative aspect-video block w-full overflow-hidden border-0 bg-black/5 p-0 text-left appearance-none',
|
|
58
|
+
{ 'cursor-pointer': !!onClick, 'cursor-default': !onClick }
|
|
59
|
+
)}
|
|
60
|
+
onClick={onClick}
|
|
61
|
+
aria-label={OverlayIcon ? 'Toggle preview' : undefined}
|
|
43
62
|
>
|
|
44
|
-
{thumbnail
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
{thumbnail ? (
|
|
64
|
+
<img
|
|
65
|
+
src={thumbnail}
|
|
66
|
+
alt=""
|
|
67
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
68
|
+
/>
|
|
69
|
+
) : (
|
|
70
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
71
|
+
{renderTypeIcon(mimeType, {
|
|
72
|
+
className: 'size-12 text-black/20',
|
|
73
|
+
weight: 'regular',
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
{OverlayIcon && (
|
|
78
|
+
<div className="pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white">
|
|
79
|
+
<OverlayIcon className="size-4" weight="fill" />
|
|
51
80
|
</div>
|
|
52
81
|
)}
|
|
53
82
|
</button>
|
|
54
83
|
)
|
|
55
84
|
}
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sourceType: AttachmentSourceType
|
|
61
|
-
poster?: string
|
|
86
|
+
|
|
87
|
+
interface AudioPreviewProps {
|
|
88
|
+
source?: string
|
|
62
89
|
thumbnail?: string
|
|
63
|
-
|
|
64
|
-
onCollapse: () => void
|
|
90
|
+
mimeType: string
|
|
65
91
|
}
|
|
66
92
|
|
|
67
|
-
const
|
|
68
|
-
|
|
93
|
+
const AudioPreview: React.FC<AudioPreviewProps> = (props) => {
|
|
94
|
+
const { source, thumbnail, mimeType } = props
|
|
95
|
+
const [playing, setPlaying] = useState(false)
|
|
96
|
+
const [played, setPlayed] = useState(0)
|
|
97
|
+
const [seeking, setSeeking] = useState(false)
|
|
98
|
+
const audioRef = useRef<HTMLAudioElement>(null)
|
|
99
|
+
const trackRef = useRef<HTMLDivElement>(null)
|
|
100
|
+
const rafRef = useRef<number | null>(null)
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const el = audioRef.current
|
|
104
|
+
if (!el) return
|
|
105
|
+
if (playing) {
|
|
106
|
+
void el.play().catch(() => setPlaying(false))
|
|
107
|
+
} else {
|
|
108
|
+
el.pause()
|
|
109
|
+
}
|
|
110
|
+
}, [playing])
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!playing) {
|
|
114
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
const tick = () => {
|
|
118
|
+
const el = audioRef.current
|
|
119
|
+
if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)
|
|
120
|
+
rafRef.current = requestAnimationFrame(tick)
|
|
121
|
+
}
|
|
122
|
+
rafRef.current = requestAnimationFrame(tick)
|
|
123
|
+
return () => {
|
|
124
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)
|
|
125
|
+
}
|
|
126
|
+
}, [playing, seeking])
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
setPlaying(false)
|
|
130
|
+
setPlayed(0)
|
|
131
|
+
}, [source])
|
|
132
|
+
|
|
133
|
+
const getFraction = useCallback(
|
|
134
|
+
(e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {
|
|
135
|
+
const track = trackRef.current
|
|
136
|
+
if (!track) return 0
|
|
137
|
+
const clientX =
|
|
138
|
+
'touches' in e
|
|
139
|
+
? (e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0)
|
|
140
|
+
: e.clientX
|
|
141
|
+
const rect = track.getBoundingClientRect()
|
|
142
|
+
return Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
|
|
143
|
+
},
|
|
144
|
+
[]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const seekTo = useCallback((fraction: number) => {
|
|
148
|
+
const el = audioRef.current
|
|
149
|
+
if (el && el.duration) el.currentTime = fraction * el.duration
|
|
150
|
+
}, [])
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!seeking) return
|
|
154
|
+
const onMove = (e: MouseEvent | TouchEvent) => {
|
|
155
|
+
const f = getFraction(e)
|
|
156
|
+
setPlayed(f)
|
|
157
|
+
seekTo(f)
|
|
158
|
+
}
|
|
159
|
+
const onUp = (e: MouseEvent | TouchEvent) => {
|
|
160
|
+
setSeeking(false)
|
|
161
|
+
seekTo(getFraction(e))
|
|
162
|
+
}
|
|
163
|
+
window.addEventListener('mousemove', onMove)
|
|
164
|
+
window.addEventListener('mouseup', onUp)
|
|
165
|
+
window.addEventListener('touchmove', onMove, { passive: true })
|
|
166
|
+
window.addEventListener('touchend', onUp)
|
|
167
|
+
return () => {
|
|
168
|
+
window.removeEventListener('mousemove', onMove)
|
|
169
|
+
window.removeEventListener('mouseup', onUp)
|
|
170
|
+
window.removeEventListener('touchmove', onMove)
|
|
171
|
+
window.removeEventListener('touchend', onUp)
|
|
172
|
+
}
|
|
173
|
+
}, [seeking, getFraction, seekTo])
|
|
174
|
+
|
|
175
|
+
const toggle = useCallback(() => setPlaying((p) => !p), [])
|
|
69
176
|
|
|
70
|
-
const ExpandedMedia: React.FC<ExpandedMediaProps> = ({ source, mimeType, sourceType, poster, thumbnail, title, onCollapse }) => {
|
|
71
|
-
if (sourceType === 'video' || sourceType === 'audio') {
|
|
72
|
-
return (
|
|
73
|
-
<div className="relative">
|
|
74
|
-
<MediaPlayer
|
|
75
|
-
source={source}
|
|
76
|
-
mimeType={mimeType}
|
|
77
|
-
poster={poster ?? thumbnail}
|
|
78
|
-
autoPlay
|
|
79
|
-
loop
|
|
80
|
-
controls={false}
|
|
81
|
-
showProgress
|
|
82
|
-
onContainerClick={onCollapse}
|
|
83
|
-
muted={sourceType === 'video'}
|
|
84
|
-
/>
|
|
85
|
-
<CloseButton onClose={onCollapse} />
|
|
86
|
-
</div>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
const imgSrc = getExpandedImgSrc(sourceType, source, poster, thumbnail)
|
|
90
177
|
return (
|
|
91
178
|
<div className="relative">
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
179
|
+
{source && (
|
|
180
|
+
<audio
|
|
181
|
+
ref={audioRef}
|
|
182
|
+
src={source}
|
|
183
|
+
loop
|
|
184
|
+
onEnded={() => {
|
|
185
|
+
setPlaying(false)
|
|
186
|
+
setPlayed(0)
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
<track kind="captions" />
|
|
190
|
+
</audio>
|
|
191
|
+
)}
|
|
192
|
+
<CollapsedThumbnail
|
|
193
|
+
thumbnail={thumbnail}
|
|
194
|
+
mimeType={mimeType}
|
|
195
|
+
overlayIcon={source ? (playing ? PauseIcon : PlayIcon) : undefined}
|
|
196
|
+
onClick={source ? toggle : undefined}
|
|
197
|
+
/>
|
|
198
|
+
{source && (
|
|
199
|
+
<div className="absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent">
|
|
200
|
+
<div
|
|
201
|
+
ref={trackRef}
|
|
202
|
+
role="slider"
|
|
203
|
+
aria-label="Playback position"
|
|
204
|
+
aria-valuenow={Math.round(played * 100)}
|
|
205
|
+
aria-valuemin={0}
|
|
206
|
+
aria-valuemax={100}
|
|
207
|
+
tabIndex={0}
|
|
208
|
+
className="relative flex h-4 w-full cursor-pointer items-center"
|
|
209
|
+
onMouseDown={(e) => {
|
|
210
|
+
e.stopPropagation()
|
|
211
|
+
setSeeking(true)
|
|
212
|
+
const f = getFraction(e)
|
|
213
|
+
setPlayed(f)
|
|
214
|
+
seekTo(f)
|
|
215
|
+
}}
|
|
216
|
+
onTouchStart={(e) => {
|
|
217
|
+
e.stopPropagation()
|
|
218
|
+
setSeeking(true)
|
|
219
|
+
const f = getFraction(e)
|
|
220
|
+
setPlayed(f)
|
|
221
|
+
seekTo(f)
|
|
222
|
+
}}
|
|
223
|
+
onClick={(e) => e.stopPropagation()}
|
|
224
|
+
onKeyDown={(e) => {
|
|
225
|
+
if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))
|
|
226
|
+
if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<div className="w-full overflow-hidden rounded-full bg-white/30 h-1">
|
|
230
|
+
<div
|
|
231
|
+
className="h-full rounded-full bg-white"
|
|
232
|
+
style={{ width: `${Math.round(played * 100)}%` }}
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
96
238
|
</div>
|
|
97
239
|
)
|
|
98
240
|
}
|
|
99
241
|
|
|
100
|
-
interface
|
|
101
|
-
|
|
102
|
-
|
|
242
|
+
interface VideoPreviewProps {
|
|
243
|
+
source?: string
|
|
244
|
+
thumbnail?: string
|
|
103
245
|
mimeType: string
|
|
104
|
-
detail?: string
|
|
105
|
-
paymentStatus?: PaymentStatus
|
|
106
|
-
displayAmountText?: string
|
|
107
|
-
isPlaceholderAmount: boolean
|
|
108
246
|
}
|
|
109
247
|
|
|
110
|
-
const
|
|
248
|
+
const VideoPreview: React.FC<VideoPreviewProps> = (props) => {
|
|
249
|
+
const { source, thumbnail, mimeType } = props
|
|
250
|
+
const [expanded, setExpanded] = useState(false)
|
|
251
|
+
const collapse = () => setExpanded(false)
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
setExpanded(false)
|
|
255
|
+
}, [source])
|
|
256
|
+
|
|
257
|
+
if (!source) {
|
|
258
|
+
return <CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} />
|
|
259
|
+
}
|
|
260
|
+
|
|
111
261
|
return (
|
|
112
|
-
<div
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
262
|
+
<div
|
|
263
|
+
className={classNames('relative overflow-hidden', {
|
|
264
|
+
'aspect-video': !expanded,
|
|
265
|
+
})}
|
|
266
|
+
>
|
|
267
|
+
<MediaPlayer
|
|
268
|
+
source={source}
|
|
269
|
+
mimeType={mimeType}
|
|
270
|
+
poster={thumbnail}
|
|
271
|
+
playing={expanded}
|
|
272
|
+
loop
|
|
273
|
+
controls={false}
|
|
274
|
+
showProgress
|
|
275
|
+
onContainerClick={collapse}
|
|
276
|
+
muted
|
|
277
|
+
/>
|
|
278
|
+
{!expanded && (
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
281
|
+
className="absolute inset-0 block cursor-pointer border-0 p-0 text-left appearance-none"
|
|
282
|
+
onClick={() => setExpanded(true)}
|
|
283
|
+
aria-label="Expand video preview"
|
|
284
|
+
>
|
|
285
|
+
{thumbnail ? (
|
|
286
|
+
<img
|
|
287
|
+
src={thumbnail}
|
|
288
|
+
alt=""
|
|
289
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
290
|
+
/>
|
|
291
|
+
) : (
|
|
292
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
293
|
+
{renderTypeIcon(mimeType, {
|
|
294
|
+
className: 'size-12 text-black/20',
|
|
295
|
+
weight: 'regular',
|
|
296
|
+
})}
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
<div className="pointer-events-none absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white">
|
|
300
|
+
<EyeSlashIcon className="size-4" weight="fill" />
|
|
301
|
+
</div>
|
|
302
|
+
</button>
|
|
303
|
+
)}
|
|
304
|
+
{expanded && <CloseButton onClose={collapse} />}
|
|
132
305
|
</div>
|
|
133
306
|
)
|
|
134
307
|
}
|
|
135
308
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
placeholderAmountText,
|
|
146
|
-
paymentStatus,
|
|
147
|
-
}) => {
|
|
148
|
-
const sourceType = getSourceType(mimeType)
|
|
309
|
+
interface ImagePreviewProps {
|
|
310
|
+
source?: string
|
|
311
|
+
thumbnail?: string
|
|
312
|
+
mimeType: string
|
|
313
|
+
title?: string
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
317
|
+
const { source, thumbnail, mimeType, title } = props
|
|
149
318
|
const [expanded, setExpanded] = useState(false)
|
|
319
|
+
const collapse = () => setExpanded(false)
|
|
150
320
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
? !!(source && (poster || thumbnail))
|
|
155
|
-
: !!source
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
setExpanded(false)
|
|
323
|
+
}, [source])
|
|
156
324
|
|
|
157
|
-
|
|
325
|
+
if (expanded && source) {
|
|
326
|
+
return (
|
|
327
|
+
<div className="relative">
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
className="block w-full cursor-pointer border-0 p-0 text-left appearance-none"
|
|
331
|
+
onClick={collapse}
|
|
332
|
+
aria-label="Close preview"
|
|
333
|
+
>
|
|
334
|
+
<img src={source} alt={title ?? ''} className="block w-full" />
|
|
335
|
+
</button>
|
|
336
|
+
<CloseButton onClose={collapse} />
|
|
337
|
+
</div>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<CollapsedThumbnail
|
|
343
|
+
thumbnail={thumbnail}
|
|
344
|
+
mimeType={mimeType}
|
|
345
|
+
overlayIcon={source ? EyeSlashIcon : undefined}
|
|
346
|
+
onClick={source ? () => setExpanded(true) : undefined}
|
|
347
|
+
/>
|
|
348
|
+
)
|
|
349
|
+
}
|
|
158
350
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
351
|
+
|
|
352
|
+
const CreatorCard: React.FC<CreatorCardProps> = (props) => {
|
|
353
|
+
const {
|
|
354
|
+
title,
|
|
355
|
+
mimeType = 'application/octet-stream',
|
|
356
|
+
thumbnail,
|
|
357
|
+
source,
|
|
358
|
+
detail,
|
|
359
|
+
amountText,
|
|
360
|
+
placeholderTitle = 'Attachment title',
|
|
361
|
+
placeholderAmountText,
|
|
362
|
+
paymentStatus,
|
|
363
|
+
onDismiss,
|
|
364
|
+
isPreview = false,
|
|
365
|
+
} = props
|
|
366
|
+
const sourceType = getSourceType(mimeType)
|
|
367
|
+
const displayAmountText = amountText ?? placeholderAmountText
|
|
368
|
+
const isPlaceholderAmount = !amountText && !!placeholderAmountText
|
|
369
|
+
|
|
370
|
+
let mediaPreview: React.ReactNode
|
|
371
|
+
if (isPreview && sourceType === 'audio') {
|
|
372
|
+
mediaPreview = (
|
|
373
|
+
<AudioPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />
|
|
374
|
+
)
|
|
375
|
+
} else if (isPreview && sourceType === 'video') {
|
|
376
|
+
mediaPreview = (
|
|
377
|
+
<VideoPreview source={source} thumbnail={thumbnail} mimeType={mimeType} />
|
|
378
|
+
)
|
|
379
|
+
} else if (isPreview && sourceType === 'image') {
|
|
380
|
+
mediaPreview = (
|
|
381
|
+
<ImagePreview
|
|
382
|
+
source={source}
|
|
383
|
+
thumbnail={thumbnail}
|
|
384
|
+
mimeType={mimeType}
|
|
385
|
+
title={title}
|
|
386
|
+
/>
|
|
387
|
+
)
|
|
388
|
+
} else {
|
|
389
|
+
mediaPreview = (
|
|
390
|
+
<CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} />
|
|
391
|
+
)
|
|
392
|
+
}
|
|
162
393
|
|
|
163
394
|
return (
|
|
164
|
-
<div className="w-[280px] overflow-hidden rounded-3xl bg-white shadow-
|
|
395
|
+
<div className="relative w-[280px] select-none overflow-hidden rounded-3xl bg-white shadow-card">
|
|
396
|
+
{onDismiss && (
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
onClick={onDismiss}
|
|
400
|
+
className="absolute right-3 top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white"
|
|
401
|
+
aria-label="Dismiss attachment"
|
|
402
|
+
>
|
|
403
|
+
<XIcon className="size-4" weight="bold" />
|
|
404
|
+
</button>
|
|
405
|
+
)}
|
|
165
406
|
{mediaPreview}
|
|
166
|
-
<
|
|
407
|
+
<div className="px-4 pb-3 pt-3">
|
|
408
|
+
<p
|
|
409
|
+
className={classNames('mb-1.5 truncate text-base font-medium', {
|
|
410
|
+
'text-black/30': !title,
|
|
411
|
+
'text-black': !!title,
|
|
412
|
+
})}
|
|
413
|
+
>
|
|
414
|
+
{title || placeholderTitle}
|
|
415
|
+
</p>
|
|
416
|
+
<div className="flex items-center gap-1">
|
|
417
|
+
{renderTypeIcon(mimeType, {
|
|
418
|
+
className: 'size-5 shrink-0 text-black/55',
|
|
419
|
+
weight: 'regular',
|
|
420
|
+
})}
|
|
421
|
+
{detail && (
|
|
422
|
+
<span className="text-xs font-medium text-black/55">{detail}</span>
|
|
423
|
+
)}
|
|
424
|
+
{paymentStatus === 'paid' ? (
|
|
425
|
+
<>
|
|
426
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
427
|
+
<span className="text-xs font-medium text-[#008236]">
|
|
428
|
+
Purchased
|
|
429
|
+
</span>
|
|
430
|
+
<CheckCircleIcon
|
|
431
|
+
className="size-4 text-[#008236]"
|
|
432
|
+
weight="bold"
|
|
433
|
+
/>
|
|
434
|
+
</>
|
|
435
|
+
) : (
|
|
436
|
+
displayAmountText && (
|
|
437
|
+
<>
|
|
438
|
+
<span
|
|
439
|
+
className={classNames('text-xs font-medium', {
|
|
440
|
+
'text-black/30': isPlaceholderAmount,
|
|
441
|
+
'text-black/55': !isPlaceholderAmount,
|
|
442
|
+
})}
|
|
443
|
+
>
|
|
444
|
+
•
|
|
445
|
+
</span>
|
|
446
|
+
<span
|
|
447
|
+
className={classNames('text-xs font-medium', {
|
|
448
|
+
'text-black/30': isPlaceholderAmount,
|
|
449
|
+
'text-black/55': !isPlaceholderAmount,
|
|
450
|
+
})}
|
|
451
|
+
>
|
|
452
|
+
{displayAmountText}
|
|
453
|
+
</span>
|
|
454
|
+
</>
|
|
455
|
+
)
|
|
456
|
+
)}
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
167
459
|
</div>
|
|
168
460
|
)
|
|
169
461
|
}
|