@linktr.ee/messaging-react 3.6.2-rc-1783314663 → 3.6.2-rc-1783332217

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 (34) hide show
  1. package/dist/{Card-ILtY9lfF.js → Card-0wf8lj5n.js} +2 -2
  2. package/dist/{Card-ILtY9lfF.js.map → Card-0wf8lj5n.js.map} +1 -1
  3. package/dist/{Card-CLfoYI1R.cjs → Card-4UyBK3XO.cjs} +2 -2
  4. package/dist/{Card-CLfoYI1R.cjs.map → Card-4UyBK3XO.cjs.map} +1 -1
  5. package/dist/{Card-UN0QRlxk.cjs → Card-6b4Ws9rn.cjs} +2 -2
  6. package/dist/{Card-UN0QRlxk.cjs.map → Card-6b4Ws9rn.cjs.map} +1 -1
  7. package/dist/{Card-CSkWY9mH.cjs → Card-DsQhJjj0.cjs} +2 -2
  8. package/dist/{Card-CSkWY9mH.cjs.map → Card-DsQhJjj0.cjs.map} +1 -1
  9. package/dist/{Card-Byosl29s.js → Card-RZ6C_IkE.js} +3 -3
  10. package/dist/{Card-Byosl29s.js.map → Card-RZ6C_IkE.js.map} +1 -1
  11. package/dist/{Card-CqEKHw9S.js → Card-iGNcxybY.js} +2 -2
  12. package/dist/{Card-CqEKHw9S.js.map → Card-iGNcxybY.js.map} +1 -1
  13. package/dist/{LockedThumbnail-BKI_6JUF.js → LockedThumbnail-Do9udNMm.js} +2 -2
  14. package/dist/{LockedThumbnail-BKI_6JUF.js.map → LockedThumbnail-Do9udNMm.js.map} +1 -1
  15. package/dist/{LockedThumbnail-DZ6C2xsr.cjs → LockedThumbnail-N0pNOO3D.cjs} +2 -2
  16. package/dist/{LockedThumbnail-DZ6C2xsr.cjs.map → LockedThumbnail-N0pNOO3D.cjs.map} +1 -1
  17. package/dist/assets/index.css +1 -1
  18. package/dist/index-B3ZJaqJY.cjs +2 -0
  19. package/dist/index-B3ZJaqJY.cjs.map +1 -0
  20. package/dist/{index-DPwo1avm.js → index-wFe1UO9t.js} +788 -780
  21. package/dist/index-wFe1UO9t.js.map +1 -0
  22. package/dist/index.cjs +1 -1
  23. package/dist/index.js +1 -1
  24. package/package.json +2 -2
  25. package/src/components/CustomMessage/CustomMessage.stories.tsx +11 -18
  26. package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +107 -0
  27. package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +86 -0
  28. package/src/components/CustomMessage/index.tsx +2 -2
  29. package/src/styles.css +25 -16
  30. package/dist/index-DPwo1avm.js.map +0 -1
  31. package/dist/index-Q0FmgUJF.cjs +0 -2
  32. package/dist/index-Q0FmgUJF.cjs.map +0 -1
  33. package/src/components/CustomMessage/MessageDeliveryStatus.test.tsx +0 -60
  34. package/src/components/CustomMessage/MessageDeliveryStatus.tsx +0 -43
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-Q0FmgUJF.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.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-B3ZJaqJY.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.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.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 c, i as u, 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 P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-DPwo1avm.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 c, i as u, 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 P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-wFe1UO9t.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.6.2-rc-1783314663",
3
+ "version": "3.6.2-rc-1783332217",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@linktr.ee/component-library": "11.8.6",
41
- "@linktr.ee/messaging-core": "2.3.0-rc-1783314663",
41
+ "@linktr.ee/messaging-core": "2.3.0-rc-1783332217",
42
42
  "@phosphor-icons/react": "^2.1.10"
43
43
  },
