@linktr.ee/messaging-react 3.10.0 → 3.11.0-rc-1783756377

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 (88) hide show
  1. package/dist/Card-415OfRhw.cjs +2 -0
  2. package/dist/Card-415OfRhw.cjs.map +1 -0
  3. package/dist/Card-BnbNynLQ.js +694 -0
  4. package/dist/Card-BnbNynLQ.js.map +1 -0
  5. package/dist/Card-UnwKmYzt.cjs +2 -0
  6. package/dist/Card-UnwKmYzt.cjs.map +1 -0
  7. package/dist/Card-qS17qnN0.js +103 -0
  8. package/dist/Card-qS17qnN0.js.map +1 -0
  9. package/dist/LoadingDots-Ct3WmVlT.js +59 -0
  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-CyHyftBi.cjs +2 -0
  15. package/dist/index-CyHyftBi.cjs.map +1 -0
  16. package/dist/{index-BxzzWK4K.js → index-DJXjS2H9.js} +1397 -1358
  17. package/dist/index-DJXjS2H9.js.map +1 -0
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.ts +196 -101
  20. package/dist/index.js +1 -1
  21. package/dist/testing.d.ts +1 -0
  22. package/package.json +2 -2
  23. package/src/components/ChannelList/CustomChannelPreview.test.tsx +95 -0
  24. package/src/components/ChannelList/CustomChannelPreview.tsx +21 -9
  25. package/src/components/ChannelList/index.test.tsx +46 -0
  26. package/src/components/ChannelList/index.tsx +12 -0
  27. package/src/components/CustomMessage/CustomMessage.stories.tsx +54 -1
  28. package/src/components/CustomMessage/LockedAttachment/LockedAttachment.stories.tsx +541 -0
  29. package/src/components/CustomMessage/LockedAttachment/LockedAttachment.test.tsx +117 -0
  30. package/src/components/CustomMessage/LockedAttachment/components/Media/Card.tsx +358 -0
  31. package/src/components/{LockedAttachment/components/Received → CustomMessage/LockedAttachment/components/Media}/CardActions.tsx +9 -2
  32. package/src/components/CustomMessage/LockedAttachment/components/Media/types.ts +79 -0
  33. package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +153 -0
  34. package/src/components/CustomMessage/LockedAttachment/components/Text/types.ts +58 -0
  35. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/CardBody.tsx +8 -7
  36. package/src/components/CustomMessage/LockedAttachment/components/_shared/CardThumbnail.tsx +92 -0
  37. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/GalleryThumbnail.tsx +31 -39
  38. package/src/components/CustomMessage/LockedAttachment/components/_shared/LoadingDots.tsx +36 -0
  39. package/src/components/CustomMessage/LockedAttachment/components/_shared/LockBadge.tsx +63 -0
  40. package/src/components/{LockedAttachment/components/_shared/LockedCardShell.tsx → CustomMessage/LockedAttachment/components/_shared/MediaShell.tsx} +8 -8
  41. package/src/components/CustomMessage/LockedAttachment/components/_shared/PreviewToggle.tsx +58 -0
  42. package/src/components/CustomMessage/LockedAttachment/components/_shared/PriceLockRow.tsx +21 -0
  43. package/src/components/CustomMessage/LockedAttachment/components/_shared/PurchaseStatusBadge.tsx +74 -0
  44. package/src/components/{LockedAttachment/components/_shared/LockedThumbnail.tsx → CustomMessage/LockedAttachment/components/_shared/SingleThumbnail.tsx} +11 -7
  45. package/src/components/CustomMessage/LockedAttachment/components/_shared/SkeletonTextLines.tsx +29 -0
  46. package/src/components/CustomMessage/LockedAttachment/components/_shared/TextShell.tsx +28 -0
  47. package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.test.ts +67 -0
  48. package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.ts +89 -0
  49. package/src/components/CustomMessage/LockedAttachment/components/_shared/useReceivedSource.ts +83 -0
  50. package/src/components/CustomMessage/LockedAttachment/index.tsx +105 -0
  51. package/src/components/CustomMessage/LockedAttachment/types.ts +44 -0
  52. package/src/components/CustomMessage/MessageTag.tsx +8 -0
  53. package/src/components/CustomMessage/context.tsx +1 -1
  54. package/src/components/CustomMessage/index.tsx +76 -42
  55. package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +5 -3
  56. package/src/index.ts +3 -8
  57. package/src/stream-custom-data.ts +1 -0
  58. package/src/styles.css +12 -0
  59. package/src/types.ts +35 -2
  60. package/dist/Card-B-L2MST4.js +0 -107
  61. package/dist/Card-B-L2MST4.js.map +0 -1
  62. package/dist/Card-B56QxJW3.cjs +0 -2
  63. package/dist/Card-B56QxJW3.cjs.map +0 -1
  64. package/dist/Card-Bqh-BQaX.js +0 -132
  65. package/dist/Card-Bqh-BQaX.js.map +0 -1
  66. package/dist/Card-ZCE6Lhfc.cjs +0 -2
  67. package/dist/Card-ZCE6Lhfc.cjs.map +0 -1
  68. package/dist/Card-g4JD-2ES.js +0 -163
  69. package/dist/Card-g4JD-2ES.js.map +0 -1
  70. package/dist/Card-wac6AMNc.cjs +0 -2
  71. package/dist/Card-wac6AMNc.cjs.map +0 -1
  72. package/dist/LockedThumbnail-BAQmoIff.js +0 -220
  73. package/dist/LockedThumbnail-BAQmoIff.js.map +0 -1
  74. package/dist/LockedThumbnail-Cr0Gqs3Y.cjs +0 -2
  75. package/dist/LockedThumbnail-Cr0Gqs3Y.cjs.map +0 -1
  76. package/dist/index-BPhZyaxT.cjs +0 -2
  77. package/dist/index-BPhZyaxT.cjs.map +0 -1
  78. package/dist/index-BxzzWK4K.js.map +0 -1
  79. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +0 -450
  80. package/src/components/LockedAttachment/components/Composer/Card.tsx +0 -221
  81. package/src/components/LockedAttachment/components/Composer/index.ts +0 -2
  82. package/src/components/LockedAttachment/components/Received/Card.tsx +0 -191
  83. package/src/components/LockedAttachment/components/Received/index.ts +0 -2
  84. package/src/components/LockedAttachment/components/Sent/Card.tsx +0 -177
  85. package/src/components/LockedAttachment/components/Sent/index.ts +0 -2
  86. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +0 -39
  87. package/src/components/LockedAttachment/index.tsx +0 -63
  88. package/src/components/LockedAttachment/types.ts +0 -51
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BPhZyaxT.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageBubble=e.MessageBubble;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-CyHyftBi.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageBubble=e.MessageBubble;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -6,7 +6,9 @@ import { ChannelPreviewUIComponentProps } from 'stream-chat-react';
6
6
  import { ChannelSort } from 'stream-chat';
