@linktr.ee/messaging-react 1.33.2 → 1.33.3
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-BfA8wq8O.js +181 -0
- package/dist/Card-BfA8wq8O.js.map +1 -0
- package/dist/{Card-Ddi8bg90.js → Card-Bpud_enW.js} +55 -55
- package/dist/Card-Bpud_enW.js.map +1 -0
- package/dist/assets/index.css +1 -1
- package/dist/index-Ydi1pTAi.js +3130 -0
- package/dist/index-Ydi1pTAi.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/ChannelInfoDialog/index.tsx +3 -1
- package/src/components/ChannelView.tsx +24 -6
- package/src/components/LockedAttachment/components/Creator/Card.tsx +33 -24
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +10 -1
- package/src/components/LockedAttachment/components/Visitor/Card.tsx +9 -9
- package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +2 -2
- package/src/components/MediaMessage/MediaMessage.stories.tsx +82 -19
- package/src/components/MediaMessage/MediaMessage.test.tsx +277 -18
- package/src/components/MediaMessage/index.tsx +388 -77
- package/src/styles.css +49 -0
- package/dist/Card-DEe10CiS.js +0 -171
- package/dist/Card-DEe10CiS.js.map +0 -1
- package/dist/Card-Ddi8bg90.js.map +0 -1
- package/dist/index-BePLvyvi.js +0 -2868
- package/dist/index-BePLvyvi.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as e, a as t, C as i, b as n, c as o, d as g, F as r, e as m, L as u, f as M, h as c, i as l, j as h, P as C, k as P, u as d, l as L, m as p, n as v } from "./index-
|
|
1
|
+
import { A as e, a as t, C as i, b as n, c as o, d as g, F as r, e as m, L as u, f as M, h as c, i as l, j as h, P as C, k as P, u as d, l as L, m as p, n as v } from "./index-Ydi1pTAi.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/package.json
CHANGED
|
@@ -250,9 +250,11 @@ export const ChannelInfoDialog: React.FC<ChannelInfoDialogProps> = ({
|
|
|
250
250
|
{followerStatusLabel}
|
|
251
251
|
</span>
|
|
252
252
|
)}
|
|
253
|
-
{customProfileContent}
|
|
254
253
|
</div>
|
|
255
254
|
</div>
|
|
255
|
+
{customProfileContent && (
|
|
256
|
+
<div className="w-full">{customProfileContent}</div>
|
|
257
|
+
)}
|
|
256
258
|
</div>
|
|
257
259
|
|
|
258
260
|
<ul className="flex flex-col gap-2 mt-2">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArrowLeftIcon, DotsThreeIcon, StarIcon } from '@phosphor-icons/react'
|
|
1
|
+
import { ArrowLeftIcon, CaretRightIcon, DotsThreeIcon, StarIcon } from '@phosphor-icons/react'
|
|
2
2
|
import classNames from 'classnames'
|
|
3
3
|
import React, { useCallback, useRef } from 'react'
|
|
4
4
|
import { Channel as ChannelType } from 'stream-chat'
|
|
@@ -96,9 +96,15 @@ const CustomChannelHeader: React.FC<{
|
|
|
96
96
|
starred={showStarButton && isStarred}
|
|
97
97
|
size={40}
|
|
98
98
|
/>
|
|
99
|
-
<
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={onShowInfo}
|
|
102
|
+
className="flex items-center gap-0.5 rounded-full bg-black/[0.05] px-3 py-1 text-xs font-medium text-black/90 hover:bg-black/[0.08] transition-colors"
|
|
103
|
+
aria-label={`View info for ${participantName}`}
|
|
104
|
+
>
|
|
100
105
|
{participantName}
|
|
101
|
-
|
|
106
|
+
<CaretRightIcon className="size-3 shrink-0" />
|
|
107
|
+
</button>
|
|
102
108
|
</div>
|
|
103
109
|
<div className="flex justify-end items-center gap-2">
|
|
104
110
|
{showStarButton && (
|
|
@@ -150,9 +156,21 @@ const CustomChannelHeader: React.FC<{
|
|
|
150
156
|
size={40}
|
|
151
157
|
/>
|
|
152
158
|
<div className="min-w-0">
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
{canShowInfo ? (
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={onShowInfo}
|
|
163
|
+
className="flex items-center gap-1 font-medium text-black/90 truncate hover:text-black/70 transition-colors"
|
|
164
|
+
aria-label={`View info for ${participantName}`}
|
|
165
|
+
>
|
|
166
|
+
<span className="truncate">{participantName}</span>
|
|
167
|
+
<CaretRightIcon className="size-4 shrink-0" />
|
|
168
|
+
</button>
|
|
169
|
+
) : (
|
|
170
|
+
<h1 className="font-medium text-black/90 truncate">
|
|
171
|
+
{participantName}
|
|
172
|
+
</h1>
|
|
173
|
+
)}
|
|
156
174
|
</div>
|
|
157
175
|
</div>
|
|
158
176
|
<div className="flex items-center gap-2">
|
|
@@ -54,8 +54,8 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
54
54
|
<div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]">
|
|
55
55
|
<CardHeader
|
|
56
56
|
onDismiss={onDismiss}
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
onToggle={onPreviewClick ? handleToggle : undefined}
|
|
58
|
+
isExpanded={!!source}
|
|
59
59
|
paymentStatus={paymentStatus}
|
|
60
60
|
/>
|
|
61
61
|
|
|
@@ -67,11 +67,11 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
67
67
|
onToggle={onPreviewClick ? handleToggle : undefined}
|
|
68
68
|
/>
|
|
69
69
|
|
|
70
|
-
<div className="px-4 pb-3 pt-3">
|
|
70
|
+
<div className="px-4 pb-3 pt-3 bg-black">
|
|
71
71
|
<p
|
|
72
72
|
className={classNames('mb-1.5 truncate text-base font-medium', {
|
|
73
|
-
'text-
|
|
74
|
-
'text-
|
|
73
|
+
'text-white/30': !title,
|
|
74
|
+
'text-white': !!title,
|
|
75
75
|
})}
|
|
76
76
|
>
|
|
77
77
|
{title || placeholderTitle}
|
|
@@ -79,30 +79,30 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
79
79
|
|
|
80
80
|
<div className="flex items-center gap-1">
|
|
81
81
|
{renderTypeIcon(mimeType, {
|
|
82
|
-
className: 'size-5 shrink-0 text-
|
|
82
|
+
className: 'size-5 shrink-0 text-white/55',
|
|
83
83
|
weight: 'regular',
|
|
84
84
|
})}
|
|
85
85
|
|
|
86
86
|
{detail && (
|
|
87
|
-
<span className="text-xs font-medium text-
|
|
87
|
+
<span className="text-xs font-medium text-white/55">{detail}</span>
|
|
88
88
|
)}
|
|
89
89
|
|
|
90
90
|
{paymentStatus === 'paid' ? (
|
|
91
91
|
<React.Fragment>
|
|
92
|
-
<span className="text-xs font-medium text-
|
|
93
|
-
<span className="text-xs font-medium text-[#
|
|
92
|
+
<span className="text-xs font-medium text-white/55">•</span>
|
|
93
|
+
<span className="text-xs font-medium text-[#4ade80]">Sold</span>
|
|
94
94
|
<CheckCircleIcon
|
|
95
|
-
className="size-4 text-[#
|
|
95
|
+
className="size-4 text-[#4ade80]"
|
|
96
96
|
weight="bold"
|
|
97
97
|
/>
|
|
98
98
|
</React.Fragment>
|
|
99
99
|
) : (
|
|
100
100
|
<React.Fragment>
|
|
101
|
-
<span className="text-xs font-medium text-
|
|
101
|
+
<span className="text-xs font-medium text-white/55">•</span>
|
|
102
102
|
<span
|
|
103
103
|
className={classNames('text-xs font-medium', {
|
|
104
|
-
'text-
|
|
105
|
-
'text-
|
|
104
|
+
'text-white/30': !amountText,
|
|
105
|
+
'text-white/55': !!amountText,
|
|
106
106
|
})}
|
|
107
107
|
>
|
|
108
108
|
{amountText || placeholderAmountText}
|
|
@@ -117,15 +117,15 @@ const CreatorCard: React.FC<CreatorCardProps> = ({
|
|
|
117
117
|
|
|
118
118
|
interface CardHeaderProps {
|
|
119
119
|
onDismiss?: () => void
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
onToggle?: () => void
|
|
121
|
+
isExpanded?: boolean
|
|
122
122
|
paymentStatus?: PaymentStatus
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
const CardHeader: React.FC<CardHeaderProps> = ({
|
|
126
126
|
onDismiss,
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
onToggle,
|
|
128
|
+
isExpanded,
|
|
129
129
|
paymentStatus,
|
|
130
130
|
}) => {
|
|
131
131
|
if (onDismiss) {
|
|
@@ -141,13 +141,22 @@ const CardHeader: React.FC<CardHeaderProps> = ({
|
|
|
141
141
|
)
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
?
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
if (onToggle) {
|
|
145
|
+
const Icon = isExpanded ? EyeIcon : EyeSlashIcon
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={onToggle}
|
|
150
|
+
className="absolute top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white left-3"
|
|
151
|
+
aria-label={isExpanded ? 'Hide preview' : 'Show preview'}
|
|
152
|
+
aria-pressed={isExpanded}
|
|
153
|
+
>
|
|
154
|
+
<Icon className="size-4" weight="fill" />
|
|
155
|
+
</button>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const Icon = paymentStatus === 'paid' ? LockOpenIcon : LockIcon
|
|
151
160
|
|
|
152
161
|
return (
|
|
153
162
|
<div className="absolute top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white left-3">
|
|
@@ -31,6 +31,12 @@ export interface MediaPlayerProps {
|
|
|
31
31
|
showProgress?: boolean
|
|
32
32
|
/** When true, requests muted playback (helps autoplay policies on video). */
|
|
33
33
|
muted?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* When provided, overrides the default click-to-play-toggle behaviour on the
|
|
36
|
+
* player container. The play/pause button (which calls stopPropagation) is
|
|
37
|
+
* unaffected, so inline playback still works.
|
|
38
|
+
*/
|
|
39
|
+
onContainerClick?: (e: React.MouseEvent) => void
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
@@ -43,6 +49,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
43
49
|
controls = true,
|
|
44
50
|
showProgress = false,
|
|
45
51
|
muted = false,
|
|
52
|
+
onContainerClick,
|
|
46
53
|
}) => {
|
|
47
54
|
// --- Derived ---
|
|
48
55
|
const sourceType = getSourceType(mimeType)
|
|
@@ -185,13 +192,15 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
|
|
|
185
192
|
tabIndex={0}
|
|
186
193
|
className={`relative cursor-pointer overflow-hidden bg-black ${aspectClass}`}
|
|
187
194
|
style={aspectStyle}
|
|
188
|
-
onClick={() => {
|
|
195
|
+
onClick={(e) => {
|
|
196
|
+
if (onContainerClick) { onContainerClick(e); return }
|
|
189
197
|
if (manualPlayRequired) return
|
|
190
198
|
if (controls) setPlaying((p) => !p)
|
|
191
199
|
}}
|
|
192
200
|
onKeyDown={(e) => {
|
|
193
201
|
if (e.key !== 'Enter' && e.key !== ' ') return
|
|
194
202
|
e.preventDefault()
|
|
203
|
+
if (onContainerClick) { onContainerClick(e as unknown as React.MouseEvent); return }
|
|
195
204
|
if (manualPlayRequired) return
|
|
196
205
|
if (controls) setPlaying((p) => !p)
|
|
197
206
|
}}
|
|
@@ -111,35 +111,35 @@ const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
|
111
111
|
paymentStatus={paymentStatus}
|
|
112
112
|
/>
|
|
113
113
|
|
|
114
|
-
<div className="px-4 pb-3 pt-3">
|
|
115
|
-
<p className="mb-1.5 truncate text-base font-medium text-
|
|
114
|
+
<div className="px-4 pb-3 pt-3 bg-black">
|
|
115
|
+
<p className="mb-1.5 truncate text-base font-medium text-white">
|
|
116
116
|
{title}
|
|
117
117
|
</p>
|
|
118
118
|
<div className="flex items-center gap-1">
|
|
119
119
|
{renderTypeIcon(mimeType, {
|
|
120
|
-
className: 'size-5 shrink-0 text-
|
|
120
|
+
className: 'size-5 shrink-0 text-white/55',
|
|
121
121
|
weight: 'regular',
|
|
122
122
|
})}
|
|
123
123
|
|
|
124
124
|
{detail && (
|
|
125
|
-
<span className="text-xs font-medium text-
|
|
125
|
+
<span className="text-xs font-medium text-white/55">{detail}</span>
|
|
126
126
|
)}
|
|
127
127
|
|
|
128
128
|
{paymentStatus === 'paid' ? (
|
|
129
129
|
<React.Fragment>
|
|
130
|
-
<span className="text-xs font-medium text-
|
|
131
|
-
<span className="text-xs font-medium text-[#
|
|
130
|
+
<span className="text-xs font-medium text-white/55">•</span>
|
|
131
|
+
<span className="text-xs font-medium text-[#4ade80]">
|
|
132
132
|
Purchased
|
|
133
133
|
</span>
|
|
134
134
|
<CheckCircleIcon
|
|
135
|
-
className="size-4 text-[#
|
|
135
|
+
className="size-4 text-[#4ade80]"
|
|
136
136
|
weight="bold"
|
|
137
137
|
/>
|
|
138
138
|
</React.Fragment>
|
|
139
139
|
) : amountText != null ? (
|
|
140
140
|
<React.Fragment>
|
|
141
|
-
<span className="text-xs font-medium text-
|
|
142
|
-
<span className="text-xs font-medium text-
|
|
141
|
+
<span className="text-xs font-medium text-white/55">•</span>
|
|
142
|
+
<span className="text-xs font-medium text-white/55">
|
|
143
143
|
{amountText}
|
|
144
144
|
</span>
|
|
145
145
|
</React.Fragment>
|
|
@@ -26,7 +26,7 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
26
26
|
type="button"
|
|
27
27
|
onClick={onUnlockClicked}
|
|
28
28
|
disabled={isUnlocking}
|
|
29
|
-
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-
|
|
29
|
+
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-white px-4 text-sm font-medium leading-none text-[#121110] hover:bg-white/90 disabled:opacity-70"
|
|
30
30
|
>
|
|
31
31
|
{isUnlocking ? (
|
|
32
32
|
<LoadingDots />
|
|
@@ -47,7 +47,7 @@ const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
|
47
47
|
target="_blank"
|
|
48
48
|
rel="noopener noreferrer"
|
|
49
49
|
onClick={onDownloadClicked}
|
|
50
|
-
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-
|
|
50
|
+
className="mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-white px-4 text-sm font-medium leading-none !text-[#121110] hover:bg-white/90"
|
|
51
51
|
>
|
|
52
52
|
<DownloadSimpleIcon className="size-4" weight="bold" />
|
|
53
53
|
Download
|
|
@@ -84,6 +84,43 @@ const VARIANTS = [
|
|
|
84
84
|
},
|
|
85
85
|
] as const
|
|
86
86
|
|
|
87
|
+
const LINK_VARIANTS = [
|
|
88
|
+
{
|
|
89
|
+
label: 'With image',
|
|
90
|
+
attachment: {
|
|
91
|
+
type: 'link',
|
|
92
|
+
og_scrape_url: 'https://linktr.ee/brieparsons',
|
|
93
|
+
title: 'World Famous 3 Bottle Grey Wash Set',
|
|
94
|
+
text: 'When its time to shade, the World Famous Grey Wash set has you covered.',
|
|
95
|
+
image_url: 'https://picsum.photos/seed/linkcard/560/315',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: 'No image',
|
|
100
|
+
attachment: {
|
|
101
|
+
type: 'link',
|
|
102
|
+
og_scrape_url: 'https://linktr.ee/brieparsons',
|
|
103
|
+
title: 'World Famous 3 Bottle Grey Wash Set',
|
|
104
|
+
text: 'When its time to shade, the World Famous Grey Wash set has you covered.',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: 'Title only',
|
|
109
|
+
attachment: {
|
|
110
|
+
type: 'link',
|
|
111
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
112
|
+
title: 'Check out my Linktree',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: 'URL only',
|
|
117
|
+
attachment: {
|
|
118
|
+
type: 'link',
|
|
119
|
+
og_scrape_url: 'https://linktr.ee/someone',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
] as const
|
|
123
|
+
|
|
87
124
|
// ---------------------------------------------------------------------------
|
|
88
125
|
// Layout primitives
|
|
89
126
|
// ---------------------------------------------------------------------------
|
|
@@ -94,11 +131,10 @@ const GridTable: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
|
94
131
|
</div>
|
|
95
132
|
)
|
|
96
133
|
|
|
97
|
-
const GridHead = () => (
|
|
134
|
+
const GridHead: React.FC<{ labels: readonly string[] }> = ({ labels }) => (
|
|
98
135
|
<thead>
|
|
99
136
|
<tr>
|
|
100
|
-
|
|
101
|
-
{VARIANTS.map(({ label }) => (
|
|
137
|
+
{labels.map((label) => (
|
|
102
138
|
<th key={label} className="text-left text-xs font-medium text-black/40 pb-2">
|
|
103
139
|
{label}
|
|
104
140
|
</th>
|
|
@@ -107,22 +143,15 @@ const GridHead = () => (
|
|
|
107
143
|
</thead>
|
|
108
144
|
)
|
|
109
145
|
|
|
110
|
-
const RowLabel: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
111
|
-
<td className="text-xs text-right font-medium text-black/40 pr-4 align-top pt-2">
|
|
112
|
-
{children}
|
|
113
|
-
</td>
|
|
114
|
-
)
|
|
115
|
-
|
|
116
146
|
// ---------------------------------------------------------------------------
|
|
117
147
|
// Stories
|
|
118
148
|
// ---------------------------------------------------------------------------
|
|
119
149
|
|
|
120
|
-
export const
|
|
150
|
+
export const Received: StoryFn = () => (
|
|
121
151
|
<GridTable>
|
|
122
|
-
<GridHead />
|
|
152
|
+
<GridHead labels={VARIANTS.map((v) => v.label)} />
|
|
123
153
|
<tbody>
|
|
124
154
|
<tr>
|
|
125
|
-
<RowLabel>Sent</RowLabel>
|
|
126
155
|
{VARIANTS.map(({ label, attachment }) => (
|
|
127
156
|
<td key={label} className="align-top">
|
|
128
157
|
<MediaMessage
|
|
@@ -135,20 +164,19 @@ export const Visitor: StoryFn = () => (
|
|
|
135
164
|
</tbody>
|
|
136
165
|
</GridTable>
|
|
137
166
|
)
|
|
138
|
-
|
|
167
|
+
Received.parameters = {
|
|
139
168
|
docs: {
|
|
140
169
|
description: {
|
|
141
|
-
story: '
|
|
170
|
+
story: 'Received messages — left-aligned with avatar, light gray card background.',
|
|
142
171
|
},
|
|
143
172
|
},
|
|
144
173
|
}
|
|
145
174
|
|
|
146
|
-
export const
|
|
175
|
+
export const Sent: StoryFn = () => (
|
|
147
176
|
<GridTable>
|
|
148
|
-
<GridHead />
|
|
177
|
+
<GridHead labels={VARIANTS.map((v) => v.label)} />
|
|
149
178
|
<tbody>
|
|
150
179
|
<tr>
|
|
151
|
-
<RowLabel>Sent</RowLabel>
|
|
152
180
|
{VARIANTS.map(({ label, attachment }) => (
|
|
153
181
|
<td key={label} className="align-top">
|
|
154
182
|
<MediaMessage
|
|
@@ -161,10 +189,45 @@ export const Creator: StoryFn = () => (
|
|
|
161
189
|
</tbody>
|
|
162
190
|
</GridTable>
|
|
163
191
|
)
|
|
164
|
-
|
|
192
|
+
Sent.parameters = {
|
|
193
|
+
docs: {
|
|
194
|
+
description: {
|
|
195
|
+
story: 'Sent messages — right-aligned, no avatar, black card background.',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const Links: StoryFn = () => (
|
|
201
|
+
<GridTable>
|
|
202
|
+
<GridHead labels={LINK_VARIANTS.map((v) => v.label)} />
|
|
203
|
+
<tbody>
|
|
204
|
+
<tr>
|
|
205
|
+
{LINK_VARIANTS.map(({ label, attachment }) => (
|
|
206
|
+
<td key={label} className="align-top">
|
|
207
|
+
<MediaMessage
|
|
208
|
+
isMyMessage={false}
|
|
209
|
+
message={base({ user: SENDER, attachments: [attachment as LocalMessage['attachments'][number]] })}
|
|
210
|
+
/>
|
|
211
|
+
</td>
|
|
212
|
+
))}
|
|
213
|
+
</tr>
|
|
214
|
+
<tr>
|
|
215
|
+
{LINK_VARIANTS.map(({ label, attachment }) => (
|
|
216
|
+
<td key={label} className="align-top">
|
|
217
|
+
<MediaMessage
|
|
218
|
+
isMyMessage={true}
|
|
219
|
+
message={base({ user: ME, attachments: [attachment as LocalMessage['attachments'][number]] })}
|
|
220
|
+
/>
|
|
221
|
+
</td>
|
|
222
|
+
))}
|
|
223
|
+
</tr>
|
|
224
|
+
</tbody>
|
|
225
|
+
</GridTable>
|
|
226
|
+
)
|
|
227
|
+
Links.parameters = {
|
|
165
228
|
docs: {
|
|
166
229
|
description: {
|
|
167
|
-
story: '
|
|
230
|
+
story: 'Link preview cards — top row received, bottom row sent. Shows thumbnail, title, description, and URL with image fallback.',
|
|
168
231
|
},
|
|
169
232
|
},
|
|
170
233
|
}
|