44
44
  "devDependencies": {
@@ -225,43 +225,36 @@ Default.args = {
225
225
  ],
226
226
  }
227
227
 
228
- // MES-1036: "✓✓ Delivered" appears below the last bubble of *every* message
229
- // group, on both the received (left) and sent (right) sides. Every mock
230
- // message carries status: 'received'; the indicator is gated on stream's
231
- // `endOfGroup`, so it shows once per consecutive same-author run here below
232
- // "Hey there!" (received), "Talk soon!" (sent), and "See you then." (sent
233
- // run of two).
228
+ // MES-1036: "✓✓ Delivered" appears below only the most recent *sent*
229
+ // message. Every mock message carries status: 'received', and stream's
230
+ // MessageList derives `lastOwnMessage`, so the indicator lands on the last
231
+ // creator message ("Talk soon!") and not on the earlier sent bubble.
234
232
  export const DeliveredStatus: StoryFn<TemplateProps> = Template.bind({})
235
233
  DeliveredStatus.args = {
236
234
  messages: [
237
235
  {
238
236
  id: 'msg-1',
239
- text: 'Hey there!',
237
+ text: 'Hey, did you get my last message?',
240
238
  user: storyUsers.visitor,
241
239
  },
242
240
  {
243
241
  id: 'msg-2',
244
- text: 'Hi! Great to hear from you.',
242
+ text: 'Yes! Just replying now.',
245
243
  user: storyUsers.creator,
246
244
  },
247
245
  {
248
246
  id: 'msg-3',
249
- text: 'Talk soon!',
250
- user: storyUsers.creator,
251
- },
252
- {
253
- id: 'msg-4',
254
- text: 'Sounds good — catch up tomorrow?',
247
+ text: 'Sounds good.',
255
248
  user: storyUsers.visitor,
256
249
  },
257
250
  {
258
- id: 'msg-5',
259
- text: 'Absolutely.',
251
+ id: 'msg-4',
252
+ text: 'Let me know if you need anything else.',
260
253
  user: storyUsers.creator,
261
254
  },
262
255
  {
263
- id: 'msg-6',
264
- text: 'See you then.',
256
+ id: 'msg-5',
257
+ text: 'Talk soon!',
265
258
  user: storyUsers.creator,
266
259
  },
267
260
  ],
@@ -0,0 +1,107 @@
1
+ import React from 'react'
2
+ import {
3
+ useChannelStateContext,
4
+ useChatContext,
5
+ useMessageContext,
6
+ } from 'stream-chat-react'
7
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
8
+
9
+ import { renderWithProviders, screen, within } from '../../test/utils'
10
+
11
+ import { SentMessageDeliveryStatus } from './SentMessageDeliveryStatus'
12
+
13
+ // stream-chat-react's `MessageStatus` owns the outer gating (own message, not
14
+ // error, not in a thread) and only exposes a terminal state once a message is
15
+ // stored — Stream's tested code. What this component owns is the "Delivered"
16
+ // label wired into the terminal slots (sent/delivered/read), its "last own
17
+ // message" gate derived from live channel state, and suppressing the sending
18
+ // spinner. Mock `MessageStatus` to render the slots it was handed, and the
19
+ // context hooks the label reads.
20
+ vi.mock('stream-chat-react', () => ({
21
+ useMessageContext: vi.fn(),
22
+ useChannelStateContext: vi.fn(),
23
+ useChatContext: vi.fn(),
24
+ MessageStatus: ({
25
+ MessageSentStatus,
26
+ MessageDeliveredStatus,
27
+ MessageReadStatus,
28
+ MessageSendingStatus,
29
+ }: Record<string, React.ComponentType>) => (
30
+ <div>
31
+ <div data-testid="sent-slot">
32
+ <MessageSentStatus />
33
+ </div>
34
+ <div data-testid="delivered-slot">
35
+ <MessageDeliveredStatus />
36
+ </div>
37
+ <div data-testid="read-slot">
38
+ <MessageReadStatus />
39
+ </div>
40
+ <div data-testid="sending-slot">
41
+ <MessageSendingStatus />
42
+ </div>
43
+ </div>
44
+ ),
45
+ }))
46
+
47
+ const mockedUseMessageContext = vi.mocked(useMessageContext)
48
+ const mockedUseChannelStateContext = vi.mocked(useChannelStateContext)
49
+ const mockedUseChatContext = vi.mocked(useChatContext)
50
+
51
+ // A run where the viewer ('me') sent the first and last messages, with an
52
+ // incoming message in between — the case that broke the memo-based approach.
53
+ const CHANNEL_MESSAGES = [
54
+ { id: 'own-1', user: { id: 'me' } },
55
+ { id: 'their-1', user: { id: 'them' } },
56
+ { id: 'own-2', user: { id: 'me' } },
57
+ ]
58
+
59
+ const TERMINAL_SLOTS = ['sent-slot', 'delivered-slot', 'read-slot'] as const
60
+
61
+ describe('SentMessageDeliveryStatus', () => {
62
+ beforeEach(() => {
63
+ mockedUseChatContext.mockReturnValue({ client: { user: { id: 'me' } } } as never)
64
+ mockedUseChannelStateContext.mockReturnValue({
65
+ messages: CHANNEL_MESSAGES,
66
+ } as never)
67
+ })
68
+
69
+ it('renders "Delivered" in every terminal slot for the last own message', () => {
70
+ // delivered/read fire ahead of sent in receipt channels, so all three
71
+ // must show the label — never Stream's default delivered icon / read
72
+ // avatars.
73
+ mockedUseMessageContext.mockReturnValue({
74
+ message: { id: 'own-2' },
75
+ } as never)
76
+
77
+ renderWithProviders(<SentMessageDeliveryStatus />)
78
+
79
+ for (const slot of TERMINAL_SLOTS) {
80
+ expect(
81
+ within(screen.getByTestId(slot)).getByText('Delivered')
82
+ ).toBeInTheDocument()
83
+ }
84
+ })
85
+
86
+ it('renders nothing for an earlier own message even though one is the last own message', () => {
87
+ mockedUseMessageContext.mockReturnValue({
88
+ message: { id: 'own-1' },
89
+ } as never)
90
+
91
+ renderWithProviders(<SentMessageDeliveryStatus />)
92
+
93
+ for (const slot of TERMINAL_SLOTS) {
94
+ expect(screen.getByTestId(slot)).toBeEmptyDOMElement()
95
+ }
96
+ })
97
+
98
+ it('always suppresses the sending spinner', () => {
99
+ mockedUseMessageContext.mockReturnValue({
100
+ message: { id: 'own-2' },
101
+ } as never)
102
+
103
+ renderWithProviders(<SentMessageDeliveryStatus />)
104
+
105
+ expect(screen.getByTestId('sending-slot')).toBeEmptyDOMElement()
106
+ })
107
+ })
@@ -0,0 +1,86 @@
1
+ import { ChecksIcon } from '@phosphor-icons/react'
2
+ import { useMemo } from 'react'
3
+ import {
4
+ MessageStatus,
5
+ useChannelStateContext,
6
+ useChatContext,
7
+ useMessageContext,
8
+ } from 'stream-chat-react'
9
+
10
+ /**
11
+ * "✓✓ Delivered" label, wired into *every* terminal `MessageStatus` state
12
+ * (sent / delivered / read).
13
+ *
14
+ * Two reasons this is more than a static label:
15
+ *
16
+ * 1. `MessageStatus` picks its `delivered`/`read` branches ahead of `sent`
17
+ * whenever Stream has `deliveredTo`/`readBy`, and — unlike `sent` — those
18
+ * branches aren't gated to the last own message. So we override all three
19
+ * terminal slots with this same label (never Stream's default delivered
20
+ * icon / read avatars — Read is out of scope) and re-apply the "last own
21
+ * message" gate ourselves.
22
+ *
23
+ * 2. We derive "last own message" from **live channel state**, not the
24
+ * message context's `lastOwnMessage`. Stream's `Message` is memoised by
25
+ * `areMessagePropsEqual`, which does not compare `lastOwnMessage`; a
26
+ * previous own row whose message/grouping props are unchanged (e.g. after
27
+ * an intervening incoming message) never re-renders, so its context
28
+ * `lastOwnMessage` goes stale and the old bubble would keep showing
29
+ * "Delivered" beside the new one. `useChannelStateContext` gets a fresh
30
+ * `messages` array on every new message and, being a context, re-renders
31
+ * this leaf straight through the stale `Message`/`MessageStatus` memos —
32
+ * so the label reliably hops to the newest own message.
33
+ *
34
+ * Rendered inside stream's `.str-chat__message-status` span, which supplies
35
+ * the flex layout; icon and copy are styled via that span in `styles.css`.
36
+ */
37
+ const DeliveredStatus = () => {
38
+ const { message } = useMessageContext('SentMessageDeliveryStatus')
39
+ const { messages } = useChannelStateContext('SentMessageDeliveryStatus')
40
+ const { client } = useChatContext('SentMessageDeliveryStatus')
41
+
42
+ const ownUserId = client.user?.id
43
+ const lastOwnMessageId = useMemo(() => {
44
+ if (!ownUserId || !messages) return undefined
45
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
46
+ if (messages[i].user?.id === ownUserId) return messages[i].id
47
+ }
48
+ return undefined
49
+ }, [messages, ownUserId])
50
+
51
+ if (message.id !== lastOwnMessageId) {
52
+ return null
53
+ }
54
+
55
+ return (
56
+ <>
57
+ <ChecksIcon weight="bold" aria-hidden="true" />
58
+ <span data-testid="sent-message-delivery-status">Delivered</span>
59
+ </>
60
+ )
61
+ }
62
+
63
+ /** Suppresses a MessageStatus slot we don't want to render. */
64
+ const NoStatus = () => null
65
+
66
+ /**
67
+ * Delivery status beneath the viewer's most recent sent message (MES-1036):
68
+ * a right-aligned "✓✓ Delivered" that appears once Stream confirms the
69
+ * message is stored and updates live.
70
+ *
71
+ * Built on stream-chat-react's `MessageStatus`, which owns the gating we keep
72
+ * — own message, not an error, not inside a thread — and only surfaces a
73
+ * terminal (sending/sent/delivered/read) state once the message is stored.
74
+ * Scope is "Delivered" only: the sending spinner is suppressed and all
75
+ * terminal states render {@link DeliveredStatus}, which supplies the label
76
+ * and the last-own-message gate (see there for why the gate is computed from
77
+ * live channel state rather than the context's `lastOwnMessage`).
78
+ */
79
+ export const SentMessageDeliveryStatus = () => (
80
+ <MessageStatus
81
+ MessageSentStatus={DeliveredStatus}
82
+ MessageDeliveredStatus={DeliveredStatus}
83
+ MessageReadStatus={DeliveredStatus}
84
+ MessageSendingStatus={NoStatus}
85
+ />
86
+ )
@@ -39,7 +39,6 @@ import {
39
39
  } from '../MessageAttachment'