7
7
  import { ComponentType } from 'react';
8
8
  import { default as default_2 } from 'react';
9
+ import { Dispatch } from 'react';
9
10
  import { EmptyStateIndicatorProps } from 'stream-chat-react';
11
+ import { Event as Event_2 } from 'stream-chat';
10
12
  import { FC } from 'react';
11
13
  import { InfiniteScrollProps } from 'stream-chat-react';
12
14
  import { JSX as JSX_2 } from 'react/jsx-runtime';
@@ -14,6 +16,7 @@ import { LocalMessage } from 'stream-chat';
14
16
  import { MessagingUser } from '@linktr.ee/messaging-core';
15
17
  import { Provider } from 'react';
16
18
  import { SendMessageAPIResponse } from 'stream-chat';
19
+ import { SetStateAction } from 'react';
17
20
  import { StreamChat } from 'stream-chat';
18
21
  import { StreamChatService } from '@linktr.ee/messaging-core';
19
22
  import { StreamChatServiceConfig } from '@linktr.ee/messaging-core';
@@ -153,8 +156,37 @@ export declare interface ChannelListProps {
153
156
  * Falls back to message.text when no matching translation exists.
154
157
  */
155
158
  viewerLanguage?: string;
159
+ /**
160
+ * Lock the visible channel order so live events don't promote a channel to
161
+ * the top. Forwarded to Stream's `<ChannelList>`. Note `lockChannelOrder`
162
+ * alone doesn't cover `notification.message_new` or `channel.visible`; pair it
163
+ * with the event handlers below to fully neutralize promotion.
164
+ */
165
+ lockChannelOrder?: boolean;
166
+ /**
167
+ * Live channel-list event handlers forwarded verbatim to Stream's
168
+ * `<ChannelList>`. Left undefined, Stream applies its default "move the
169
+ * changed channel to the top" behaviour.
170
+ *
171
+ * Hosts that order by a custom channel field (`last_conversation_at` for the
172
+ * inbox, `priority_score` for Spotlight) use these to neutralize promotion on
173
+ * new-message / visibility events and re-sort on `channel.updated` instead.
174
+ * Each receives Stream's `setChannels` dispatcher and the triggering event.
175
+ * See MES-1214 / MES-1219.
176
+ */
177
+ onMessageNewHandler?: ChannelListReorderHandler;
178
+ onMessageNew?: ChannelListReorderHandler;
179
+ onChannelVisible?: ChannelListReorderHandler;
180
+ onAddedToChannel?: ChannelListReorderHandler;
181
+ onChannelUpdated?: ChannelListReorderHandler;
156
182
  }
