@linktr.ee/messaging-react 2.0.0 → 2.0.1-rc-1778694826
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-CFFNq49v.js +163 -0
- package/dist/Card-CFFNq49v.js.map +1 -0
- package/dist/Card-CsJvUF_b.js +107 -0
- package/dist/Card-CsJvUF_b.js.map +1 -0
- package/dist/Card-D32U6KfZ.js +85 -0
- package/dist/Card-D32U6KfZ.js.map +1 -0
- package/dist/Card-DlMSDSdm.js +132 -0
- package/dist/Card-DlMSDSdm.js.map +1 -0
- package/dist/Card-DlSSJPip.js +60 -0
- package/dist/Card-DlSSJPip.js.map +1 -0
- package/dist/Card-zGbhRBwv.js +48 -0
- package/dist/Card-zGbhRBwv.js.map +1 -0
- package/dist/CardThumbnail-DTBuRQHF.js +239 -0
- package/dist/CardThumbnail-DTBuRQHF.js.map +1 -0
- package/dist/LockedThumbnail-DpJx169C.js +220 -0
- package/dist/LockedThumbnail-DpJx169C.js.map +1 -0
- package/dist/assets/index.css +1 -1
- package/dist/{index-Brz9orsI.js → index-DfcRe-Hj.js} +939 -889
- package/dist/index-DfcRe-Hj.js.map +1 -0
- package/dist/index.d.ts +217 -28
- package/dist/index.js +16 -15
- package/package.json +1 -1
- package/src/components/ChannelView.test.tsx +11 -0
- package/src/components/ChannelView.tsx +35 -32
- package/src/components/CustomMessage/index.tsx +2 -3
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +57 -17
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.test.tsx +187 -0
- package/src/components/CustomTypingIndicator/DmAgentContext.ts +3 -0
- package/src/components/CustomTypingIndicator/index.tsx +101 -37
- package/src/components/LinkAttachment/LinkAttachment.stories.tsx +307 -0
- package/src/components/LinkAttachment/components/Composer/Card.tsx +117 -0
- package/src/components/LinkAttachment/components/Composer/index.ts +2 -0
- package/src/components/LinkAttachment/components/Received/Card.tsx +132 -0
- package/src/components/LinkAttachment/components/Received/index.ts +2 -0
- package/src/components/LinkAttachment/components/Sent/Card.tsx +57 -0
- package/src/components/LinkAttachment/components/Sent/index.ts +2 -0
- package/src/components/LinkAttachment/components/_shared/CardBody.tsx +117 -0
- package/src/components/LinkAttachment/components/_shared/CardCta.tsx +69 -0
- package/src/components/LinkAttachment/components/_shared/CardShell.tsx +120 -0
- package/src/components/LinkAttachment/components/_shared/CardThumbnail.tsx +156 -0
- package/src/components/LinkAttachment/components/_shared/normalizeExternalHref.ts +56 -0
- package/src/components/LinkAttachment/index.tsx +68 -0
- package/src/components/LinkAttachment/types.ts +69 -0
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +230 -89
- package/src/components/LockedAttachment/components/Composer/Card.tsx +221 -0
- package/src/components/LockedAttachment/components/Composer/index.ts +2 -0
- package/src/components/LockedAttachment/components/Received/Card.tsx +191 -0
- package/src/components/LockedAttachment/components/Received/CardActions.tsx +91 -0
- package/src/components/LockedAttachment/components/Received/index.ts +2 -0
- package/src/components/LockedAttachment/components/Sent/Card.tsx +177 -0
- package/src/components/LockedAttachment/components/Sent/index.ts +2 -0
- package/src/components/LockedAttachment/components/_shared/CardBody.tsx +94 -0
- package/src/components/LockedAttachment/components/_shared/GalleryThumbnail.tsx +178 -0
- package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +39 -0
- package/src/components/LockedAttachment/components/_shared/LockedCardShell.tsx +36 -0
- package/src/components/LockedAttachment/components/_shared/LockedThumbnail.tsx +128 -0
- package/src/components/LockedAttachment/index.tsx +43 -12
- package/src/components/LockedAttachment/types.ts +17 -0
- package/src/components/MediaMessage/index.tsx +8 -2
- package/src/index.ts +15 -1
- package/src/styles.css +7 -0
- package/dist/Card-BHknCeHw.js +0 -138
- package/dist/Card-BHknCeHw.js.map +0 -1
- package/dist/Card-DT7_ms2p.js +0 -127
- package/dist/Card-DT7_ms2p.js.map +0 -1
- package/dist/index-Brz9orsI.js.map +0 -1
- package/src/components/LockedAttachment/components/Creator/Card.tsx +0 -210
- package/src/components/LockedAttachment/components/Creator/index.tsx +0 -2
- package/src/components/LockedAttachment/components/Visitor/Card.tsx +0 -155
- package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +0 -62
- package/src/components/LockedAttachment/components/Visitor/LockBadge.tsx +0 -12
- package/src/components/LockedAttachment/components/Visitor/index.ts +0 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsxs as f, jsx as s } from "react/jsx-runtime";
|
|
2
|
+
import { C as b, A as m, a as u, b as v, i as A } from "./CardThumbnail-DTBuRQHF.js";
|
|
3
|
+
const k = ({
|
|
4
|
+
title: a,
|
|
5
|
+
placeholderTitle: i,
|
|
6
|
+
description: t,
|
|
7
|
+
url: o,
|
|
8
|
+
mimeType: r,
|
|
9
|
+
thumbnailUrl: e,
|
|
10
|
+
sourceUrl: d,
|
|
11
|
+
layout: n = "featured",
|
|
12
|
+
appIcon: C,
|
|
13
|
+
cta: l
|
|
14
|
+
}) => /* @__PURE__ */ f(
|
|
15
|
+
b,
|
|
16
|
+
{
|
|
17
|
+
variant: "dark",
|
|
18
|
+
bgClassName: A(r, d) ? m : void 0,
|
|
19
|
+
children: [
|
|
20
|
+
n === "featured" && /* @__PURE__ */ s(
|
|
21
|
+
u,
|
|
22
|
+
{
|
|
23
|
+
variant: "dark",
|
|
24
|
+
thumbnailUrl: e,
|
|
25
|
+
sourceUrl: d,
|
|
26
|
+
title: a,
|
|
27
|
+
mimeType: r
|
|
28
|
+
}
|
|
29
|
+
),
|
|
30
|
+
/* @__PURE__ */ s(
|
|
31
|
+
v,
|
|
32
|
+
{
|
|
33
|
+
variant: "dark",
|
|
34
|
+
title: a,
|
|
35
|
+
placeholderTitle: i,
|
|
36
|
+
description: t,
|
|
37
|
+
url: o,
|
|
38
|
+
appIcon: C,
|
|
39
|
+
cta: l
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
export {
|
|
46
|
+
k as default
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=Card-zGbhRBwv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card-zGbhRBwv.js","sources":["../src/components/LinkAttachment/components/Sent/Card.tsx"],"sourcesContent":["import React from 'react'\n\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n} from '../_shared/CardThumbnail'\n\nexport interface SentCardProps extends LinkAttachmentBaseProps {}\n\n/**\n * The card the sender sees in chat after a link attachment has been posted.\n * Matches the Sent column of the messaging design system in Figma — same\n * dark chrome as the Composer card minus the dismiss / edit affordances.\n */\nconst SentCard: React.FC<SentCardProps> = ({\n title,\n placeholderTitle,\n description,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n}) => (\n <CardShell\n variant=\"dark\"\n bgClassName={\n isPlayableAudio(mimeType, sourceUrl) ? AUDIO_BG_CLASS : undefined\n }\n >\n {layout === 'featured' && (\n <CardThumbnail\n variant=\"dark\"\n thumbnailUrl={thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n />\n )}\n <CardBody\n variant=\"dark\"\n title={title}\n placeholderTitle={placeholderTitle}\n description={description}\n url={url}\n appIcon={appIcon}\n cta={cta}\n />\n </CardShell>\n)\n\nexport default SentCard\n"],"names":["SentCard","title","placeholderTitle","description","url","mimeType","thumbnailUrl","sourceUrl","layout","appIcon","cta","jsxs","CardShell","isPlayableAudio","AUDIO_BG_CLASS","jsx","CardThumbnail","CardBody"],"mappings":";;AAiBA,MAAMA,IAAoC,CAAC;AAAA,EACzC,OAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,aAAAC;AAAA,EACA,KAAAC;AAAA,EACA,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,EACA,QAAAC,IAAS;AAAA,EACT,SAAAC;AAAA,EACA,KAAAC;AACF,MACE,gBAAAC;AAAA,EAACC;AAAA,EAAA;AAAA,IACC,SAAQ;AAAA,IACR,aACEC,EAAgBR,GAAUE,CAAS,IAAIO,IAAiB;AAAA,IAGzD,UAAA;AAAA,MAAAN,MAAW,cACV,gBAAAO;AAAA,QAACC;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,cAAAV;AAAA,UACA,WAAAC;AAAA,UACA,OAAAN;AAAA,UACA,UAAAI;AAAA,QAAA;AAAA,MAAA;AAAA,MAGJ,gBAAAU;AAAA,QAACE;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,OAAAhB;AAAA,UACA,kBAAAC;AAAA,UACA,aAAAC;AAAA,UACA,KAAAC;AAAA,UACA,SAAAK;AAAA,UACA,KAAAC;AAAA,QAAA;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AACF;"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { jsx as l, jsxs as c } from "react/jsx-runtime";
|
|
2
|
+
import d from "classnames";
|
|
3
|
+
import { g as p, r as v } from "./index-DfcRe-Hj.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import "@phosphor-icons/react";
|
|
6
|
+
const w = /^([a-z][a-z0-9+.-]*):/i, k = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel", "sms"]);
|
|
7
|
+
function C(t) {
|
|
8
|
+
if (typeof t != "string") return;
|
|
9
|
+
const e = t.trim();
|
|
10
|
+
if (e === "") return;
|
|
11
|
+
const s = w.exec(e);
|
|
12
|
+
if (s) {
|
|
13
|
+
const n = s[1].toLowerCase();
|
|
14
|
+
return k.has(n) ? e : void 0;
|
|
15
|
+
}
|
|
16
|
+
return e.startsWith("//") || e.startsWith("/") ? e : `https://${e}`;
|
|
17
|
+
}
|
|
18
|
+
const S = {
|
|
19
|
+
dark: "bg-white text-[#121110] hover:bg-white/90",
|
|
20
|
+
light: "bg-[#121110] text-white hover:bg-[#2a2928]"
|
|
21
|
+
}, A = ({ variant: t, cta: e }) => {
|
|
22
|
+
const s = d(
|
|
23
|
+
"mt-2 inline-flex h-10 w-full items-center justify-center rounded-full px-4 text-sm font-medium leading-none transition-colors",
|
|
24
|
+
S[t]
|
|
25
|
+
), n = C(e.href);
|
|
26
|
+
return n ? /* @__PURE__ */ l(
|
|
27
|
+
"a",
|
|
28
|
+
{
|
|
29
|
+
href: n,
|
|
30
|
+
target: "_blank",
|
|
31
|
+
rel: "noopener noreferrer",
|
|
32
|
+
onClick: (a) => {
|
|
33
|
+
var r;
|
|
34
|
+
a.stopPropagation(), (r = e.onClick) == null || r.call(e);
|
|
35
|
+
},
|
|
36
|
+
className: `${s} no-underline`,
|
|
37
|
+
children: e.label
|
|
38
|
+
}
|
|
39
|
+
) : /* @__PURE__ */ l(
|
|
40
|
+
"button",
|
|
41
|
+
{
|
|
42
|
+
type: "button",
|
|
43
|
+
onClick: (a) => {
|
|
44
|
+
var r;
|
|
45
|
+
a.stopPropagation(), (r = e.onClick) == null || r.call(e);
|
|
46
|
+
},
|
|
47
|
+
className: s,
|
|
48
|
+
children: e.label
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}, E = {
|
|
52
|
+
dark: "text-white",
|
|
53
|
+
light: "text-black/90"
|
|
54
|
+
}, L = "text-white/30", y = {
|
|
55
|
+
dark: "text-white/55",
|
|
56
|
+
light: "text-black/55"
|
|
57
|
+
}, F = ({
|
|
58
|
+
variant: t,
|
|
59
|
+
title: e,
|
|
60
|
+
placeholderTitle: s,
|
|
61
|
+
description: n,
|
|
62
|
+
url: a,
|
|
63
|
+
appIcon: r,
|
|
64
|
+
cta: i,
|
|
65
|
+
trailingAction: u
|
|
66
|
+
}) => {
|
|
67
|
+
const o = t === "dark", f = e ?? (o ? s : void 0) ?? "", h = f.trim() !== "", m = n != null && n.trim() !== "", b = typeof a == "string" ? a.trim() : "", g = b !== "", x = i != null;
|
|
68
|
+
if (!h && !m && !g && !x) return null;
|
|
69
|
+
const _ = d(
|
|
70
|
+
"truncate text-base font-medium leading-6",
|
|
71
|
+
o && !e ? L : E[t]
|
|
72
|
+
), N = d(
|
|
73
|
+
"truncate text-xs leading-4",
|
|
74
|
+
y[t]
|
|
75
|
+
);
|
|
76
|
+
return /* @__PURE__ */ c("div", { className: "px-4 py-3", children: [
|
|
77
|
+
/* @__PURE__ */ c("div", { className: "flex items-end gap-3", children: [
|
|
78
|
+
/* @__PURE__ */ c("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
|
|
79
|
+
/* @__PURE__ */ c("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
80
|
+
h && /* @__PURE__ */ c("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
81
|
+
r ? /* @__PURE__ */ l("span", { className: "shrink-0", children: r }) : null,
|
|
82
|
+
/* @__PURE__ */ l("p", { className: d("min-w-0", _), children: f })
|
|
83
|
+
] }),
|
|
84
|
+
m && /* @__PURE__ */ l("p", { className: N, children: n })
|
|
85
|
+
] }),
|
|
86
|
+
!x && g && /* @__PURE__ */ l("p", { className: N, children: b })
|
|
87
|
+
] }),
|
|
88
|
+
u && /* @__PURE__ */ l("div", { className: "shrink-0", children: u })
|
|
89
|
+
] }),
|
|
90
|
+
i && /* @__PURE__ */ l(A, { variant: t, cta: i })
|
|
91
|
+
] });
|
|
92
|
+
}, T = d(
|
|
93
|
+
"relative block w-[280px] select-none overflow-hidden rounded-md",
|
|
94
|
+
"shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]"
|
|
95
|
+
), V = ({
|
|
96
|
+
variant: t,
|
|
97
|
+
children: e,
|
|
98
|
+
href: s,
|
|
99
|
+
onClick: n,
|
|
100
|
+
ariaLabel: a,
|
|
101
|
+
rootRef: r,
|
|
102
|
+
topRight: i,
|
|
103
|
+
bgClassName: u,
|
|
104
|
+
"data-testid": o
|
|
105
|
+
}) => {
|
|
106
|
+
const f = s != null || n != null, h = d(
|
|
107
|
+
T,
|
|
108
|
+
u ?? (t === "dark" ? "bg-[#121110]" : "bg-white"),
|
|
109
|
+
// `focus-ring` is a design-system utility from the component-library
|
|
110
|
+
// tailwind preset — outline-none + a black 2px focus-visible ring
|
|
111
|
+
// with offset, so keyboard users can see the focused card.
|
|
112
|
+
f ? "cursor-pointer no-underline focus-ring" : null
|
|
113
|
+
), m = i ? /* @__PURE__ */ l("div", { className: "pointer-events-auto absolute right-3 top-3 z-10", children: i }) : null;
|
|
114
|
+
return s ? /* @__PURE__ */ c(
|
|
115
|
+
"a",
|
|
116
|
+
{
|
|
117
|
+
ref: r,
|
|
118
|
+
href: s,
|
|
119
|
+
target: "_blank",
|
|
120
|
+
rel: "noopener noreferrer",
|
|
121
|
+
onClick: n,
|
|
122
|
+
"data-testid": o,
|
|
123
|
+
className: h,
|
|
124
|
+
children: [
|
|
125
|
+
e,
|
|
126
|
+
m
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
) : n ? /* @__PURE__ */ c(
|
|
130
|
+
"button",
|
|
131
|
+
{
|
|
132
|
+
ref: r,
|
|
133
|
+
type: "button",
|
|
134
|
+
onClick: n,
|
|
135
|
+
"aria-label": a,
|
|
136
|
+
"data-testid": o,
|
|
137
|
+
className: d(h, "text-left"),
|
|
138
|
+
children: [
|
|
139
|
+
e,
|
|
140
|
+
m
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
) : /* @__PURE__ */ c(
|
|
144
|
+
"div",
|
|
145
|
+
{
|
|
146
|
+
ref: r,
|
|
147
|
+
"data-testid": o,
|
|
148
|
+
className: h,
|
|
149
|
+
children: [
|
|
150
|
+
e,
|
|
151
|
+
m
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}, D = {
|
|
156
|
+
dark: "bg-white/10",
|
|
157
|
+
light: "bg-black/5"
|
|
158
|
+
}, z = {
|
|
159
|
+
dark: "size-16 text-white/25",
|
|
160
|
+
light: "size-16 text-black/25"
|
|
161
|
+
}, I = (t, e) => !!e && !!t && p(t) === "audio", Y = (t, e) => {
|
|
162
|
+
if (!e || !t) return !1;
|
|
163
|
+
const s = p(t);
|
|
164
|
+
return s === "video" || s === "audio";
|
|
165
|
+
}, R = "bg-[#F2F3F4]", G = ({
|
|
166
|
+
variant: t,
|
|
167
|
+
thumbnailUrl: e,
|
|
168
|
+
sourceUrl: s,
|
|
169
|
+
title: n,
|
|
170
|
+
mimeType: a = "image/*",
|
|
171
|
+
topLeft: r,
|
|
172
|
+
topRight: i
|
|
173
|
+
}) => {
|
|
174
|
+
const u = p(a), o = !!s && u === "video";
|
|
175
|
+
return I(a, s) ? /* @__PURE__ */ l("div", { className: "p-3", children: /* @__PURE__ */ l(
|
|
176
|
+
"audio",
|
|
177
|
+
{
|
|
178
|
+
src: s,
|
|
179
|
+
controls: !0,
|
|
180
|
+
preload: "metadata",
|
|
181
|
+
className: "block w-full",
|
|
182
|
+
children: /* @__PURE__ */ l("track", { kind: "captions" })
|
|
183
|
+
}
|
|
184
|
+
) }) : /* @__PURE__ */ c(
|
|
185
|
+
"div",
|
|
186
|
+
{
|
|
187
|
+
className: d(
|
|
188
|
+
"relative h-[180px] w-full overflow-hidden",
|
|
189
|
+
o && "bg-black"
|
|
190
|
+
),
|
|
191
|
+
children: [
|
|
192
|
+
o ? /* @__PURE__ */ l(
|
|
193
|
+
"video",
|
|
194
|
+
{
|
|
195
|
+
src: s,
|
|
196
|
+
poster: e,
|
|
197
|
+
controls: !0,
|
|
198
|
+
playsInline: !0,
|
|
199
|
+
preload: "metadata",
|
|
200
|
+
className: "absolute inset-0 h-full w-full object-contain",
|
|
201
|
+
children: /* @__PURE__ */ l("track", { kind: "captions" })
|
|
202
|
+
}
|
|
203
|
+
) : e ? /* @__PURE__ */ l(
|
|
204
|
+
"img",
|
|
205
|
+
{
|
|
206
|
+
src: e,
|
|
207
|
+
alt: n ?? "",
|
|
208
|
+
draggable: !1,
|
|
209
|
+
className: "absolute inset-0 h-full w-full object-cover"
|
|
210
|
+
}
|
|
211
|
+
) : /* @__PURE__ */ l(
|
|
212
|
+
"div",
|
|
213
|
+
{
|
|
214
|
+
className: d(
|
|
215
|
+
"flex h-full w-full items-center justify-center",
|
|
216
|
+
D[t]
|
|
217
|
+
),
|
|
218
|
+
children: v(a, {
|
|
219
|
+
className: z[t],
|
|
220
|
+
weight: "regular"
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
r ? /* @__PURE__ */ l("div", { className: "pointer-events-auto absolute left-3 top-3 z-10", children: r }) : null,
|
|
225
|
+
i ? /* @__PURE__ */ l("div", { className: "pointer-events-auto absolute right-3 top-3 z-10", children: i }) : null
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
export {
|
|
231
|
+
R as A,
|
|
232
|
+
V as C,
|
|
233
|
+
G as a,
|
|
234
|
+
F as b,
|
|
235
|
+
Y as c,
|
|
236
|
+
I as i,
|
|
237
|
+
C as n
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=CardThumbnail-DTBuRQHF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CardThumbnail-DTBuRQHF.js","sources":["../src/components/LinkAttachment/components/_shared/normalizeExternalHref.ts","../src/components/LinkAttachment/components/_shared/CardCta.tsx","../src/components/LinkAttachment/components/_shared/CardBody.tsx","../src/components/LinkAttachment/components/_shared/CardShell.tsx","../src/components/LinkAttachment/components/_shared/CardThumbnail.tsx"],"sourcesContent":["/**\n * Scheme detector: `protocol:` per RFC 3986 — a letter followed by any\n * combination of letters, digits, `+`, `.`, or `-` then `:`.\n */\nconst SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i\n\n/**\n * Allowlist of schemes that are safe to forward into `<a href>` for\n * external navigation. `javascript:` / `data:` / `vbscript:` etc. are\n * intentionally **not** on this list — link-attachment data is\n * effectively user-controlled, so passing them through would let a\n * recipient click execute attacker-supplied code or markup.\n */\nconst SAFE_SCHEMES = new Set(['http', 'https', 'mailto', 'tel', 'sms'])\n\n/**\n * Normalize a user-supplied URL into something safe to assign to\n * `<a href>` for external navigation.\n *\n * Link attachments / link apps always point at external destinations\n * (Spotify, TikTok, FAQ links, bare-hostname Linktree URLs like\n * `tr.ee/briemix`, etc.). Without normalization, a bare hostname is\n * treated as a relative path by the browser and clicks navigate within\n * the host site (e.g. `https://linktr.ee/admin/tr.ee/briemix`) instead\n * of opening the intended destination.\n *\n * Rules:\n * - Empty / whitespace-only → returns `undefined` (no href).\n * - Explicit scheme in the safe allowlist (`http`, `https`, `mailto`,\n * `tel`, `sms`) → returned trimmed.\n * - Explicit scheme **not** on the allowlist (`javascript:`, `data:`,\n * `vbscript:`, custom protocols, …) → returns `undefined` so the\n * shell falls back to a non-navigational chrome instead of letting\n * an attacker-controlled URL execute on click.\n * - Protocol-relative (`//example.com/…`) → returned as-is; browsers\n * resolve these against the current page's scheme.\n * - Site-relative path (`/admin/…`) → returned as-is so consumers can\n * still opt into in-app navigation if they really want to.\n * - Bare hostname or anything else → `https://` is prepended so the\n * browser treats it as an external URL.\n */\nexport function normalizeExternalHref(value?: string): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (trimmed === '') return undefined\n\n const schemeMatch = SCHEME_PATTERN.exec(trimmed)\n if (schemeMatch) {\n const scheme = schemeMatch[1].toLowerCase()\n return SAFE_SCHEMES.has(scheme) ? trimmed : undefined\n }\n\n if (trimmed.startsWith('//')) return trimmed\n if (trimmed.startsWith('/')) return trimmed\n return `https://${trimmed}`\n}\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport type { LinkAttachmentCta } from '../../types'\n\nimport type { LinkAttachmentVariant } from './CardShell'\nimport { normalizeExternalHref } from './normalizeExternalHref'\n\nexport interface CardCtaProps {\n variant: LinkAttachmentVariant\n cta: LinkAttachmentCta\n}\n\nconst BUTTON_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: 'bg-white text-[#121110] hover:bg-white/90',\n light: 'bg-[#121110] text-white hover:bg-[#2a2928]',\n}\n\n/**\n * Pill-shaped CTA rendered below the description on Link App cards that\n * surface an action instead of a URL (e.g. FAQ \"View FAQs\", Form \"Complete form\").\n * Renders as `<a target=\"_blank\">` when `cta.href` is set, otherwise as a\n * plain `<button>`.\n */\nconst CardCta: React.FC<CardCtaProps> = ({ variant, cta }) => {\n const className = classNames(\n 'mt-2 inline-flex h-10 w-full items-center justify-center rounded-full px-4 text-sm font-medium leading-none transition-colors',\n BUTTON_CLASS_BY_VARIANT[variant]\n )\n\n // Mirror the URL normalization used by the shell anchor so bare\n // hostnames (e.g. `tr.ee/foo`) open as external links rather than\n // resolving against the current host.\n const normalizedHref = normalizeExternalHref(cta.href)\n\n if (normalizedHref) {\n return (\n <a\n href={normalizedHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => {\n // Stop the click from bubbling up to the card's anchor wrapper\n // (Received variant) so we don't navigate twice.\n e.stopPropagation()\n cta.onClick?.()\n }}\n className={`${className} no-underline`}\n >\n {cta.label}\n </a>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n cta.onClick?.()\n }}\n className={className}\n >\n {cta.label}\n </button>\n )\n}\n\nexport default CardCta\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport type { LinkAttachmentCta } from '../../types'\n\nimport CardCta from './CardCta'\nimport type { LinkAttachmentVariant } from './CardShell'\n\nexport interface CardBodyProps {\n variant: LinkAttachmentVariant\n title?: string\n /** Placeholder shown in the title slot when no title is set (dark variants only). */\n placeholderTitle?: string\n description?: string\n /** Footer URL shown below the description. Ignored when `cta` is set. */\n url?: string\n /**\n * Optional 16x16 brand badge rendered before the title (used by Link Apps:\n * Spotify, TikTok, FAQ, Form, etc.).\n */\n appIcon?: React.ReactNode\n /** Optional CTA rendered in place of the URL footer. */\n cta?: LinkAttachmentCta\n /** Trailing action rendered on the right of the title/description block. */\n trailingAction?: React.ReactNode\n}\n\nconst TITLE_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: 'text-white',\n light: 'text-black/90',\n}\n\nconst TITLE_DIMMED_CLASS = 'text-white/30'\n\nconst SECONDARY_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: 'text-white/55',\n light: 'text-black/55',\n}\n\n/**\n * Body of a `LinkAttachment.*` card. Matches the Figma `Container > Labels`\n * group: 16px horizontal padding, 12px vertical padding, 8px gap between\n * the title/description group and the URL/CTA footer, 4px gap within the\n * title group.\n *\n * Returns `null` when there's nothing to render so plain image / file\n * attachments collapse to a thumbnail-only card.\n */\nconst CardBody: React.FC<CardBodyProps> = ({\n variant,\n title,\n placeholderTitle,\n description,\n url,\n appIcon,\n cta,\n trailingAction,\n}) => {\n const isDark = variant === 'dark'\n const displayTitle = title ?? (isDark ? placeholderTitle : undefined) ?? ''\n const hasTitle = displayTitle.trim() !== ''\n const hasDescription =\n description != null && description.trim() !== ''\n // Mirror the trimming applied by `ReceivedCard` so a whitespace-only\n // `url` collapses the body footer (and the whole body, for media-only\n // cards) instead of rendering an empty line.\n const trimmedUrl = typeof url === 'string' ? url.trim() : ''\n const hasUrl = trimmedUrl !== ''\n const hasCta = cta != null\n\n if (!hasTitle && !hasDescription && !hasUrl && !hasCta) return null\n\n const titleDimmed = isDark && !title\n\n const titleClass = classNames(\n 'truncate text-base font-medium leading-6',\n titleDimmed ? TITLE_DIMMED_CLASS : TITLE_CLASS_BY_VARIANT[variant]\n )\n\n const secondaryClass = classNames(\n 'truncate text-xs leading-4',\n SECONDARY_CLASS_BY_VARIANT[variant]\n )\n\n return (\n <div className=\"px-4 py-3\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <div className=\"flex min-w-0 flex-col gap-1\">\n {hasTitle && (\n <div className=\"flex min-w-0 items-center gap-2\">\n {appIcon ? <span className=\"shrink-0\">{appIcon}</span> : null}\n <p className={classNames('min-w-0', titleClass)}>\n {displayTitle}\n </p>\n </div>\n )}\n\n {hasDescription && (\n <p className={secondaryClass}>{description}</p>\n )}\n </div>\n\n {!hasCta && hasUrl && (\n <p className={secondaryClass}>{trimmedUrl}</p>\n )}\n </div>\n\n {trailingAction && <div className=\"shrink-0\">{trailingAction}</div>}\n </div>\n\n {cta && <CardCta variant={variant} cta={cta} />}\n </div>\n )\n}\n\nexport default CardBody\n","import classNames from 'classnames'\nimport React from 'react'\n\nexport type LinkAttachmentVariant = 'dark' | 'light'\n\nexport interface CardShellProps {\n variant: LinkAttachmentVariant\n children: React.ReactNode\n /**\n * When provided, the entire card chrome is rendered as an anchor (used by\n * the Received card to open the link target on click). Falls back to a\n * `<div>` when omitted so Composer / Sent cards stay non-navigational.\n */\n href?: string\n /**\n * Click handler for the card chrome. When `href` is set the shell is an\n * anchor and `onClick` is invoked in addition to navigation. When `href`\n * is omitted but `onClick` is set, the shell renders as a clickable\n * button (used by media-only Received cards to open an image preview).\n */\n onClick?: () => void\n /** Accessible label for the clickable variant (when `onClick` is set without `href`). */\n ariaLabel?: string\n rootRef?: React.Ref<HTMLElement>\n /**\n * Absolutely-positioned slot rendered in the top-right corner of the\n * shell. Used by the Composer card to surface its dismiss affordance\n * when there's no hero thumbnail to anchor it to.\n */\n topRight?: React.ReactNode\n /**\n * Overrides the variant-derived background colour (e.g. audio cards\n * use `bg-[#F2F3F4]` regardless of the dark/light variant).\n */\n bgClassName?: string\n 'data-testid'?: string\n}\n\nconst SHELL_CLASS = classNames(\n 'relative block w-[280px] select-none overflow-hidden rounded-md',\n 'shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]'\n)\n\n/**\n * Outer chrome for every `LinkAttachment.*` card. Matches the 280px width,\n * 16px corner radius, and shadow-400 treatment from the Figma design system.\n */\nconst CardShell: React.FC<CardShellProps> = ({\n variant,\n children,\n href,\n onClick,\n ariaLabel,\n rootRef,\n topRight,\n bgClassName,\n 'data-testid': dataTestId,\n}) => {\n const isInteractive = href != null || onClick != null\n const className = classNames(\n SHELL_CLASS,\n bgClassName ?? (variant === 'dark' ? 'bg-[#121110]' : 'bg-white'),\n // `focus-ring` is a design-system utility from the component-library\n // tailwind preset — outline-none + a black 2px focus-visible ring\n // with offset, so keyboard users can see the focused card.\n isInteractive ? 'cursor-pointer no-underline focus-ring' : null\n )\n\n const corner = topRight ? (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-10\">\n {topRight}\n </div>\n ) : null\n\n if (href) {\n return (\n <a\n ref={rootRef as React.Ref<HTMLAnchorElement>}\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={onClick}\n data-testid={dataTestId}\n className={className}\n >\n {children}\n {corner}\n </a>\n )\n }\n\n if (onClick) {\n return (\n <button\n ref={rootRef as React.Ref<HTMLButtonElement>}\n type=\"button\"\n onClick={onClick}\n aria-label={ariaLabel}\n data-testid={dataTestId}\n className={classNames(className, 'text-left')}\n >\n {children}\n {corner}\n </button>\n )\n }\n\n return (\n <div\n ref={rootRef as React.Ref<HTMLDivElement>}\n data-testid={dataTestId}\n className={className}\n >\n {children}\n {corner}\n </div>\n )\n}\n\nexport default CardShell\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport { renderTypeIcon } from '../../../AttachmentCard'\nimport { getSourceType } from '../../../AttachmentCard/utils/mimeType'\n\nimport type { LinkAttachmentVariant } from './CardShell'\n\nexport interface CardThumbnailProps {\n variant: LinkAttachmentVariant\n /** Source URL of the hero image (or poster for video). */\n thumbnailUrl?: string\n /**\n * Playable media URL. When provided alongside a video / audio `mimeType`,\n * the hero region renders a native HTML5 player with controls instead of\n * the static thumbnail / placeholder.\n */\n sourceUrl?: string\n /** Alt text — typically the card's title. */\n title?: string\n /**\n * Drives the placeholder type icon when no `thumbnailUrl` is provided,\n * and selects between image / video / audio rendering when `sourceUrl`\n * is set. Defaults to a generic image icon when unset.\n */\n mimeType?: string\n /** Optional decorations layered into the top corners of the thumbnail. */\n topLeft?: React.ReactNode\n topRight?: React.ReactNode\n}\n\nconst PLACEHOLDER_BG: Record<LinkAttachmentVariant, string> = {\n dark: 'bg-white/10',\n light: 'bg-black/5',\n}\n\nconst PLACEHOLDER_ICON: Record<LinkAttachmentVariant, string> = {\n dark: 'size-16 text-white/25',\n light: 'size-16 text-black/25',\n}\n\n/**\n * 180px hero region shown above the card body. Renders, in priority order:\n * 1. A native `<video controls>` when `sourceUrl` is set and the mime is\n * video — `thumbnailUrl` acts as the poster.\n * 2. A native `<audio controls>` when `sourceUrl` is set and the mime is\n * audio — laid over the audio type-icon backdrop.\n * 3. The supplied `thumbnailUrl` image.\n * 4. A placeholder type-icon derived from `mimeType`.\n */\n/** Mime + sourceUrl gives us a playable audio attachment. */\nexport const isPlayableAudio = (mimeType?: string, sourceUrl?: string) =>\n !!sourceUrl && !!mimeType && getSourceType(mimeType) === 'audio'\n\n/**\n * Mime + sourceUrl gives us a playable video or audio attachment. Used by\n * Received to skip wrapping the shell in an interactive `<button>` so the\n * native media controls remain operable.\n */\nexport const isPlayableMedia = (mimeType?: string, sourceUrl?: string) => {\n if (!sourceUrl || !mimeType) return false\n const source = getSourceType(mimeType)\n return source === 'video' || source === 'audio'\n}\n\n/**\n * Background colour the LinkAttachment cards switch to when the source is\n * audio — flat neutral around the native `<audio>` chrome regardless of\n * the dark / light variant.\n */\nexport const AUDIO_BG_CLASS = 'bg-[#F2F3F4]'\n\nconst CardThumbnail: React.FC<CardThumbnailProps> = ({\n variant,\n thumbnailUrl,\n sourceUrl,\n title,\n mimeType = 'image/*',\n topLeft,\n topRight,\n}) => {\n const sourceType = getSourceType(mimeType)\n const isPlayableVideo = !!sourceUrl && sourceType === 'video'\n\n if (isPlayableAudio(mimeType, sourceUrl)) {\n // Audio collapses the hero entirely — the native player sits inside\n // the card chrome with a bit of padding so the card background\n // (typically `bg-[#F2F3F4]`) is visible around it.\n return (\n <div className=\"p-3\">\n <audio\n src={sourceUrl}\n controls\n preload=\"metadata\"\n className=\"block w-full\"\n >\n <track kind=\"captions\" />\n </audio>\n </div>\n )\n }\n\n return (\n <div\n className={classNames(\n 'relative h-[180px] w-full overflow-hidden',\n isPlayableVideo && 'bg-black'\n )}\n >\n {isPlayableVideo ? (\n <video\n src={sourceUrl}\n poster={thumbnailUrl}\n controls\n playsInline\n preload=\"metadata\"\n className=\"absolute inset-0 h-full w-full object-contain\"\n >\n <track kind=\"captions\" />\n </video>\n ) : thumbnailUrl ? (\n <img\n src={thumbnailUrl}\n alt={title ?? ''}\n draggable={false}\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : (\n <div\n className={classNames(\n 'flex h-full w-full items-center justify-center',\n PLACEHOLDER_BG[variant]\n )}\n >\n {renderTypeIcon(mimeType, {\n className: PLACEHOLDER_ICON[variant],\n weight: 'regular',\n })}\n </div>\n )}\n\n {topLeft ? (\n <div className=\"pointer-events-auto absolute left-3 top-3 z-10\">\n {topLeft}\n </div>\n ) : null}\n {topRight ? (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-10\">\n {topRight}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default CardThumbnail\n"],"names":["SCHEME_PATTERN","SAFE_SCHEMES","normalizeExternalHref","value","trimmed","schemeMatch","scheme","BUTTON_CLASS_BY_VARIANT","CardCta","variant","cta","className","classNames","normalizedHref","jsx","e","_a","TITLE_CLASS_BY_VARIANT","TITLE_DIMMED_CLASS","SECONDARY_CLASS_BY_VARIANT","CardBody","title","placeholderTitle","description","url","appIcon","trailingAction","isDark","displayTitle","hasTitle","hasDescription","trimmedUrl","hasUrl","hasCta","titleClass","secondaryClass","jsxs","SHELL_CLASS","CardShell","children","href","onClick","ariaLabel","rootRef","topRight","bgClassName","dataTestId","isInteractive","corner","PLACEHOLDER_BG","PLACEHOLDER_ICON","isPlayableAudio","mimeType","sourceUrl","getSourceType","isPlayableMedia","source","AUDIO_BG_CLASS","CardThumbnail","thumbnailUrl","topLeft","sourceType","isPlayableVideo"],"mappings":"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"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { jsxs as o, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import m from "classnames";
|
|
3
|
+
import { r as N, A as y, g as w } from "./index-DfcRe-Hj.js";
|
|
4
|
+
import U, { useState as _ } from "react";
|
|
5
|
+
import { CaretLeftIcon as T, CaretRightIcon as C, PlayIcon as k, LockOpenIcon as z, LockSimpleIcon as I } from "@phosphor-icons/react";
|
|
6
|
+
const F = ({
|
|
7
|
+
variant: l,
|
|
8
|
+
title: t,
|
|
9
|
+
placeholderTitle: n = "Attachment title",
|
|
10
|
+
mimeType: c,
|
|
11
|
+
detail: s,
|
|
12
|
+
statusBadge: d,
|
|
13
|
+
action: b,
|
|
14
|
+
icon: u,
|
|
15
|
+
trailingAction: r
|
|
16
|
+
}) => {
|
|
17
|
+
const a = l === "dark", i = a ? t ?? n : t ?? "", h = a && !t, f = u ?? N(c, {
|
|
18
|
+
className: m(
|
|
19
|
+
"size-5 shrink-0",
|
|
20
|
+
a ? "text-white/55" : "text-black/55"
|
|
21
|
+
),
|
|
22
|
+
weight: "regular"
|
|
23
|
+
});
|
|
24
|
+
return /* @__PURE__ */ o("div", { className: "px-4 py-3", children: [
|
|
25
|
+
/* @__PURE__ */ o("div", { className: "flex items-end gap-3", children: [
|
|
26
|
+
/* @__PURE__ */ o("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
27
|
+
i.trim() !== "" && /* @__PURE__ */ e(
|
|
28
|
+
"p",
|
|
29
|
+
{
|
|
30
|
+
className: m("truncate text-base font-medium leading-6", {
|
|
31
|
+
"text-black/90": !a,
|
|
32
|
+
"text-white/30": a && h,
|
|
33
|
+
"text-white": a && !h
|
|
34
|
+
}),
|
|
35
|
+
children: i
|
|
36
|
+
}
|
|
37
|
+
),
|
|
38
|
+
/* @__PURE__ */ o("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
39
|
+
f,
|
|
40
|
+
s != null && s !== "" && /* @__PURE__ */ e(
|
|
41
|
+
"span",
|
|
42
|
+
{
|
|
43
|
+
className: m(
|
|
44
|
+
"text-xs font-medium",
|
|
45
|
+
a ? "text-white/55" : "text-black/55"
|
|
46
|
+
),
|
|
47
|
+
children: s
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
d
|
|
51
|
+
] })
|
|
52
|
+
] }),
|
|
53
|
+
r && /* @__PURE__ */ e("div", { className: "shrink-0", children: r })
|
|
54
|
+
] }),
|
|
55
|
+
b
|
|
56
|
+
] });
|
|
57
|
+
};
|
|
58
|
+
function S(l) {
|
|
59
|
+
const t = w(l.mimeType);
|
|
60
|
+
return t === "video" || t === "audio";
|
|
61
|
+
}
|
|
62
|
+
const O = ({
|
|
63
|
+
variant: l,
|
|
64
|
+
gallery: t,
|
|
65
|
+
title: n,
|
|
66
|
+
showLocked: c,
|
|
67
|
+
paymentStatus: s,
|
|
68
|
+
topRight: d
|
|
69
|
+
}) => {
|
|
70
|
+
const [b, u] = _(0), r = t.length, a = Math.min(b, r - 1), i = t[a], h = S(i), f = h ? /* @__PURE__ */ e(k, { className: "size-6", weight: "fill" }) : c ? s === "paid" ? /* @__PURE__ */ e(z, { className: "size-6", weight: "fill" }) : /* @__PURE__ */ e(I, { className: "size-6", weight: "fill" }) : null, j = (p) => {
|
|
71
|
+
p.stopPropagation(), u((x) => Math.max(0, x - 1));
|
|
72
|
+
}, P = (p) => {
|
|
73
|
+
p.stopPropagation(), u((x) => Math.min(r - 1, x + 1));
|
|
74
|
+
}, g = a === 0, v = a === r - 1;
|
|
75
|
+
return /* @__PURE__ */ o("div", { className: "relative aspect-[337/386] overflow-hidden rounded-t-2xl", children: [
|
|
76
|
+
c ? /* @__PURE__ */ e(
|
|
77
|
+
"div",
|
|
78
|
+
{
|
|
79
|
+
className: m(
|
|
80
|
+
"absolute inset-0",
|
|
81
|
+
l === "dark" ? "bg-white/10" : "bg-black/5"
|
|
82
|
+
),
|
|
83
|
+
children: i.thumbnailUrl && /* @__PURE__ */ e(
|
|
84
|
+
"img",
|
|
85
|
+
{
|
|
86
|
+
src: i.thumbnailUrl,
|
|
87
|
+
alt: "",
|
|
88
|
+
"aria-hidden": !0,
|
|
89
|
+
draggable: !1,
|
|
90
|
+
className: "absolute inset-0 h-full w-full scale-110 object-cover blur-[40px]"
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
) : /* @__PURE__ */ e(
|
|
95
|
+
y,
|
|
96
|
+
{
|
|
97
|
+
mimeType: i.mimeType,
|
|
98
|
+
sourceUrl: i.sourceUrl,
|
|
99
|
+
thumbnailUrl: i.thumbnailUrl,
|
|
100
|
+
title: n,
|
|
101
|
+
variant: l
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
f && /* @__PURE__ */ e("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ e("div", { className: "flex size-12 items-center justify-center rounded-full bg-black/25 text-white", children: f }) }),
|
|
105
|
+
r > 1 && /* @__PURE__ */ o(U.Fragment, { children: [
|
|
106
|
+
/* @__PURE__ */ e(
|
|
107
|
+
"button",
|
|
108
|
+
{
|
|
109
|
+
type: "button",
|
|
110
|
+
onClick: j,
|
|
111
|
+
disabled: g,
|
|
112
|
+
"aria-label": "Previous item",
|
|
113
|
+
className: m(
|
|
114
|
+
"absolute left-3 top-1/2 -translate-y-1/2 flex size-7 items-center justify-center rounded-full bg-black/30 text-white transition-opacity",
|
|
115
|
+
g ? "opacity-40" : "hover:bg-black/40"
|
|
116
|
+
),
|
|
117
|
+
children: /* @__PURE__ */ e(T, { className: "size-5", weight: "bold" })
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ e(
|
|
121
|
+
"button",
|
|
122
|
+
{
|
|
123
|
+
type: "button",
|
|
124
|
+
onClick: P,
|
|
125
|
+
disabled: v,
|
|
126
|
+
"aria-label": "Next item",
|
|
127
|
+
className: m(
|
|
128
|
+
"absolute right-3 top-1/2 -translate-y-1/2 flex size-7 items-center justify-center rounded-full bg-black/30 text-white transition-opacity",
|
|
129
|
+
v ? "opacity-40" : "hover:bg-black/40"
|
|
130
|
+
),
|
|
131
|
+
children: /* @__PURE__ */ e(C, { className: "size-5", weight: "bold" })
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
/* @__PURE__ */ e("div", { className: "pointer-events-none absolute bottom-5 left-1/2 -translate-x-1/2 rounded-lg bg-black/30 px-2 py-1", children: /* @__PURE__ */ o("span", { className: "text-xs font-medium leading-4 text-white", children: [
|
|
135
|
+
a + 1,
|
|
136
|
+
"/",
|
|
137
|
+
r
|
|
138
|
+
] }) })
|
|
139
|
+
] }),
|
|
140
|
+
d && /* @__PURE__ */ e("div", { className: "pointer-events-auto absolute right-3 top-3 z-10", children: d })
|
|
141
|
+
] });
|
|
142
|
+
}, V = ({
|
|
143
|
+
variant: l,
|
|
144
|
+
children: t,
|
|
145
|
+
rootRef: n,
|
|
146
|
+
"data-testid": c
|
|
147
|
+
}) => /* @__PURE__ */ e(
|
|
148
|
+
"div",
|
|
149
|
+
{
|
|
150
|
+
ref: n,
|
|
151
|
+
"data-testid": c,
|
|
152
|
+
className: m(
|
|
153
|
+
"relative w-[280px] select-none overflow-hidden rounded-md",
|
|
154
|
+
"shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]",
|
|
155
|
+
l === "dark" ? "bg-[#121110]" : "bg-white"
|
|
156
|
+
),
|
|
157
|
+
children: t
|
|
158
|
+
}
|
|
159
|
+
), D = ({ paymentStatus: l, mimeType: t }) => {
|
|
160
|
+
const n = t ? w(t) : void 0;
|
|
161
|
+
return /* @__PURE__ */ e("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ e("div", { className: "flex size-12 items-center justify-center rounded-full bg-black/25 text-white", children: /* @__PURE__ */ e(n === "video" || n === "audio" ? k : l === "paid" ? z : I, { className: "size-6", weight: "fill" }) }) });
|
|
162
|
+
}, q = ({
|
|
163
|
+
variant: l,
|
|
164
|
+
mimeType: t,
|
|
165
|
+
thumbnailUrl: n,
|
|
166
|
+
title: c,
|
|
167
|
+
source: s,
|
|
168
|
+
showLocked: d,
|
|
169
|
+
paymentStatus: b,
|
|
170
|
+
topRight: u,
|
|
171
|
+
containedImage: r = !1
|
|
172
|
+
}) => {
|
|
173
|
+
const a = d || s == null ? void 0 : s.sourceUrl, i = d ? n : (s == null ? void 0 : s.thumbnailUrl) ?? n, h = l === "dark";
|
|
174
|
+
return /* @__PURE__ */ o("div", { className: "relative overflow-hidden rounded-t-2xl", children: [
|
|
175
|
+
d ? /* @__PURE__ */ o(
|
|
176
|
+
"div",
|
|
177
|
+
{
|
|
178
|
+
className: m(
|
|
179
|
+
"relative aspect-video overflow-hidden",
|
|
180
|
+
h ? "bg-white/10" : "bg-black/5"
|
|
181
|
+
),
|
|
182
|
+
children: [
|
|
183
|
+
i ? /* @__PURE__ */ e(
|
|
184
|
+
"img",
|
|
185
|
+
{
|
|
186
|
+
src: i,
|
|
187
|
+
alt: "",
|
|
188
|
+
"aria-hidden": !0,
|
|
189
|
+
draggable: !1,
|
|
190
|
+
className: "absolute inset-0 h-full w-full scale-110 object-cover blur-[40px]"
|
|
191
|
+
}
|
|
192
|
+
) : /* @__PURE__ */ e("div", { className: "absolute inset-0 flex items-center justify-center", children: N(t, {
|
|
193
|
+
className: h ? "size-12 text-white/20" : "size-12 text-black/20",
|
|
194
|
+
weight: "regular"
|
|
195
|
+
}) }),
|
|
196
|
+
/* @__PURE__ */ e(D, { paymentStatus: b, mimeType: t })
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
) : /* @__PURE__ */ e(
|
|
200
|
+
y,
|
|
201
|
+
{
|
|
202
|
+
mimeType: t,
|
|
203
|
+
sourceUrl: a,
|
|
204
|
+
thumbnailUrl: i,
|
|
205
|
+
title: c,
|
|
206
|
+
variant: l,
|
|
207
|
+
containedImage: r,
|
|
208
|
+
mediaPlayerProps: a ? { autoPlay: !0, loop: !0, controls: !0, muted: !1 } : void 0
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
u && /* @__PURE__ */ e("div", { className: "pointer-events-auto absolute right-3 top-3 z-10", children: u })
|
|
212
|
+
] });
|
|
213
|
+
};
|
|
214
|
+
export {
|
|
215
|
+
F as C,
|
|
216
|
+
O as G,
|
|
217
|
+
V as L,
|
|
218
|
+
q as a
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=LockedThumbnail-DpJx169C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LockedThumbnail-DpJx169C.js","sources":["../src/components/LockedAttachment/components/_shared/CardBody.tsx","../src/components/LockedAttachment/components/_shared/GalleryThumbnail.tsx","../src/components/LockedAttachment/components/_shared/LockedCardShell.tsx","../src/components/LockedAttachment/components/_shared/LockBadge.tsx","../src/components/LockedAttachment/components/_shared/LockedThumbnail.tsx"],"sourcesContent":["import classNames from 'classnames'\nimport React from 'react'\n\nimport { renderTypeIcon } from '../../../AttachmentCard'\n\nimport type { LockedCardVariant } from './LockedCardShell'\n\nexport interface CardBodyProps {\n variant: LockedCardVariant\n title?: string\n placeholderTitle?: string\n mimeType: string\n detail?: string\n statusBadge?: React.ReactNode\n action?: React.ReactNode\n /** Overrides the auto-detected type icon (used by Gallery to swap in an `Images` icon). */\n icon?: React.ReactNode\n /** Optional control rendered on the right of the title/status block (e.g. Composer edit pencil). */\n trailingAction?: React.ReactNode\n}\n\n/**\n * Title + status row layout shared by Composer / Sent / Received cards.\n * Layout matches the Figma `Container > Labels` group (16px h-padding, 12px v-padding,\n * 4px gap between title and status row, 4px gap inside status row).\n */\nconst CardBody: React.FC<CardBodyProps> = ({\n variant,\n title,\n placeholderTitle = 'Attachment title',\n mimeType,\n detail,\n statusBadge,\n action,\n icon,\n trailingAction,\n}) => {\n const isDark = variant === 'dark'\n const displayTitle = isDark ? (title ?? placeholderTitle) : (title ?? '')\n const titleDimmed = isDark && !title\n\n const typeIcon =\n icon ??\n renderTypeIcon(mimeType, {\n className: classNames(\n 'size-5 shrink-0',\n isDark ? 'text-white/55' : 'text-black/55'\n ),\n weight: 'regular',\n })\n\n return (\n <div className=\"px-4 py-3\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {displayTitle.trim() !== '' && (\n <p\n className={classNames('truncate text-base font-medium leading-6', {\n 'text-black/90': !isDark,\n 'text-white/30': isDark && titleDimmed,\n 'text-white': isDark && !titleDimmed,\n })}\n >\n {displayTitle}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-1\">\n {typeIcon}\n\n {detail != null && detail !== '' && (\n <span\n className={classNames(\n 'text-xs font-medium',\n isDark ? 'text-white/55' : 'text-black/55'\n )}\n >\n {detail}\n </span>\n )}\n\n {statusBadge}\n </div>\n </div>\n\n {trailingAction && <div className=\"shrink-0\">{trailingAction}</div>}\n </div>\n\n {action}\n </div>\n )\n}\n\nexport default CardBody\n","import {\n CaretLeftIcon,\n CaretRightIcon,\n LockOpenIcon,\n LockSimpleIcon,\n PlayIcon,\n} from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React, { useState } from 'react'\n\nimport { AttachmentThumbnail, getSourceType } from '../../../AttachmentCard'\nimport type {\n LockedAttachmentGalleryItem,\n PaymentStatus,\n} from '../../types'\n\nimport type { LockedCardVariant } from './LockedCardShell'\n\nexport interface GalleryThumbnailProps {\n variant: LockedCardVariant\n gallery: LockedAttachmentGalleryItem[]\n title?: string\n /** When true, all items are shown blurred + the locked/play badge is overlaid. */\n showLocked: boolean\n paymentStatus?: PaymentStatus\n /** Top-right slot (e.g. dismiss X on Composer). Rendered inside the thumbnail. */\n topRight?: React.ReactNode\n}\n\nfunction isPlayableItem(item: LockedAttachmentGalleryItem): boolean {\n const sourceType = getSourceType(item.mimeType)\n return sourceType === 'video' || sourceType === 'audio'\n}\n\n/**\n * Mixed-media carousel used by every LockedAttachment card when its `gallery`\n * prop has 2+ items. Matches the \"Gallery\" frames in the Figma design system:\n * 337/386 aspect ratio, prev/next caret buttons, a counter pill, and a center\n * lock / play indicator that swaps based on the current item.\n */\nconst GalleryThumbnail: React.FC<GalleryThumbnailProps> = ({\n variant,\n gallery,\n title,\n showLocked,\n paymentStatus,\n topRight,\n}) => {\n const [index, setIndex] = useState(0)\n const total = gallery.length\n const safeIndex = Math.min(index, total - 1)\n const current = gallery[safeIndex]\n const isVideoItem = isPlayableItem(current)\n\n // Center badge: PLAY for video items, LOCK for non-video locked items.\n // Unlocked image items show no center badge — the image is the content.\n const centerBadge = (() => {\n if (isVideoItem) {\n return <PlayIcon className=\"size-6\" weight=\"fill\" />\n }\n if (showLocked) {\n return paymentStatus === 'paid' ? (\n <LockOpenIcon className=\"size-6\" weight=\"fill\" />\n ) : (\n <LockSimpleIcon className=\"size-6\" weight=\"fill\" />\n )\n }\n return null\n })()\n\n // Stop propagation so arrow taps don't bubble up to the outer preview-toggle\n // wrapper on ComposerCard / SentCard.\n const goPrev = (e: React.MouseEvent) => {\n e.stopPropagation()\n setIndex((i) => Math.max(0, i - 1))\n }\n const goNext = (e: React.MouseEvent) => {\n e.stopPropagation()\n setIndex((i) => Math.min(total - 1, i + 1))\n }\n const atStart = safeIndex === 0\n const atEnd = safeIndex === total - 1\n\n const isDark = variant === 'dark'\n\n // Locked + unlocked render entirely separate trees so the locked state\n // doesn't end up stacking a blurred `<img>` on top of an underlying\n // `AttachmentThumbnail` (whose `bg-white/10` poster shell otherwise\n // shows through as a visible horizontal seam at the 16:9 boundary\n // inside the taller 337/386 gallery aspect).\n //\n // The locked branch uses `filter: blur` on a single full-size `<img>`\n // — not `backdrop-filter` — so the rounded corners are clipped\n // normally without any clip-path workarounds (see LockedThumbnail).\n return (\n <div className=\"relative aspect-[337/386] overflow-hidden rounded-t-2xl\">\n {showLocked ? (\n <div\n className={classNames(\n 'absolute inset-0',\n isDark ? 'bg-white/10' : 'bg-black/5'\n )}\n >\n {current.thumbnailUrl && (\n <img\n src={current.thumbnailUrl}\n alt=\"\"\n aria-hidden\n draggable={false}\n // `scale-110` hides the fuzzy edge that `filter: blur` leaves\n // around the image bounds.\n className=\"absolute inset-0 h-full w-full scale-110 object-cover blur-[40px]\"\n />\n )}\n </div>\n ) : (\n <AttachmentThumbnail\n mimeType={current.mimeType}\n sourceUrl={current.sourceUrl}\n thumbnailUrl={current.thumbnailUrl}\n title={title}\n variant={variant}\n />\n )}\n\n {centerBadge && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"flex size-12 items-center justify-center rounded-full bg-black/25 text-white\">\n {centerBadge}\n </div>\n </div>\n )}\n\n {total > 1 && (\n <React.Fragment>\n <button\n type=\"button\"\n onClick={goPrev}\n disabled={atStart}\n aria-label=\"Previous item\"\n className={classNames(\n 'absolute left-3 top-1/2 -translate-y-1/2 flex size-7 items-center justify-center rounded-full bg-black/30 text-white transition-opacity',\n atStart ? 'opacity-40' : 'hover:bg-black/40'\n )}\n >\n <CaretLeftIcon className=\"size-5\" weight=\"bold\" />\n </button>\n <button\n type=\"button\"\n onClick={goNext}\n disabled={atEnd}\n aria-label=\"Next item\"\n className={classNames(\n 'absolute right-3 top-1/2 -translate-y-1/2 flex size-7 items-center justify-center rounded-full bg-black/30 text-white transition-opacity',\n atEnd ? 'opacity-40' : 'hover:bg-black/40'\n )}\n >\n <CaretRightIcon className=\"size-5\" weight=\"bold\" />\n </button>\n\n <div className=\"pointer-events-none absolute bottom-5 left-1/2 -translate-x-1/2 rounded-lg bg-black/30 px-2 py-1\">\n <span className=\"text-xs font-medium leading-4 text-white\">\n {safeIndex + 1}/{total}\n </span>\n </div>\n </React.Fragment>\n )}\n\n {topRight && (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-10\">\n {topRight}\n </div>\n )}\n </div>\n )\n}\n\nexport default GalleryThumbnail\n","import classNames from 'classnames'\nimport React from 'react'\n\nexport type LockedCardVariant = 'dark' | 'light'\n\nexport interface LockedCardShellProps {\n variant: LockedCardVariant\n children: React.ReactNode\n rootRef?: React.Ref<HTMLDivElement>\n 'data-testid'?: string\n}\n\n/**\n * Outer chrome for every LockedAttachment card.\n * Mirrors the `shadow-400` + 16px radius treatment from the Figma design system.\n */\nconst LockedCardShell: React.FC<LockedCardShellProps> = ({\n variant,\n children,\n rootRef,\n 'data-testid': dataTestId,\n}) => (\n <div\n ref={rootRef}\n data-testid={dataTestId}\n className={classNames(\n 'relative w-[280px] select-none overflow-hidden rounded-md',\n 'shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]',\n variant === 'dark' ? 'bg-[#121110]' : 'bg-white'\n )}\n >\n {children}\n </div>\n)\n\nexport default LockedCardShell\n","import {\n LockOpenIcon,\n LockSimpleIcon,\n PlayIcon,\n} from '@phosphor-icons/react'\nimport React from 'react'\n\nimport { getSourceType } from '../../../AttachmentCard'\nimport type { PaymentStatus } from '../../types'\n\n/**\n * Centered 48x48 lock badge that overlays the thumbnail of any locked attachment.\n * Uses `bg-black/25` from the design system's `alpha-black-25` token.\n *\n * For playable media (video/audio), the badge shows a play icon instead of a\n * lock — matching the convention used by `GalleryThumbnail`.\n */\nexport const LockBadge: React.FC<{\n paymentStatus?: PaymentStatus\n mimeType?: string\n}> = ({ paymentStatus, mimeType }) => {\n const sourceType = mimeType ? getSourceType(mimeType) : undefined\n const isPlayable = sourceType === 'video' || sourceType === 'audio'\n\n const Icon = isPlayable\n ? PlayIcon\n : paymentStatus === 'paid'\n ? LockOpenIcon\n : LockSimpleIcon\n return (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"flex size-12 items-center justify-center rounded-full bg-black/25 text-white\">\n <Icon className=\"size-6\" weight=\"fill\" />\n </div>\n </div>\n )\n}\n\nexport default LockBadge\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport {\n AttachmentThumbnail,\n renderTypeIcon,\n} from '../../../AttachmentCard'\nimport type { LockedAttachmentSource, PaymentStatus } from '../../types'\n\nimport { LockBadge } from './LockBadge'\nimport type { LockedCardVariant } from './LockedCardShell'\n\nexport interface LockedThumbnailProps {\n variant: LockedCardVariant\n mimeType: string\n thumbnailUrl?: string\n title?: string\n /**\n * When provided, the underlying source is shown unblurred (preview / unlocked state).\n * When undefined, the thumbnail is treated as locked: blurred with a central lock badge.\n */\n source?: LockedAttachmentSource\n /**\n * Forces the locked appearance even when a source is provided. Used for the\n * Composer card where the locked treatment is the default and the user toggles\n * a preview in place.\n */\n showLocked: boolean\n paymentStatus?: PaymentStatus\n /** Top-right overlay (e.g. dismiss X on Composer). Rendered inside the thumbnail. */\n topRight?: React.ReactNode\n /** When true, renders the unlocked image with aspect-video + object-contain. */\n containedImage?: boolean\n}\n\n/**\n * Renders the 180px-tall media area for a LockedAttachment card.\n * Handles three visual states:\n * - locked: blurred thumbnail + centered 48px lock badge\n * - preview/paid: clear thumbnail (the underlying source)\n * - empty: type-icon placeholder\n */\nconst LockedThumbnail: React.FC<LockedThumbnailProps> = ({\n variant,\n mimeType,\n thumbnailUrl,\n title,\n source,\n showLocked,\n paymentStatus,\n topRight,\n containedImage = false,\n}) => {\n const effectiveSourceUrl = showLocked ? undefined : source?.sourceUrl\n const effectiveThumbnailUrl = showLocked\n ? thumbnailUrl\n : (source?.thumbnailUrl ?? thumbnailUrl)\n\n const isDark = variant === 'dark'\n\n // Locked + unlocked render entirely separate trees so the locked state\n // doesn't end up stacking a blurred `<img>` on top of an underlying\n // `AttachmentThumbnail` (whose `bg-white/10` poster shell otherwise\n // shows through as a visible horizontal seam at the 16:9 boundary).\n //\n // The locked branch uses `filter: blur` on a single full-size `<img>`\n // — not `backdrop-filter` — because `backdrop-filter` paints in its\n // own compositing layer that escapes ancestor `overflow: hidden` on\n // Chrome/Safari and flattens the card's top corners. `filter` is\n // clipped normally, so the rounded corners stay round without any\n // clip-path workarounds.\n return (\n <div className=\"relative overflow-hidden rounded-t-2xl\">\n {showLocked ? (\n <div\n className={classNames(\n 'relative aspect-video overflow-hidden',\n isDark ? 'bg-white/10' : 'bg-black/5'\n )}\n >\n {effectiveThumbnailUrl ? (\n <img\n src={effectiveThumbnailUrl}\n alt=\"\"\n aria-hidden\n draggable={false}\n // `scale-110` hides the fuzzy semi-transparent edge that\n // `filter: blur` leaves around the image bounds.\n className=\"absolute inset-0 h-full w-full scale-110 object-cover blur-[40px]\"\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: isDark\n ? 'size-12 text-white/20'\n : 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n <LockBadge paymentStatus={paymentStatus} mimeType={mimeType} />\n </div>\n ) : (\n <AttachmentThumbnail\n mimeType={mimeType}\n sourceUrl={effectiveSourceUrl}\n thumbnailUrl={effectiveThumbnailUrl}\n title={title}\n variant={variant}\n containedImage={containedImage}\n mediaPlayerProps={\n effectiveSourceUrl\n ? { autoPlay: true, loop: true, controls: true, muted: false }\n : undefined\n }\n />\n )}\n\n {topRight && (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-10\">\n {topRight}\n </div>\n )}\n </div>\n )\n}\n\nexport default LockedThumbnail\n"],"names":["CardBody","variant","title","placeholderTitle","mimeType","detail","statusBadge","action","icon","trailingAction","isDark","displayTitle","titleDimmed","typeIcon","renderTypeIcon","classNames","jsxs","jsx","isPlayableItem","item","sourceType","getSourceType","GalleryThumbnail","gallery","showLocked","paymentStatus","topRight","index","setIndex","useState","total","safeIndex","current","isVideoItem","centerBadge","PlayIcon","LockOpenIcon","LockSimpleIcon","goPrev","e","i","goNext","atStart","atEnd","AttachmentThumbnail","React","CaretLeftIcon","CaretRightIcon","LockedCardShell","children","rootRef","dataTestId","LockBadge","LockedThumbnail","thumbnailUrl","source","containedImage","effectiveSourceUrl","effectiveThumbnailUrl"],"mappings":"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"}
|