@linktr.ee/messaging-react 1.40.2 → 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 (61) 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-B1h46F9x.js +3092 -0
  11. package/dist/index-B1h46F9x.js.map +1 -0
  12. package/dist/index.d.ts +109 -30
  13. package/dist/index.js +14 -12
  14. package/package.json +2 -2
  15. package/src/components/ChannelInfoDialog/ChannelInfoDialog.test.tsx +2 -14
  16. package/src/components/ChannelInfoDialog/index.tsx +4 -8
  17. package/src/components/ChannelList/ChannelListContext.tsx +2 -0
  18. package/src/components/ChannelList/CustomChannelPreview.tsx +14 -3
  19. package/src/components/ChannelList/index.tsx +9 -1
  20. package/src/components/ChannelView.test.tsx +11 -0
  21. package/src/components/ChannelView.tsx +44 -33
  22. package/src/components/CustomMessage/index.tsx +24 -7
  23. package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +57 -17
  24. package/src/components/CustomTypingIndicator/CustomTypingIndicator.test.tsx +187 -0
  25. package/src/components/CustomTypingIndicator/DmAgentContext.ts +3 -0
  26. package/src/components/CustomTypingIndicator/index.tsx +101 -37
  27. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +230 -89
  28. package/src/components/LockedAttachment/components/Composer/Card.tsx +221 -0
  29. package/src/components/LockedAttachment/components/Composer/index.ts +2 -0
  30. package/src/components/LockedAttachment/components/Received/Card.tsx +191 -0
  31. package/src/components/LockedAttachment/components/Received/CardActions.tsx +91 -0
  32. package/src/components/LockedAttachment/components/Received/index.ts +2 -0
  33. package/src/components/LockedAttachment/components/Sent/Card.tsx +177 -0
  34. package/src/components/LockedAttachment/components/Sent/index.ts +2 -0
  35. package/src/components/LockedAttachment/components/_shared/CardBody.tsx +94 -0
  36. package/src/components/LockedAttachment/components/_shared/GalleryThumbnail.tsx +178 -0
  37. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +39 -0
  38. package/src/components/LockedAttachment/components/_shared/LockedCardShell.tsx +36 -0
  39. package/src/components/LockedAttachment/components/_shared/LockedThumbnail.tsx +128 -0
  40. package/src/components/LockedAttachment/index.tsx +43 -12
  41. package/src/components/LockedAttachment/types.ts +17 -0
  42. package/src/components/MediaMessage/index.tsx +2 -2
  43. package/src/components/MessagingShell/index.tsx +4 -4
  44. package/src/index.ts +18 -2
  45. package/src/stories/mocks.tsx +2 -9
  46. package/src/styles.css +7 -0
  47. package/src/types.ts +11 -1
  48. package/src/utils/getMessageDisplayText.test.ts +44 -0
  49. package/src/utils/getMessageDisplayText.ts +27 -0
  50. package/dist/Card-A0lkei-S.js +0 -138
  51. package/dist/Card-A0lkei-S.js.map +0 -1
  52. package/dist/Card-DXoAKkv0.js +0 -127
  53. package/dist/Card-DXoAKkv0.js.map +0 -1
  54. package/dist/index-B_PLgcDi.js +0 -2994
  55. package/dist/index-B_PLgcDi.js.map +0 -1
  56. package/src/components/LockedAttachment/components/Creator/Card.tsx +0 -210
  57. package/src/components/LockedAttachment/components/Creator/index.tsx +0 -2
  58. package/src/components/LockedAttachment/components/Visitor/Card.tsx +0 -155
  59. package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +0 -62
  60. package/src/components/LockedAttachment/components/Visitor/LockBadge.tsx +0 -12
  61. package/src/components/LockedAttachment/components/Visitor/index.ts +0 -2
@@ -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,
@@ -35,6 +35,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
35
35
  onMessageSent,
36
36
  showStarButton = false,
37
37
  chatbotVotingEnabled = false,
38
+ viewerLanguage,
38
39
  renderMessagePreview,
39
40
  renderChannelBanner,
40
41
  customProfileContent,
@@ -60,9 +61,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
60
61
  string | null
61
62
  >(null)
62
63
 
63
- const {
64
- showDeleteConversation = true,
65
- } = capabilities
64
+ const { showDeleteConversation = true } = capabilities
66
65
 
67
66
  // Create default filters and merge with provided filters
