@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.
Files changed (58) hide show
  1. package/dist/{Card-CsJvUF_b.js → Card-BdTueeyk.js} +2 -2
  2. package/dist/{Card-CsJvUF_b.js.map → Card-BdTueeyk.js.map} +1 -1
  3. package/dist/{Card-DlMSDSdm.js → Card-ChR37pLZ.js} +2 -2
  4. package/dist/{Card-DlMSDSdm.js.map → Card-ChR37pLZ.js.map} +1 -1
  5. package/dist/{Card-CFFNq49v.js → Card-EKxCn56j.js} +3 -3
  6. package/dist/{Card-CFFNq49v.js.map → Card-EKxCn56j.js.map} +1 -1
  7. package/dist/{LockedThumbnail-DpJx169C.js → LockedThumbnail-B16qP3eH.js} +2 -2
  8. package/dist/{LockedThumbnail-DpJx169C.js.map → LockedThumbnail-B16qP3eH.js.map} +1 -1
  9. package/dist/index-Dn7BC9xK.js +4748 -0
  10. package/dist/index-Dn7BC9xK.js.map +1 -0
  11. package/dist/index.d.ts +591 -25
  12. package/dist/index.js +24 -19
  13. package/package.json +1 -1
  14. package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +841 -0
  15. package/src/components/LinkAttachment/LinkAttachment.stories.tsx +7 -92
  16. package/src/components/LinkAttachment/LinkAttachment.test.tsx +69 -0
  17. package/src/components/LinkAttachment/components/Received/Card.tsx +10 -30
  18. package/src/components/LinkAttachment/components/_shared/CardShell.tsx +5 -1
  19. package/src/components/LinkAttachment/index.tsx +24 -50
  20. package/src/components/LinkAttachment/types.ts +12 -5
  21. package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +203 -0
  22. package/src/components/MessageAttachment/Audio/index.tsx +189 -0
  23. package/src/components/MessageAttachment/File/FileAttachment.stories.tsx +352 -0
  24. package/src/components/MessageAttachment/File/index.tsx +240 -0
  25. package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +288 -0
  26. package/src/components/MessageAttachment/Image/index.tsx +257 -0
  27. package/src/components/MessageAttachment/MessageAttachment.test.tsx +783 -0
  28. package/src/components/MessageAttachment/Pdf/PdfAttachment.stories.tsx +292 -0
  29. package/src/components/MessageAttachment/Pdf/index.tsx +228 -0
  30. package/src/components/MessageAttachment/Video/VideoAttachment.stories.tsx +272 -0
  31. package/src/components/MessageAttachment/Video/index.tsx +281 -0
  32. package/src/components/MessageAttachment/_shared/Bubble.tsx +173 -0
  33. package/src/components/MessageAttachment/_shared/CompactDocumentRow.tsx +152 -0
  34. package/src/components/MessageAttachment/_shared/DismissButton.tsx +39 -0
  35. package/src/components/MessageAttachment/_shared/DownloadAction.tsx +175 -0
  36. package/src/components/MessageAttachment/_shared/ImageViewer.tsx +314 -0
  37. package/src/components/MessageAttachment/_shared/MediaStackGrid.tsx +139 -0
  38. package/src/components/MessageAttachment/_shared/PdfViewer.tsx +100 -0
  39. package/src/components/MessageAttachment/_shared/VideoViewer.tsx +171 -0
  40. package/src/components/MessageAttachment/_shared/ViewerShell.tsx +159 -0
  41. package/src/components/MessageAttachment/_shared/fileMeta.test.ts +82 -0
  42. package/src/components/MessageAttachment/_shared/fileMeta.ts +95 -0
  43. package/src/components/MessageAttachment/_shared/triggerDownload.ts +54 -0
  44. package/src/components/MessageAttachment/_shared/useViewer.ts +53 -0
  45. package/src/components/MessageAttachment/index.tsx +149 -0
  46. package/src/components/MessageAttachment/stories/StoryTable.tsx +72 -0
  47. package/src/components/MessageAttachment/types.ts +178 -0
  48. package/src/index.ts +32 -0
  49. package/dist/Card-D32U6KfZ.js +0 -85
  50. package/dist/Card-D32U6KfZ.js.map +0 -1
  51. package/dist/Card-DlSSJPip.js +0 -60
  52. package/dist/Card-DlSSJPip.js.map +0 -1
  53. package/dist/Card-zGbhRBwv.js +0 -48
  54. package/dist/Card-zGbhRBwv.js.map +0 -1
  55. package/dist/CardThumbnail-DTBuRQHF.js +0 -239
  56. package/dist/CardThumbnail-DTBuRQHF.js.map +0 -1
  57. package/dist/index-DfcRe-Hj.js +0 -3103
  58. 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