@linktr.ee/messaging-react 4.1.0-rc-1786608731 → 4.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.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-D1x28quP.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.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.RichCard=e.RichCard;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.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-B88BeZjY.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.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.RichCard=e.RichCard;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.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -208,7 +208,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
208
208
  * Props that MessagingShell passes through to ChannelView.
209
209
  * ChannelViewProps is the source of truth for these props.
210
210
  */
211
- declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderMessageInputFooter' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'onOutboundClick' | 'resolveOutbound' | 'showChannelInfo' | 'composerInput'>;
211
+ declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'onOutboundClick' | 'resolveOutbound' | 'showChannelInfo' | 'composerInput'>;
212
212
 
213
213
  /**
214
214
  * ChannelView component props
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 r, f as l, L as g, h as c, M, i as h, j as L, k as u, l as d, R as p, m as C, n as v, p as A, q as k, s as F, t as b, u as P, v as f, w as x, o as y, x as z, y as S, z as q, B, D, E, G as R } from "./index-CH6y1lmh.js";
1
+ import { a as e, b as t, C as i, c as n, d as o, e as m, F as r, f as l, L as g, h as c, M, i as h, j as L, k as u, l as d, R as p, m as C, n as v, p as A, q as k, s as F, t as b, u as P, v as f, w as x, o as y, x as z, y as S, z as q, B, D, E, G as R } from "./index-Cxj0F8aQ.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": "4.1.0-rc-1786608731",
3
+ "version": "4.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",
@@ -50,7 +50,7 @@
50
50
  "chromatic:local": "yarn storybook:build && chromatic"
51
51
  },
52
52
  "dependencies": {
53
- "@linktr.ee/messaging-core": "2.4.0-rc-1786608731",
53
+ "@linktr.ee/messaging-core": "^2.4.0",
54
54
  "@linktr.ee/messaging-taxonomy": "^0.10.0",
55
55
  "@phosphor-icons/react": "^2.1.10"
56
56
  },
@@ -1,3 +1,4 @@
1
+ import { OFFICIAL_CHANNEL_TYPE } from '@linktr.ee/messaging-taxonomy'
1
2
  import React from 'react'
2
3
  import type { Channel } from 'stream-chat'
3
4
  import { describe, expect, it, vi, beforeEach } from 'vitest'
@@ -131,6 +132,7 @@ vi.mock('../hooks/useChannelStar', () => ({
131
132
  const createChannel = ({
132
133
  currentUserId = 'visitor-1',
133
134
  currentUserRole = 'owner',
135
+ channelType,
134
136
  messages = [],
135
137
  otherUser = {
136
138
  id: 'linker-1' as const,
@@ -143,6 +145,7 @@ const createChannel = ({
143
145
  }: {
144
146
  currentUserId?: 'visitor-1' | 'linker-1'
145
147
  currentUserRole?: 'owner' | 'member'
148
+ channelType?: string
146
149
  messages?: Channel['state']['messages']
147
150
  channelCustomerTag?: string
148
151
  otherMemberIsFollower?: boolean
@@ -156,6 +159,7 @@ const createChannel = ({
156
159
  ({
157
160
  id: 'channel-1',
158
161
  cid: 'messaging:channel-1',
162
+ type: channelType,
159
163
  data: {
160
164
  customer_tag: channelCustomerTag,
161
165
  },
@@ -217,6 +221,24 @@ describe('ChannelView', () => {
217
221
  expect(lastActionsMenuProps().showReportParticipant).not.toBe(false)
218
222
  })
219
223
 
224
+ it('widens the content column in the official (read-only) channel via the width var', () => {
225
+ const { container } = renderWithProviders(
226
+ <ChannelView channel={createChannel({ channelType: OFFICIAL_CHANNEL_TYPE })} />
227
+ )
228
+
229
+ const root = container.querySelector('.messaging-channel-view') as HTMLElement
230
+ expect(root.style.getPropertyValue('--lt-msg-card-width')).toBe('368px')
231
+ })
232
+
233
+ it('leaves the card-width var unset in a regular channel (default widths)', () => {
234
+ const { container } = renderWithProviders(
235
+ <ChannelView channel={createChannel({ channelType: 'messaging' })} />
236
+ )
237
+
238
+ const root = container.querySelector('.messaging-channel-view') as HTMLElement
239
+ expect(root.style.getPropertyValue('--lt-msg-card-width')).toBe('')
240
+ })
241
+
220
242
  it('hides block and report in the actions menu when restricted', () => {
221
243
  renderWithProviders(
222
244
  <ChannelView
@@ -1,3 +1,4 @@
1
+ import { isOfficialChannel } from '@linktr.ee/messaging-taxonomy'
1
2
  import classNames from 'classnames'
2
3
  import React, { useCallback, useEffect, useRef } from 'react'
3
4
  import { Channel as ChannelType } from 'stream-chat'
@@ -379,6 +380,14 @@ export const ChannelView = React.memo<ChannelViewProps>(
379
380
  return () => container.removeEventListener('click', handler)
380
381
  }, [onMessageLinkClick, channel])
381
382
 
383
+ // The official channel is read-only (no composer), so the design gives its content a
384
+ // wider column. Publish the width as a CSS var on the channel root; every card/bubble
385
+ // inside reads `w-[var(--lt-msg-card-width,…)]` and widens. Other channels leave the var
386
+ // unset and keep their default widths.
387
+ const messageCardWidthStyle = isOfficialChannel(channel.type)
388
+ ? ({ '--lt-msg-card-width': '368px' } as React.CSSProperties)
389
+ : undefined
390
+
382
391
  return (
383
392
  <div
384
393
  ref={containerRef}
@@ -386,6 +395,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
386
395
  'messaging-channel-view h-full flex flex-col',
387
396
  className
388
397
  )}
398
+ style={messageCardWidthStyle}
389
399
  >
390
400
  <DmAgentEnabledContext.Provider value={dmAgentEnabled ?? false}>
391
401
  <Channel
@@ -65,7 +65,12 @@ export interface CardShellProps {
65
65
  export const LOADING_LABEL = 'Loading link preview'
66
66
 
67
67
  const SHELL_CLASS = classNames(
68
- 'relative w-[290px] select-none overflow-hidden',
68
+ // Width is a channel-scoped token: default 290px, but a read-only surface with no
69
+ // composer (the official channel) sets `--lt-msg-card-width` on its root to widen the
70
+ // content column (see ChannelView). Consumers that don't set the var keep 290px.
71
+ // `max-w-full` caps the card to its container so a widened value can never overflow
72
+ // horizontally on a narrow (sub-393px) viewport — it shrinks to fit instead.
73
+ 'relative w-[var(--lt-msg-card-width,290px)] max-w-full select-none overflow-hidden',
69
74
  // 20px corners — no token exists for it in the component-library preset,
70
75
  // whose radius scale is sm .5rem / md 1rem / lg 1.5rem / xl 4rem.
71
76
  'rounded-[20px]'
@@ -169,26 +169,6 @@ describe('MessagingShell', () => {
169
169
  )
170
170
  })
171
171
 
172
- it('forwards renderMessageInputFooter through to ChannelView', async () => {
173
- queryChannelsMock.mockResolvedValue([makeChannel('existing-1')])
174
- const renderMessageInputFooter = vi.fn()
175
-
176
- renderWithProviders(
177
- <MessagingShell
178
- initialParticipantFilter="other-1"
179
- renderMessageInputFooter={renderMessageInputFooter}
180
- />
181
- )
182
-
183
- await waitFor(() => {
184
- expect(screen.getByTestId('channel-view')).toBeInTheDocument()
185
- })
186
-
187
- expect(capturedChannelViewProps.renderMessageInputFooter).toBe(
188
- renderMessageInputFooter
189
- )
190
- })
191
-
192
172
  it('does not create the channel again when the load effect re-runs for the same pair (e.g. service identity settles during connect)', async () => {
193
173
  queryChannelsMock.mockResolvedValue([])
194
174
  startChannelWithParticipantMock.mockResolvedValue(makeChannel('created-1'))
@@ -22,7 +22,6 @@ import { LoadingState } from './LoadingState'
22
22
  export const MessagingShell: React.FC<MessagingShellProps> = ({
23
23
  capabilities = {},
24
24
  renderMessageInputActions,
25
- renderMessageInputFooter,
26
25
  renderConversationFooter,
27
26
  onChannelSelect,
28
27
  onExitConversation,
@@ -250,7 +249,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
250
249
  channel={selectedChannel}
251
250
  key={selectedChannel.id}
252
251
  renderMessageInputActions={renderMessageInputActions}
253
- renderMessageInputFooter={renderMessageInputFooter}
254
252
  renderConversationFooter={renderConversationFooter}
255
253
  renderChannelBanner={renderChannelBanner}
256
254
  onLeaveConversation={handleExitConversation}
@@ -25,6 +25,22 @@ describe('RichCard', () => {
25
25
  ).toBeInTheDocument()
26
26
  })
27
27
 
28
+ it('renders multiline bullet copy as separate lines', async () => {
29
+ renderWithProviders(
30
+ <RichCard.Received
31
+ title="Welcome"
32
+ subtitle={'👋 Say hello as people arrive\n✨ Spark conversations\n💬 Add automated FAQs'}
33
+ subtitleLines={6}
34
+ images={[HERO]}
35
+ />
36
+ )
37
+
38
+ const card = await screen.findByTestId('rich-card')
39
+ expect(card).toHaveTextContent('👋 Say hello as people arrive')
40
+ expect(card).toHaveTextContent('✨ Spark conversations')
41
+ expect(card).toHaveTextContent('💬 Add automated FAQs')
42
+ })
43
+
28
44
  it('renders a single hero image for the hero presentation', async () => {
29
45
  renderWithProviders(
30
46
  <RichCard.Received
@@ -128,6 +128,7 @@ const RichCardBody: React.FC<RichCardBodyProps> = ({
128
128
  <p
129
129
  className={classNames(
130
130
  SUBTITLE_CLASS,
131
+ subtitleLines > 1 && 'whitespace-pre-line',
131
132
  subtitleLines <= 1 && 'truncate'
132
133
  )}
133
134
  style={subtitleStyle}
package/src/types.ts CHANGED
@@ -436,7 +436,6 @@ export interface ChannelViewProps {
436
436
  export type ChannelViewPassthroughProps = Pick<
437
437
  ChannelViewProps,
438
438
  | 'renderMessageInputActions'
439
- | 'renderMessageInputFooter'
440
439
  | 'renderConversationFooter'
441
440
  | 'CustomChannelEmptyState'
442
441
  | 'onBlockParticipantClick'