@linktr.ee/messaging-react 3.9.0-rc-1783472582 → 3.9.0-rc-1783575293

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 (85) hide show
  1. package/dist/Card-B7ccxsKf.cjs +2 -0
  2. package/dist/Card-B7ccxsKf.cjs.map +1 -0
  3. package/dist/Card-CXRSSrCh.cjs +2 -0
  4. package/dist/Card-CXRSSrCh.cjs.map +1 -0
  5. package/dist/Card-DaUKmwtb.js +105 -0
  6. package/dist/Card-DaUKmwtb.js.map +1 -0
  7. package/dist/Card-Q1Ee_UCB.js +693 -0
  8. package/dist/Card-Q1Ee_UCB.js.map +1 -0
  9. package/dist/{LoadingDots-nW6r0-Kg.js → LoadingDots-Ct3WmVlT.js} +18 -21
  10. package/dist/LoadingDots-Ct3WmVlT.js.map +1 -0
  11. package/dist/LoadingDots-D9GGphgM.cjs +2 -0
  12. package/dist/LoadingDots-D9GGphgM.cjs.map +1 -0
  13. package/dist/assets/index.css +1 -1
  14. package/dist/{index-B8gqCioX.js → index-BiBvPADG.js} +1301 -1315
  15. package/dist/index-BiBvPADG.js.map +1 -0
  16. package/dist/index-DuPa2mUh.cjs +2 -0
  17. package/dist/index-DuPa2mUh.cjs.map +1 -0
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.ts +149 -129
  20. package/dist/index.js +1 -1
  21. package/package.json +2 -2
  22. package/src/components/ChannelList/CustomChannelPreview.test.tsx +95 -0
  23. package/src/components/ChannelList/CustomChannelPreview.tsx +21 -9
  24. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/LockedAttachment.stories.tsx +87 -78
  25. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/LockedAttachment.test.tsx +24 -11
  26. package/src/components/CustomMessage/LockedAttachment/components/Media/Card.tsx +358 -0
  27. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/Media/CardActions.tsx +5 -0
  28. package/src/components/CustomMessage/LockedAttachment/components/Media/types.ts +79 -0
  29. package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +155 -0
  30. package/src/components/CustomMessage/LockedAttachment/components/Text/types.ts +58 -0
  31. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/CardBody.tsx +5 -4
  32. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/GalleryThumbnail.tsx +2 -2
  33. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/LockBadge.tsx +1 -1
  34. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/PurchaseStatusBadge.tsx +0 -12
  35. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/SingleThumbnail.tsx +1 -1
  36. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/SkeletonTextLines.tsx +3 -13
  37. package/src/components/CustomMessage/LockedAttachment/components/_shared/TextShell.tsx +30 -0
  38. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/usePreviewToggle.test.ts +1 -1
  39. package/src/components/CustomMessage/LockedAttachment/index.tsx +105 -0
  40. package/src/components/CustomMessage/LockedAttachment/types.ts +44 -0
  41. package/src/components/CustomMessage/context.tsx +1 -1
  42. package/src/components/CustomMessage/index.tsx +45 -50
  43. package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +5 -3
  44. package/src/index.ts +3 -5
  45. package/src/styles.css +8 -1
  46. package/src/types.ts +1 -1
  47. package/dist/Card-BVZM0-06.js +0 -108
  48. package/dist/Card-BVZM0-06.js.map +0 -1
  49. package/dist/Card-D0hLrOxX.js +0 -221
  50. package/dist/Card-D0hLrOxX.js.map +0 -1
  51. package/dist/Card-DIr1UnfC.cjs +0 -2
  52. package/dist/Card-DIr1UnfC.cjs.map +0 -1
  53. package/dist/Card-DeSgaRTL.cjs +0 -2
  54. package/dist/Card-DeSgaRTL.cjs.map +0 -1
  55. package/dist/Card-DnaXMVrT.cjs +0 -2
  56. package/dist/Card-DnaXMVrT.cjs.map +0 -1
  57. package/dist/Card-xv52Ug43.js +0 -61
  58. package/dist/Card-xv52Ug43.js.map +0 -1
  59. package/dist/LoadingDots-Cn2y7g7L.cjs +0 -2
  60. package/dist/LoadingDots-Cn2y7g7L.cjs.map +0 -1
  61. package/dist/LoadingDots-nW6r0-Kg.js.map +0 -1
  62. package/dist/TextShell-CKQOFxoH.cjs +0 -2
  63. package/dist/TextShell-CKQOFxoH.cjs.map +0 -1
  64. package/dist/TextShell-DIyj9zQ7.js +0 -40
  65. package/dist/TextShell-DIyj9zQ7.js.map +0 -1
  66. package/dist/index-B8gqCioX.js.map +0 -1
  67. package/dist/index-IkikzyWM.cjs +0 -2
  68. package/dist/index-IkikzyWM.cjs.map +0 -1
  69. package/dist/usePreviewToggle-B0B8cfxy.cjs +0 -2
  70. package/dist/usePreviewToggle-B0B8cfxy.cjs.map +0 -1
  71. package/dist/usePreviewToggle-DRhRTUou.js +0 -357
  72. package/dist/usePreviewToggle-DRhRTUou.js.map +0 -1
  73. package/src/components/LockedAttachment/components/Composer/Card.tsx +0 -201
  74. package/src/components/LockedAttachment/components/Media/Card.tsx +0 -235
  75. package/src/components/LockedAttachment/components/Text/Card.tsx +0 -177
  76. package/src/components/LockedAttachment/components/_shared/TextShell.tsx +0 -19
  77. package/src/components/LockedAttachment/index.tsx +0 -52
  78. package/src/components/LockedAttachment/types.ts +0 -75
  79. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/CardThumbnail.tsx +0 -0
  80. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/LoadingDots.tsx +0 -0
  81. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/MediaShell.tsx +0 -0
  82. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/PreviewToggle.tsx +0 -0
  83. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/PriceLockRow.tsx +0 -0
  84. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/usePreviewToggle.ts +0 -0
  85. /package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/useReceivedSource.ts +0 -0
