@linktr.ee/messaging-react 2.0.0 → 2.0.1-rc-1778656305

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/Card-CAC3fPjy.js +107 -0
  2. package/dist/Card-CAC3fPjy.js.map +1 -0
  3. package/dist/Card-DLUBUg_w.js +132 -0
  4. package/dist/Card-DLUBUg_w.js.map +1 -0
  5. package/dist/Card-_StSlnYh.js +163 -0
  6. package/dist/Card-_StSlnYh.js.map +1 -0
  7. package/dist/LockedThumbnail-p5RsFOug.js +220 -0
  8. package/dist/LockedThumbnail-p5RsFOug.js.map +1 -0
  9. package/dist/assets/index.css +1 -1
  10. package/dist/{index-Brz9orsI.js → index-B1h46F9x.js} +811 -772
  11. package/dist/index-B1h46F9x.js.map +1 -0
  12. package/dist/index.d.ts +87 -28
  13. package/dist/index.js +3 -3
  14. package/package.json +1 -1
  15. package/src/components/ChannelView.test.tsx +11 -0
  16. package/src/components/ChannelView.tsx +35 -32
  17. package/src/components/CustomMessage/index.tsx +2 -3
  18. package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +57 -17
  19. package/src/components/CustomTypingIndicator/CustomTypingIndicator.test.tsx +187 -0
  20. package/src/components/CustomTypingIndicator/DmAgentContext.ts +3 -0
  21. package/src/components/CustomTypingIndicator/index.tsx +101 -37
  22. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +230 -89
  23. package/src/components/LockedAttachment/components/Composer/Card.tsx +221 -0
  24. package/src/components/LockedAttachment/components/Composer/index.ts +2 -0
  25. package/src/components/LockedAttachment/components/Received/Card.tsx +191 -0
  26. package/src/components/LockedAttachment/components/Received/CardActions.tsx +91 -0
  27. package/src/components/LockedAttachment/components/Received/index.ts +2 -0
  28. package/src/components/LockedAttachment/components/Sent/Card.tsx +177 -0
  29. package/src/components/LockedAttachment/components/Sent/index.ts +2 -0
  30. package/src/components/LockedAttachment/components/_shared/CardBody.tsx +94 -0
  31. package/src/components/LockedAttachment/components/_shared/GalleryThumbnail.tsx +178 -0
  32. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +39 -0
  33. package/src/components/LockedAttachment/components/_shared/LockedCardShell.tsx +36 -0
  34. package/src/components/LockedAttachment/components/_shared/LockedThumbnail.tsx +128 -0
  35. package/src/components/LockedAttachment/index.tsx +43 -12
  36. package/src/components/LockedAttachment/types.ts +17 -0
  37. package/src/components/MediaMessage/index.tsx +2 -2
  38. package/src/index.ts +6 -1
  39. package/src/styles.css +7 -0
  40. package/dist/Card-BHknCeHw.js +0 -138
  41. package/dist/Card-BHknCeHw.js.map +0 -1
  42. package/dist/Card-DT7_ms2p.js +0 -127
  43. package/dist/Card-DT7_ms2p.js.map +0 -1
  44. package/dist/index-Brz9orsI.js.map +0 -1
  45. package/src/components/LockedAttachment/components/Creator/Card.tsx +0 -210
  46. package/src/components/LockedAttachment/components/Creator/index.tsx +0 -2
  47. package/src/components/LockedAttachment/components/Visitor/Card.tsx +0 -155
  48. package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +0 -62
  49. package/src/components/LockedAttachment/components/Visitor/LockBadge.tsx +0 -12
  50. package/src/components/LockedAttachment/components/Visitor/index.ts +0 -2
