@linktr.ee/messaging-react 2.1.0 → 2.2.0-rc-1778753733
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/{Card-CsJvUF_b.js → Card-BdTueeyk.js} +2 -2
- package/dist/{Card-CsJvUF_b.js.map → Card-BdTueeyk.js.map} +1 -1
- package/dist/{Card-DlMSDSdm.js → Card-ChR37pLZ.js} +2 -2
- package/dist/{Card-DlMSDSdm.js.map → Card-ChR37pLZ.js.map} +1 -1
- package/dist/{Card-CFFNq49v.js → Card-EKxCn56j.js} +3 -3
- package/dist/{Card-CFFNq49v.js.map → Card-EKxCn56j.js.map} +1 -1
- package/dist/{LockedThumbnail-DpJx169C.js → LockedThumbnail-B16qP3eH.js} +2 -2
- package/dist/{LockedThumbnail-DpJx169C.js.map → LockedThumbnail-B16qP3eH.js.map} +1 -1
- package/dist/index-Dn7BC9xK.js +4748 -0
- package/dist/index-Dn7BC9xK.js.map +1 -0
- package/dist/index.d.ts +591 -25
- package/dist/index.js +24 -19
- package/package.json +1 -1
- package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +841 -0
- package/src/components/LinkAttachment/LinkAttachment.stories.tsx +7 -92
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +69 -0
- package/src/components/LinkAttachment/components/Received/Card.tsx +10 -30
- package/src/components/LinkAttachment/components/_shared/CardShell.tsx +5 -1
- package/src/components/LinkAttachment/index.tsx +24 -50
- package/src/components/LinkAttachment/types.ts +12 -5
- package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +203 -0
- package/src/components/MessageAttachment/Audio/index.tsx +189 -0
- package/src/components/MessageAttachment/File/FileAttachment.stories.tsx +352 -0
- package/src/components/MessageAttachment/File/index.tsx +240 -0
- package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +288 -0
- package/src/components/MessageAttachment/Image/index.tsx +257 -0
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +783 -0
- package/src/components/MessageAttachment/Pdf/PdfAttachment.stories.tsx +292 -0
- package/src/components/MessageAttachment/Pdf/index.tsx +228 -0
- package/src/components/MessageAttachment/Video/VideoAttachment.stories.tsx +272 -0
- package/src/components/MessageAttachment/Video/index.tsx +281 -0
- package/src/components/MessageAttachment/_shared/Bubble.tsx +173 -0
- package/src/components/MessageAttachment/_shared/CompactDocumentRow.tsx +152 -0
- package/src/components/MessageAttachment/_shared/DismissButton.tsx +39 -0
- package/src/components/MessageAttachment/_shared/DownloadAction.tsx +175 -0
- package/src/components/MessageAttachment/_shared/ImageViewer.tsx +314 -0
- package/src/components/MessageAttachment/_shared/MediaStackGrid.tsx +139 -0
- package/src/components/MessageAttachment/_shared/PdfViewer.tsx +100 -0
- package/src/components/MessageAttachment/_shared/VideoViewer.tsx +171 -0
- package/src/components/MessageAttachment/_shared/ViewerShell.tsx +159 -0
- package/src/components/MessageAttachment/_shared/fileMeta.test.ts +82 -0
- package/src/components/MessageAttachment/_shared/fileMeta.ts +95 -0
- package/src/components/MessageAttachment/_shared/triggerDownload.ts +54 -0
- package/src/components/MessageAttachment/_shared/useViewer.ts +53 -0
- package/src/components/MessageAttachment/index.tsx +149 -0
- package/src/components/MessageAttachment/stories/StoryTable.tsx +72 -0
- package/src/components/MessageAttachment/types.ts +178 -0
- package/src/index.ts +32 -0
- package/dist/Card-D32U6KfZ.js +0 -85
- package/dist/Card-D32U6KfZ.js.map +0 -1
- package/dist/Card-DlSSJPip.js +0 -60
- package/dist/Card-DlSSJPip.js.map +0 -1
- package/dist/Card-zGbhRBwv.js +0 -48
- package/dist/Card-zGbhRBwv.js.map +0 -1
- package/dist/CardThumbnail-DTBuRQHF.js +0 -239
- package/dist/CardThumbnail-DTBuRQHF.js.map +0 -1
- package/dist/index-DfcRe-Hj.js +0 -3103
- package/dist/index-DfcRe-Hj.js.map +0 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import Bubble from '../_shared/Bubble'
|
|
4
|
+
import DismissButton from '../_shared/DismissButton'
|
|
5
|
+
import ImageViewer, {
|
|
6
|
+
type ImageViewerItem,
|
|
7
|
+
} from '../_shared/ImageViewer'
|
|
8
|
+
import MediaStackGrid, {
|
|
9
|
+
type MediaStackTile,
|
|
10
|
+
} from '../_shared/MediaStackGrid'
|
|
11
|
+
import { useViewer } from '../_shared/useViewer'
|
|
12
|
+
import {
|
|
13
|
+
bubbleVariantForState,
|
|
14
|
+
type ImageItem,
|
|
15
|
+
type ImageLoadingMode,
|
|
16
|
+
type MessageAttachmentBaseProps,
|
|
17
|
+
type MessageAttachmentState,
|
|
18
|
+
} from '../types'
|
|
19
|
+
|
|
20
|
+
export interface ImageAttachmentSharedProps extends MessageAttachmentBaseProps {
|
|
21
|
+
/** Single image — convenience for the most common case. */
|
|
22
|
+
src?: string
|
|
23
|
+
alt?: string
|
|
24
|
+
/** Filename surfaced in the viewer toolbar + download default name. */
|
|
25
|
+
filename?: string
|
|
26
|
+
/**
|
|
27
|
+
* Stacked images. Takes precedence over `src` when set. Renders a
|
|
28
|
+
* 1 / 2 / 3 / 4-tile grid (5+ collapse into a `+N` overflow tile).
|
|
29
|
+
* Sent + Received only — the composer surface intentionally accepts
|
|
30
|
+
* a single attachment at a time.
|
|
31
|
+
*/
|
|
32
|
+
items?: ImageItem[]
|
|
33
|
+
/**
|
|
34
|
+
* Native lazy-load hint forwarded to every `<img>` rendered on the
|
|
35
|
+
* bubble surface (Composer thumbnail, single + stacked tiles, and
|
|
36
|
+
* the `+N` overflow tile). Defaults to `'lazy'` — chat surfaces
|
|
37
|
+
* usually scroll a long history, and lazy loading prevents every
|
|
38
|
+
* historical image from being fetched on mount. Set `'eager'` for
|
|
39
|
+
* above-the-fold hero attachments. Per-tile overrides live on
|
|
40
|
+
* `ImageItem.loading`. The opened `ImageViewer` always eager-loads
|
|
41
|
+
* the active image regardless of this value.
|
|
42
|
+
*/
|
|
43
|
+
loading?: ImageLoadingMode
|
|
44
|
+
/**
|
|
45
|
+
* Forwarded to the Image viewer trigger. When omitted the click
|
|
46
|
+
* still opens the built-in viewer — supply this for analytics, or
|
|
47
|
+
* return `false` to intercept the open (e.g. switch to a route-
|
|
48
|
+
* based gallery / confirmation modal). Any other return value
|
|
49
|
+
* (including `void`/`undefined`) lets the built-in viewer open.
|
|
50
|
+
* For stacked attachments the `index` argument identifies the tile.
|
|
51
|
+
*/
|
|
52
|
+
onClick?: (index: number) => boolean | void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tileFromItem = (
|
|
56
|
+
item: ImageItem,
|
|
57
|
+
index: number,
|
|
58
|
+
totalCount: number,
|
|
59
|
+
fallbackLoading: ImageLoadingMode
|
|
60
|
+
): MediaStackTile => ({
|
|
61
|
+
ariaLabel: `Open image ${index + 1} of ${totalCount}`,
|
|
62
|
+
content: (
|
|
63
|
+
<img
|
|
64
|
+
src={item.src}
|
|
65
|
+
alt={item.alt ?? ''}
|
|
66
|
+
width={item.width}
|
|
67
|
+
height={item.height}
|
|
68
|
+
draggable={false}
|
|
69
|
+
loading={item.loading ?? fallbackLoading}
|
|
70
|
+
decoding="async"
|
|
71
|
+
className="absolute inset-0 size-full rounded-md object-cover"
|
|
72
|
+
/>
|
|
73
|
+
),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const resolveItems = ({
|
|
77
|
+
src,
|
|
78
|
+
alt,
|
|
79
|
+
items,
|
|
80
|
+
}: {
|
|
81
|
+
src?: string
|
|
82
|
+
alt?: string
|
|
83
|
+
items?: ImageItem[]
|
|
84
|
+
}): ImageItem[] => {
|
|
85
|
+
if (items && items.length > 0) return items
|
|
86
|
+
if (src) return [{ src, alt }]
|
|
87
|
+
return []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface InternalImageRowProps extends ImageAttachmentSharedProps {
|
|
91
|
+
state: MessageAttachmentState
|
|
92
|
+
/**
|
|
93
|
+
* Renders a dismiss button on each tile (Composer only). When the
|
|
94
|
+
* stack has multiple tiles, the dismiss applies to the whole stack
|
|
95
|
+
* — that mirrors the mobile composer where attachments are removed
|
|
96
|
+
* as a single unit.
|
|
97
|
+
*/
|
|
98
|
+
onDismiss?: () => void
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const buildViewerItems = (
|
|
102
|
+
resolvedItems: ImageItem[],
|
|
103
|
+
filename?: string
|
|
104
|
+
): ImageViewerItem[] =>
|
|
105
|
+
resolvedItems.map((item, index) => ({
|
|
106
|
+
src: item.src,
|
|
107
|
+
alt: item.alt,
|
|
108
|
+
filename:
|
|
109
|
+
filename && resolvedItems.length === 1
|
|
110
|
+
? filename
|
|
111
|
+
: filename
|
|
112
|
+
? `${filename} (${index + 1})`
|
|
113
|
+
: undefined,
|
|
114
|
+
}))
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Composer rendering — bare 280px-square image with a dismiss `×`
|
|
118
|
+
* overlay and `rounded-md` corners. Intentionally renders without the
|
|
119
|
+
* shared `Bubble` chrome (no border / no background / no padding) so
|
|
120
|
+
* the in-progress attachment looks like a draft preview, not a sent
|
|
121
|
+
* message. The composer surface only supports a single attachment at
|
|
122
|
+
* a time — `items` and `text` are ignored here.
|
|
123
|
+
*/
|
|
124
|
+
const ImageComposerInner: React.FC<{
|
|
125
|
+
src: string
|
|
126
|
+
alt?: string
|
|
127
|
+
filename?: string
|
|
128
|
+
loading?: ImageLoadingMode
|
|
129
|
+
onClick?: (index: number) => boolean | void
|
|
130
|
+
onDismiss?: () => void
|
|
131
|
+
}> = ({ src, alt, filename, loading = 'lazy', onClick, onDismiss }) => {
|
|
132
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
133
|
+
onClick
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div className="relative w-fit">
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => handleActivate(0)}
|
|
141
|
+
aria-label="Open image"
|
|
142
|
+
className="block size-[280px] cursor-zoom-in overflow-hidden rounded-md outline-none focus-visible:ring-2 focus-visible:ring-black/40"
|
|
143
|
+
>
|
|
144
|
+
<img
|
|
145
|
+
src={src}
|
|
146
|
+
alt={alt ?? ''}
|
|
147
|
+
draggable={false}
|
|
148
|
+
loading={loading}
|
|
149
|
+
decoding="async"
|
|
150
|
+
className="size-full object-cover"
|
|
151
|
+
/>
|
|
152
|
+
</button>
|
|
153
|
+
{onDismiss ? (
|
|
154
|
+
<div className="absolute right-2 top-2 z-10">
|
|
155
|
+
<DismissButton onClick={onDismiss} />
|
|
156
|
+
</div>
|
|
157
|
+
) : null}
|
|
158
|
+
|
|
159
|
+
<ImageViewer
|
|
160
|
+
open={viewerOpen}
|
|
161
|
+
items={buildViewerItems([{ src, alt }], filename)}
|
|
162
|
+
initialIndex={viewerIndex}
|
|
163
|
+
onClose={closeViewer}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sent / Received rendering — wrapped in the shared `Bubble` chrome,
|
|
171
|
+
* supports single or stacked items, and renders an optional caption
|
|
172
|
+
* below the media.
|
|
173
|
+
*/
|
|
174
|
+
const ImageBubbleRow: React.FC<InternalImageRowProps> = ({
|
|
175
|
+
state,
|
|
176
|
+
src,
|
|
177
|
+
alt,
|
|
178
|
+
filename,
|
|
179
|
+
items,
|
|
180
|
+
text,
|
|
181
|
+
groupPosition,
|
|
182
|
+
loading = 'lazy',
|
|
183
|
+
onClick,
|
|
184
|
+
}) => {
|
|
185
|
+
const resolvedItems = resolveItems({ src, alt, items })
|
|
186
|
+
const variant = bubbleVariantForState(state)
|
|
187
|
+
const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(
|
|
188
|
+
onClick
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if (resolvedItems.length === 0) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const tiles: MediaStackTile[] = resolvedItems.map((item, index) =>
|
|
196
|
+
tileFromItem(item, index, resolvedItems.length, loading)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<Bubble
|
|
201
|
+
variant={variant}
|
|
202
|
+
text={text}
|
|
203
|
+
groupPosition={groupPosition}
|
|
204
|
+
data-testid="image-attachment"
|
|
205
|
+
>
|
|
206
|
+
<div className="relative">
|
|
207
|
+
<MediaStackGrid tiles={tiles} onTileActivate={handleActivate} />
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<ImageViewer
|
|
211
|
+
open={viewerOpen}
|
|
212
|
+
items={buildViewerItems(resolvedItems, filename)}
|
|
213
|
+
initialIndex={viewerIndex}
|
|
214
|
+
onClose={closeViewer}
|
|
215
|
+
/>
|
|
216
|
+
</Bubble>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Composer-only props. Single image (`src`) is required; stacked
|
|
222
|
+
* `items` and `text` captions are not supported in the composer state.
|
|
223
|
+
*/
|
|
224
|
+
export interface ImageComposerProps {
|
|
225
|
+
src: string
|
|
226
|
+
alt?: string
|
|
227
|
+
filename?: string
|
|
228
|
+
/**
|
|
229
|
+
* Native lazy-load hint forwarded to the composer thumbnail `<img>`.
|
|
230
|
+
* Defaults to `'lazy'`. See `ImageAttachmentSharedProps.loading` for
|
|
231
|
+
* the rationale.
|
|
232
|
+
*/
|
|
233
|
+
loading?: ImageLoadingMode
|
|
234
|
+
onClick?: (index: number) => boolean | void
|
|
235
|
+
onDismiss?: () => void
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type ImageSentProps = ImageAttachmentSharedProps
|
|
239
|
+
export type ImageReceivedProps = ImageAttachmentSharedProps
|
|
240
|
+
|
|
241
|
+
const ImageComposer: React.FC<ImageComposerProps> = (props) => (
|
|
242
|
+
<ImageComposerInner {...props} />
|
|
243
|
+
)
|
|
244
|
+
const ImageSent: React.FC<ImageSentProps> = (props) => (
|
|
245
|
+
<ImageBubbleRow {...props} state="sent" />
|
|
246
|
+
)
|
|
247
|
+
const ImageReceived: React.FC<ImageReceivedProps> = (props) => (
|
|
248
|
+
<ImageBubbleRow {...props} state="received" />
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
const ImageAttachment = {
|
|
252
|
+
Composer: ImageComposer,
|
|
253
|
+
Sent: ImageSent,
|
|
254
|
+
Received: ImageReceived,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default ImageAttachment
|