40
40
 
41
41
  import { useCustomMessage } from './context'
42
- import { MessageDeliveryStatus } from './MessageDeliveryStatus'
43
42
  import {
44
43
  MessageTag,
45
44
  isAttachmentMessage,
@@ -47,6 +46,7 @@ import {
47
46
  isTipOnlyMessage,
48
47
  } from './MessageTag'
49
48
  import { MessageVoteButtons } from './MessageVoteButtons'
49
+ import { SentMessageDeliveryStatus } from './SentMessageDeliveryStatus'
50
50
  import StreamAttachmentMessage, {
51
51
  buildOrderedAttachmentSegments,
52
52
  } from './StreamAttachmentMessage'
@@ -398,7 +398,7 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
398
398
  )}
399
399
  </div>
400
400
  )}
401
- <MessageDeliveryStatus message={message} endOfGroup={endOfGroup} />
401
+ <SentMessageDeliveryStatus />
402
402
  {showReplyCountButton && (
403
403
  <MessageRepliesCountButton
404
404
  onClick={handleOpenThread}
package/src/styles.css CHANGED
@@ -333,16 +333,12 @@
333
333
  grid-column-gap: 4px;
334
334
  }
335
335
 
336
- /* Outer grid for other (incoming) messages — footer + delivery-status rows
337
- after message. `delivery-status` carries the "Delivered" status on the last
338
- bubble of each incoming group (MES-1036); it aligns under the message, not
339
- the avatar, and collapses to 0 height on messages that don't render it. */
336
+ /* Outer grid for other (incoming) messages — add footer row after message */
340
337
  .str-chat__li .str-chat__message--other {
341
338
  grid-template-areas:
342
339
  '. message-reminder'
343
340
  'avatar message'
344
341
  '. footer'
345
- '. delivery-status'
346
342
  '. replies'
347
343
  '. translation-notice'
348
344
  '. custom-metadata'
@@ -356,9 +352,9 @@
356
352
  }
357
353
 
358
354
  /* Outer grid for me (outgoing) messages — footer + delivery-status rows
359
- after message. `delivery-status` carries the "Delivered" status on the last
360
- bubble of each outgoing group (MES-1036); the row collapses to 0 height on
361
- messages that don't render it. */
355
+ after message. `delivery-status` carries the "Delivered" receipt on the
356
+ last sent message (MES-1036); the row collapses to 0 height on messages
357
+ that don't render it. */
362
358
  .str-chat__li .str-chat__message--me {
363
359
  grid-template-areas:
364
360
  'message-reminder'
@@ -420,21 +416,34 @@
420
416
  align-items: flex-end;
421
417
  }
422
418
 
423
- /* "✓✓ Delivered" delivery status beneath the last bubble of each message
424
- group (MES-1036), for both sent and received messages. It sits in the
425
- `delivery-status` grid row and inherits left/right alignment from each
426
- side's grid `justify-items` (start for `--other`, end for `--me`). */
427
- .str-chat__li .str-chat__message-delivery-status {
419
+ /* "✓✓ Delivered" delivery status under the last sent message (MES-1036).
420
+ `SentMessageDeliveryStatus` renders it through stream's `MessageStatus`,
421
+ whose `.str-chat__message-status` span is the grid item here (right-aligned
422
+ via the outer grid's `justify-items: end`). Stream emits that span for every
423
+ own message and leaves it empty when no state is active (i.e. not the last
424
+ sent message), so `:empty` collapses those away. */
425
+ .str-chat__li .str-chat__message--me .str-chat__message-status {
428
426
  grid-area: delivery-status;
429
- display: inline-flex;
430
- align-items: center;
427
+ justify-content: flex-end;
431
428
  gap: 2px;
432
429
  padding-block-start: 2px;
433
- padding-inline: 4px;
430
+ padding-inline-end: 4px;
434
431
  font-size: 12px;
435
432
  line-height: 1.4;
436
433
  color: var(--str-chat__text-low-emphasis-color, rgba(0, 0, 0, 0.5));
437
434
  }
435
+ .str-chat__li .str-chat__message-status:empty {
436
+ display: none;
437
+ }
438
+ /* Match the check icon to the label size/colour (overrides stream's default
439
+ per-state icon sizing and status-colour fill). */
440
+ .str-chat__li .str-chat__message--me .str-chat__message-status svg {
441
+ width: 14px;
442
+ height: 14px;
443
+ }
444
+ .str-chat__li .str-chat__message--me .str-chat__message-status svg path {
445
+ fill: currentColor;
446
+ }
438
447
 
439
448
  /* Channel list load-more button overrides */
440
449
  .str-chat__channel-list .str-chat__load-more-button__button {