157
183
 
184
+ /**
185
+ * Signature of Stream's `<ChannelList>` live-event handlers: mutate the loaded
186
+ * channel array via `setChannels` in response to a websocket `event`.
187
+ */
188
+ declare type ChannelListReorderHandler = (setChannels: Dispatch<SetStateAction<Channel[]>>, event: Event_2) => void;
189
+
158
190
  declare type ChannelMember = ChannelMembers[string];
159
191
 
160
192
  declare type ChannelMembers = NonNullable<Channel['state']>['members'];
@@ -404,44 +436,12 @@ export declare interface ChannelViewProps {
404
436
  onParticipantNameClick?: () => void;
405
437
  }
406
438
 
407
- export declare interface ComposerCardProps extends LockedAttachmentBaseProps {
408
- /** Placeholder shown in the title slot before the composer types one. */
409
- placeholderTitle?: string;
410
- /** Placeholder shown in the amount slot before one is configured. */
411
- placeholderAmountText?: string;
412
- /**
413
- * When provided, renders a dismiss X in the thumbnail corner. Called when
414
- * the composer clicks it to remove the attachment.
415
- */
416
- onDismiss?: () => void;
417
- /** Fired the first time the composer taps the thumbnail to preview. */
418
- onPreviewClick?: () => void;
419
- /**
420
- * Lazily loads the underlying source so the composer can preview the
421
- * attachment they're about to send. Called the first time the thumbnail is
422
- * tapped; the returned source is cached and reused on subsequent toggles.
423
- */
424
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
425
- /**
426
- * When provided, renders a pencil button in the body bottom-right that the
427
- * composer can use to edit the attachment metadata (e.g. open the price /
428
- * gallery editor). Matches the Composer "Button" instance in Figma.
429
- */
430
- onEditClick?: () => void;
431
- }
432
-
433
439
  /** Shared props for the `Composer` state of every attachment type. */
434
440
  declare interface ComposerExtras {
435
441
  /** Renders a dismiss `×` button overlaid on the attachment. */
436
442
  onDismiss?: () => void;
437
443
  }
438
444
 
439
- /**
440
- * @deprecated Renamed to `SentCardProps`. Drafting usages (with `onDismiss`)
441
- * should migrate to `ComposerCardProps`.
442
- */
443
- export declare type CreatorCardProps = SentCardProps;
444
-
445
445
  export declare const CustomMessageProvider: Provider<Partial<CustomMessageRegistry>>;
446
446
 
