@linktr.ee/messaging-react 1.26.1 → 1.27.0

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.
Files changed (58) hide show
  1. package/dist/Creator-B6M8dB0U.js +87 -0
  2. package/dist/Creator-B6M8dB0U.js.map +1 -0
  3. package/dist/MediaPlayer-DsjlYGGH.js +539 -0
  4. package/dist/MediaPlayer-DsjlYGGH.js.map +1 -0
  5. package/dist/Preview-DqAv16NS.js +87 -0
  6. package/dist/Preview-DqAv16NS.js.map +1 -0
  7. package/dist/Visitor-CpmFZRGO.js +175 -0
  8. package/dist/Visitor-CpmFZRGO.js.map +1 -0
  9. package/dist/dash.all.min-Duv4lvGS.js +18858 -0
  10. package/dist/dash.all.min-Duv4lvGS.js.map +1 -0
  11. package/dist/hls-Bogc7CBn.js +21710 -0
  12. package/dist/hls-Bogc7CBn.js.map +1 -0
  13. package/dist/index-Da-xN4Yq.js +16142 -0
  14. package/dist/index-Da-xN4Yq.js.map +1 -0
  15. package/dist/index-Dj9rqWcU.js +69 -0
  16. package/dist/index-Dj9rqWcU.js.map +1 -0
  17. package/dist/index.d.ts +73 -10
  18. package/dist/index.js +979 -934
  19. package/dist/index.js.map +1 -1
  20. package/dist/mixin-B6jYfIcp.js +808 -0
  21. package/dist/mixin-B6jYfIcp.js.map +1 -0
  22. package/dist/react-BxlQMOfz.js +419 -0
  23. package/dist/react-BxlQMOfz.js.map +1 -0
  24. package/dist/react-COAP-MIW.js +377 -0
  25. package/dist/react-COAP-MIW.js.map +1 -0
  26. package/dist/react-Cn4WlMcl.js +3108 -0
  27. package/dist/react-Cn4WlMcl.js.map +1 -0
  28. package/dist/react-CwTJArKY.js +459 -0
  29. package/dist/react-CwTJArKY.js.map +1 -0
  30. package/dist/react-DkfS_atT.js +373 -0
  31. package/dist/react-DkfS_atT.js.map +1 -0
  32. package/dist/react-Pea5fum1.js +286 -0
  33. package/dist/react-Pea5fum1.js.map +1 -0
  34. package/dist/react-RiBbsUDd.js +534 -0
  35. package/dist/react-RiBbsUDd.js.map +1 -0
  36. package/dist/react-dS1WBxxz.js +238 -0
  37. package/dist/react-dS1WBxxz.js.map +1 -0
  38. package/package.json +2 -1
  39. package/src/components/ChannelView.tsx +12 -2
  40. package/src/components/CustomMessage/CustomMessage.stories.tsx +173 -41
  41. package/src/components/CustomMessage/MessageTag.tsx +5 -0
  42. package/src/components/CustomMessage/index.tsx +43 -4
  43. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +249 -0
  44. package/src/components/LockedAttachment/components/Creator.tsx +171 -0
  45. package/src/components/LockedAttachment/components/MediaPlayer.tsx +299 -0
  46. package/src/components/LockedAttachment/components/Visitor.tsx +293 -0
  47. package/src/components/LockedAttachment/index.tsx +39 -0
  48. package/src/components/LockedAttachment/types.ts +18 -0
  49. package/src/components/LockedAttachment/utils/icons.ts +52 -0
  50. package/src/components/LockedAttachment/utils/mimeType.test.ts +97 -0
  51. package/src/components/LockedAttachment/utils/mimeType.ts +35 -0
  52. package/src/components/ParticipantPicker/index.tsx +8 -1
  53. package/src/hooks/useParticipants.ts +3 -2
  54. package/src/index.ts +4 -0
  55. package/src/stories/decorators/storyUser.tsx +37 -0
  56. package/src/stream-custom-data.ts +9 -3
  57. package/src/types.ts +21 -1
  58. package/src/utils/isDevBuild.ts +10 -0