@@ -2,7 +2,7 @@ import { CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react'
2
2
  import classNames from 'classnames'
3
3
  import React, { useState } from 'react'
4
4
 
5
- import { AttachmentThumbnail, getSourceType } from '../../../AttachmentCard'
5
+ import { AttachmentThumbnail, getSourceType } from '../../../../AttachmentCard'
6
6
  import type {
7
7
  LockedAttachmentGalleryItem,
8
8
  PaymentStatus,
@@ -48,7 +48,7 @@ const GalleryThumbnail: React.FC<GalleryThumbnailProps> = ({
48
48
  const isVideoItem = isPlayableItem(current)
49
49
 
50
50
  // Stop propagation so arrow taps don't bubble up to the outer preview-toggle
51
- // wrapper on ComposerCard / OwnMediaCard.
51
+ // wrapper on PreviewMediaCard / OwnMediaCard.
52
52
  const goPrev = (e: React.MouseEvent) => {
53
53
  e.stopPropagation()
54
54
  setIndex((i) => Math.max(0, i - 1))
@@ -2,7 +2,7 @@ import { LockOpenIcon, LockSimpleIcon, PlayIcon } from '@phosphor-icons/react'
2
2
  import classNames from 'classnames'
3
3
  import React from 'react'
4
4
 
5
- import { getSourceType } from '../../../AttachmentCard'
5
+ import { getSourceType } from '../../../../AttachmentCard'
6
6
  import type { PaymentStatus } from '../../types'
7
7
 
8
8
  export interface LockBadgeProps {
@@ -54,18 +54,6 @@ const PurchaseStatusBadge: React.FC<PurchaseStatusBadgeProps> = ({
54
54
  )
55
55
  }
56
56
 
57
- if (paymentStatus === 'pending') {
58
- return (
59
- <React.Fragment>
60
- <span className={classNames('text-xs font-medium', labelColour)}>
61
- &bull;
62
- </span>
63
- <span className={classNames('text-xs font-medium', labelColour)}>
64
- Sent
65
- </span>
66
- </React.Fragment>
67
- )
68
- }
69
57
 
70
58
  if (paymentStatus === 'failed' || paymentStatus === 'refunded') {
71
59
  return (
@@ -4,7 +4,7 @@ import React from 'react'
4
4
  import {
5
5
  AttachmentThumbnail,
6
6
  renderTypeIcon,
7
- } from '../../../AttachmentCard'
7
+ } from '../../../../AttachmentCard'
8
8
  import type { LockedAttachmentSource, PaymentStatus } from '../../types'
9
9
 
10
10
  import { LockBadge } from './LockBadge'
@@ -4,12 +4,6 @@ import React from 'react'
4
4
  export interface SkeletonTextLinesProps {
5
5
  /** Matches the bubble it sits in — "dark" for the sender's own bubble. */
6
6
  variant: 'light' | 'dark'
7
- /**
8
- * Whether the bars pulse. Pass `false` once the outcome is settled (paid,
9
- * failed, refunded) — a static skeleton reads as "nothing more will
10
- * change", while a pulsing one implies something is still in flight.
11
- */
12
- animate: boolean
13
7
  }
14
8
 
15
9
  /**
@@ -21,17 +15,13 @@ export interface SkeletonTextLinesProps {
21
15
  * support opacity modifiers on the `current` color — `bg-current/30` silently
22
16
  * produces no CSS rule at all.
23
17
  */
24
- const SkeletonTextLines: React.FC<SkeletonTextLinesProps> = ({
25
- variant = 'dark',
26
- animate = true,
27
- }) => {
28
- const pulse = animate ? 'animate-pulse motion-reduce:animate-none' : ''
18
+ const SkeletonTextLines: React.FC<SkeletonTextLinesProps> = ({ variant = 'dark' }) => {
29
19
  const colour = variant === 'dark' ? 'bg-white/25' : 'bg-black/15'
30
20
 
31
21
  return (
32
22
  <div className="flex flex-col gap-2 str-chat__message-text my-1" aria-hidden>
33
- <div className={classNames('h-3 w-40 rounded-full', pulse, colour)} />
34
- <div className={classNames('h-3 w-24 rounded-full', pulse, colour)} />
23
+ <div className={classNames('h-3 w-40 rounded-full', colour)} />
24
+ <div className={classNames('h-3 w-24 rounded-full', colour)} />
35
25
  </div>
36
26
  )
37
27
  }
@@ -0,0 +1,30 @@
1
+ import classNames from 'classnames'
2
+ import React from 'react'
3
+
4
+ /**
5
+ * Chat-bubble chrome for a paid text-only message/preview. Deliberately
6
+ * doesn't lean on `.str-chat`'s CSS variables (`--str-chat__message-bubble-*`)
7
+ * — this renders in the Composer preview too, which sits outside any
8
+ * `.str-chat` ancestor, so every value here (radius, colour, type scale) is
9
+ * hardcoded to match the Figma spec directly.
10
+ */
11
+ const TextShell: React.FC<
12
+ React.PropsWithChildren<{ isMine: boolean; className?: string }>
13
+ > = ({ isMine, children, className }) => (
14
+ <div
15
+ className={classNames(
16
+ 'message-locked-attachment-text',
17
+ 'relative flex min-w-[225px] max-w-[280px] flex-col gap-1 rounded-[1.5rem] px-4 py-3',
18
+ 'text-[0.875rem] leading-[1.3125rem] tracking-[0.14px]',
19
+ className
20
+ )}
21
+ style={{
22
+ backgroundColor: isMine ? '#1e2330' : '#f2f1ef',
23
+ color: isMine ? '#fff' : '#000',
24
+ }}
25
+ >
26
+ {children}
27
+ </div>
28
+ )
29
+
30
+ export default TextShell
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
2
 
3
- import { act, renderHook } from '../../../../test/utils'
3
+ import { act, renderHook } from '../../../../../test/utils'
4
4
 
5
5
  import { usePreviewToggle } from './usePreviewToggle'
6
6
 
@@ -0,0 +1,105 @@
1
+ import React, { Suspense } from 'react'
2
+
3
+ import type {
4
+ MediaCardDraftProps,
5
+ MediaCardOtherProps,
6
+ MediaCardOwnProps,
7
+ MediaCardPreviewProps,
8
+ } from './components/Media/types'
9
+ import type {
10
+ TextCardOtherProps,
11
+ TextCardOwnProps,
12
+ } from './components/Text/types'
13
+
14
+ const TextCardLazy = React.lazy(() => import('./components/Text/Card'))
15
+ const MediaCardLazy = React.lazy(() => import('./components/Media/Card'))
16
+
17
+ const LockedAttachmentFallback = () => (
18
+ <div
19
+ className="w-[280px] min-h-[200px] 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)]"
20
+ aria-hidden
21
+ />
22
+ )
23
+
24
+ export type DraftProps = MediaCardDraftProps & {
25
+ /** Drafting is always the sender — doesn't apply here. */
26
+ isMine?: never
27
+ /**
28
+ * "text" renders as a chat bubble showing the real draft `text` instead of
29
+ * a media thumbnail card. Media-only fields below are ignored in text mode.
30
+ * Defaults to `"media"`.
31
+ */
32
+ contentType?: 'text' | 'media'
33
+ /** The message's own text, shown as-is (not redacted yet). Only used when `contentType` is `"text"`. */
34
+ renderedText?: React.ReactNode
35
+ }
36
+
37
+ export type PreviewProps = MediaCardPreviewProps & {
38
+ /** Drafting is always the sender — doesn't apply here. */
39
+ isMine?: never
40
+ /**
41
+ * "text" renders as a chat bubble instead of a media thumbnail card.
42
+ * Media-only fields below are ignored in text mode. Defaults to `"media"`.
43
+ */
44
+ contentType?: 'text' | 'media'
45
+ }
46
+
47
+ export type SentTextOwnProps = TextCardOwnProps & {
48
+ contentType: 'text'
49
+ /** Sender's view has no unlock flow — doesn't apply here. */
50
+ onUnlockClick?: never
51
+ isUnlocking?: never
52
+ }
53
+
54
+ export type SentTextOtherProps = TextCardOtherProps & {
55
+ contentType: 'text'
56
+ }
57
+
58
+ export type SentMediaOwnProps = MediaCardOwnProps & {
59
+ contentType?: 'media'
60
+ /** Sender's view has no unlock/download flow — doesn't apply here. */
61
+ onUnlockClick?: never
62
+ isUnlocking?: never
63
+ onDownloadClick?: never
64
+ }
65
+
66
+ export type SentMediaOtherProps = MediaCardOtherProps & {
67
+ contentType?: 'media'
68
+ }
69
+
70
+ /**
71
+ * The card for a paid attachment — a still-drafting `isDraft` card, a static
72
+ * receiver-facing `isPreview` mockup, or a sent text/media card discriminated
73
+ * by `isMine` + `contentType`. Internally lazy-loads only the one underlying
74
+ * implementation the resolved branch needs.
75
+ */
76
+ export type LockedAttachmentProps =
77
+ | DraftProps
78
+ | PreviewProps
79
+ | SentTextOwnProps
80
+ | SentTextOtherProps
81
+ | SentMediaOwnProps
82
+ | SentMediaOtherProps
83
+
84
+ const LockedAttachment: React.FC<LockedAttachmentProps> = (props) => {
85
+ return (
86
+ <Suspense fallback={<LockedAttachmentFallback />}>
87
+ {props.contentType === 'text' ? (
88
+ <TextCardLazy {...props} />
89
+ ) : (
90
+ <MediaCardLazy {...props} />
91
+ )}
92
+ </Suspense>
93
+ )
94
+ }
95
+
96
+ export default LockedAttachment
97
+ export { default as PurchaseStatusBadge } from './components/_shared/PurchaseStatusBadge'
98
+ export type { PurchaseStatusBadgeProps } from './components/_shared/PurchaseStatusBadge'
99
+ export type {
100
+ PaymentStatus,
101
+ FetchSourceHandler,
102
+ LockedAttachmentSource,
103
+ LockedAttachmentGalleryItem,
104
+ LockedAttachmentContextValue,
105
+ } from './types'
@@ -0,0 +1,44 @@
1
+ import type { Channel, LocalMessage } from 'stream-chat'
2
+
3
+ import type { PaymentStatus } from '../../../stream-custom-data'
4
+
5
+ export interface LockedAttachmentGalleryItem {
6
+ /** Drives the per-item play/lock affordance. */
7
+ mimeType: string
8
+ /** Poster image, locked or unlocked. */
9
+ thumbnailUrl?: string
10
+ /** Media source, shown only when unlocked. */
11
+ sourceUrl?: string
12
+ }
13
+
14
+ export interface LockedAttachmentSource {
15
+ /** Proxied URL for in-app playback. */
16
+ sourceUrl: string
17
+ /** Opened when the visitor clicks Download. */
18
+ redeemUrl?: string
19
+ /** Overrides the metadata thumbnail when present. */
20
+ thumbnailUrl?: string
21
+ }
22
+
23
+ export type { PaymentStatus }
24
+
25
+ /** Fetches the underlying source; `void` return means none available. */
26
+ export type FetchSourceHandler = () => Promise<LockedAttachmentSource | void>
27
+
28
+ export interface LockedAttachmentContextValue {
29
+ /** Whether the given message is mid-checkout. */
30
+ isUnlocking: (id: string) => boolean
31
+ /** Opens the checkout flow for a message. */
32
+ onUnlockClick?: (message: LocalMessage, channel: Channel) => void
33
+ /** Handles a Download click for a message. */
34
+ onDownloadClick?: (message: LocalMessage, channel: Channel) => void
35
+ /** Reveals the locked media's source. Unused for text — payment writes `message.text` directly. */
36
+ onFetchSource?: (
37
+ message: LocalMessage,
38
+ channel: Channel
39
+ ) => Promise<LockedAttachmentSource | void>
40
+ }
41
+
42
+ export const defaultLockedAttachmentContextValue: LockedAttachmentContextValue = {
43
+ isUnlocking: () => false,
44
+ }
@@ -1,6 +1,6 @@
1
1
  import { createContext, useContext } from 'react'
2
2
 
3
- import { defaultLockedAttachmentContextValue, type LockedAttachmentContextValue } from '../LockedAttachment/types'
3
+ import { defaultLockedAttachmentContextValue, type LockedAttachmentContextValue } from './LockedAttachment/types'
4
4
 
5
5
  export interface CustomMessageRegistry {
6
6
  LockedAttachment: LockedAttachmentContextValue
@@ -32,11 +32,11 @@ import {
32
32
  import { useMessageVote } from '../../hooks/useMessageVote'
33
33
  import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
34
34
  import { Avatar } from '../Avatar'
35
- import LockedAttachment from '../LockedAttachment'
36
35
  import { isLinkAttachment } from '../MediaMessage'
37
36
  import { bubbleGroupPositionFromStream as messageAttachmentGroupPositionFromStream } from '../MessageAttachment'
38
37
 
39
38
  import { useCustomMessage } from './context'
39
+ import LockedAttachment from './LockedAttachment'
40
40
  import {
41
41
  MessageTag,
42
42
  isAttachmentMessage,
@@ -145,13 +145,12 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
145
145
  (total, segment) => total + segment.attachments.length,
146
146
  0
147
147
  ) === (finalAttachments?.length ?? 0)
148
- const streamAttachmentGroupPosition = messageAttachmentGroupPositionFromStream(
149
- {
148
+ const streamAttachmentGroupPosition =
149
+ messageAttachmentGroupPositionFromStream({
150
150
  endOfGroup,
151
151
  firstOfGroup,
152
152
  groupedByUser,
153
- }
154
- )
153
+ })
155
154
 
156
155
  if (isDateSeparatorMessage(message)) {
157
156
  return null
@@ -226,6 +225,26 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
226
225
  !isAIGenerated &&
227
226
  !isChatbot
228
227
 
228
+ const sharedLockedTextProps = {
229
+ contentType: 'text' as const,
230
+ amountText: message.metadata?.amount_text,
231
+ paymentStatus: message.metadata?.payment_status,
232
+ renderedText: message.text && (
233
+ <MessageText message={displayMessage} renderText={renderText} />
234
+ ),
235
+ }
236
+ const sharedLockedMediaProps = {
237
+ contentType: 'media' as const,
238
+ title: message.metadata?.attachment_title,
239
+ mimeType: message.metadata?.attachment_mime_type,
240
+ thumbnailUrl: message.metadata?.attachment_thumbnail,
241
+ amountText: message.metadata?.amount_text,
242
+ detail: message.metadata?.attachment_detail,
243
+ paymentStatus: message.metadata?.payment_status,
244
+ onFetchSource: onFetchSource
245
+ ? () => onFetchSource(message, channel)
246
+ : undefined,
247
+ }
229
248
  return (
230
249
  <>
231
250
  {editing && (
@@ -283,33 +302,17 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
283
302
  <div className="flex items-center gap-2">
284
303
  {isMine && MessageActions && <MessageActions />}
285
304
  {isMine ? (
286
- <LockedAttachment.Text
305
+ <LockedAttachment
306
+ {...sharedLockedTextProps}
287
307
  isMine={true}
288
- amountText={message.metadata?.amount_text}
289
- paymentStatus={message.metadata?.payment_status}
290
- >
291
- {message.text && (
292
- <MessageText
293
- message={displayMessage}
294
- renderText={renderText}
295
- />
296
- )}
297
- </LockedAttachment.Text>
308
+ />
298
309
  ) : (
299
- <LockedAttachment.Text
310
+ <LockedAttachment
311
+ {...sharedLockedTextProps}
300
312
  isMine={false}
301
- amountText={message.metadata?.amount_text}
302
- paymentStatus={message.metadata?.payment_status}
303
313
  isUnlocking={isUnlocking(message.id)}
304
314
  onUnlockClick={() => onUnlockClick?.(message, channel)}
305
- >
306
- {message.text && (
307
- <MessageText
308
- message={displayMessage}
309
- renderText={renderText}
310
- />
311
- )}
312
- </LockedAttachment.Text>
315
+ />
313
316
  )}
314
317
  {!isMine && MessageActions && <MessageActions />}
315
318
  </div>
@@ -319,38 +322,30 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
319
322
  <div className="flex items-center gap-2">
320
323
  {isMine && MessageActions && <MessageActions />}
321
324
  {isMine ? (
322
- <LockedAttachment.Media
325
+ <LockedAttachment
326
+ {...sharedLockedMediaProps}
323
327
  isMine={true}
324
- title={message.metadata?.attachment_title}
325
- mimeType={message.metadata?.attachment_mime_type}
326
- thumbnailUrl={message.metadata?.attachment_thumbnail}
327
- amountText={message.metadata?.amount_text}
328
- detail={message.metadata?.attachment_detail}
329
- paymentStatus={message.metadata?.payment_status}
330
- onPreviewClick={() => onUnlockClick?.(message, channel)}
331
- onFetchSource={
332
- onFetchSource
333
- ? () => onFetchSource(message, channel)
328
+ onPreviewClick={() =>
329
+ onUnlockClick
330
+ ? onUnlockClick(message, channel)
334
331
  : undefined
335
332
  }
336
333
  />
337
334
  ) : (
338
- <LockedAttachment.Media
335
+ <LockedAttachment
336
+ {...sharedLockedMediaProps}
339
337
  isMine={false}
340
- title={message.metadata?.attachment_title}
341
- mimeType={message.metadata?.attachment_mime_type}
342
- thumbnailUrl={message.metadata?.attachment_thumbnail}
343
- amountText={message.metadata?.amount_text}
344
- detail={message.metadata?.attachment_detail}
345
- paymentStatus={message.metadata?.payment_status}
346
338
  isUnlocking={isUnlocking(message.id)}
347
- onUnlockClick={() => onUnlockClick?.(message, channel)}
348
- onFetchSource={
349
- onFetchSource
350
- ? () => onFetchSource(message, channel)
339
+ onUnlockClick={() =>
340
+ onUnlockClick
341
+ ? onUnlockClick(message, channel)
342
+ : undefined
343
+ }
344
+ onDownloadClick={() =>
345
+ onDownloadClick
346
+ ? onDownloadClick(message, channel)
351
347
  : undefined
352
348
  }
353
- onDownloadClick={() => onDownloadClick?.(message, channel)}
354
349
  />
355
350
  )}
356
351
  {!isMine && MessageActions && <MessageActions />}
@@ -5,7 +5,7 @@ import { QueryChannelAPIResponse, StreamChat } from 'stream-chat'
5
5
  import { Channel, Chat, type SendButtonProps } from 'stream-chat-react'
6
6
 
7
7
  import { createMockStreamChatClient } from '../../testing/createMockStreamChatClient'
8
- import LockedAttachment from '../LockedAttachment'
8
+ import LockedAttachment from '../CustomMessage/LockedAttachment'
9
9
 
10
10
  import { CustomMessageInput } from '.'
11
11
 
@@ -229,7 +229,9 @@ WithAttachments.parameters = {
229
229
  export const WithPaidAttachment: StoryFn<WrapperProps> = (args) => <Wrapper {...args} />
230
230
  WithPaidAttachment.args = {
231
231
  attachmentPreviewList: () => (
232
- <LockedAttachment.Composer
232
+ <LockedAttachment
233
+ isPreview
234
+ contentType="media"
233
235
  title="Exclusive Content"
234
236
  mimeType="image/jpeg"
235
237
  thumbnailUrl="https://picsum.photos/seed/paid/320/180"
@@ -266,7 +268,7 @@ WithPaidAttachment.parameters = {
266
268
  docs: {
267
269
  description: {
268
270
  story:
269
- "Message input with a paid attachment staged inside the input bubble. Uses `Channel`'s `AttachmentPreviewList` override to render a `LockedAttachment.Composer` card at the top of the input. `onDismiss` removes it; `onEditClick` reopens the paid content wizard.",
271
+ "Message input with a paid attachment staged inside the input bubble. Uses `Channel`'s `AttachmentPreviewList` override to render a `LockedAttachment isPreview` card at the top of the input. `onDismiss` removes it; `onEditClick` reopens the paid content wizard.",
270
272
  },
271
273
  },
272
274
  }
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ export { MessagingShell } from './components/MessagingShell'
6
6
  export { ChannelList } from './components/ChannelList'
7
7
  export { ChannelView } from './components/ChannelView'
8
8
  export { default as ActionButton } from './components/ActionButton'
9
- export { default as LockedAttachment } from './components/LockedAttachment'
9
+ export { default as LockedAttachment } from './components/CustomMessage/LockedAttachment'
10
10
  export { default as LinkAttachment } from './components/LinkAttachment'
11
11
  export { default as MessageAttachment } from './components/MessageAttachment'
12
12
  export { Avatar } from './components/Avatar'
@@ -69,12 +69,10 @@ export type {
69
69
  export type { AvatarProps } from './components/Avatar'
70
70
  export type { ActionButtonProps } from './components/ActionButton'
71
71
  export type {
72
- ComposerCardProps,
73
- TextCardProps as LockedAttachmentTextCardProps,
74
- MediaCardProps as LockedAttachmentMediaCardProps,
72
+ LockedAttachmentProps,
75
73
  LockedAttachmentGalleryItem,
76
74
  LockedAttachmentContextValue,
77
- } from './components/LockedAttachment'
75
+ } from './components/CustomMessage/LockedAttachment'
78
76
  export type {
79
77
  ComposerCardProps as LinkAttachmentComposerCardProps,
80
78
  SentCardProps as LinkAttachmentSentCardProps,
package/src/styles.css CHANGED
@@ -832,7 +832,14 @@
832
832
  background-color: #dbf0e0;
833
833
  }
834
834
 
835
- /** Remove padding from the text inside the locked attachment text shell */
835
+ /** Remove padding from the text inside the locked attachment text shell,
836
+ and inherit TextShell's explicit colour — stream-chat-react's own
837
+ `MessageText` sets its own (black) colour that only gets overridden by
838
+ `.str-chat`'s CSS vars, which the Composer preview doesn't have. */
836
839
  .message-locked-attachment-text .str-chat__message-text {
837
840
  padding: 0rem !important;
841
+ }
842
+
843
+ .message-locked-attachment-text .str-chat__message-text-inner {
844
+ color: inherit;
838
845
  }
package/src/types.ts CHANGED
@@ -20,7 +20,7 @@ import type {
20
20
 
21
21
  import type { MessageMetadata } from './stream-custom-data'
22
22
 
23
- export type { LockedAttachmentSource } from './components/LockedAttachment'
23
+ export type { LockedAttachmentSource } from './components/CustomMessage/LockedAttachment'
24
24
 
25
25
  /**
26
26
  * Callback invoked when a user clicks a link inside the channel view.
@@ -1,108 +0,0 @@
1
- import { jsx as e, jsxs as r } from "react/jsx-runtime";
2
- import { XIcon as m, PencilSimpleIcon as z } from "@phosphor-icons/react";
3
- import B from "react";
4
- import { i as T, u as j, M as S, P as L, C as M, a as I, g as R } from "./usePreviewToggle-DRhRTUou.js";
5
- import { T as A, S as G, P as V } from "./TextShell-DIyj9zQ7.js";
6
- const F = ({
7
- text: t,
8
- amountText: i,
9
- placeholderAmountText: s,
10
- onDismiss: a
11
- }) => /* @__PURE__ */ r(A, { isMine: !0, children: [
12
- /* @__PURE__ */ e("div", { className: "pr-8", children: t || /* @__PURE__ */ e(G, { variant: "dark", animate: !1 }) }),
13
- /* @__PURE__ */ e(V, { children: /* @__PURE__ */ e("span", { className: i ? "text-white/55" : "text-white/30", children: i || s }) }),
14
- a && /* @__PURE__ */ e(
15
- "button",
16
- {
17
- type: "button",
18
- onClick: a,
19
- className: "absolute right-3 top-3 flex size-6 shrink-0 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15",
20
- "aria-label": "Dismiss attachment",
21
- children: /* @__PURE__ */ e(m, { className: "size-3", weight: "bold" })
22
- }
23
- )
24
- ] }), X = ({
25
- title: t,
26
- mimeType: i = "application/octet-stream",
27
- thumbnailUrl: s,
28
- detail: a,
29
- amountText: n,
30
- placeholderTitle: u = "Attachment title",
31
- placeholderAmountText: g,
32
- gallery: o = [],
33
- onDismiss: l,
34
- onPreviewClick: w,
35
- onFetchSource: x,
36
- onEditClick: c
37
- }) => {
38
- const d = T(o), { source: b, isPreviewVisible: f, isLoadingPreview: p, togglePreview: v } = j({ isGallery: d, onFetchSource: x, onPreviewClick: w }), h = !f, C = p, k = /* @__PURE__ */ r(B.Fragment, { children: [
39
- /* @__PURE__ */ e("span", { className: "text-xs font-medium text-white/55", children: "•" }),
40
- /* @__PURE__ */ e(
41
- "span",
42
- {
43
- className: n ? "text-xs font-medium text-white/55" : "text-xs font-medium text-white/30",
44
- children: n || g
45
- }
46
- )
47
- ] }), y = l ? /* @__PURE__ */ e(
48
- "button",
49
- {
50
- type: "button",
51
- onClick: (N) => {
52
- N.stopPropagation(), l();
53
- },
54
- className: "flex size-6 items-center justify-center rounded-full bg-[#121110] text-white",
55
- "aria-label": "Dismiss attachment",
56
- children: /* @__PURE__ */ e(m, { className: "size-3", weight: "bold" })
57
- }
58
- ) : void 0, P = c ? /* @__PURE__ */ e(
59
- "button",
60
- {
61
- type: "button",
62
- onClick: c,
63
- "aria-label": "Edit attachment",
64
- className: "flex size-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15",
65
- children: /* @__PURE__ */ e(z, { className: "size-5", weight: "regular" })
66
- }
67
- ) : void 0;
68
- return /* @__PURE__ */ r(S, { variant: "dark", children: [
69
- /* @__PURE__ */ e(
70
- L,
71
- {
72
- onToggle: v,
73
- isBusy: C,
74
- isPreviewVisible: !h,
75
- children: /* @__PURE__ */ e(
76
- M,
77
- {
78
- variant: "dark",
79
- gallery: o,
80
- title: t,
81
- showLocked: h,
82
- mimeType: i,
83
- thumbnailUrl: s,
84
- source: b,
85
- topRight: y
86
- }
87
- )
88
- }
89
- ),
90
- /* @__PURE__ */ e(
91
- I,
92
- {
93
- variant: "dark",
94
- title: t,
95
- placeholderTitle: u,
96
- mimeType: i,
97
- detail: a,
98
- statusBadge: k,
99
- icon: d ? R("dark") : void 0,
100
- trailingAction: P
101
- }
102
- )
103
- ] });
104
- }, K = (t) => t.contentType === "text" ? /* @__PURE__ */ e(F, { ...t }) : /* @__PURE__ */ e(X, { ...t });
105
- export {
106
- K as default
107
- };
108
- //# sourceMappingURL=Card-BVZM0-06.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Card-BVZM0-06.js","sources":["../src/components/LockedAttachment/components/Composer/Card.tsx"],"sourcesContent":["import { PencilSimpleIcon, XIcon } from '@phosphor-icons/react'\nimport React from 'react'\n\nimport type { FetchSourceHandler, LockedAttachmentBaseProps } from '../../types'\nimport CardBody from '../_shared/CardBody'\nimport CardThumbnail, {\n galleryBodyIcon,\n isGalleryCard,\n} from '../_shared/CardThumbnail'\nimport MediaShell from '../_shared/MediaShell'\nimport PreviewToggle from '../_shared/PreviewToggle'\nimport PriceLockRow from '../_shared/PriceLockRow'\nimport SkeletonTextLines from '../_shared/SkeletonTextLines'\nimport TextShell from '../_shared/TextShell'\nimport { usePreviewToggle } from '../_shared/usePreviewToggle'\n\nexport interface ComposerCardProps extends LockedAttachmentBaseProps {\n /**\n * The message's own text, shown as-is (not redacted yet — the message\n * hasn't been sent). Only used when `contentType` is `\"text\"`.\n */\n text?: string\n /** Placeholder shown in the amount slot before one is configured. */\n placeholderAmountText?: string\n /**\n * Renders a persistent dismiss X on the thumbnail (media) or next to the\n * text (text mode); called to remove the draft attachment. Unrelated to\n * `TextCardProps.onDismiss`, which only appears for `isMine` after a\n * failed payment.\n */\n onDismiss?: () => void\n /** Fired the first time the composer taps the thumbnail to preview. */\n onPreviewClick?: () => void\n /**\n * Lazily loads the underlying source so the composer can preview the\n * attachment they're about to send. Called the first time the thumbnail is\n * tapped; the returned source is cached and reused on subsequent toggles.\n * Ignored when `contentType` is `\"text\"`.\n */\n onFetchSource?: FetchSourceHandler\n /**\n * When provided, renders a pencil button in the body bottom-right that the\n * composer can use to edit the attachment metadata (e.g. open the price /\n * gallery editor). Matches the Composer \"Button\" instance in Figma.\n */\n onEditClick?: () => void\n}\n\n/**\n * Composer card in text mode: a normal outgoing chat bubble showing the real\n * draft text (it hasn't been sent/redacted yet) plus a dismiss X and a price\n * row. Media-only props are ignored here.\n */\nconst ComposerTextCard: React.FC<ComposerCardProps> = ({\n text,\n amountText,\n placeholderAmountText,\n onDismiss,\n}) => (\n <TextShell isMine>\n <div className=\"pr-8\">\n {text ? text : <SkeletonTextLines variant=\"dark\" animate={false} />}\n </div>\n <PriceLockRow>\n <span className={amountText ? 'text-white/55' : 'text-white/30'}>\n {amountText || placeholderAmountText}\n </span>\n </PriceLockRow>\n {onDismiss && (\n <button\n type=\"button\"\n onClick={onDismiss}\n className=\"absolute right-3 top-3 flex size-6 shrink-0 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15\"\n aria-label=\"Dismiss attachment\"\n >\n <XIcon className=\"size-3\" weight=\"bold\" />\n </button>\n )}\n </TextShell>\n)\n\n/**\n * Composer card in media mode: the locked thumbnail (single or gallery) the\n * composer taps to preview the attachment they're about to send, plus the\n * title/price body with optional dismiss X and edit pencil.\n */\nconst ComposerMediaCard: React.FC<ComposerCardProps> = ({\n title,\n mimeType = 'application/octet-stream',\n thumbnailUrl,\n detail,\n amountText,\n placeholderTitle = 'Attachment title',\n placeholderAmountText,\n gallery = [],\n onDismiss,\n onPreviewClick,\n onFetchSource,\n onEditClick,\n}) => {\n const isGallery = isGalleryCard(gallery)\n\n // Gallery is always previewable in the composer because each item's source\n // is already provided up-front via `gallery[*].sourceUrl`.\n const { source, isPreviewVisible, isLoadingPreview, togglePreview } =\n usePreviewToggle({ isGallery, onFetchSource, onPreviewClick })\n\n const showLocked = !isPreviewVisible\n const isBusy = isLoadingPreview\n\n const statusBadge = (\n <React.Fragment>\n <span className=\"text-xs font-medium text-white/55\">&bull;</span>\n <span\n className={\n amountText\n ? 'text-xs font-medium text-white/55'\n : 'text-xs font-medium text-white/30'\n }\n >\n {amountText || placeholderAmountText}\n </span>\n </React.Fragment>\n )\n\n const dismissButton = onDismiss ? (\n <button\n type=\"button\"\n onClick={(e) => {\n // Stop the click from bubbling up into the outer preview-toggle\n // wrapper when both are wired up at the same time.\n e.stopPropagation()\n onDismiss()\n }}\n className=\"flex size-6 items-center justify-center rounded-full bg-[#121110] text-white\"\n aria-label=\"Dismiss attachment\"\n >\n <XIcon className=\"size-3\" weight=\"bold\" />\n </button>\n ) : undefined\n\n const editButton = onEditClick ? (\n <button\n type=\"button\"\n onClick={onEditClick}\n aria-label=\"Edit attachment\"\n className=\"flex size-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15\"\n >\n <PencilSimpleIcon className=\"size-5\" weight=\"regular\" />\n </button>\n ) : undefined\n\n return (\n <MediaShell variant=\"dark\">\n <PreviewToggle\n onToggle={togglePreview}\n isBusy={isBusy}\n isPreviewVisible={!showLocked}\n >\n <CardThumbnail\n variant=\"dark\"\n gallery={gallery}\n title={title}\n showLocked={showLocked}\n mimeType={mimeType}\n thumbnailUrl={thumbnailUrl}\n source={source}\n topRight={dismissButton}\n />\n </PreviewToggle>\n\n <CardBody\n variant=\"dark\"\n title={title}\n placeholderTitle={placeholderTitle}\n mimeType={mimeType}\n detail={detail}\n statusBadge={statusBadge}\n icon={isGallery ? galleryBodyIcon('dark') : undefined}\n trailingAction={editButton}\n />\n </MediaShell>\n )\n}\n\n/**\n * The card the composer sees while drafting a paid attachment.\n * Matches the Composer column of the messaging design system in Figma.\n *\n * When `contentType === \"text\"`, renders as a normal outgoing chat bubble\n * showing the real draft text (it hasn't been sent/redacted yet) plus a\n * dismiss X and a price row, instead of a media thumbnail card.\n */\nconst ComposerCard: React.FC<ComposerCardProps> = (props) =>\n props.contentType === 'text' ? (\n <ComposerTextCard {...props} />\n ) : (\n <ComposerMediaCard {...props} />\n )\n\nexport default ComposerCard\n"],"names":["ComposerTextCard","text","amountText","placeholderAmountText","onDismiss","jsxs","TextShell","jsx","SkeletonTextLines","PriceLockRow","XIcon","ComposerMediaCard","title","mimeType","thumbnailUrl","detail","placeholderTitle","gallery","onPreviewClick","onFetchSource","onEditClick","isGallery","isGalleryCard","source","isPreviewVisible","isLoadingPreview","togglePreview","usePreviewToggle","showLocked","isBusy","statusBadge","React","dismissButton","e","editButton","PencilSimpleIcon","MediaShell","PreviewToggle","CardThumbnail","CardBody","galleryBodyIcon","ComposerCard","props"],"mappings":"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"}