447
447
  export declare interface CustomMessageRegistry {
@@ -450,6 +450,19 @@ export declare interface CustomMessageRegistry {
450
450
 
451
451
  declare type DmAgentSystemType = 'SYSTEM_DM_AGENT_PAUSED' | 'SYSTEM_DM_AGENT_RESUMED';
452
452
 
453
+ declare type DraftProps = MediaCardDraftProps & {
454
+ /** Drafting is always the sender — doesn't apply here. */
455
+ isMine?: never;
456
+ /**
457
+ * "text" renders as a chat bubble showing the real draft `text` instead of
458
+ * a media thumbnail card. Media-only fields below are ignored in text mode.
459
+ * Defaults to `"media"`.
460
+ */
461
+ contentType?: 'text' | 'media';
462
+ /** The message's own text, shown as-is (not redacted yet). Only used when `contentType` is `"text"`. */
463
+ renderedText?: default_2.ReactNode;
464
+ };
465
+
453
466
  export declare interface Faq {
454
467
  id: string;
455
468
  question: string;
@@ -479,6 +492,9 @@ export declare interface FaqListProps {
479
492
  avatarName?: string;
480
493
  }
481
494
 
495
+ /** Fetches the underlying source; `void` return means none available. */
496
+ declare type FetchSourceHandler = () => Promise<LockedAttachmentSource | void>;
497
+
482
498
  declare interface FileAttachmentSharedProps extends MessageAttachmentBaseProps {
483
499
  /** Source URL of the file (used as the download target). */
484
500
  src?: string;
@@ -728,54 +744,111 @@ export declare interface LinkAttachmentSentCardProps extends LinkAttachmentBaseP
728
744
  onClick?: () => void;
729
745
  }
730
746
 
731
- export declare const LockedAttachment: {
732
- Composer: (props: ComposerCardProps) => JSX_2.Element;
733
- Sent: (props: SentCardProps) => JSX_2.Element;
734
- Received: (props: ReceivedCardProps) => JSX_2.Element;
735
- Creator: (props: SentCardProps) => JSX_2.Element;
736
- Visitor: (props: ReceivedCardProps) => JSX_2.Element;
737
- };
738
-
739
- declare interface LockedAttachmentBaseProps {
740
- title?: string;
741
- mimeType?: string;
742
- thumbnailUrl?: string;
743
- detail?: string;
744
- amountText?: string;
745
- paymentStatus?: PaymentStatus;
746
- /**
747
- * When provided with 2+ items, the card renders as a mixed-media carousel
748
- * (e.g. a couple of photos + a video) instead of a single thumbnail. Each
749
- * item brings its own thumbnail and optional source so that
750
- * `LockedAttachment.Composer` / `.Sent` / `.Received` can all share the
751
- * same carousel chrome.
752
- */
753
- gallery?: LockedAttachmentGalleryItem[];
754
- }
747
+ export declare const LockedAttachment: default_2.FC<LockedAttachmentProps>;
755
748
 
756
749
  export declare interface LockedAttachmentContextValue {
750
+ /** Whether the given message is mid-checkout. */
757
751
  isUnlocking: (id: string) => boolean;
752
+ /** Opens the checkout flow for a message. */
758
753
  onUnlockClick?: (message: LocalMessage, channel: Channel) => void;
754
+ /** Handles a Download click for a message. */
759
755
  onDownloadClick?: (message: LocalMessage, channel: Channel) => void;
756
+ /** Reveals the locked media's source. Unused for text — payment writes `message.text` directly. */
760
757
  onFetchSource?: (message: LocalMessage, channel: Channel) => Promise<LockedAttachmentSource | void>;
761
758
  }
762
759
 
763
760
  export declare interface LockedAttachmentGalleryItem {
764
- /** MIME type of this carousel item — drives the per-item play / lock affordance. */
761
+ /** Drives the per-item play/lock affordance. */
765
762
  mimeType: string;
766
- /** Poster image used for both the locked (blurred) and unlocked preview state. */
763
+ /** Poster image, locked or unlocked. */
767
764
  thumbnailUrl?: string;
768
- /** Underlying source (image or video URL) — shown only when unlocked. */
765
+ /** Media source, shown only when unlocked. */
769
766
  sourceUrl?: string;
770
767
  }
771
768
 
769
+ /**
770
+ * The card for a paid attachment — a still-drafting `isDraft` card, a static
771
+ * receiver-facing `isPreview` mockup, or a sent text/media card discriminated
772
+ * by `isMine` + `contentType`. Internally lazy-loads only the one underlying
773
+ * implementation the resolved branch needs.
774
+ */
775
+ export declare type LockedAttachmentProps = DraftProps | PreviewProps | SentTextOwnProps | SentTextOtherProps | SentMediaOwnProps | SentMediaOtherProps;
776
+
772
777
  export declare interface LockedAttachmentSource {
773
- /** Proxied URL used by the media player for in-app playback. */
778
+ /** Proxied URL for in-app playback. */
774
779
  sourceUrl: string;
775
- /** URL opened when the visitor clicks Download — may be a file or a web destination. */
780
+ /** Opened when the visitor clicks Download. */
776
781
  redeemUrl?: string;
777
- /** Thumbnail URL from the fetched asset — overrides the metadata thumbnail when present. */
782
+ /** Overrides the metadata thumbnail when present. */
783
+ thumbnailUrl?: string;
784
+ }
785
+
786
+ declare interface MediaCardBaseProps {
787
+ /** Attachment title. */
788
+ title?: string;
789
+ /** Fallback shown when `title` is unset. */
790
+ placeholderTitle?: string;
791
+ /** Drives icon/thumbnail rendering. */
792
+ mimeType?: string;
793
+ /** Poster image, locked or unlocked. */
778
794
  thumbnailUrl?: string;
795
+ /** Secondary description text. */
796
+ detail?: string;
797
+ /** Amount label. */
798
+ amountText?: string;
799
+ /** 2+ items renders as a carousel instead of a single thumbnail. */
800
+ gallery?: LockedAttachmentGalleryItem[];
801
+ }
802
+
803
+ declare interface MediaCardDraftProps extends MediaCardBaseProps {
804
+ /** True while composing — shows the real (unredacted) thumbnail. */
805
+ isDraft: true;
806
+ isPreview?: false;
807
+ /** Fallback amount before a price is set. */
808
+ placeholderAmountText?: string;
809
+ /** Lazily fetches the source on first tap; cached and reused after. */
810
+ onFetchSource?: FetchSourceHandler;
811
+ /** Fired on first tap to preview. */
812
+ onPreviewClick?: () => void;
813
+ /** Removes the draft attachment. */
814
+ onDismiss?: () => void;
815
+ /** Renders a pencil button to edit the attachment metadata. */
816
+ onEditClick?: () => void;
817
+ }
818
+
819
+ declare type MediaCardOtherProps = MediaCardSentBaseProps & {
820
+ /** Recipient's view. */
821
+ isMine: false;
822
+ /** Opens checkout on click. Omit to hide the button. */
823
+ onUnlockClick?: () => void;
824
+ /** Omit to hide the Download button. */
825
+ onDownloadClick?: () => void;
826
+ /** Spinner on Unlock while checkout runs. */
827
+ isUnlocking?: boolean;
828
+ };
829
+
830
+ declare type MediaCardOwnProps = MediaCardSentBaseProps & {
831
+ /** Sender's own view. */
832
+ isMine: true;
833
+ /** Fired on first tap to preview own attachment. */
834
+ onPreviewClick?: () => void;
835
+ };
836
+
837
+ declare interface MediaCardPreviewProps extends MediaCardBaseProps {
838
+ /** True to render exactly as the recipient will see it before purchase — always locked/blurred. */
839
+ isDraft?: false;
840
+ isPreview: true;
841
+ /** Fallback amount before a price is set. */
842
+ placeholderAmountText?: string;
843
+ }
844
+
845
+ declare interface MediaCardSentBaseProps extends MediaCardBaseProps {
846
+ /** False once sent. */
847
+ isDraft?: false;
848
+ isPreview?: false;
849
+ /** Drives the lock/skeleton/badge state. */
850
+ paymentStatus?: PaymentStatus;
851
+ onFetchSource?: FetchSourceHandler;
779
852
  }
780
853
 
781
854
  export declare const MediaMessage: default_2.FC<MediaMessageProps> & {
@@ -1233,6 +1306,7 @@ export declare interface MessageMetadata {
1233
1306
  amount_text?: string;
1234
1307
  payment_status?: PaymentStatus;
1235
1308
  attachment_title?: string;
1309
+ attachment_content_type?: 'text' | 'media';
1236
1310
  attachment_mime_type?: string;
1237
1311
  attachment_thumbnail?: string;
1238
1312
  attachment_detail?: string;
@@ -1445,6 +1519,16 @@ declare interface PdfAttachmentSharedProps extends MessageAttachmentBaseProps {
1445
1519
  onClick?: (index: number) => boolean | void;
1446
1520
  }
1447
1521
 
1522
+ declare type PreviewProps = MediaCardPreviewProps & {
1523
+ /** Drafting is always the sender — doesn't apply here. */
1524
+ isMine?: never;
1525
+ /**
1526
+ * "text" renders as a chat bubble instead of a media thumbnail card.
1527
+ * Media-only fields below are ignored in text mode. Defaults to `"media"`.
1528
+ */
1529
+ contentType?: 'text' | 'media';
1530
+ };
1531
+
1448
1532
  export declare interface ReceivedBubbleProps extends MessageBubbleBaseProps {
1449
1533
  /**
1450
1534
  * Optional header rendered above the text inside the bubble — used by the
@@ -1453,31 +1537,6 @@ export declare interface ReceivedBubbleProps extends MessageBubbleBaseProps {
1453
1537
  header?: default_2.ReactNode;
1454
1538
  }
1455
1539
 
1456
- export declare interface ReceivedCardProps extends LockedAttachmentBaseProps {
1457
- /**
1458
- * Called when the recipient clicks Unlock on an unpaid attachment.
1459
- * Use this to open a checkout flow. Omit to hide the Unlock button.
1460
- */
1461
- onUnlockClick?: () => void;
1462
- /**
1463
- * Called to fetch the attachment source — fired automatically when
1464
- * `paymentStatus` transitions to `'paid'`, or immediately on click when
1465
- * `paymentStatus` is already `'paid'`. Return a `LockedAttachmentSource`
1466
- * to unlock the card.
1467
- */
1468
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
1469
- /**
1470
- * Called when the recipient clicks Download on an unlocked card.
1471
- * Omit to hide the Download button.
1472
- */
1473
- onDownloadClick?: () => void;
1474
- /**
1475
- * When true, shows a loading spinner on the Unlock button.
1476
- * Driven by the LockedAttachmentContext (e.g. checkout in progress).
1477
- */
1478
- isUnlocking?: boolean;
1479
- }
1480
-
1481
1540
  /**
1482
1541
  * Resolves the member to display as the conversation's "other party".
1483
1542
  *
@@ -1503,17 +1562,56 @@ export declare function resolveParticipantDisplayName(user?: ParticipantDisplayU
1503
1562
  export declare interface SentBubbleProps extends MessageBubbleBaseProps {
1504
1563
  }
1505
1564
 
1506
- export declare interface SentCardProps extends LockedAttachmentBaseProps {
1507
- /** Placeholder shown in the title slot when no title is set. */
1508
- placeholderTitle?: string;
1509
- /** Fired the first time the sender taps the thumbnail to preview their own attachment. */
1510
- onPreviewClick?: () => void;
1511
- /**
1512
- * Lazily loads the underlying source so the sender can preview the attachment.
1513
- * Called the first time the thumbnail is tapped; the returned source is cached
1514
- * and reused on subsequent toggles.
1515
- */
1516
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
1565
+ declare type SentMediaOtherProps = MediaCardOtherProps & {
1566
+ contentType?: 'media';
1567
+ };
1568
+
1569
+ declare type SentMediaOwnProps = MediaCardOwnProps & {
1570
+ contentType?: 'media';
1571
+ /** Sender's view has no unlock/download flow doesn't apply here. */
1572
+ onUnlockClick?: never;
1573
+ isUnlocking?: never;
1574
+ onDownloadClick?: never;
1575
+ };
1576
+
1577
+ declare type SentTextOtherProps = TextCardOtherProps & {
1578
+ contentType: 'text';
1579
+ };
1580
+
1581
+ declare type SentTextOwnProps = TextCardOwnProps & {
1582
+ contentType: 'text';
1583
+ /** Sender's view has no unlock flow — doesn't apply here. */
1584
+ onUnlockClick?: never;
1585
+ isUnlocking?: never;
1586
+ };
1587
+
1588
+ declare interface TextCardBaseProps {
1589
+ /** Amount label. */
1590
+ amountText?: string;
1591
+ }
1592
+
1593
+ declare type TextCardOtherProps = TextCardSentBaseProps & {
1594
+ /** Recipient's view. */
1595
+ isMine: false;
1596
+ /** Spinner on Unlock while checkout runs. */
1597
+ isUnlocking?: boolean;
1598
+ /** Opens checkout on click. Omit to hide the button. */
1599
+ onUnlockClick?: () => void;
1600
+ };
1601
+
1602
+ declare type TextCardOwnProps = TextCardSentBaseProps & {
1603
+ /** Sender's own view. */
1604
+ isMine: true;
1605
+ };
1606
+
1607
+ declare interface TextCardSentBaseProps extends TextCardBaseProps {
1608
+ /** False once sent. */
1609
+ isDraft?: false;
1610
+ isPreview?: false;
1611
+ /** Revealed content once paid. */
1612
+ renderedText?: default_2.ReactNode;
1613
+ /** Drives the lock/skeleton/badge state. */
1614
+ paymentStatus?: PaymentStatus;
1517
1615
  }
1518
1616
 
1519
1617
  /**
@@ -1584,9 +1682,6 @@ declare interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps
1584
1682
  onClick?: (index: number) => boolean | void;
1585
1683
  }
1586
1684
 
1587
- /** @deprecated Renamed to `ReceivedCardProps`. */
1588
- export declare type VisitorCardProps = ReceivedCardProps;
1589
-
1590
1685
  export declare type VoteSelection = 'up' | 'down' | null;
1591
1686
 
1592
1687
  export { }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as u, i as c, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as b, u as P, v as f, w as x, x as y, y as B, z as S, B as q, D as z, E as D, G as E, H as V } from "./index-BxzzWK4K.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as u, i as c, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as b, u as P, v as f, w as x, x as y, y as B, z as S, B as q, D as z, E as D, G as E, H as V } from "./index-DJXjS2H9.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/dist/testing.d.ts CHANGED
@@ -47,6 +47,7 @@ export declare interface MessageMetadata {
47
47
  amount_text?: string;
48
48
  payment_status?: PaymentStatus;
49
49
  attachment_title?: string;
50
+ attachment_content_type?: 'text' | 'media';
50
51
  attachment_mime_type?: string;
51
52
  attachment_thumbnail?: string;
52
53
  attachment_detail?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.10.0",
3
+ "version": "3.11.0-rc-1783756377",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@linktr.ee/component-library": "11.8.6",
53
- "@linktr.ee/messaging-core": "^2.2.0",
53
+ "@linktr.ee/messaging-core": "2.3.0-rc-1783756377",
54
54
  "@phosphor-icons/react": "^2.1.10"
55
55
  },
56
56
  "devDependencies": {
@@ -260,6 +260,101 @@ describe('CustomChannelPreview', () => {
260
260
  expect(screen.getByText('No messages yet')).toBeInTheDocument()
261
261
  })
262
262
 
263
+ it('shows a locked indicator for a text-only attachment message, even when paid', () => {
264
+ // The channel-list preview always shows the locked wording for an
265
+ // attachment-type message and never echoes `message.text` — the
266
+ // revealed content is only ever shown inside the full conversation.
267
+ const channel = createMockChannel([
268
+ {
269
+ id: 'msg-1',
270
+ text: 'REDACTED',
271
+ type: 'regular',
272
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
273
+ user: { id: 'participant-1', name: 'Alice' },
274
+ metadata: {
275
+ custom_type: 'MESSAGE_ATTACHMENT',
276
+ attachment_content_type: 'text',
277
+ payment_status: 'paid',
278
+ },
279
+ },
280
+ ])
281
+
282
+ renderWithProviders(
283
+ <CustomChannelPreview {...defaultProps} channel={channel} />
284
+ )
285
+
286
+ expect(screen.getByText('🔒 Sent a locked message')).toBeInTheDocument()
287
+ expect(screen.queryByText(/REDACTED/)).not.toBeInTheDocument()
288
+ })
289
+
290
+ it('shows a locked indicator for an unpaid text-only attachment message', () => {
291
+ const channel = createMockChannel([
292
+ {
293
+ id: 'msg-1',
294
+ text: 'REDACTED',
295
+ type: 'regular',
296
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
297
+ user: { id: 'participant-1', name: 'Alice' },
298
+ metadata: {
299
+ custom_type: 'MESSAGE_ATTACHMENT',
300
+ attachment_content_type: 'text',
301
+ payment_status: 'pending',
302
+ },
303
+ },
304
+ ])
305
+
306
+ renderWithProviders(
307
+ <CustomChannelPreview {...defaultProps} channel={channel} />
308
+ )
309
+
310
+ expect(screen.getByText('🔒 Sent a locked message')).toBeInTheDocument()
311
+ expect(screen.queryByText(/REDACTED/)).not.toBeInTheDocument()
312
+ })
313
+
314
+ it('shows a locked indicator for a media attachment message, even when paid', () => {
315
+ const channel = createMockChannel([
316
+ {
317
+ id: 'msg-1',
318
+ type: 'regular',
319
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
320
+ user: { id: 'participant-1', name: 'Alice' },
321
+ metadata: {
322
+ custom_type: 'MESSAGE_ATTACHMENT',
323
+ attachment_content_type: 'media',
324
+ payment_status: 'paid',
325
+ },
326
+ },
327
+ ])
328
+
329
+ renderWithProviders(
330
+ <CustomChannelPreview {...defaultProps} channel={channel} />
331
+ )
332
+
333
+ expect(screen.getByText('🔒 Sent a locked attachment')).toBeInTheDocument()
334
+ })
335
+
336
+ it('shows a locked indicator for an unpaid media attachment message', () => {
337
+ const channel = createMockChannel([
338
+ {
339
+ id: 'msg-1',
340
+ type: 'regular',
341
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
342
+ user: { id: 'participant-1', name: 'Alice' },
343
+ metadata: {
344
+ custom_type: 'MESSAGE_ATTACHMENT',
345
+ attachment_content_type: 'media',
346
+ payment_status: 'pending',
347
+ },
348
+ },
349
+ ])
350
+
351
+ renderWithProviders(
352
+ <CustomChannelPreview {...defaultProps} channel={channel} />
353
+ )
354
+
355
+ expect(screen.getByText('🔒 Sent a locked attachment')).toBeInTheDocument()
356
+ })
357
+
263
358
  it('updates starred state when the channel membership changes', () => {
264
359
  const channel = createMockChannel([])
265
360
 
@@ -1,6 +1,9 @@
1
1
  import classNames from 'classnames'
2
2
  import React from 'react'
3
- import { ChannelPreviewUIComponentProps, useChatContext } from 'stream-chat-react'
3
+ import {
4
+ ChannelPreviewUIComponentProps,
5
+ useChatContext,
6
+ } from 'stream-chat-react'
4
7
 
5
8
  import { useChannelStar } from '../../hooks/useChannelStar'
6
9
  import { formatRelativeTime } from '../../utils/formatRelativeTime'
@@ -85,14 +88,23 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
85
88
  viewerLanguage,
86
89
  })
87
90
 
88
- const isTip = lastMessage?.metadata?.custom_type === 'MESSAGE_TIP'
89
- if (isTip) return displayText ? `💵 ${displayText}` : '💵 Sent a tip'
90
- const isPaid = lastMessage?.metadata?.custom_type === 'MESSAGE_PAID'
91
- if (isPaid) return displayText ? `💰 ${displayText}` : '💰 Sent a message'
92
- const isPaidAttachment =
93
- lastMessage?.metadata?.custom_type === 'MESSAGE_ATTACHMENT'
94
- if (isPaidAttachment)
95
- return displayText ? `📎 ${displayText}` : '📎 Sent an attachment'
91
+ const { custom_type, attachment_content_type } =
92
+ lastMessage?.metadata || {}
93
+
94
+ if (custom_type === 'MESSAGE_TIP') {
95
+ return displayText ? displayText : 'Sent a tip'
96
+ }
97
+ if (custom_type === 'MESSAGE_PAID') {
98
+ return displayText ? displayText : 'Sent a message'
99
+ }
100
+ if (custom_type === 'MESSAGE_ATTACHMENT') {
101
+ if (attachment_content_type === 'text') {
102
+ return '🔒 Sent a locked message'
103
+ }
104
+ if (attachment_content_type === 'media') {
105
+ return '🔒 Sent a locked attachment'
106
+ }
107
+ }
96
108
 
97
109
  if (displayText) return displayText
98
110
 
@@ -125,6 +125,52 @@ describe('ChannelList', () => {
125
125
  })
126
126
  })
127
127
 
128
+ it('leaves live-reorder controls undefined by default so Stream keeps its default ordering', () => {
129
+ renderWithProviders(<ChannelList {...defaultProps} />)
130
+
131
+ expect(streamChannelListMock).toHaveBeenCalledOnce()
132
+ const streamProps = streamChannelListMock.mock.calls[0][0] as Record<
133
+ string,
134
+ unknown
135
+ >
136
+ expect(streamProps.lockChannelOrder).toBeUndefined()
137
+ expect(streamProps.onMessageNewHandler).toBeUndefined()
138
+ expect(streamProps.onMessageNew).toBeUndefined()
139
+ expect(streamProps.onChannelVisible).toBeUndefined()
140
+ expect(streamProps.onAddedToChannel).toBeUndefined()
141
+ expect(streamProps.onChannelUpdated).toBeUndefined()
142
+ })
143
+
144
+ it('forwards live-reorder controls to Stream ChannelList when provided', () => {
145
+ const onMessageNewHandler = vi.fn()
146
+ const onMessageNew = vi.fn()
147
+ const onChannelVisible = vi.fn()
148
+ const onAddedToChannel = vi.fn()
149
+ const onChannelUpdated = vi.fn()
150
+
151
+ renderWithProviders(
152
+ <ChannelList
153
+ {...defaultProps}
154
+ lockChannelOrder
155
+ onMessageNewHandler={onMessageNewHandler}
156
+ onMessageNew={onMessageNew}
157
+ onChannelVisible={onChannelVisible}
158
+ onAddedToChannel={onAddedToChannel}
159
+ onChannelUpdated={onChannelUpdated}
160
+ />
161
+ )
162
+
163
+ expect(streamChannelListMock).toHaveBeenCalledOnce()
164
+ expect(streamChannelListMock.mock.calls[0][0]).toMatchObject({
165
+ lockChannelOrder: true,
166
+ onMessageNewHandler,
167
+ onMessageNew,
168
+ onChannelVisible,
169
+ onAddedToChannel,
170
+ onChannelUpdated,
171
+ })
172
+ })
173
+
128
174
  it('wraps channelRenderFilterFn to restore pending messages and delegates to consumer filter', () => {
129
175
  const filterFn = vi.fn((channels: Channel[]) => channels)
130
176
 
@@ -32,6 +32,12 @@ export const ChannelList = React.memo<ChannelListProps>(
32
32
  channelPreview,
33
33
  renderMessagePreview,
34
34
  viewerLanguage,
35
+ lockChannelOrder,
36
+ onMessageNewHandler,
37
+ onMessageNew,
38
+ onChannelVisible,
39
+ onAddedToChannel,
40
+ onChannelUpdated,
35
41
  }) => {
36
42
  // Track renders
37
43
  const renderCountRef = React.useRef(0)
@@ -100,6 +106,12 @@ export const ChannelList = React.memo<ChannelListProps>(
100
106
  allowNewMessagesFromUnfilteredChannels
101
107
  }
102
108
  channelRenderFilterFn={wrappedChannelRenderFilterFn}
109
+ lockChannelOrder={lockChannelOrder}
110
+ onMessageNewHandler={onMessageNewHandler}
111
+ onMessageNew={onMessageNew}
112
+ onChannelVisible={onChannelVisible}
113
+ onAddedToChannel={onAddedToChannel}
114
+ onChannelUpdated={onChannelUpdated}
103
115
  Paginator={Paginator}
104
116
  Preview={CustomChannelPreview}
105
117
  EmptyStateIndicator={customEmptyStateIndicator}