68
67
  const channelFilters = React.useMemo(() => {
@@ -194,7 +193,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
194
193
  const channel = await service.startChannelWithParticipant({
195
194
  id: initialParticipantData.id,
196
195
  name: initialParticipantData.name,
197
- email: initialParticipantData.email,
198
196
  phone: initialParticipantData.phone,
199
197
  })
200
198
 
@@ -375,6 +373,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
375
373
  channelRenderFilterFn={channelRenderFilterFn}
376
374
  customEmptyStateIndicator={channelListCustomEmptyStateIndicator}
377
375
  renderMessagePreview={renderMessagePreview}
376
+ viewerLanguage={viewerLanguage}
378
377
  />
379
378
  </div>
380
379
 
@@ -418,6 +417,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
418
417
  onMessageSent={onMessageSent}
419
418
  showStarButton={showStarButton}
420
419
  chatbotVotingEnabled={chatbotVotingEnabled}
420
+ viewerLanguage={viewerLanguage}
421
421
  customProfileContent={customProfileContent}
422
422
  customChannelActions={customChannelActions}
423
423
  renderMessage={renderMessage}
package/src/index.ts CHANGED
@@ -18,7 +18,10 @@ export {
18
18
  resolveLinkAttachment,
19
19
  resolveMediaFromMessage,
20
20
  } from './components/MediaMessage'
21
- export type { MediaMessageProps, MediaMessageResolved } from './components/MediaMessage'
21
+ export type {
22
+ MediaMessageProps,
23
+ MediaMessageResolved,
24
+ } from './components/MediaMessage'
22
25
 
23
26
  // Providers
24
27
  export { MessagingProvider } from './providers/MessagingProvider'
@@ -31,6 +34,10 @@ export { useCustomMessage } from './components/CustomMessage/context'
31
34
 
32
35
  // Utils
33
36
  export { formatRelativeTime } from './utils/formatRelativeTime'
37
+ export {
38
+ getMessageDisplayText,
39
+ normalizeLanguageCode,
40
+ } from './utils/getMessageDisplayText'
34
41
 
35
42
  // Types
36
43
  export type {
@@ -45,7 +52,16 @@ export type {
45
52
  export type { MessageMetadata } from './stream-custom-data'
46
53
  export type { AvatarProps } from './components/Avatar'
47
54
  export type { ActionButtonProps } from './components/ActionButton'
48
- export type { CreatorCardProps, VisitorCardProps, LockedAttachmentContextValue } from './components/LockedAttachment'
55
+ export type {
56
+ ComposerCardProps,
57
+ SentCardProps,
58
+ ReceivedCardProps,
59
+ LockedAttachmentGalleryItem,
60
+ LockedAttachmentContextValue,
61
+ // Deprecated aliases preserved for backwards compatibility:
62
+ CreatorCardProps,
63
+ VisitorCardProps,
64
+ } from './components/LockedAttachment'
49
65
  export type { CustomMessageRegistry } from './components/CustomMessage/context'
50
66
  export type { AttachmentSourceType } from './components/AttachmentCard/utils/mimeType'
51
67
  export type { Faq, FaqListProps } from './components/FaqList'
@@ -110,31 +110,26 @@ export const mockParticipants = [
110
110
  {
111
111
  id: 'participant-1',
112
112
  name: 'Alice Johnson',
113
- email: 'alice@example.com',
114
113
  image: 'https://i.pravatar.cc/150?img=2',
115
114
  },
116
115
  {
117
116
  id: 'participant-2',
118
117
  name: 'Bob Smith',
119
- email: 'bob@example.com',
120
118
  image: 'https://i.pravatar.cc/150?img=3',
121
119
  },
122
120
  {
123
121
  id: 'participant-3',
124
122
  name: 'Carol Williams',
125
- email: 'carol@example.com',
126
123
  image: 'https://i.pravatar.cc/150?img=4',
127
124
  },
128
125
  {
129
126
  id: 'participant-4',
130
127
  name: 'David Brown',
131
- email: 'david@example.com',
132
128
  image: 'https://i.pravatar.cc/150?img=5',
133
129
  },
134
130
  {
135
131
  id: 'participant-5',
136
132
  name: 'Emma Davis',
137
- email: 'emma@example.com',
138
133
  image: 'https://i.pravatar.cc/150?img=6',
139
134
  },
140
135
  ]
@@ -143,10 +138,8 @@ export const mockParticipants = [
143
138
  export const mockParticipantSource = {
144
139
  loadParticipants: async (options?: { search?: string; limit?: number }) => {
145
140
  const searchTerm = options?.search || ''
146
- const filtered = mockParticipants.filter(
147
- (p) =>
148
- p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
149
- p.email.toLowerCase().includes(searchTerm.toLowerCase())
141
+ const filtered = mockParticipants.filter((p) =>
142
+ p.name.toLowerCase().includes(searchTerm.toLowerCase())
150
143
  )
151
144
  return {
152
145
  participants: filtered,
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 {
package/src/types.ts CHANGED
@@ -25,7 +25,6 @@ export type { LockedAttachmentSource } from './components/LockedAttachment'
25
25
  export interface Participant {
26
26
  id: string
27
27
  name: string
28
- email?: string
29
28
  image?: string
30
29
  username?: string
31
30
  phone?: string
@@ -95,6 +94,11 @@ export interface ChannelListProps {
95
94
  message: LocalMessage | undefined,
96
95
  defaultPreview?: string
97
96
  ) => React.ReactNode
97
+ /**
98
+ * Language code used to pick translated message text from Stream Chat i18n.
99
+ * Falls back to message.text when no matching translation exists.
100
+ */
101
+ viewerLanguage?: string
98
102
  }
99
103
 
100
104
  /**
@@ -173,6 +177,11 @@ export interface ChannelViewProps {
173
177
  * Defaults to false.
174
178
  */
175
179
  chatbotVotingEnabled?: boolean
180
+ /**
181
+ * Language code used to pick translated message text from Stream Chat i18n.
182
+ * Falls back to message.text when no matching translation exists.
183
+ */
184
+ viewerLanguage?: string
176
185
 
177
186
  /**
178
187
  * Custom render function for a banner/card component that renders
@@ -244,6 +253,7 @@ export type ChannelViewPassthroughProps = Pick<
244
253
  | 'onMessageSent'
245
254
  | 'showStarButton'
246
255
  | 'chatbotVotingEnabled'
256
+ | 'viewerLanguage'
247
257
  | 'renderChannelBanner'
248
258
  | 'customProfileContent'
249
259
  | 'customChannelActions'