@@ -0,0 +1,94 @@
1
+ import classNames from 'classnames'
2
+ import React from 'react'
3
+
4
+ import { renderTypeIcon } from '../../../AttachmentCard'
5
+
6
+ import type { LockedCardVariant } from './LockedCardShell'
7
+
8
+ export interface CardBodyProps {
9
+ variant: LockedCardVariant
10
+ title?: string
11
+ placeholderTitle?: string
12
+ mimeType: string
13
+ detail?: string
14
+ statusBadge?: React.ReactNode
15
+ action?: React.ReactNode
16
+ /** Overrides the auto-detected type icon (used by Gallery to swap in an `Images` icon). */
17
+ icon?: React.ReactNode
18
+ /** Optional control rendered on the right of the title/status block (e.g. Composer edit pencil). */
19
+ trailingAction?: React.ReactNode
20
+ }
21
+
22
+ /**
23
+ * Title + status row layout shared by Composer / Sent / Received cards.
24
+ * Layout matches the Figma `Container > Labels` group (16px h-padding, 12px v-padding,
25
+ * 4px gap between title and status row, 4px gap inside status row).
26
+ */
27
+ const CardBody: React.FC<CardBodyProps> = ({
28
+ variant,
29
+ title,
30
+ placeholderTitle = 'Attachment title',
31
+ mimeType,
32
+ detail,
33
+ statusBadge,
34
+ action,
35
+ icon,
36
+ trailingAction,
37
+ }) => {
38
+ const isDark = variant === 'dark'
39
+ const displayTitle = isDark ? (title ?? placeholderTitle) : (title ?? '')
40
+ const titleDimmed = isDark && !title
41
+
42
+ const typeIcon =
43
+ icon ??
44
+ renderTypeIcon(mimeType, {
45
+ className: classNames(
46
+ 'size-5 shrink-0',
47
+ isDark ? 'text-white/55' : 'text-black/55'
48
+ ),
49
+ weight: 'regular',
50
+ })
51
+
52
+ return (
53
+ <div className="px-4 py-3">
54
+ <div className="flex items-end gap-3">
55
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
56
+ {displayTitle.trim() !== '' && (
57
+ <p
58
+ className={classNames('truncate text-base font-medium leading-6', {
59
+ 'text-black/90': !isDark,
60
+ 'text-white/30': isDark && titleDimmed,
61
+ 'text-white': isDark && !titleDimmed,
62
+ })}
63
+ >
64
+ {displayTitle}
65
+ </p>
66
+ )}
67
+
68
+ <div className="flex flex-wrap items-center gap-1">
69
+ {typeIcon}
70
+
71
+ {detail != null && detail !== '' && (
72
+ <span
73
+ className={classNames(
74
+ 'text-xs font-medium',
75
+ isDark ? 'text-white/55' : 'text-black/55'
76
+ )}
77
+ >
78
+ {detail}
79
+ </span>
80
+ )}
81
+
82
+ {statusBadge}
83
+ </div>
84
+ </div>
85
+
86
+ {trailingAction && <div className="shrink-0">{trailingAction}</div>}
87
+ </div>
88
+
89
+ {action}
90
+ </div>
91
+ )
92
+ }
93
+
94
+ export default CardBody
@@ -0,0 +1,178 @@
1
+ import {
2
+ CaretLeftIcon,
3
+ CaretRightIcon,
4
+ LockOpenIcon,
5
+ LockSimpleIcon,
6
+ PlayIcon,
7
+ } from '@phosphor-icons/react'
8
+ import classNames from 'classnames'
9
+ import React, { useState } from 'react'
10
+
11
+ import { AttachmentThumbnail, getSourceType } from '../../../AttachmentCard'
12
+ import type {
13
+ LockedAttachmentGalleryItem,
14
+ PaymentStatus,
15
+ } from '../../types'
16
+
17
+ import type { LockedCardVariant } from './LockedCardShell'
18
+
19
+ export interface GalleryThumbnailProps {
20
+ variant: LockedCardVariant
21
+ gallery: LockedAttachmentGalleryItem[]
22
+ title?: string
23
+ /** When true, all items are shown blurred + the locked/play badge is overlaid. */
24
+ showLocked: boolean
25
+ paymentStatus?: PaymentStatus
26
+ /** Top-right slot (e.g. dismiss X on Composer). Rendered inside the thumbnail. */
27
+ topRight?: React.ReactNode
28
+ }
29
+
30
+ function isPlayableItem(item: LockedAttachmentGalleryItem): boolean {
31
+ const sourceType = getSourceType(item.mimeType)
32
+ return sourceType === 'video' || sourceType === 'audio'
33
+ }
34
+
35
+ /**
36
+ * Mixed-media carousel used by every LockedAttachment card when its `gallery`
37
+ * prop has 2+ items. Matches the "Gallery" frames in the Figma design system:
38
+ * 337/386 aspect ratio, prev/next caret buttons, a counter pill, and a center
39
+ * lock / play indicator that swaps based on the current item.
40
+ */
41
+ const GalleryThumbnail: React.FC<GalleryThumbnailProps> = ({
42
+ variant,
43
+ gallery,
44
+ title,
45
+ showLocked,
46
+ paymentStatus,
47
+ topRight,
48
+ }) => {
49
+ const [index, setIndex] = useState(0)
50
+ const total = gallery.length
51
+ const safeIndex = Math.min(index, total - 1)
52
+ const current = gallery[safeIndex]
53
+ const isVideoItem = isPlayableItem(current)
54
+
55
+ // Center badge: PLAY for video items, LOCK for non-video locked items.
56
+ // Unlocked image items show no center badge — the image is the content.
57
+ const centerBadge = (() => {
58
+ if (isVideoItem) {
59
+ return <PlayIcon className="size-6" weight="fill" />
60
+ }
61
+ if (showLocked) {
62
+ return paymentStatus === 'paid' ? (
63
+ <LockOpenIcon className="size-6" weight="fill" />
64
+ ) : (
65
+ <LockSimpleIcon className="size-6" weight="fill" />
66
+ )
67
+ }
68
+ return null
69
+ })()
70
+
71
+ // Stop propagation so arrow taps don't bubble up to the outer preview-toggle
72
+ // wrapper on ComposerCard / SentCard.
73
+ const goPrev = (e: React.MouseEvent) => {
74
+ e.stopPropagation()
75
+ setIndex((i) => Math.max(0, i - 1))
76
+ }
77
+ const goNext = (e: React.MouseEvent) => {
78
+ e.stopPropagation()
79
+ setIndex((i) => Math.min(total - 1, i + 1))
80
+ }
81
+ const atStart = safeIndex === 0
82
+ const atEnd = safeIndex === total - 1
83
+
84
+ const isDark = variant === 'dark'
85
+
86
+ // Locked + unlocked render entirely separate trees so the locked state
87
+ // doesn't end up stacking a blurred `<img>` on top of an underlying
88
+ // `AttachmentThumbnail` (whose `bg-white/10` poster shell otherwise
89
+ // shows through as a visible horizontal seam at the 16:9 boundary
90
+ // inside the taller 337/386 gallery aspect).
91
+ //
92
+ // The locked branch uses `filter: blur` on a single full-size `<img>`
93
+ // — not `backdrop-filter` — so the rounded corners are clipped
94
+ // normally without any clip-path workarounds (see LockedThumbnail).
95
+ return (
96
+ <div className="relative aspect-[337/386] overflow-hidden rounded-t-2xl">
97
+ {showLocked ? (
98
+ <div
99
+ className={classNames(
100
+ 'absolute inset-0',
101
+ isDark ? 'bg-white/10' : 'bg-black/5'
102
+ )}
103
+ >
104
+ {current.thumbnailUrl && (
105
+ <img
106
+ src={current.thumbnailUrl}
107
+ alt=""
108
+ aria-hidden
109
+ draggable={false}
110
+ // `scale-110` hides the fuzzy edge that `filter: blur` leaves
111
+ // around the image bounds.
112
+ className="absolute inset-0 h-full w-full scale-110 object-cover blur-[40px]"
113
+ />
114
+ )}
115
+ </div>
116
+ ) : (
117
+ <AttachmentThumbnail
118
+ mimeType={current.mimeType}
119
+ sourceUrl={current.sourceUrl}
120
+ thumbnailUrl={current.thumbnailUrl}
121
+ title={title}
122
+ variant={variant}
123
+ />
124
+ )}
125
+
126
+ {centerBadge && (
127
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
128
+ <div className="flex size-12 items-center justify-center rounded-full bg-black/25 text-white">
129
+ {centerBadge}
130
+ </div>
131
+ </div>
132
+ )}
133
+
134
+ {total > 1 && (
135
+ <React.Fragment>
136
+ <button
137
+ type="button"
138
+ onClick={goPrev}
139
+ disabled={atStart}
140
+ aria-label="Previous item"
141
+ className={classNames(
142
+ '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',
143
+ atStart ? 'opacity-40' : 'hover:bg-black/40'
144
+ )}
145
+ >
146
+ <CaretLeftIcon className="size-5" weight="bold" />
147
+ </button>
148
+ <button
149
+ type="button"
150
+ onClick={goNext}
151
+ disabled={atEnd}
152
+ aria-label="Next item"
153
+ className={classNames(
154
+ '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',
155
+ atEnd ? 'opacity-40' : 'hover:bg-black/40'
156
+ )}
157
+ >
158
+ <CaretRightIcon className="size-5" weight="bold" />
159
+ </button>
160
+
161
+ <div className="pointer-events-none absolute bottom-5 left-1/2 -translate-x-1/2 rounded-lg bg-black/30 px-2 py-1">
162
+ <span className="text-xs font-medium leading-4 text-white">
163
+ {safeIndex + 1}/{total}
164
+ </span>
165
+ </div>
166
+ </React.Fragment>
167
+ )}
168
+
169
+ {topRight && (
170
+ <div className="pointer-events-auto absolute right-3 top-3 z-10">
171
+ {topRight}
172
+ </div>
173
+ )}
174
+ </div>
175
+ )
176
+ }
177
+
178
+ export default GalleryThumbnail
@@ -0,0 +1,39 @@
1
+ import {
2
+ LockOpenIcon,
3
+ LockSimpleIcon,
4
+ PlayIcon,
5
+ } from '@phosphor-icons/react'
6
+ import React from 'react'
7
+
8
+ import { getSourceType } from '../../../AttachmentCard'
9
+ import type { PaymentStatus } from '../../types'
10
+
11
+ /**
12
+ * Centered 48x48 lock badge that overlays the thumbnail of any locked attachment.
13
+ * Uses `bg-black/25` from the design system's `alpha-black-25` token.
14
+ *
15
+ * For playable media (video/audio), the badge shows a play icon instead of a
16
+ * lock — matching the convention used by `GalleryThumbnail`.
17
+ */
18
+ export const LockBadge: React.FC<{
19
+ paymentStatus?: PaymentStatus
20
+ mimeType?: string
21
+ }> = ({ paymentStatus, mimeType }) => {
22
+ const sourceType = mimeType ? getSourceType(mimeType) : undefined
23
+ const isPlayable = sourceType === 'video' || sourceType === 'audio'
24
+
25
+ const Icon = isPlayable
26
+ ? PlayIcon
27
+ : paymentStatus === 'paid'
28
+ ? LockOpenIcon
29
+ : LockSimpleIcon
30
+ return (
31
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
32
+ <div className="flex size-12 items-center justify-center rounded-full bg-black/25 text-white">
33
+ <Icon className="size-6" weight="fill" />
34
+ </div>
35
+ </div>
36
+ )
37
+ }
38
+
39
+ export default LockBadge
@@ -0,0 +1,36 @@
1
+ import classNames from 'classnames'
2
+ import React from 'react'
3
+
4
+ export type LockedCardVariant = 'dark' | 'light'
5
+
6
+ export interface LockedCardShellProps {
7
+ variant: LockedCardVariant
8
+ children: React.ReactNode
9
+ rootRef?: React.Ref<HTMLDivElement>
10
+ 'data-testid'?: string
11
+ }
12
+
13
+ /**
14
+ * Outer chrome for every LockedAttachment card.
15
+ * Mirrors the `shadow-400` + 16px radius treatment from the Figma design system.
16
+ */
17
+ const LockedCardShell: React.FC<LockedCardShellProps> = ({
18
+ variant,
19
+ children,
20
+ rootRef,
21
+ 'data-testid': dataTestId,
22
+ }) => (
23
+ <div
24
+ ref={rootRef}
25
+ data-testid={dataTestId}
26
+ className={classNames(
27
+ 'relative w-[280px] select-none overflow-hidden rounded-2xl',
28
+ '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)]',
29
+ variant === 'dark' ? 'bg-[#121110]' : 'bg-white'
30
+ )}
31
+ >
32
+ {children}
33
+ </div>
34
+ )
35
+
36
+ export default LockedCardShell
@@ -0,0 +1,128 @@
1
+ import classNames from 'classnames'
2
+ import React from 'react'
3
+
4
+ import {
5
+ AttachmentThumbnail,
6
+ renderTypeIcon,
7
+ } from '../../../AttachmentCard'
8
+ import type { LockedAttachmentSource, PaymentStatus } from '../../types'
9
+
10
+ import { LockBadge } from './LockBadge'
11
+ import type { LockedCardVariant } from './LockedCardShell'
12
+
13
+ export interface LockedThumbnailProps {
14
+ variant: LockedCardVariant
15
+ mimeType: string
16
+ thumbnailUrl?: string
17
+ title?: string
18
+ /**
19
+ * When provided, the underlying source is shown unblurred (preview / unlocked state).
20
+ * When undefined, the thumbnail is treated as locked: blurred with a central lock badge.
21
+ */
22
+ source?: LockedAttachmentSource
23
+ /**
24
+ * Forces the locked appearance even when a source is provided. Used for the
25
+ * Composer card where the locked treatment is the default and the user toggles
26
+ * a preview in place.
27
+ */
28
+ showLocked: boolean
29
+ paymentStatus?: PaymentStatus
30
+ /** Top-right overlay (e.g. dismiss X on Composer). Rendered inside the thumbnail. */
31
+ topRight?: React.ReactNode
32
+ /** When true, renders the unlocked image with aspect-video + object-contain. */
33
+ containedImage?: boolean
34
+ }
35
+
36
+ /**
37
+ * Renders the 180px-tall media area for a LockedAttachment card.
38
+ * Handles three visual states:
39
+ * - locked: blurred thumbnail + centered 48px lock badge
40
+ * - preview/paid: clear thumbnail (the underlying source)
41
+ * - empty: type-icon placeholder
42
+ */
43
+ const LockedThumbnail: React.FC<LockedThumbnailProps> = ({
44
+ variant,
45
+ mimeType,
46
+ thumbnailUrl,
47
+ title,
48
+ source,
49
+ showLocked,
50
+ paymentStatus,
51
+ topRight,
52
+ containedImage = false,
53
+ }) => {
54
+ const effectiveSourceUrl = showLocked ? undefined : source?.sourceUrl
55
+ const effectiveThumbnailUrl = showLocked
56
+ ? thumbnailUrl
57
+ : (source?.thumbnailUrl ?? thumbnailUrl)
58
+
59
+ const isDark = variant === 'dark'
60
+
61
+ // Locked + unlocked render entirely separate trees so the locked state
62
+ // doesn't end up stacking a blurred `<img>` on top of an underlying
63
+ // `AttachmentThumbnail` (whose `bg-white/10` poster shell otherwise
64
+ // shows through as a visible horizontal seam at the 16:9 boundary).
65
+ //
66
+ // The locked branch uses `filter: blur` on a single full-size `<img>`
67
+ // — not `backdrop-filter` — because `backdrop-filter` paints in its
68
+ // own compositing layer that escapes ancestor `overflow: hidden` on
69
+ // Chrome/Safari and flattens the card's top corners. `filter` is
70
+ // clipped normally, so the rounded corners stay round without any
71
+ // clip-path workarounds.
72
+ return (
73
+ <div className="relative overflow-hidden rounded-t-2xl">
74
+ {showLocked ? (
75
+ <div
76
+ className={classNames(
77
+ 'relative aspect-video overflow-hidden',
78
+ isDark ? 'bg-white/10' : 'bg-black/5'
79
+ )}
80
+ >
81
+ {effectiveThumbnailUrl ? (
82
+ <img
83
+ src={effectiveThumbnailUrl}
84
+ alt=""
85
+ aria-hidden
86
+ draggable={false}
87
+ // `scale-110` hides the fuzzy semi-transparent edge that
88
+ // `filter: blur` leaves around the image bounds.
89
+ className="absolute inset-0 h-full w-full scale-110 object-cover blur-[40px]"
90
+ />
91
+ ) : (
92
+ <div className="absolute inset-0 flex items-center justify-center">
93
+ {renderTypeIcon(mimeType, {
94
+ className: isDark
95
+ ? 'size-12 text-white/20'
96
+ : 'size-12 text-black/20',
97
+ weight: 'regular',
98
+ })}
99
+ </div>
100
+ )}
101
+ <LockBadge paymentStatus={paymentStatus} mimeType={mimeType} />
102
+ </div>
103
+ ) : (
104
+ <AttachmentThumbnail
105
+ mimeType={mimeType}
106
+ sourceUrl={effectiveSourceUrl}
107
+ thumbnailUrl={effectiveThumbnailUrl}
108
+ title={title}
109
+ variant={variant}
110
+ containedImage={containedImage}
111
+ mediaPlayerProps={
112
+ effectiveSourceUrl
113
+ ? { autoPlay: true, loop: true, controls: true, muted: false }
114
+ : undefined
115
+ }
116
+ />
117
+ )}
118
+
119
+ {topRight && (
120
+ <div className="pointer-events-auto absolute right-3 top-3 z-10">
121
+ {topRight}
122
+ </div>
123
+ )}
124
+ </div>
125
+ )
126
+ }
127
+
128
+ export default LockedThumbnail
@@ -1,32 +1,63 @@
1
1
  import React, { Suspense } from 'react'