@@ -0,0 +1,249 @@
1
+ import type { Meta, StoryFn } from '@storybook/react'
2
+ import React from 'react'
3
+
4
+ import LockedAttachment, { type VisitorCardProps } from '.'
5
+
6
+ const VIDEO_THUMBNAIL = '/video-thumbnail.jpg'
7
+ const VIDEO_THUMBNAIL_BLURRED = '/video-thumbnail-blurred.jpg'
8
+ const VIDEO_SOURCE = '/video-source.mp4'
9
+
10
+ const IMAGE_THUMBNAIL = '/image-thumbnail.jpg'
11
+ const IMAGE_THUMBNAIL_BLURRED = '/image-thumbnail-blurred.jpg'
12
+ const IMAGE_SOURCE = '/image-source.jpg'
13
+
14
+ const DOCUMENT_THUMBNAIL = '/document-thumbnail.jpg'
15
+ const DOCUMENT_THUMBNAIL_BLURRED = '/document-thumbnail-blurred.jpg'
16
+ const DOCUMENT_SOURCE = '/document-source.pdf'
17
+
18
+ const AUDIO_THUMBNAIL = '/audio-thumbnail.jpg'
19
+ const AUDIO_THUMBNAIL_BLURRED = '/audio-thumbnail-blurred.jpg'
20
+ const AUDIO_SOURCE = '/audio-source.mp3'
21
+
22
+ const meta: Meta = {
23
+ title: 'Components/LockedAttachment',
24
+ component: LockedAttachment,
25
+ parameters: { layout: 'centered' },
26
+ }
27
+ export default meta
28
+
29
+ type InteractiveProps = Omit<VisitorCardProps, 'onUnlock' | 'onDownload'> & {
30
+ unlockedSource: string
31
+ unlockedPoster?: string
32
+ }
33
+
34
+ const Interactive = ({ unlockedSource, unlockedPoster, ...props }: InteractiveProps) => (
35
+ <LockedAttachment
36
+ {...props}
37
+ onUnlock={async () => {
38
+ await new Promise(resolve => setTimeout(resolve, 1000))
39
+ const res = await fetch(unlockedSource)
40
+ const blob = await res.blob()
41
+ return { source: URL.createObjectURL(blob), poster: unlockedPoster }
42
+ }}
43
+ onDownload={() => {}}
44
+ />
45
+ )
46
+
47
+ const VARIANTS = [
48
+ { label: 'Video', title: "Alicia's Workout Plan", mimeType: 'video/mp4', detail: '1:20', poster: VIDEO_THUMBNAIL, thumbnail: VIDEO_THUMBNAIL_BLURRED, source: VIDEO_SOURCE },
49
+ { label: 'Audio', title: 'Morning Meditation', mimeType: 'audio/mpeg', detail: '4:35', poster: AUDIO_THUMBNAIL, thumbnail: AUDIO_THUMBNAIL_BLURRED, source: AUDIO_SOURCE },
50
+ { label: 'Image', title: 'Picture of my cat', mimeType: 'image/jpeg', detail: '3.2 MB', poster: IMAGE_THUMBNAIL, thumbnail: IMAGE_THUMBNAIL_BLURRED, source: IMAGE_SOURCE },
51
+ { label: 'Document', title: 'Strength Training Guide', mimeType: 'application/zip', detail: '14 files', poster: DOCUMENT_THUMBNAIL, thumbnail: DOCUMENT_THUMBNAIL_BLURRED, source: DOCUMENT_SOURCE },
52
+ { label: 'Unknown', title: 'Unknown Attachment', mimeType: 'application/octet-stream', detail: undefined, poster: undefined, thumbnail: undefined, source: DOCUMENT_SOURCE },
53
+ ]
54
+
55
+ const NO_POSTER_VARIANTS = [
56
+ { label: 'Video', title: "Alicia's Workout Plan", mimeType: 'video/mp4', detail: '1:20', source: VIDEO_SOURCE },
57
+ { label: 'Audio', title: 'Morning Meditation', mimeType: 'audio/mpeg', detail: '4:35', source: AUDIO_SOURCE },
58
+ { label: 'Image', title: 'Picture of my cat', mimeType: 'image/jpeg', detail: '3.2 MB', source: IMAGE_SOURCE },
59
+ { label: 'Document', title: 'Strength Training Guide', mimeType: 'application/zip', detail: '14 files', source: DOCUMENT_SOURCE },
60
+ ]
61
+
62
+ const Table = ({ children }: { children: React.ReactNode }) => (
63
+ <div className="p-12">
64
+ <table className="border-separate border-spacing-4">
65
+ {children}
66
+ </table>
67
+ </div>
68
+ )
69
+
70
+ const TableHead = ({ variants }: { variants: { label: string; mimeType: string }[] }) => (
71
+ <thead>
72
+ <tr>
73
+ <th className="text-left text-xs font-medium text-black/40 pb-2" />
74
+ {variants.map(({ label, mimeType }) => (
75
+ <th key={mimeType} className="text-left text-xs font-medium text-black/40 pb-2">{label}</th>
76
+ ))}
77
+ </tr>
78
+ </thead>
79
+ )
80
+
81
+ export const Visitor: StoryFn = () => (
82
+ <Table>
83
+ <TableHead variants={VARIANTS} />
84
+ <tbody>
85
+ <tr>
86
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Locked</td>
87
+ {VARIANTS.map(({ title, mimeType, detail, thumbnail, poster, source }) => (
88
+ <td key={mimeType} className="align-top">
89
+ <Interactive
90
+ title={title}
91
+ amountText="AU$9.99"
92
+ thumbnail={thumbnail}
93
+ poster={poster}
94
+ mimeType={mimeType}
95
+ detail={detail}
96
+ unlockedSource={source}
97
+ />
98
+ </td>
99
+ ))}
100
+ </tr>
101
+ <tr>
102
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Purchased</td>
103
+ {VARIANTS.map(({ title, mimeType, detail, thumbnail, poster, source }) => (
104
+ <td key={mimeType} className="align-top">
105
+ <Interactive
106
+ title={title}
107
+ amountText="AU$9.99"
108
+ thumbnail={thumbnail}
109
+ unlockedPoster={poster}
110
+ mimeType={mimeType}
111
+ detail={detail}
112
+ unlockedSource={source}
113
+ paymentStatus="paid"
114
+ />
115
+ </td>
116
+ ))}
117
+ </tr>
118
+ <tr>
119
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Unlocked</td>
120
+ {VARIANTS.map(({ title, mimeType, detail, poster, source }) => (
121
+ <td key={mimeType} className="align-top">
122
+ <LockedAttachment
123
+ title={title}
124
+ poster={poster}
125
+ source={source}
126
+ mimeType={mimeType}
127
+ detail={detail}
128
+ amountText="AU$9.99"
129
+ paymentStatus="paid"
130
+ onDownload={() => {}}
131
+ />
132
+ </td>
133
+ ))}
134
+ </tr>
135
+ </tbody>
136
+ </Table>
137
+ )
138
+
139
+ export const Creator: StoryFn = () => (
140
+ <Table>
141
+ <TableHead variants={VARIANTS} />
142
+ <tbody>
143
+ <tr>
144
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Preview</td>
145
+ {VARIANTS.map(({ mimeType, detail, thumbnail, poster, source }) => (
146
+ <td key={mimeType} className="align-top">
147
+ <LockedAttachment
148
+ isCreator
149
+ thumbnail={thumbnail}
150
+ poster={poster}
151
+ source={source}
152
+ mimeType={mimeType}
153
+ detail={detail}
154
+ placeholderAmountText="A$0.00"
155
+ />
156
+ </td>
157
+ ))}
158
+ </tr>
159
+ <tr>
160
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Sent</td>
161
+ {VARIANTS.map(({ title, mimeType, detail, thumbnail, poster, source }) => (
162
+ <td key={mimeType} className="align-top">
163
+ <LockedAttachment
164
+ isCreator
165
+ title={title}
166
+ thumbnail={thumbnail}
167
+ poster={poster}
168
+ source={source}
169
+ mimeType={mimeType}
170
+ detail={detail}
171
+ amountText="AU$9.99"
172
+ />
173
+ </td>
174
+ ))}
175
+ </tr>
176
+ <tr>
177
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Sold</td>
178
+ {VARIANTS.map(({ title, mimeType, detail, thumbnail, poster, source }) => (
179
+ <td key={mimeType} className="align-top">
180
+ <LockedAttachment
181
+ isCreator
182
+ title={title}
183
+ thumbnail={thumbnail}
184
+ poster={poster}
185
+ source={source}
186
+ mimeType={mimeType}
187
+ detail={detail}
188
+ amountText="AU$9.99"
189
+ paymentStatus="paid"
190
+ />
191
+ </td>
192
+ ))}
193
+ </tr>
194
+ </tbody>
195
+ </Table>
196
+ )
197
+
198
+ export const NoPoster: StoryFn = () => (
199
+ <Table>
200
+ <TableHead variants={NO_POSTER_VARIANTS} />
201
+ <tbody>
202
+ <tr>
203
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Creator</td>
204
+ {NO_POSTER_VARIANTS.map(({ title, mimeType, detail, source }) => (
205
+ <td key={mimeType} className="align-top">
206
+ <LockedAttachment
207
+ isCreator
208
+ title={title}
209
+ source={source}
210
+ mimeType={mimeType}
211
+ detail={detail}
212
+ amountText="AU$9.99"
213
+ />
214
+ </td>
215
+ ))}
216
+ </tr>
217
+ <tr>
218
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Locked</td>
219
+ {NO_POSTER_VARIANTS.map(({ title, mimeType, detail, source }) => (
220
+ <td key={mimeType} className="align-top">
221
+ <Interactive
222
+ title={title}
223
+ amountText="AU$9.99"
224
+ mimeType={mimeType}
225
+ detail={detail}
226
+ unlockedSource={source}
227
+ />
228
+ </td>
229
+ ))}
230
+ </tr>
231
+ <tr>
232
+ <td className="text-xs font-medium text-black/40 pr-4 align-top pt-2">Unlocked</td>
233
+ {NO_POSTER_VARIANTS.map(({ title, mimeType, detail, source }) => (
234
+ <td key={mimeType} className="align-top">
235
+ <LockedAttachment
236
+ title={title}
237
+ source={source}
238
+ mimeType={mimeType}
239
+ detail={detail}
240
+ amountText="AU$9.99"
241
+ paymentStatus="paid"
242
+ onDownload={() => {}}
243
+ />
244
+ </td>
245
+ ))}
246
+ </tr>
247
+ </tbody>
248
+ </Table>
249
+ )
@@ -0,0 +1,171 @@
1
+ import { CheckCircleIcon, EyeIcon, EyeSlashIcon } from '@phosphor-icons/react'
2
+ import classNames from 'classnames'
3
+ import React, { useState } from 'react'
4
+
5
+ import type { LockedAttachmentBaseProps, PaymentStatus } from '../types'
6
+ import { renderTypeIcon } from '../utils/icons'
7
+ import { getSourceType, type AttachmentSourceType } from '../utils/mimeType'
8
+
9
+ import MediaPlayer from './MediaPlayer'
10
+
11
+ export interface CreatorCardProps extends LockedAttachmentBaseProps {
12
+ title?: string
13
+ placeholderTitle?: string
14
+ placeholderAmountText?: string
15
+ }
16
+
17
+ const CloseButton = ({ onClose }: { onClose: () => void }) => (
18
+ <button
19
+ type="button"
20
+ onClick={onClose}
21
+ className="absolute right-3 top-3 z-40 flex size-8 items-center justify-center rounded-full bg-black/60 text-white"
22
+ aria-label="Close preview"
23
+ >
24
+ <EyeIcon className="size-4" weight="fill" />
25
+ </button>
26
+ )
27
+
28
+ interface CollapsedThumbnailProps {
29
+ thumbnail?: string
30
+ mimeType: string
31
+ canExpand: boolean
32
+ onExpand: () => void
33
+ }
34
+
35
+ const CollapsedThumbnail: React.FC<CollapsedThumbnailProps> = ({ thumbnail, mimeType, canExpand, onExpand }) => {
36
+ return (
37
+ <button
38
+ type="button"
39
+ disabled={!canExpand}
40
+ className={classNames('relative aspect-video block w-full overflow-hidden border-0 bg-black/5 p-0 text-left appearance-none', { 'cursor-pointer': canExpand, 'cursor-default': !canExpand })}
41
+ onClick={onExpand}
42
+ aria-label={canExpand ? 'Expand attachment preview' : undefined}
43
+ >
44
+ {thumbnail
45
+ ? <img src={thumbnail} alt="" className="absolute inset-0 h-full w-full object-cover" />
46
+ : <div className="absolute inset-0 flex items-center justify-center">{renderTypeIcon(mimeType, { className: 'size-12 text-black/20', weight: 'regular' })}</div>
47
+ }
48
+ {canExpand && (
49
+ <div className="pointer-events-none absolute right-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60 text-white">
50
+ <EyeSlashIcon className="size-4" weight="fill" />
51
+ </div>
52
+ )}
53
+ </button>
54
+ )
55
+ }
56
+
57
+ interface ExpandedMediaProps {
58
+ source: string
59
+ mimeType: string
60
+ sourceType: AttachmentSourceType
61
+ poster?: string
62
+ thumbnail?: string
63
+ title?: string
64
+ onCollapse: () => void
65
+ }
66
+
67
+ const getExpandedImgSrc = (sourceType: AttachmentSourceType, source: string, poster?: string, thumbnail?: string) =>
68
+ sourceType === 'document' ? (poster ?? thumbnail) : source
69
+
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
+ return (
91
+ <div className="relative">
92
+ <button type="button" className="block w-full cursor-pointer border-0 p-0 text-left appearance-none" onClick={onCollapse} aria-label="Close preview">
93
+ <img src={imgSrc} alt={title ?? ''} className="block w-full" />
94
+ </button>
95
+ <CloseButton onClose={onCollapse} />
96
+ </div>
97
+ )
98
+ }
99
+
100
+ interface CreatorCardMetaProps {
101
+ title?: string
102
+ placeholderTitle: string
103
+ mimeType: string
104
+ detail?: string
105
+ paymentStatus?: PaymentStatus
106
+ displayAmountText?: string
107
+ isPlaceholderAmount: boolean
108
+ }
109
+
110
+ const CreatorCardMeta: React.FC<CreatorCardMetaProps> = ({ title, placeholderTitle, mimeType, detail, paymentStatus, displayAmountText, isPlaceholderAmount }) => {
111
+ return (
112
+ <div className="px-4 pb-3 pt-3">
113
+ <p className={classNames('mb-1.5 truncate text-base font-medium', { 'text-black/30': !title, 'text-black': !!title })}>
114
+ {title || placeholderTitle}
115
+ </p>
116
+ <div className="flex items-center gap-1">
117
+ {renderTypeIcon(mimeType, { className: 'size-5 shrink-0 text-black/55', weight: 'regular' })}
118
+ {detail && <span className="text-xs font-medium text-black/55">{detail}</span>}
119
+ {paymentStatus === 'paid' ? (
120
+ <>
121
+ <span className="text-xs font-medium text-black/55">•</span>
122
+ <span className="text-xs font-medium text-[#008236]">Purchased</span>
123
+ <CheckCircleIcon className="size-4 text-[#008236]" weight="bold" />
124
+ </>
125
+ ) : displayAmountText && (
126
+ <>
127
+ <span className={classNames('text-xs font-medium', { 'text-black/30': isPlaceholderAmount, 'text-black/55': !isPlaceholderAmount })}>•</span>
128
+ <span className={classNames('text-xs font-medium', { 'text-black/30': isPlaceholderAmount, 'text-black/55': !isPlaceholderAmount })}>{displayAmountText}</span>
129
+ </>
130
+ )}
131
+ </div>
132
+ </div>
133
+ )
134
+ }
135
+
136
+ const CreatorCard: React.FC<CreatorCardProps> = ({
137
+ title,
138
+ mimeType = 'application/octet-stream',
139
+ thumbnail,
140
+ poster,
141
+ source,
142
+ detail,
143
+ amountText,
144
+ placeholderTitle = 'Attachment title',
145
+ placeholderAmountText,
146
+ paymentStatus,
147
+ }) => {
148
+ const sourceType = getSourceType(mimeType)
149
+ const [expanded, setExpanded] = useState(false)
150
+
151
+ const displayAmountText = placeholderAmountText ?? amountText
152
+ const isPlaceholderAmount = !!placeholderAmountText
153
+ const canExpand = sourceType === 'document'
154
+ ? !!(source && (poster || thumbnail))
155
+ : !!source
156
+
157
+ const collapse = () => setExpanded(false)
158
+
159
+ const mediaPreview = expanded && source
160
+ ? <ExpandedMedia source={source} mimeType={mimeType} sourceType={sourceType} poster={poster} thumbnail={thumbnail} title={title} onCollapse={collapse} />
161
+ : <CollapsedThumbnail thumbnail={thumbnail} mimeType={mimeType} canExpand={canExpand} onExpand={() => setExpanded(true)} />
162
+
163
+ return (
164
+ <div className="w-[280px] overflow-hidden rounded-3xl bg-white shadow-[0px_0px_0px_1px_rgba(0,0,0,0.04),0px_1px_2px_0px_rgba(0,0,0,0.04),0px_8px_32px_0px_rgba(0,0,0,0.1)]">
165
+ {mediaPreview}
166
+ <CreatorCardMeta title={title} placeholderTitle={placeholderTitle} mimeType={mimeType} detail={detail} paymentStatus={paymentStatus} displayAmountText={displayAmountText} isPlaceholderAmount={isPlaceholderAmount} />
167
+ </div>
168
+ )
169
+ }
170
+
171
+ export default CreatorCard