@linktr.ee/messaging-react 3.15.3 → 3.16.0-rc-1785214411

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.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BfEoDUXO.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.optimizeMessagingAttachmentUrl=e.optimizeMessagingAttachmentUrl;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-2STuN782.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.optimizeMessagingAttachmentUrl=e.optimizeMessagingAttachmentUrl;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 p, m as d, n as v, p as C, q as A, s as k, t as F, u as b, v as P, w as f, x, o as y, y as z, z as B, B as S, D as q, E as D, G as E, H as V, I as w } from "./index-B5xEngCd.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 p, m as d, n as v, p as C, q as A, s as k, t as F, u as b, v as P, w as f, x, o as y, y as z, z as B, B as S, D as q, E as D, G as E, H as V, I as w } from "./index-vYOR0x6o.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.15.3",
3
+ "version": "3.16.0-rc-1785214411",
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-1785214411",
54
54
  "@phosphor-icons/react": "^2.1.10"
55
55
  },
56
56
  "devDependencies": {
@@ -13,8 +13,8 @@ import { SentMessageDeliveryStatus } from './SentMessageDeliveryStatus'
13
13
  // error, not in a thread) and only exposes a terminal state once a message is
14
14
  // stored — Stream's tested code. What this component owns is the "Delivered"
15
15
  // label wired into the terminal slots (sent/delivered/read), its "latest
16
- // message in the thread" gate derived from live channel state, and suppressing
17
- // the sending spinner. Mock `MessageStatus` to render the slots it was handed,
16
+ // message in the thread" gate derived from live channel state. Mock
17
+ // `MessageStatus` to render the slots it was handed,
18
18
  // and the context hooks the label reads.
19
19
  vi.mock('stream-chat-react', () => ({
20
20
  useMessageContext: vi.fn(),
@@ -135,13 +135,63 @@ describe('SentMessageDeliveryStatus', () => {
135
135
  }
136
136
  })
137
137
 
138
- it('always suppresses the sending spinner', () => {
138
+ it('renders "Sending" in the sending slot for the latest message', () => {
139
139
  mockedUseMessageContext.mockReturnValue({
140
- message: { id: 'own-2' },
140
+ message: { id: 'own-2', status: 'sending' },
141
+ } as never)
142
+
143
+ renderWithProviders(<SentMessageDeliveryStatus />)
144
+
145
+ expect(screen.getByTestId('sending-slot')).toHaveTextContent(/sending/i)
146
+ })
147
+
148
+ it('does not render "Sending" for an earlier message', () => {
149
+ mockedUseMessageContext.mockReturnValue({
150
+ message: { id: 'own-1', status: 'sending' },
141
151
  } as never)
142
152
 
143
153
  renderWithProviders(<SentMessageDeliveryStatus />)
144
154
 
145
155
  expect(screen.getByTestId('sending-slot')).toBeEmptyDOMElement()
146
156
  })
157
+
158
+ it('renders "Failed to send" for every own failed message', () => {
159
+ mockedUseMessageContext.mockReturnValue({
160
+ message: { id: 'own-1', status: 'failed', type: 'regular' },
161
+ isMyMessage: () => true,
162
+ threadList: false,
163
+ } as never)
164
+
165
+ renderWithProviders(<SentMessageDeliveryStatus />)
166
+
167
+ expect(screen.getByTestId('sent-message-failed-status')).toHaveTextContent(
168
+ /failed to send/i
169
+ )
170
+ expect(screen.queryByTestId('sending-slot')).not.toBeInTheDocument()
171
+ })
172
+
173
+ it('does not render failure status for a received message or thread message', () => {
174
+ mockedUseMessageContext.mockReturnValue({
175
+ message: { id: 'own-1', status: 'failed', type: 'regular' },
176
+ isMyMessage: () => false,
177
+ threadList: false,
178
+ } as never)
179
+
180
+ const { rerender } = renderWithProviders(<SentMessageDeliveryStatus />)
181
+
182
+ expect(
183
+ screen.queryByTestId('sent-message-failed-status')
184
+ ).not.toBeInTheDocument()
185
+
186
+ mockedUseMessageContext.mockReturnValue({
187
+ message: { id: 'own-1', status: 'failed', type: 'regular' },
188
+ isMyMessage: () => true,
189
+ threadList: true,
190
+ } as never)
191
+ rerender(<SentMessageDeliveryStatus />)
192
+
193
+ expect(
194
+ screen.queryByTestId('sent-message-failed-status')
195
+ ).not.toBeInTheDocument()
196
+ })
147
197
  })
@@ -1,4 +1,8 @@
1
- import { ChecksIcon } from '@phosphor-icons/react'
1
+ import {
2
+ ChecksIcon,
3
+ CircleNotchIcon,
4
+ WarningCircleIcon,
5
+ } from '@phosphor-icons/react'
2
6
  import {
3
7
  MessageStatus,
4
8
  useChannelStateContext,
@@ -6,8 +10,8 @@ import {
6
10
  } from 'stream-chat-react'
7
11
 
8
12
  /**
9
- * "✓✓ Delivered" label, wired into *every* terminal `MessageStatus` state
10
- * (sent / delivered / read).
13
+ * Message status stamps for sending and terminal `MessageStatus` states
14
+ * (sent / delivered / read), plus failed messages.
11
15
  *
12
16
  * Two reasons this is more than a static label:
13
17
  *
@@ -16,9 +20,7 @@ import {
16
20
  * branches aren't gated to the last message. So we override all three
17
21
  * terminal slots with this same label (never Stream's default delivered
18
22
  * icon / read avatars — Read is out of scope) and re-apply the "latest
19
- * message in the thread" gate ourselves: the label only shows beneath the
20
- * viewer's sent message while it is still the last message in the channel,
21
- * and disappears as soon as a reply arrives after it.
23
+ * message in the thread" gate ourselves.
22
24
  *
23
25
  * 2. We derive "latest message" from **live channel state**, not the message
24
26
  * context's `lastOwnMessage`/`lastReceivedId`. Stream's `Message` is
@@ -36,7 +38,7 @@ import {
36
38
  * Rendered inside stream's `.str-chat__message-status` span, which supplies
37
39
  * the flex layout; icon and copy are styled via that span in `styles.css`.
38
40
  */
39
- const DeliveredStatus = () => {
41
+ const useIsLatestRealMessage = () => {
40
42
  const { message } = useMessageContext('SentMessageDeliveryStatus')
41
43
  const { messages } = useChannelStateContext('SentMessageDeliveryStatus')
42
44
 
@@ -62,9 +64,11 @@ const DeliveredStatus = () => {
62
64
  return undefined
63
65
  })()
64
66
 
65
- if (message.id !== lastMessageId) {
66
- return null
67
- }
67
+ return message.id === lastMessageId
68
+ }
69
+
70
+ const DeliveredStatus = () => {
71
+ if (!useIsLatestRealMessage()) return null
68
72
 
69
73
  return (
70
74
  <>
@@ -74,8 +78,27 @@ const DeliveredStatus = () => {
74
78
  )
75
79
  }
76
80
 
77
- /** Suppresses a MessageStatus slot we don't want to render. */
78
- const NoStatus = () => null
81
+ const SendingStatus = () => {
82
+ if (!useIsLatestRealMessage()) return null
83
+
84
+ return (
85
+ <>
86
+ <CircleNotchIcon
87
+ className="sent-message-status__spinner animate-spin"
88
+ weight="bold"
89
+ aria-hidden="true"
90
+ />
91
+ <span data-testid="sent-message-sending-status">Sending</span>
92
+ </>
93
+ )
94
+ }
95
+
96
+ const FailedStatus = () => (
97
+ <span className="str-chat__message-status sent-message-status--failed">
98
+ <WarningCircleIcon weight="bold" aria-hidden="true" />
99
+ <span data-testid="sent-message-failed-status">Failed to send</span>
100
+ </span>
101
+ )
79
102
 
80
103
  /**
81
104
  * Delivery status beneath the viewer's most recent sent message (MES-1036):
@@ -85,16 +108,30 @@ const NoStatus = () => null
85
108
  * Built on stream-chat-react's `MessageStatus`, which owns the gating we keep
86
109
  * — own message, not an error, not inside a thread — and only surfaces a
87
110
  * terminal (sending/sent/delivered/read) state once the message is stored.
88
- * Scope is "Delivered" only: the sending spinner is suppressed and all
89
- * terminal states render {@link DeliveredStatus}, which supplies the label
90
- * and the latest-message-in-thread gate (see there for why the gate is
91
- * computed from live channel state rather than the context's props).
111
+ * Sending uses the same latest-message-in-thread gate, while failed messages
112
+ * bypass `MessageStatus` because it has no failed slot and are shown on every
113
+ * own, non-thread failed message.
92
114
  */
93
- export const SentMessageDeliveryStatus = () => (
94
- <MessageStatus
95
- MessageSentStatus={DeliveredStatus}
96
- MessageDeliveredStatus={DeliveredStatus}
97
- MessageReadStatus={DeliveredStatus}
98
- MessageSendingStatus={NoStatus}
99
- />
100
- )
115
+ export const SentMessageDeliveryStatus = () => {
116
+ const { isMyMessage, message, threadList } = useMessageContext(
117
+ 'SentMessageDeliveryStatus'
118
+ )
119
+
120
+ if (
121
+ message.status === 'failed' &&
122
+ isMyMessage() &&
123
+ message.type !== 'error' &&
124
+ !threadList
125
+ ) {
126
+ return <FailedStatus />
127
+ }
128
+
129
+ return (
130
+ <MessageStatus
131
+ MessageSentStatus={DeliveredStatus}
132
+ MessageDeliveredStatus={DeliveredStatus}
133
+ MessageReadStatus={DeliveredStatus}
134
+ MessageSendingStatus={SendingStatus}
135
+ />
136
+ )
137
+ }
package/src/styles.css CHANGED
@@ -583,7 +583,7 @@
583
583
  align-items: flex-end;
584
584
  }
585
585
 
586
- /* "✓✓ Delivered" delivery status under the last sent message (MES-1036).
586
+ /* Message delivery status under sent messages (MES-1036/MES-1296).
587
587
  `SentMessageDeliveryStatus` renders it through stream's `MessageStatus`,
588
588
  whose `.str-chat__message-status` span is the grid item here (right-aligned
589
589
  via the outer grid's `justify-items: end`). Stream emits that span for every
@@ -595,7 +595,7 @@
595
595
  gap: 4px;
596
596
  padding-block-start: 4px;
597
597
  padding-inline-end: 16px;
598
- font-size: 12px;
598
+ font-size: 10px;
599
599
  line-height: 1.4;
600
600
  color: var(--str-chat__text-low-emphasis-color, rgba(0, 0, 0, 0.5));
601
601
  }
@@ -611,6 +611,9 @@
611
611
  .str-chat__li .str-chat__message--me .str-chat__message-status svg path {
612
612
  fill: currentColor;
613
613
  }
614
+ .str-chat__li .str-chat__message--me .sent-message-status--failed {
615
+ color: var(--str-chat__message-error-color, #d92d20);
616
+ }
614
617
 
615
618
  /* Channel list load-more button overrides */
616
619
  .str-chat__channel-list .str-chat__load-more-button__button {