@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,240 @@
|
|
|
1
|
+
import { DownloadSimpleIcon } from '@phosphor-icons/react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
import Bubble from '../_shared/Bubble'
|
|
6
|
+
import CompactDocumentRow from '../_shared/CompactDocumentRow'
|
|
7
|
+
import DismissButton from '../_shared/DismissButton'
|
|
8
|
+
import { filenameFromUrl } from '../_shared/fileMeta'
|
|
9
|
+
import { triggerDownload } from '../_shared/triggerDownload'
|
|
10
|
+
import {
|
|
11
|
+
bubbleVariantForState,
|
|
12
|
+
type BubbleVariant,
|
|
13
|
+
type ComposerExtras,
|
|
14
|
+
type FileItem,
|
|
15
|
+
type MessageAttachmentBaseProps,
|
|
16
|
+
type MessageAttachmentState,
|
|
17
|
+
} from '../types'
|
|
18
|
+
|
|
19
|
+
export interface FileAttachmentSharedProps extends MessageAttachmentBaseProps {
|
|
20
|
+
/** Source URL of the file (used as the download target). */
|
|
21
|
+
src?: string
|
|
22
|
+
/** Filename — drives the title + the meta line + the download default name. */
|
|
23
|
+
filename?: string
|
|
24
|
+
fileSize?: number
|
|
25
|
+
/** MIME type — drives the type icon. Defaults to `application/octet-stream`. */
|
|
26
|
+
mimeType?: string
|
|
27
|
+
/**
|
|
28
|
+
* Override displayed title (defaults to `filename`). Useful when a
|
|
29
|
+
* sender renames the attachment before sending.
|
|
30
|
+
*/
|
|
31
|
+
title?: string
|
|
32
|
+
/**
|
|
33
|
+
* Stacked files. Takes precedence over `src` when set. Each item
|
|
34
|
+
* renders as its own row inside the bubble; clicking a row downloads
|
|
35
|
+
* that file. Sent + Received only — the composer surface accepts a
|
|
36
|
+
* single attachment at a time.
|
|
37
|
+
*/
|
|
38
|
+
items?: FileItem[]
|
|
39
|
+
/**
|
|
40
|
+
* Forwarded to the row trigger. When omitted the click still
|
|
41
|
+
* downloads the file. Supply this for analytics, or return `false`
|
|
42
|
+
* to fully intercept the download (e.g. show a confirmation modal,
|
|
43
|
+
* route to a custom preview, throttle large files). Any other
|
|
44
|
+
* return value — including `void`/`undefined` — lets the built-in
|
|
45
|
+
* download proceed. For stacked attachments the `index` argument
|
|
46
|
+
* identifies the row.
|
|
47
|
+
*/
|
|
48
|
+
onClick?: (index: number) => boolean | void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolveItems = ({
|
|
52
|
+
src,
|
|
53
|
+
filename,
|
|
54
|
+
fileSize,
|
|
55
|
+
mimeType,
|
|
56
|
+
title,
|
|
57
|
+
items,
|
|
58
|
+
}: {
|
|
59
|
+
src?: string
|
|
60
|
+
filename?: string
|
|
61
|
+
fileSize?: number
|
|
62
|
+
mimeType?: string
|
|
63
|
+
title?: string
|
|
64
|
+
items?: FileItem[]
|
|
65
|
+
}): FileItem[] => {
|
|
66
|
+
if (items && items.length > 0) return items
|
|
67
|
+
if (src) return [{ src, filename, fileSize, mimeType, title }]
|
|
68
|
+
return []
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface FileRowButtonProps {
|
|
72
|
+
variant: BubbleVariant
|
|
73
|
+
item: FileItem
|
|
74
|
+
index: number
|
|
75
|
+
onActivate: (index: number) => void
|
|
76
|
+
trailingAction: React.ReactNode
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Single tappable file row — used both for the lone file case and
|
|
81
|
+
* for each child of a stacked attachment. The icon + filename area
|
|
82
|
+
* is the click target (downloads the asset); the trailing slot is
|
|
83
|
+
* decorative on Sent / Received and hosts the dismiss control on
|
|
84
|
+
* Composer.
|
|
85
|
+
*/
|
|
86
|
+
const FileRowButton: React.FC<FileRowButtonProps> = ({
|
|
87
|
+
variant,
|
|
88
|
+
item,
|
|
89
|
+
index,
|
|
90
|
+
onActivate,
|
|
91
|
+
trailingAction,
|
|
92
|
+
}) => {
|
|
93
|
+
const resolvedFilename = item.filename ?? filenameFromUrl(item.src)
|
|
94
|
+
return (
|
|
95
|
+
<CompactDocumentRow
|
|
96
|
+
variant={variant}
|
|
97
|
+
filename={resolvedFilename}
|
|
98
|
+
title={item.title}
|
|
99
|
+
mimeType={item.mimeType ?? 'application/octet-stream'}
|
|
100
|
+
fileSize={item.fileSize}
|
|
101
|
+
onActivate={() => onActivate(index)}
|
|
102
|
+
activateLabel={`Download ${resolvedFilename}`}
|
|
103
|
+
trailingAction={trailingAction}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface InternalFileRowProps extends FileAttachmentSharedProps {
|
|
109
|
+
state: MessageAttachmentState
|
|
110
|
+
onDismiss?: () => void
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const FileAttachmentRow: React.FC<InternalFileRowProps> = ({
|
|
114
|
+
state,
|
|
115
|
+
src,
|
|
116
|
+
filename,
|
|
117
|
+
fileSize,
|
|
118
|
+
mimeType,
|
|
119
|
+
title,
|
|
120
|
+
items,
|
|
121
|
+
text,
|
|
122
|
+
groupPosition,
|
|
123
|
+
onClick,
|
|
124
|
+
onDismiss,
|
|
125
|
+
}) => {
|
|
126
|
+
const variant = bubbleVariantForState(state)
|
|
127
|
+
const showDismiss = state === 'composer' && !!onDismiss
|
|
128
|
+
const resolvedItems = resolveItems({
|
|
129
|
+
src,
|
|
130
|
+
filename,
|
|
131
|
+
fileSize,
|
|
132
|
+
mimeType,
|
|
133
|
+
title,
|
|
134
|
+
items,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// `useCallback` would be pointless here — `resolvedItems` is rebuilt
|
|
138
|
+
// every render, so memoizing on it produces a fresh function every
|
|
139
|
+
// render anyway. Keep this as a plain function.
|
|
140
|
+
const handleActivate = (index: number) => {
|
|
141
|
+
// `onClick` returning `false` cancels the built-in download so
|
|
142
|
+
// consumers can route the click to a confirmation modal, custom
|
|
143
|
+
// preview, etc. Any other return value (including void) lets the
|
|
144
|
+
// default download proceed.
|
|
145
|
+
if (onClick?.(index) === false) return
|
|
146
|
+
const item = resolvedItems[index]
|
|
147
|
+
if (!item) return
|
|
148
|
+
const resolvedFilename = item.filename ?? filenameFromUrl(item.src)
|
|
149
|
+
void triggerDownload(item.src, resolvedFilename)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (resolvedItems.length === 0) {
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const downloadIcon = (
|
|
157
|
+
<span
|
|
158
|
+
className={classNames(
|
|
159
|
+
'flex size-8 items-center justify-center rounded-full',
|
|
160
|
+
variant === 'dark' ? 'text-white/70' : 'text-black/70'
|
|
161
|
+
)}
|
|
162
|
+
aria-hidden
|
|
163
|
+
>
|
|
164
|
+
<DownloadSimpleIcon className="size-5" weight="bold" />
|
|
165
|
+
</span>
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<Bubble
|
|
170
|
+
variant={variant}
|
|
171
|
+
text={text}
|
|
172
|
+
groupPosition={groupPosition}
|
|
173
|
+
data-testid="file-attachment"
|
|
174
|
+
>
|
|
175
|
+
{/* Stacked rows get a small vertical gap so each file reads as
|
|
176
|
+
a discrete attachment rather than one merged blob. Single
|
|
177
|
+
row falls through to a no-op `gap-0`. */}
|
|
178
|
+
<div className="flex flex-col gap-2">
|
|
179
|
+
{resolvedItems.map((item, index) => {
|
|
180
|
+
// Composer only supports a single attachment so the dismiss
|
|
181
|
+
// control sits on the only row; otherwise every row gets
|
|
182
|
+
// the trailing download icon hint.
|
|
183
|
+
const trailingAction =
|
|
184
|
+
showDismiss && index === 0 ? (
|
|
185
|
+
<DismissButton onClick={onDismiss!} variant="inline" />
|
|
186
|
+
) : (
|
|
187
|
+
downloadIcon
|
|
188
|
+
)
|
|
189
|
+
return (
|
|
190
|
+
<FileRowButton
|
|
191
|
+
key={`${item.src}-${index}`}
|
|
192
|
+
variant={variant}
|
|
193
|
+
item={item}
|
|
194
|
+
index={index}
|
|
195
|
+
onActivate={handleActivate}
|
|
196
|
+
trailingAction={trailingAction}
|
|
197
|
+
/>
|
|
198
|
+
)
|
|
199
|
+
})}
|
|
200
|
+
</div>
|
|
201
|
+
</Bubble>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Composer-only props. Single file (`src`) only — the composer surface
|
|
207
|
+
* accepts a single attachment at a time, so `items` is not supported.
|
|
208
|
+
* Captions (`text`) and `groupPosition` are also dropped: the composer
|
|
209
|
+
* renders a standalone draft, not part of a same-author run, and
|
|
210
|
+
* captions live in the message-input textarea, not inside the draft
|
|
211
|
+
* attachment preview.
|
|
212
|
+
*/
|
|
213
|
+
export interface FileComposerProps extends ComposerExtras {
|
|
214
|
+
src?: string
|
|
215
|
+
filename?: string
|
|
216
|
+
fileSize?: number
|
|
217
|
+
mimeType?: string
|
|
218
|
+
title?: string
|
|
219
|
+
onClick?: (index: number) => boolean | void
|
|
220
|
+
}
|
|
221
|
+
export type FileSentProps = FileAttachmentSharedProps
|
|
222
|
+
export type FileReceivedProps = FileAttachmentSharedProps
|
|
223
|
+
|
|
224
|
+
const FileComposer: React.FC<FileComposerProps> = (props) => (
|
|
225
|
+
<FileAttachmentRow {...props} state="composer" />
|
|
226
|
+
)
|
|
227
|
+
const FileSent: React.FC<FileSentProps> = (props) => (
|
|
228
|
+
<FileAttachmentRow {...props} state="sent" />
|
|
229
|
+
)
|
|
230
|
+
const FileReceived: React.FC<FileReceivedProps> = (props) => (
|
|
231
|
+
<FileAttachmentRow {...props} state="received" />
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const FileAttachment = {
|
|
235
|
+
Composer: FileComposer,
|
|
236
|
+
Sent: FileSent,
|
|
237
|
+
Received: FileReceived,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export default FileAttachment
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
StoryGrid,
|
|
6
|
+
StoryHeading,
|
|
7
|
+
StoryPage,
|
|
8
|
+
StoryRow,
|
|
9
|
+
} from '../stories/StoryTable'
|
|
10
|
+
import type { ImageItem } from '../types'
|
|
11
|
+
|
|
12
|
+
import ImageAttachment from '.'
|
|
13
|
+
|
|
14
|
+
const SINGLE_IMAGE = 'https://picsum.photos/seed/portrait/720/720'
|
|
15
|
+
const STACK_IMAGES: ImageItem[] = [
|
|
16
|
+
{ src: 'https://picsum.photos/seed/img-1/720/720', alt: 'Photo 1' },
|
|
17
|
+
{ src: 'https://picsum.photos/seed/img-2/720/720', alt: 'Photo 2' },
|
|
18
|
+
{ src: 'https://picsum.photos/seed/img-3/720/720', alt: 'Photo 3' },
|
|
19
|
+
{ src: 'https://picsum.photos/seed/img-4/720/720', alt: 'Photo 4' },
|
|
20
|
+
{ src: 'https://picsum.photos/seed/img-5/720/720', alt: 'Photo 5' },
|
|
21
|
+
{ src: 'https://picsum.photos/seed/img-6/720/720', alt: 'Photo 6' },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const meta: Meta = {
|
|
25
|
+
title: 'MessageAttachment/Image',
|
|
26
|
+
parameters: { layout: 'fullscreen' },
|
|
27
|
+
}
|
|
28
|
+
export default meta
|
|
29
|
+
|
|
30
|
+
const handleDismiss = () => {
|
|
31
|
+
// eslint-disable-next-line no-console
|
|
32
|
+
console.log('Dismissed image attachment')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Single image — renders inside a chat bubble with rounded corners.
|
|
37
|
+
* Composer / Sent / Received are all interactive: clicking opens the
|
|
38
|
+
* built-in `ImageViewer` lightbox with mouse-wheel zoom, drag-to-pan,
|
|
39
|
+
* double-click toggle, and a download action in the toolbar.
|
|
40
|
+
*/
|
|
41
|
+
export const Single: StoryFn = () => (
|
|
42
|
+
<StoryPage>
|
|
43
|
+
<StoryHeading
|
|
44
|
+
title="Single image"
|
|
45
|
+
description="Click any bubble to open the lightbox viewer (mouse-wheel zoom, drag-to-pan, double-click toggle, download)."
|
|
46
|
+
/>
|
|
47
|
+
<StoryGrid>
|
|
48
|
+
<StoryRow
|
|
49
|
+
label="Default"
|
|
50
|
+
composer={
|
|
51
|
+
<ImageAttachment.Composer
|
|
52
|
+
src={SINGLE_IMAGE}
|
|
53
|
+
alt="Portrait"
|
|
54
|
+
filename="portrait.jpg"
|
|
55
|
+
onDismiss={handleDismiss}
|
|
56
|
+
/>
|
|
57
|
+
}
|
|
58
|
+
sent={
|
|
59
|
+
<ImageAttachment.Sent
|
|
60
|
+
src={SINGLE_IMAGE}
|
|
61
|
+
alt="Portrait"
|
|
62
|
+
filename="portrait.jpg"
|
|
63
|
+
/>
|
|
64
|
+
}
|
|
65
|
+
received={
|
|
66
|
+
<ImageAttachment.Received
|
|
67
|
+
src={SINGLE_IMAGE}
|
|
68
|
+
alt="Portrait"
|
|
69
|
+
filename="portrait.jpg"
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
72
|
+
/>
|
|
73
|
+
</StoryGrid>
|
|
74
|
+
</StoryPage>
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
/** Single image with an accompanying caption rendered inside the bubble.
|
|
78
|
+
* Sent + Received only — captions are not rendered on the composer
|
|
79
|
+
* surface (the user types them in the message input, not on the
|
|
80
|
+
* attachment preview). */
|
|
81
|
+
export const SingleWithText: StoryFn = () => (
|
|
82
|
+
<StoryPage>
|
|
83
|
+
<StoryHeading
|
|
84
|
+
title="Single image with caption"
|
|
85
|
+
description="Caption renders inside the same bubble, below the image — matches the mobile chat 'Here is the image' design. Composer column is intentionally blank: composer captions live in the message input, not the attachment preview."
|
|
86
|
+
/>
|
|
87
|
+
<StoryGrid>
|
|
88
|
+
<StoryRow
|
|
89
|
+
label="Short caption"
|
|
90
|
+
composer={
|
|
91
|
+
<ImageAttachment.Composer
|
|
92
|
+
src={SINGLE_IMAGE}
|
|
93
|
+
alt="Portrait"
|
|
94
|
+
filename="portrait.jpg"
|
|
95
|
+
onDismiss={handleDismiss}
|
|
96
|
+
/>
|
|
97
|
+
}
|
|
98
|
+
sent={
|
|
99
|
+
<ImageAttachment.Sent
|
|
100
|
+
src={SINGLE_IMAGE}
|
|
101
|
+
filename="portrait.jpg"
|
|
102
|
+
text="Here is the image"
|
|
103
|
+
/>
|
|
104
|
+
}
|
|
105
|
+
received={
|
|
106
|
+
<ImageAttachment.Received
|
|
107
|
+
src={SINGLE_IMAGE}
|
|
108
|
+
filename="portrait.jpg"
|
|
109
|
+
text="Here is the image"
|
|
110
|
+
/>
|
|
111
|
+
}
|
|
112
|
+
/>
|
|
113
|
+
<StoryRow
|
|
114
|
+
label="Long caption"
|
|
115
|
+
composer={
|
|
116
|
+
<ImageAttachment.Composer
|
|
117
|
+
src={SINGLE_IMAGE}
|
|
118
|
+
alt="Portrait"
|
|
119
|
+
filename="portrait.jpg"
|
|
120
|
+
onDismiss={handleDismiss}
|
|
121
|
+
/>
|
|
122
|
+
}
|
|
123
|
+
sent={
|
|
124
|
+
<ImageAttachment.Sent
|
|
125
|
+
src={SINGLE_IMAGE}
|
|
126
|
+
filename="portrait.jpg"
|
|
127
|
+
text="Just got this shot from the new photographer — let me know if you want to use it for the campaign or pick something different."
|
|
128
|
+
/>
|
|
129
|
+
}
|
|
130
|
+
received={
|
|
131
|
+
<ImageAttachment.Received
|
|
132
|
+
src={SINGLE_IMAGE}
|
|
133
|
+
filename="portrait.jpg"
|
|
134
|
+
text="Just got this shot from the new photographer — let me know if you want to use it for the campaign or pick something different."
|
|
135
|
+
/>
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
</StoryGrid>
|
|
139
|
+
</StoryPage>
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Stacked image attachments — multiple images in one bubble laid out
|
|
144
|
+
* in a 1 / 2 / 3 / 4-tile grid. Five or more collapse into a `+N`
|
|
145
|
+
* overflow tile on the bottom-right. Click any tile to open the
|
|
146
|
+
* lightbox at that index, then arrow-navigate between siblings.
|
|
147
|
+
*
|
|
148
|
+
* Sent + Received only — the composer surface accepts a single image
|
|
149
|
+
* at a time, so the Composer column is intentionally blank.
|
|
150
|
+
*/
|
|
151
|
+
export const Stacked: StoryFn = () => (
|
|
152
|
+
<StoryPage>
|
|
153
|
+
<StoryHeading
|
|
154
|
+
title="Stacked images"
|
|
155
|
+
description="Click any tile to open the lightbox at that index. Use arrow keys to navigate between siblings. Composer column is intentionally blank — the composer accepts a single image at a time."
|
|
156
|
+
/>
|
|
157
|
+
<StoryGrid>
|
|
158
|
+
<StoryRow
|
|
159
|
+
label="2 photos"
|
|
160
|
+
composer={
|
|
161
|
+
<ImageAttachment.Composer
|
|
162
|
+
src={SINGLE_IMAGE}
|
|
163
|
+
alt="Portrait"
|
|
164
|
+
filename="portrait.jpg"
|
|
165
|
+
onDismiss={handleDismiss}
|
|
166
|
+
/>
|
|
167
|
+
}
|
|
168
|
+
sent={
|
|
169
|
+
<ImageAttachment.Sent items={STACK_IMAGES.slice(0, 2)} filename="album" />
|
|
170
|
+
}
|
|
171
|
+
received={
|
|
172
|
+
<ImageAttachment.Received
|
|
173
|
+
items={STACK_IMAGES.slice(0, 2)}
|
|
174
|
+
filename="album"
|
|
175
|
+
/>
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
<StoryRow
|
|
179
|
+
label="3 photos"
|
|
180
|
+
composer={
|
|
181
|
+
<ImageAttachment.Composer
|
|
182
|
+
src={SINGLE_IMAGE}
|
|
183
|
+
alt="Portrait"
|
|
184
|
+
filename="portrait.jpg"
|
|
185
|
+
onDismiss={handleDismiss}
|
|
186
|
+
/>
|
|
187
|
+
}
|
|
188
|
+
sent={
|
|
189
|
+
<ImageAttachment.Sent items={STACK_IMAGES.slice(0, 3)} filename="album" />
|
|
190
|
+
}
|
|
191
|
+
received={
|
|
192
|
+
<ImageAttachment.Received
|
|
193
|
+
items={STACK_IMAGES.slice(0, 3)}
|
|
194
|
+
filename="album"
|
|
195
|
+
/>
|
|
196
|
+
}
|
|
197
|
+
/>
|
|
198
|
+
<StoryRow
|
|
199
|
+
label="4 photos"
|
|
200
|
+
composer={
|
|
201
|
+
<ImageAttachment.Composer
|
|
202
|
+
src={SINGLE_IMAGE}
|
|
203
|
+
alt="Portrait"
|
|
204
|
+
filename="portrait.jpg"
|
|
205
|
+
onDismiss={handleDismiss}
|
|
206
|
+
/>
|
|
207
|
+
}
|
|
208
|
+
sent={
|
|
209
|
+
<ImageAttachment.Sent items={STACK_IMAGES.slice(0, 4)} filename="album" />
|
|
210
|
+
}
|
|
211
|
+
received={
|
|
212
|
+
<ImageAttachment.Received
|
|
213
|
+
items={STACK_IMAGES.slice(0, 4)}
|
|
214
|
+
filename="album"
|
|
215
|
+
/>
|
|
216
|
+
}
|
|
217
|
+
/>
|
|
218
|
+
<StoryRow
|
|
219
|
+
label="6 photos (+overflow)"
|
|
220
|
+
sent={<ImageAttachment.Sent items={STACK_IMAGES} filename="album" />}
|
|
221
|
+
received={<ImageAttachment.Received items={STACK_IMAGES} filename="album" />}
|
|
222
|
+
/>
|
|
223
|
+
</StoryGrid>
|
|
224
|
+
</StoryPage>
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
/** Stacked image attachments with an accompanying caption.
|
|
228
|
+
* Sent + Received only — see `Stacked` for the rationale. */
|
|
229
|
+
export const StackedWithText: StoryFn = () => (
|
|
230
|
+
<StoryPage>
|
|
231
|
+
<StoryHeading
|
|
232
|
+
title="Stacked images with caption"
|
|
233
|
+
description="Caption sits inside the same bubble. The grid layout adapts to the tile count above it. Composer column is intentionally blank — the composer accepts a single image at a time."
|
|
234
|
+
/>
|
|
235
|
+
<StoryGrid>
|
|
236
|
+
<StoryRow
|
|
237
|
+
label="3 photos + caption"
|
|
238
|
+
composer={
|
|
239
|
+
<ImageAttachment.Composer
|
|
240
|
+
src={SINGLE_IMAGE}
|
|
241
|
+
alt="Portrait"
|
|
242
|
+
filename="portrait.jpg"
|
|
243
|
+
onDismiss={handleDismiss}
|
|
244
|
+
/>
|
|
245
|
+
}
|
|
246
|
+
sent={
|
|
247
|
+
<ImageAttachment.Sent
|
|
248
|
+
items={STACK_IMAGES.slice(0, 3)}
|
|
249
|
+
filename="album"
|
|
250
|
+
text="Some shots from the studio session"
|
|
251
|
+
/>
|
|
252
|
+
}
|
|
253
|
+
received={
|
|
254
|
+
<ImageAttachment.Received
|
|
255
|
+
items={STACK_IMAGES.slice(0, 3)}
|
|
256
|
+
filename="album"
|
|
257
|
+
text="Some shots from the studio session"
|
|
258
|
+
/>
|
|
259
|
+
}
|
|
260
|
+
/>
|
|
261
|
+
<StoryRow
|
|
262
|
+
label="6 photos + caption"
|
|
263
|
+
composer={
|
|
264
|
+
<ImageAttachment.Composer
|
|
265
|
+
src={SINGLE_IMAGE}
|
|
266
|
+
alt="Portrait"
|
|
267
|
+
filename="portrait.jpg"
|
|
268
|
+
onDismiss={handleDismiss}
|
|
269
|
+
/>
|
|
270
|
+
}
|
|
271
|
+
sent={
|
|
272
|
+
<ImageAttachment.Sent
|
|
273
|
+
items={STACK_IMAGES}
|
|
274
|
+
filename="album"
|
|
275
|
+
text="The whole album from yesterday"
|
|
276
|
+
/>
|
|
277
|
+
}
|
|
278
|
+
received={
|
|
279
|
+
<ImageAttachment.Received
|
|
280
|
+
items={STACK_IMAGES}
|
|
281
|
+
filename="album"
|
|
282
|
+
text="The whole album from yesterday"
|
|
283
|
+
/>
|
|
284
|
+
}
|
|
285
|
+
/>
|
|
286
|
+
</StoryGrid>
|
|
287
|
+
</StoryPage>
|
|
288
|
+
)
|