@linktr.ee/messaging-react 3.1.0-rc-1780514752 → 3.1.0

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 (31) hide show
  1. package/dist/{Card-DZVa2CeI.js → Card-BGOWR4lW.js} +2 -2
  2. package/dist/{Card-DZVa2CeI.js.map → Card-BGOWR4lW.js.map} +1 -1
  3. package/dist/{Card-0TLA8XHU.js → Card-BfvsO78k.js} +3 -3
  4. package/dist/{Card-0TLA8XHU.js.map → Card-BfvsO78k.js.map} +1 -1
  5. package/dist/{Card-BaaerKBC.js → Card-BhO5jeP9.js} +2 -2
  6. package/dist/{Card-BaaerKBC.js.map → Card-BhO5jeP9.js.map} +1 -1
  7. package/dist/{Card-Bfxdewx_.cjs → Card-CRJ4l5KM.cjs} +2 -2
  8. package/dist/{Card-Bfxdewx_.cjs.map → Card-CRJ4l5KM.cjs.map} +1 -1
  9. package/dist/{Card-DbdWDBMe.cjs → Card-Cq-cN9n1.cjs} +2 -2
  10. package/dist/{Card-DbdWDBMe.cjs.map → Card-Cq-cN9n1.cjs.map} +1 -1
  11. package/dist/{Card-B-D_LbnV.cjs → Card-NPXVehHb.cjs} +2 -2
  12. package/dist/{Card-B-D_LbnV.cjs.map → Card-NPXVehHb.cjs.map} +1 -1
  13. package/dist/{LockedThumbnail-DkwFwgpU.cjs → LockedThumbnail-B8MKBVXz.cjs} +2 -2
  14. package/dist/{LockedThumbnail-DkwFwgpU.cjs.map → LockedThumbnail-B8MKBVXz.cjs.map} +1 -1
  15. package/dist/{LockedThumbnail-B4gDHeh7.js → LockedThumbnail-Bu9jNPUi.js} +2 -2
  16. package/dist/{LockedThumbnail-B4gDHeh7.js.map → LockedThumbnail-Bu9jNPUi.js.map} +1 -1
  17. package/dist/{index-BmCc1-F3.js → index-CJEl_fID.js} +503 -506
  18. package/dist/index-CJEl_fID.js.map +1 -0
  19. package/dist/index-D-5Igybf.cjs +2 -0
  20. package/dist/index-D-5Igybf.cjs.map +1 -0
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.ts +9 -6
  23. package/dist/index.js +1 -1
  24. package/package.json +1 -1
  25. package/src/components/ChannelView.stories.tsx +5 -6
  26. package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +21 -23
  27. package/src/components/CustomMessageInput/index.tsx +42 -24
  28. package/src/types.ts +9 -6
  29. package/dist/index-BmCc1-F3.js.map +0 -1
  30. package/dist/index-Cg-bxSZn.cjs +0 -2
  31. package/dist/index-Cg-bxSZn.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-Cg-bxSZn.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.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;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-D-5Igybf.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.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -200,15 +200,18 @@ export declare interface ChannelViewProps {
200
200
  */
201
201
  showFollowerStatus?: boolean;
202
202
  /**
203
- * Lock the message composer (read-only, send disabled). Defaults to false.
204
- * Combined with the channel's `frozen` flag either one locks the input.
205
- * Used by the Linktree official channel, where the composer stays locked
206
- * until the linker messages Linktree from its public profile.
203
+ * Replace the message composer with a non-interactive locked panel showing
204
+ * `composerDisabledReason`. Defaults to false. Used by the Linktree official
205
+ * channel, where the linker cannot message Linktree from the inbox (they
206
+ * message Linktree from its public profile instead).
207
+ *
208
+ * Distinct from the channel's `frozen` flag, which keeps the composer
209
+ * rendered but read-only/dimmed.
207
210
  */
208
211
  composerDisabled?: boolean;
209
212
  /**
210
- * Explanatory text rendered below the composer while it is locked.
211
- * Only shown when `composerDisabled` is true (or the channel is frozen).
213
+ * Explanatory text shown inside the locked panel. Only rendered when
214
+ * `composerDisabled` is true.
212
215
  */
213
216
  composerDisabledReason?: string;
214
217
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B } from "./index-BmCc1-F3.js";
1
+ import { a as e, b as t, C as i, c as n, d as o, e as m, F as g, f as l, L as r, h as M, M as u, i as L, j as c, k as h, l as d, m as p, n as v, o as A, p as C, q as F, s as k, t as b, u as f, v as x, w as y, x as P, y as S, z as q, B as z, D as B } from "./index-CJEl_fID.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.1.0-rc-1780514752",
3
+ "version": "3.1.0",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -225,7 +225,7 @@ const Template: StoryFn<TemplateProps> = (args) => {
225
225
 
226
226
  return (
227
227
  <Chat client={client}>
228
- <div className="h-screen w-full bg-white">
228
+ <div className="h-screen w-full bg-[#FBFAF9]">
229
229
  <ChannelView {...channelViewProps} channel={channel} />
230
230
  </div>
231
231
  </Chat>
@@ -279,8 +279,7 @@ RestrictedOfficialChannel.args = {
279
279
  showReportParticipant: false,
280
280
  showFollowerStatus: false,
281
281
  composerDisabled: true,
282
- composerDisabledReason:
283
- 'Message Linktree from your profile to start the conversation',
282
+ composerDisabledReason: 'Only Linktree can send messages on this thread',
284
283
  followerStatus: true, // would normally render "Subscribed to you" — suppressed here
285
284
  onLeaveConversation: (channel) =>
286
285
  console.log('Leave conversation:', channel.id),
@@ -289,7 +288,7 @@ RestrictedOfficialChannel.parameters = {
289
288
  docs: {
290
289
  description: {
291
290
  story:
292
- 'Restricted action surface used by the Linktree official channel: block, report, and the subscription-status label are hidden, and the composer is locked with an explanatory reason. Delete conversation, favorite, and chat info remain available. Open the chat info dialog (3-dot / name click) to see block & report removed.',
291
+ 'Restricted action surface used by the Linktree official channel: block, report, and the subscription-status label are hidden, and the composer is replaced by a locked panel explaining the linker cannot send messages on this thread. Delete conversation, favorite, and chat info remain available. Open the chat info dialog (3-dot / name click) to see block & report removed.',
293
292
  },
294
293
  },
295
294
  }
@@ -463,7 +462,7 @@ const WithStarButtonTemplate: StoryFn<ComponentProps> = (args) => {
463
462
 
464
463
  return (
465
464
  <Chat client={client}>
466
- <div className="h-screen w-full bg-white">
465
+ <div className="h-screen w-full bg-[#FBFAF9]">
467
466
  <ChannelView {...args} channel={channel} />
468
467
  </div>
469
468
  </Chat>
@@ -520,7 +519,7 @@ const EmptyTemplate: StoryFn<ComponentProps> = (args) => {
520
519
 
521
520
  return (
522
521
  <Chat client={client}>
523
- <div className="h-screen w-full bg-white">
522
+ <div className="h-screen w-full bg-[#FBFAF9]">
524
523
  <ChannelView {...args} channel={channel} />
525
524
  </div>
526
525
  </Chat>
@@ -178,45 +178,43 @@ describe('CustomMessageInput', () => {
178
178
  expect(sendButton).toBeDisabled()
179
179
  })
180
180
 
181
- it('locks the composer when the disabled prop is true (channel not frozen)', () => {
181
+ it('replaces the composer with the locked panel when disabled (channel not frozen)', () => {
182
182
  mockChannelData = {}
183
183
 
184
- const { container } = renderWithProviders(<CustomMessageInput disabled />)
185
-
186
- const messageInput = container.querySelector('.message-input')
187
- expect(messageInput).toHaveAttribute('aria-disabled', 'true')
188
- expect(messageInput).toHaveAttribute('inert')
189
-
190
- const textarea = screen.getByTestId('textarea-composer')
191
- expect(textarea).toHaveAttribute('readonly')
192
- expect(textarea).toHaveAttribute('tabindex', '-1')
193
-
194
- const sendButton = screen.getByRole('button', { name: /send/i })
195
- expect(sendButton).toBeDisabled()
196
- })
197
-
198
- it('renders the disabled reason when the composer is disabled with a reason', () => {
199
- mockChannelData = {}
200
-
201
- renderWithProviders(
184
+ const { container } = renderWithProviders(
202
185
  <CustomMessageInput
203
186
  disabled
204
- disabledReason="Message Linktree from your profile to reply"
187
+ disabledReason="Only Linktree can send messages on this thread"
205
188
  />
206
189
  )
207
190
 
191
+ // The interactive input is gone entirely — no textarea, no send button.
192
+ expect(
193
+ screen.queryByTestId('stream-message-input')
194
+ ).not.toBeInTheDocument()
195
+ expect(screen.queryByTestId('textarea-composer')).not.toBeInTheDocument()
196
+ expect(container.querySelector('.message-input')).not.toBeInTheDocument()
197
+
198
+ // The locked panel with the reason replaces it.
208
199
  expect(
209
- screen.getByText(/Message Linktree from your profile to reply/i)
200
+ container.querySelector('.messaging-composer-locked-panel')
201
+ ).toBeInTheDocument()
202
+ expect(
203
+ screen.getByText(/Only Linktree can send messages on this thread/i)
210
204
  ).toBeInTheDocument()
211
205
  })
212
206
 
213
- it('does not render the disabled reason when the composer is not disabled', () => {
207
+ it('does not render the locked panel when the composer is not disabled', () => {
214
208
  mockChannelData = {}
215
209
 
216
- renderWithProviders(
210
+ const { container } = renderWithProviders(
217
211
  <CustomMessageInput disabledReason="should not show" />
218
212
  )
219
213
 
214
+ expect(
215
+ container.querySelector('.messaging-composer-locked-panel')
216
+ ).not.toBeInTheDocument()
220
217
  expect(screen.queryByText(/should not show/i)).not.toBeInTheDocument()
218
+ expect(screen.getByTestId('stream-message-input')).toBeInTheDocument()
221
219
  })
222
220
  })
@@ -1,5 +1,5 @@
1
1
  import { ArrowUpIcon } from '@phosphor-icons/react'
2
- import React, { useCallback } from 'react'
2
+ import React, { useContext } from 'react'
3
3
  import {
4
4
  AttachmentPreviewList as DefaultAttachmentPreviewList,
5
5
  MessageInput,
@@ -13,6 +13,16 @@ import {
13
13
 
14
14
  import { CustomLinkPreviewList } from '../CustomLinkPreviewList'
15
15
 
16
+ /**
17
+ * Carries the channel's `frozen` state down to CustomMessageInputInner, which
18
+ * renders the composer as a read-only/disabled input. Using a context — rather
19
+ * than closing the value into the `Input` component passed to Stream's
20
+ * <MessageInput> — keeps CustomMessageInputInner a stable reference, so the
21
+ * value toggling re-renders the composer instead of unmounting and remounting
22
+ * it (which would drop textarea focus and interrupt IME composition).
23
+ */
24
+ const ComposerLockedContext = React.createContext(false)
25
+
16
26
  const DefaultSendButton: React.FC<{
17
27
  sendMessage: () => void
18
28
  disabled?: boolean
@@ -29,9 +39,8 @@ const DefaultSendButton: React.FC<{
29
39
  </button>
30
40
  )
31
41
 
32
- const CustomMessageInputInner: React.FC<{ disabled?: boolean }> = ({
33
- disabled = false,
34
- }) => {
42
+ const CustomMessageInputInner: React.FC = () => {
43
+ const disabled = useContext(ComposerLockedContext)
35
44
  const { handleSubmit } = useMessageInputContext()
36
45
 
37
46
  const hasSendableData = useMessageComposerHasSendableData()
@@ -79,16 +88,17 @@ export interface CustomMessageInputProps {
79
88
  renderActions?: () => React.ReactNode
80
89
  renderFooter?: () => React.ReactNode
81
90
  /**
82
- * Lock the composer (read-only textarea, disabled send, no autofocus).
83
- * Combined with the channel's `frozen` flag either one locks the input.
84
- * Used by the Linktree official channel, where the composer stays locked
85
- * until the linker messages Linktree from its public profile.
86
- * Defaults to false.
91
+ * Replace the composer entirely with a non-interactive locked panel that
92
+ * shows `disabledReason`. Used by the Linktree official channel, where the
93
+ * linker cannot message Linktree from the inbox. Defaults to false.
94
+ *
95
+ * Distinct from the channel's `frozen` flag, which keeps the composer
96
+ * rendered but read-only/dimmed.
87
97
  */
88
98
  disabled?: boolean
89
99
  /**
90
- * Explanatory text rendered below the composer while it is locked.
91
- * Only shown when the composer is disabled (via `disabled` or `frozen`).
100
+ * Explanatory text shown inside the locked panel. Only rendered when
101
+ * `disabled` is true.
92
102
  */
93
103
  disabledReason?: string
94
104
  }
@@ -101,19 +111,30 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
101
111
  }) => {
102
112
  const { channel } = useChannelStateContext()
103
113
  const isFrozen = channel?.data?.frozen === true
104
- const isLocked = isFrozen || disabled
105
114
 
106
- const Input = useCallback(
107
- () => <CustomMessageInputInner disabled={isLocked} />,
108
- [isLocked]
109
- )
115
+ // Linktree official channel: the composer is replaced by an info panel
116
+ // explaining the linker cannot send messages on this thread.
117
+ if (disabled) {
118
+ return (
119
+ <>
120
+ <div className="messaging-composer-locked-panel flex w-full flex-col items-center justify-center gap-3 px-6 py-4">
121
+ {disabledReason ? (
122
+ <p className="max-w-[345px] text-center text-xs font-normal leading-[1.3] tracking-[0.12px] text-black/40">
123
+ {disabledReason}
124
+ </p>
125
+ ) : null}
126
+ </div>
127
+ {renderFooter?.()}
128
+ </>
129
+ )
130
+ }
110
131
 
111
132
  return (
112
133
  <div className="flex flex-col gap-4 p-4">
113
134
  <div
114
135
  // @ts-expect-error Only React 19 onwards has `inert` in its types.
115
- inert={isLocked ? '' : undefined}
116
- aria-disabled={isLocked || undefined}
136
+ inert={isFrozen ? '' : undefined}
137
+ aria-disabled={isFrozen || undefined}
117
138
  className="message-input flex items-end gap-4 aria-disabled:opacity-40"
118
139
  >
119
140
  {renderActions && (
@@ -121,13 +142,10 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
121
142
  {renderActions()}
122
143
  </div>
123
144
  )}
124
- <MessageInput Input={Input} />
145
+ <ComposerLockedContext.Provider value={isFrozen}>
146
+ <MessageInput Input={CustomMessageInputInner} />
147
+ </ComposerLockedContext.Provider>
125
148
  </div>
126
- {isLocked && disabledReason ? (
127
- <p className="message-input-disabled-reason px-2 text-center text-sm text-black/55">
128
- {disabledReason}
129
- </p>
130
- ) : null}
131
149
  {renderFooter?.()}
132
150
  </div>
133
151
  )
package/src/types.ts CHANGED
@@ -153,16 +153,19 @@ export interface ChannelViewProps {
153
153
  showFollowerStatus?: boolean
154
154
 
155
155
  /**
156
- * Lock the message composer (read-only, send disabled). Defaults to false.
157
- * Combined with the channel's `frozen` flag either one locks the input.
158
- * Used by the Linktree official channel, where the composer stays locked
159
- * until the linker messages Linktree from its public profile.
156
+ * Replace the message composer with a non-interactive locked panel showing
157
+ * `composerDisabledReason`. Defaults to false. Used by the Linktree official
158
+ * channel, where the linker cannot message Linktree from the inbox (they
159
+ * message Linktree from its public profile instead).
160
+ *
161
+ * Distinct from the channel's `frozen` flag, which keeps the composer
162
+ * rendered but read-only/dimmed.
160
163
  */
161
164
  composerDisabled?: boolean
162
165
 
163
166
  /**
164
- * Explanatory text rendered below the composer while it is locked.
165
- * Only shown when `composerDisabled` is true (or the channel is frozen).
167
+ * Explanatory text shown inside the locked panel. Only rendered when
168
+ * `composerDisabled` is true.
166
169
  */
167
170
  composerDisabledReason?: string
168
171