2
2
 
3
- import type { CreatorCardProps } from './components/Creator/Card'
4
- import type { VisitorCardProps } from './components/Visitor/Card'
3
+ import type { ComposerCardProps } from './components/Composer/Card'
4
+ import type { ReceivedCardProps } from './components/Received/Card'
5
+ import type { SentCardProps } from './components/Sent/Card'
5
6
 
6
- const CreatorCardLazy = React.lazy(() => import('./components/Creator/Card'))
7
- const VisitorCardLazy = React.lazy(() => import('./components/Visitor/Card'))
7
+ const ComposerCardLazy = React.lazy(() => import('./components/Composer/Card'))
8
+ const SentCardLazy = React.lazy(() => import('./components/Sent/Card'))
9
+ const ReceivedCardLazy = React.lazy(() => import('./components/Received/Card'))
8
10
 
9
11
  const LockedAttachmentFallback = () => (
10
12
  <div
11
- className="w-[280px] min-h-[200px] animate-pulse rounded-[24px] bg-black/[0.06] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]"
13
+ className="w-[280px] min-h-[200px] animate-pulse rounded-2xl 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)]"
12
14
  aria-hidden
13
15
  />
14
16
  )
15
17
 
16
- const Creator = (props: CreatorCardProps) => (
18
+ const Composer = (props: ComposerCardProps) => (
17
19
  <Suspense fallback={<LockedAttachmentFallback />}>
18
- <CreatorCardLazy {...props} />
20
+ <ComposerCardLazy {...props} />
19
21
  </Suspense>
20
22
  )
21
23
 
22
- const Visitor = (props: VisitorCardProps) => (
24
+ const Sent = (props: SentCardProps) => (
23
25
  <Suspense fallback={<LockedAttachmentFallback />}>
24
- <VisitorCardLazy {...props} />
26
+ <SentCardLazy {...props} />
25
27
  </Suspense>
26
28
  )
27
29
 
28
- const LockedAttachment = { Creator, Visitor }
30
+ const Received = (props: ReceivedCardProps) => (
31
+ <Suspense fallback={<LockedAttachmentFallback />}>
32
+ <ReceivedCardLazy {...props} />
33
+ </Suspense>
34
+ )
35
+
36
+ /**
37
+ * @deprecated Renamed to `LockedAttachment.Sent` (post-send sender card) or
38
+ * `LockedAttachment.Composer` (drafting state). This alias maps to `Sent`
39
+ * because that matches the previous in-thread usage; pre-send / drafting
40
+ * usages (e.g. with `onDismiss`) should migrate to `LockedAttachment.Composer`.
41
+ */
42
+ const Creator = Sent
43
+
44
+ /** @deprecated Renamed to `LockedAttachment.Received`. */
45
+ const Visitor = Received
46
+
47
+ const LockedAttachment = { Composer, Sent, Received, Creator, Visitor }
29
48
 
30
49
  export default LockedAttachment
31
- export type { CreatorCardProps, VisitorCardProps }
32
- export type { PaymentStatus, LockedAttachmentSource, LockedAttachmentContextValue } from './types'
50
+ export type { ComposerCardProps, SentCardProps, ReceivedCardProps }
51
+ /**
52
+ * @deprecated Renamed to `SentCardProps`. Drafting usages (with `onDismiss`)
53
+ * should migrate to `ComposerCardProps`.
54
+ */
55
+ export type CreatorCardProps = SentCardProps
56
+ /** @deprecated Renamed to `ReceivedCardProps`. */
57
+ export type VisitorCardProps = ReceivedCardProps
58
+ export type {
59
+ PaymentStatus,
60
+ LockedAttachmentSource,
61
+ LockedAttachmentGalleryItem,
62
+ LockedAttachmentContextValue,
63
+ } from './types'
@@ -9,6 +9,23 @@ export interface LockedAttachmentBaseProps {
9
9
  detail?: string
10
10
  amountText?: string
11
11
  paymentStatus?: PaymentStatus
12
+ /**
13
+ * When provided with 2+ items, the card renders as a mixed-media carousel
14
+ * (e.g. a couple of photos + a video) instead of a single thumbnail. Each
15
+ * item brings its own thumbnail and optional source so that
16
+ * `LockedAttachment.Composer` / `.Sent` / `.Received` can all share the
17
+ * same carousel chrome.
18
+ */
19
+ gallery?: LockedAttachmentGalleryItem[]
20
+ }
21
+
22
+ export interface LockedAttachmentGalleryItem {
23
+ /** MIME type of this carousel item — drives the per-item play / lock affordance. */
24
+ mimeType: string
25
+ /** Poster image used for both the locked (blurred) and unlocked preview state. */
26
+ thumbnailUrl?: string
27
+ /** Underlying source (image or video URL) — shown only when unlocked. */
28
+ sourceUrl?: string
12
29
  }
13
30
 
14
31
  export interface LockedAttachmentSource {
@@ -216,7 +216,7 @@ export function resolveMediaFromMessage(
216
216
  }
217
217
  }
218
218
 
219
- /** Dark card (sent / own message) — matches LockedAttachment.Creator preview without toggle. */
219
+ /** Dark card (sent / own message) — matches LockedAttachment.Sent preview without toggle. */
220
220
  const Creator: React.FC<MediaMessageResolved> = ({
221
221
  resolvedUrl,
222
222
  resolvedType,
@@ -246,7 +246,7 @@ const Creator: React.FC<MediaMessageResolved> = ({
246
246
  )
247
247
  }
248
248
 
249
- /** Light card (received) — matches LockedAttachment.Visitor unlocked without Purchased copy. */
249
+ /** Light card (received) — matches LockedAttachment.Received unlocked without Purchased copy. */
250
250
  const Visitor: React.FC<MediaMessageResolved> = ({
251
251
  resolvedUrl,
252
252
  resolvedType,
package/src/index.ts CHANGED
@@ -53,9 +53,14 @@ export type { MessageMetadata } from './stream-custom-data'
53
53
  export type { AvatarProps } from './components/Avatar'
54
54
  export type { ActionButtonProps } from './components/ActionButton'
55
55
  export type {
56
+ ComposerCardProps,
57
+ SentCardProps,
58
+ ReceivedCardProps,
59
+ LockedAttachmentGalleryItem,
60
+ LockedAttachmentContextValue,
61
+ // Deprecated aliases preserved for backwards compatibility:
56
62
  CreatorCardProps,
57
63
  VisitorCardProps,
58
- LockedAttachmentContextValue,
59
64
  } from './components/LockedAttachment'
60
65
  export type { CustomMessageRegistry } from './components/CustomMessage/context'
61
66
  export type { AttachmentSourceType } from './components/AttachmentCard/utils/mimeType'
package/src/styles.css CHANGED
@@ -1,6 +1,13 @@
1
1
  /* Stream Chat base styles */
2
2
  @import 'stream-chat-react/dist/css/v2/index.css';
3
3
 
4
+ /* Inherit the host's font instead of stream-chat-react's hardcoded system stack.
5
+ In admin (federation host) this resolves to Link Sans; on linktr.ee profile
6
+ pages it resolves to the creator's profile font. */
7
+ .str-chat {
8
+ --str-chat__font-family: inherit;
9
+ }
10
+
4
11
  /* Dialog component styles - used by messaging components */
5
12
  /* Note: Dialogs get moved to the top layer when opened with .showModal() */
6
13
  .mes-dialog {