@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,156 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { renderTypeIcon } from '../../../AttachmentCard'
|
|
5
|
+
import { getSourceType } from '../../../AttachmentCard/utils/mimeType'
|
|
6
|
+
|
|
7
|
+
import type { LinkAttachmentVariant } from './CardShell'
|
|
8
|
+
|
|
9
|
+
export interface CardThumbnailProps {
|
|
10
|
+
variant: LinkAttachmentVariant
|
|
11
|
+
/** Source URL of the hero image (or poster for video). */
|
|
12
|
+
thumbnailUrl?: string
|
|
13
|
+
/**
|
|
14
|
+
* Playable media URL. When provided alongside a video / audio `mimeType`,
|
|
15
|
+
* the hero region renders a native HTML5 player with controls instead of
|
|
16
|
+
* the static thumbnail / placeholder.
|
|
17
|
+
*/
|
|
18
|
+
sourceUrl?: string
|
|
19
|
+
/** Alt text — typically the card's title. */
|
|
20
|
+
title?: string
|
|
21
|
+
/**
|
|
22
|
+
* Drives the placeholder type icon when no `thumbnailUrl` is provided,
|
|
23
|
+
* and selects between image / video / audio rendering when `sourceUrl`
|
|
24
|
+
* is set. Defaults to a generic image icon when unset.
|
|
25
|
+
*/
|
|
26
|
+
mimeType?: string
|
|
27
|
+
/** Optional decorations layered into the top corners of the thumbnail. */
|
|
28
|
+
topLeft?: React.ReactNode
|
|
29
|
+
topRight?: React.ReactNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const PLACEHOLDER_BG: Record<LinkAttachmentVariant, string> = {
|
|
33
|
+
dark: 'bg-white/10',
|
|
34
|
+
light: 'bg-black/5',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const PLACEHOLDER_ICON: Record<LinkAttachmentVariant, string> = {
|
|
38
|
+
dark: 'size-16 text-white/25',
|
|
39
|
+
light: 'size-16 text-black/25',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 180px hero region shown above the card body. Renders, in priority order:
|
|
44
|
+
* 1. A native `<video controls>` when `sourceUrl` is set and the mime is
|
|
45
|
+
* video — `thumbnailUrl` acts as the poster.
|
|
46
|
+
* 2. A native `<audio controls>` when `sourceUrl` is set and the mime is
|
|
47
|
+
* audio — laid over the audio type-icon backdrop.
|
|
48
|
+
* 3. The supplied `thumbnailUrl` image.
|
|
49
|
+
* 4. A placeholder type-icon derived from `mimeType`.
|
|
50
|
+
*/
|
|
51
|
+
/** Mime + sourceUrl gives us a playable audio attachment. */
|
|
52
|
+
export const isPlayableAudio = (mimeType?: string, sourceUrl?: string) =>
|
|
53
|
+
!!sourceUrl && !!mimeType && getSourceType(mimeType) === 'audio'
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mime + sourceUrl gives us a playable video or audio attachment. Used by
|
|
57
|
+
* Received to skip wrapping the shell in an interactive `<button>` so the
|
|
58
|
+
* native media controls remain operable.
|
|
59
|
+
*/
|
|
60
|
+
export const isPlayableMedia = (mimeType?: string, sourceUrl?: string) => {
|
|
61
|
+
if (!sourceUrl || !mimeType) return false
|
|
62
|
+
const source = getSourceType(mimeType)
|
|
63
|
+
return source === 'video' || source === 'audio'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Background colour the LinkAttachment cards switch to when the source is
|
|
68
|
+
* audio — flat neutral around the native `<audio>` chrome regardless of
|
|
69
|
+
* the dark / light variant.
|
|
70
|
+
*/
|
|
71
|
+
export const AUDIO_BG_CLASS = 'bg-[#F2F3F4]'
|
|
72
|
+
|
|
73
|
+
const CardThumbnail: React.FC<CardThumbnailProps> = ({
|
|
74
|
+
variant,
|
|
75
|
+
thumbnailUrl,
|
|
76
|
+
sourceUrl,
|
|
77
|
+
title,
|
|
78
|
+
mimeType = 'image/*',
|
|
79
|
+
topLeft,
|
|
80
|
+
topRight,
|
|
81
|
+
}) => {
|
|
82
|
+
const sourceType = getSourceType(mimeType)
|
|
83
|
+
const isPlayableVideo = !!sourceUrl && sourceType === 'video'
|
|
84
|
+
|
|
85
|
+
if (isPlayableAudio(mimeType, sourceUrl)) {
|
|
86
|
+
// Audio collapses the hero entirely — the native player sits inside
|
|
87
|
+
// the card chrome with a bit of padding so the card background
|
|
88
|
+
// (typically `bg-[#F2F3F4]`) is visible around it.
|
|
89
|
+
return (
|
|
90
|
+
<div className="p-3">
|
|
91
|
+
<audio
|
|
92
|
+
src={sourceUrl}
|
|
93
|
+
controls
|
|
94
|
+
preload="metadata"
|
|
95
|
+
className="block w-full"
|
|
96
|
+
>
|
|
97
|
+
<track kind="captions" />
|
|
98
|
+
</audio>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
className={classNames(
|
|
106
|
+
'relative h-[180px] w-full overflow-hidden',
|
|
107
|
+
isPlayableVideo && 'bg-black'
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
{isPlayableVideo ? (
|
|
111
|
+
<video
|
|
112
|
+
src={sourceUrl}
|
|
113
|
+
poster={thumbnailUrl}
|
|
114
|
+
controls
|
|
115
|
+
playsInline
|
|
116
|
+
preload="metadata"
|
|
117
|
+
className="absolute inset-0 h-full w-full object-contain"
|
|
118
|
+
>
|
|
119
|
+
<track kind="captions" />
|
|
120
|
+
</video>
|
|
121
|
+
) : thumbnailUrl ? (
|
|
122
|
+
<img
|
|
123
|
+
src={thumbnailUrl}
|
|
124
|
+
alt={title ?? ''}
|
|
125
|
+
draggable={false}
|
|
126
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
127
|
+
/>
|
|
128
|
+
) : (
|
|
129
|
+
<div
|
|
130
|
+
className={classNames(
|
|
131
|
+
'flex h-full w-full items-center justify-center',
|
|
132
|
+
PLACEHOLDER_BG[variant]
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
{renderTypeIcon(mimeType, {
|
|
136
|
+
className: PLACEHOLDER_ICON[variant],
|
|
137
|
+
weight: 'regular',
|
|
138
|
+
})}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{topLeft ? (
|
|
143
|
+
<div className="pointer-events-auto absolute left-3 top-3 z-10">
|
|
144
|
+
{topLeft}
|
|
145
|
+
</div>
|
|
146
|
+
) : null}
|
|
147
|
+
{topRight ? (
|
|
148
|
+
<div className="pointer-events-auto absolute right-3 top-3 z-10">
|
|
149
|
+
{topRight}
|
|
150
|
+
</div>
|
|
151
|
+
) : null}
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default CardThumbnail
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheme detector: `protocol:` per RFC 3986 — a letter followed by any
|
|
3
|
+
* combination of letters, digits, `+`, `.`, or `-` then `:`.
|
|
4
|
+
*/
|
|
5
|
+
const SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Allowlist of schemes that are safe to forward into `<a href>` for
|
|
9
|
+
* external navigation. `javascript:` / `data:` / `vbscript:` etc. are
|
|
10
|
+
* intentionally **not** on this list — link-attachment data is
|
|
11
|
+
* effectively user-controlled, so passing them through would let a
|
|
12
|
+
* recipient click execute attacker-supplied code or markup.
|
|
13
|
+
*/
|
|
14
|
+
const SAFE_SCHEMES = new Set(['http', 'https', 'mailto', 'tel', 'sms'])
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a user-supplied URL into something safe to assign to
|
|
18
|
+
* `<a href>` for external navigation.
|
|
19
|
+
*
|
|
20
|
+
* Link attachments / link apps always point at external destinations
|
|
21
|
+
* (Spotify, TikTok, FAQ links, bare-hostname Linktree URLs like
|
|
22
|
+
* `tr.ee/briemix`, etc.). Without normalization, a bare hostname is
|
|
23
|
+
* treated as a relative path by the browser and clicks navigate within
|
|
24
|
+
* the host site (e.g. `https://linktr.ee/admin/tr.ee/briemix`) instead
|
|
25
|
+
* of opening the intended destination.
|
|
26
|
+
*
|
|
27
|
+
* Rules:
|
|
28
|
+
* - Empty / whitespace-only → returns `undefined` (no href).
|
|
29
|
+
* - Explicit scheme in the safe allowlist (`http`, `https`, `mailto`,
|
|
30
|
+
* `tel`, `sms`) → returned trimmed.
|
|
31
|
+
* - Explicit scheme **not** on the allowlist (`javascript:`, `data:`,
|
|
32
|
+
* `vbscript:`, custom protocols, …) → returns `undefined` so the
|
|
33
|
+
* shell falls back to a non-navigational chrome instead of letting
|
|
34
|
+
* an attacker-controlled URL execute on click.
|
|
35
|
+
* - Protocol-relative (`//example.com/…`) → returned as-is; browsers
|
|
36
|
+
* resolve these against the current page's scheme.
|
|
37
|
+
* - Site-relative path (`/admin/…`) → returned as-is so consumers can
|
|
38
|
+
* still opt into in-app navigation if they really want to.
|
|
39
|
+
* - Bare hostname or anything else → `https://` is prepended so the
|
|
40
|
+
* browser treats it as an external URL.
|
|
41
|
+
*/
|
|
42
|
+
export function normalizeExternalHref(value?: string): string | undefined {
|
|
43
|
+
if (typeof value !== 'string') return undefined
|
|
44
|
+
const trimmed = value.trim()
|
|
45
|
+
if (trimmed === '') return undefined
|
|
46
|
+
|
|
47
|
+
const schemeMatch = SCHEME_PATTERN.exec(trimmed)
|
|
48
|
+
if (schemeMatch) {
|
|
49
|
+
const scheme = schemeMatch[1].toLowerCase()
|
|
50
|
+
return SAFE_SCHEMES.has(scheme) ? trimmed : undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (trimmed.startsWith('//')) return trimmed
|
|
54
|
+
if (trimmed.startsWith('/')) return trimmed
|
|
55
|
+
return `https://${trimmed}`
|
|
56
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { Suspense } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { ComposerCardProps } from './components/Composer/Card'
|
|
4
|
+
import type { ReceivedCardProps } from './components/Received/Card'
|
|
5
|
+
import type { SentCardProps } from './components/Sent/Card'
|
|
6
|
+
|
|
7
|
+
const ComposerCardLazy = React.lazy(
|
|
8
|
+
() => import('./components/Composer/Card')
|
|
9
|
+
)
|
|
10
|
+
const SentCardLazy = React.lazy(() => import('./components/Sent/Card'))
|
|
11
|
+
const ReceivedCardLazy = React.lazy(
|
|
12
|
+
() => import('./components/Received/Card')
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const LinkAttachmentFallback = () => (
|
|
16
|
+
<div
|
|
17
|
+
className="h-[280px] w-[280px] animate-pulse rounded-md bg-black/[0.06] 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)]"
|
|
18
|
+
aria-hidden
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const Composer = (props: ComposerCardProps) => (
|
|
23
|
+
<Suspense fallback={<LinkAttachmentFallback />}>
|
|
24
|
+
<ComposerCardLazy {...props} />
|
|
25
|
+
</Suspense>
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const Sent = (props: SentCardProps) => (
|
|
29
|
+
<Suspense fallback={<LinkAttachmentFallback />}>
|
|
30
|
+
<SentCardLazy {...props} />
|
|
31
|
+
</Suspense>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const Received = (props: ReceivedCardProps) => (
|
|
35
|
+
<Suspense fallback={<LinkAttachmentFallback />}>
|
|
36
|
+
<ReceivedCardLazy {...props} />
|
|
37
|
+
</Suspense>
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Link attachments (image / file media + 1P/3P Link Apps) shown in the chat
|
|
42
|
+
* thread. Mirrors the `LockedAttachment` API — render `LinkAttachment.Composer`
|
|
43
|
+
* while drafting, `LinkAttachment.Sent` after posting, and
|
|
44
|
+
* `LinkAttachment.Received` in the recipient's thread. Maps to the
|
|
45
|
+
* "Attachments" and "LinkApps" sections of the messaging design system.
|
|
46
|
+
*
|
|
47
|
+
* Two visual layouts via the `layout` prop:
|
|
48
|
+
* - **Featured** (default) — 180px hero thumbnail above the body. Used by
|
|
49
|
+
* image / file Attachments and by hero-image LinkApps (Spotify with
|
|
50
|
+
* cover art, TikTok with a frame, etc.).
|
|
51
|
+
* - **Classic** — compact card with no hero thumbnail; title /
|
|
52
|
+
* description / URL / CTA only. Used for LinkApp embeds without
|
|
53
|
+
* artwork (FAQ, Form) and any link preview that lacks OG imagery.
|
|
54
|
+
*
|
|
55
|
+
* Image / file Attachments use `layout="featured"` and skip the title /
|
|
56
|
+
* description / URL body entirely (`CardBody` collapses to nothing when no
|
|
57
|
+
* text content is provided). LinkApps always carry a title + description
|
|
58
|
+
* and prefix the title with an `appIcon` brand badge.
|
|
59
|
+
*/
|
|
60
|
+
const LinkAttachment = { Composer, Sent, Received }
|
|
61
|
+
|
|
62
|
+
export default LinkAttachment
|
|
63
|
+
export type { ComposerCardProps, SentCardProps, ReceivedCardProps }
|
|
64
|
+
export type {
|
|
65
|
+
LinkAttachmentBaseProps,
|
|
66
|
+
LinkAttachmentCta,
|
|
67
|
+
LinkAttachmentLayout,
|
|
68
|
+
} from './types'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visual layout for a `LinkAttachment.*` card.
|
|
5
|
+
*
|
|
6
|
+
* - `featured` — full card with a 180px hero thumbnail above the body.
|
|
7
|
+
* The default; matches the "Attachments" frames and the hero-image
|
|
8
|
+
* "LinkApps" frames in Figma.
|
|
9
|
+
* - `classic` — compact card without a hero thumbnail. Title /
|
|
10
|
+
* description / URL / CTA only. Used for Link App embeds that don't
|
|
11
|
+
* carry artwork (and for plain link previews without OG imagery).
|
|
12
|
+
*/
|
|
13
|
+
export type LinkAttachmentLayout = 'featured' | 'classic'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Shared props for the three `LinkAttachment.*` variants (Composer, Sent,
|
|
17
|
+
* Received). Maps to the "Attachments" + "LinkApps" sections of the messaging
|
|
18
|
+
* design system in Figma — a 280px-wide card with an optional 180px hero
|
|
19
|
+
* thumbnail, a title (optionally prefixed with a brand badge for Link Apps),
|
|
20
|
+
* a description, and either a URL footer or a CTA button.
|
|
21
|
+
*/
|
|
22
|
+
export interface LinkAttachmentBaseProps {
|
|
23
|
+
title?: string
|
|
24
|
+
/** Placeholder shown in the title slot before one is configured (dark variants only). */
|
|
25
|
+
placeholderTitle?: string
|
|
26
|
+
/** Secondary description rendered below the title. */
|
|
27
|
+
description?: string
|
|
28
|
+
/**
|
|
29
|
+
* Optional URL displayed at the bottom of the card (e.g. `tr.ee/briemix`).
|
|
30
|
+
* Ignored when `cta` is provided. Also used as the navigation target for
|
|
31
|
+
* the Received card when no `cta` is set.
|
|
32
|
+
*/
|
|
33
|
+
url?: string
|
|
34
|
+
/** MIME type of the hero thumbnail — drives the type icon for empty states. */
|
|
35
|
+
mimeType?: string
|
|
36
|
+
/** Hero thumbnail (180px tall) shown above the title block. */
|
|
37
|
+
thumbnailUrl?: string
|
|
38
|
+
/**
|
|
39
|
+
* Source URL for playable media (video, audio). When provided alongside a
|
|
40
|
+
* video / audio `mimeType`, the hero region renders an inline player with
|
|
41
|
+
* native controls instead of the static thumbnail / type-icon.
|
|
42
|
+
*/
|
|
43
|
+
sourceUrl?: string
|
|
44
|
+
/**
|
|
45
|
+
* Visual layout — `'featured'` keeps the 180px hero thumbnail above the
|
|
46
|
+
* body, `'classic'` drops the hero entirely for a compact text-only
|
|
47
|
+
* card. Defaults to `'featured'`.
|
|
48
|
+
*/
|
|
49
|
+
layout?: LinkAttachmentLayout
|
|
50
|
+
/**
|
|
51
|
+
* Optional 16x16 brand badge rendered before the title (used by Link Apps:
|
|
52
|
+
* Spotify, TikTok, FAQ, Form, etc.). Consumers render whatever they want
|
|
53
|
+
* — typically a colored 4px-rounded square containing a glyph or `<img>`.
|
|
54
|
+
*/
|
|
55
|
+
appIcon?: React.ReactNode
|
|
56
|
+
/**
|
|
57
|
+
* Optional call-to-action rendered below the description. When set,
|
|
58
|
+
* replaces the URL footer (e.g. FAQ "View FAQs", Form "Complete form").
|
|
59
|
+
*/
|
|
60
|
+
cta?: LinkAttachmentCta
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface LinkAttachmentCta {
|
|
64
|
+
label: string
|
|
65
|
+
/** When set, the CTA renders as an `<a>` opening in a new tab. */
|
|
66
|
+
href?: string
|
|
67
|
+
/** When set, called on click (in addition to `href` navigation if provided). */
|
|
68
|
+
onClick?: () => void
|
|
69
|
+
}
|