@linktr.ee/messaging-react 4.1.1 → 4.1.2

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-Bxat0g1O.cjs → Card-1LeQJY-E.cjs} +2 -2
  2. package/dist/{Card-Bxat0g1O.cjs.map → Card-1LeQJY-E.cjs.map} +1 -1
  3. package/dist/{Card-xYcNcZC_.js → Card-BXlNaNGk.js} +2 -2
  4. package/dist/{Card-xYcNcZC_.js.map → Card-BXlNaNGk.js.map} +1 -1
  5. package/dist/{index-t0Okjl1p.js → index-C1cbPKI7.js} +847 -853
  6. package/dist/index-C1cbPKI7.js.map +1 -0
  7. package/dist/index-yg1xEb2Y.cjs +3 -0
  8. package/dist/index-yg1xEb2Y.cjs.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.js +1 -1
  11. package/package.json +1 -1
  12. package/src/components/ActionButton/ActionButton.stories.tsx +1 -0
  13. package/src/components/AttachmentCard/MediaPlayer.tsx +10 -8
  14. package/src/components/Avatar/Avatar.stories.tsx +44 -5
  15. package/src/components/ChannelList/CustomChannelPreview.stories.tsx +332 -1
  16. package/src/components/CloseButton/CloseButton.stories.tsx +1 -1
  17. package/src/components/CustomDateSeparator/CustomDateSeparator.stories.tsx +1 -1
  18. package/src/components/CustomMessage/CustomMessage.stories.tsx +5 -0
  19. package/src/components/CustomMessage/MessageTag.stories.tsx +4 -6
  20. package/src/components/CustomMessageInput/index.tsx +13 -16
  21. package/src/components/FaqList/FaqList.stories.tsx +204 -0
  22. package/src/components/FaqList/FaqListItem.stories.tsx +1 -0
  23. package/src/components/IconButton/IconButton.stories.tsx +1 -0
  24. package/src/components/Loading/Loading.stories.tsx +1 -0
  25. package/src/components/MessagingShell/ErrorState.stories.tsx +1 -0
  26. package/src/components/MessagingShell/LoadingState.stories.tsx +1 -0
  27. package/src/components/SearchInput/SearchInput.stories.tsx +1 -0
  28. package/src/testing/createMockMessagingClient.stories.tsx +1 -1
  29. package/dist/index-CAD8AXBg.cjs +0 -3
  30. package/dist/index-CAD8AXBg.cjs.map +0 -1
  31. package/dist/index-t0Okjl1p.js.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-CAD8AXBg.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-yg1xEb2Y.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.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-t0Okjl1p.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-C1cbPKI7.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.1",
3
+ "version": "4.1.2",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -10,6 +10,7 @@ const meta: Meta<ComponentProps> = {
10
10
  component: ActionButton,
11
11
  parameters: {
12
12
  layout: 'centered',
13
+ chromatic: { disableSnapshot: true },
13
14
  },
14
15
  argTypes: {
15
16
  onClick: { action: 'clicked' },
@@ -104,6 +104,14 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
104
104
  seekTo(fraction)
105
105
  }
106
106
 
107
+ const handleScrubberKeyDown = useCallback(
108
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
109
+ if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))
110
+ if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))
111
+ },
112
+ [seekTo, played]
113
+ )
114
+
107
115
  // --- Effects ---
108
116
 
109
117
  // Sync controlled playing prop to internal state
@@ -331,10 +339,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
331
339
  onMouseDown={handleTrackPointerDown}
332
340
  onTouchStart={handleTrackPointerDown}
333
341
  onClick={(e) => e.stopPropagation()}
334
- onKeyDown={(e) => {
335
- if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))
336
- if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))
337
- }}
342
+ onKeyDown={handleScrubberKeyDown}
338
343
  >
339
344
  <div className="w-full overflow-hidden rounded-full bg-white/30 h-1">
340
345
  <div
@@ -378,10 +383,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
378
383
  onClick={(e) => e.stopPropagation()}
379
384
  onMouseEnter={() => setScrubberHovered(true)}
380
385
  onMouseLeave={() => setScrubberHovered(false)}
381
- onKeyDown={(e) => {
382
- if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))
383
- if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))
384
- }}
386
+ onKeyDown={handleScrubberKeyDown}
385
387
  >
386
388
  <div
387
389
  className={`w-full overflow-hidden rounded-full bg-white/30 transition-all duration-200 ${scrubberHovered || seeking ? 'h-1.5' : 'h-1'}`}
@@ -22,17 +22,24 @@ const Template: StoryFn<ComponentProps> = (args) => {
22
22
  )
23
23
  }
24
24
 
25
+ // Loose single stories (Default/WithoutImage/Starred) are each a cell of the
26
+ // States grid; the per-size stories (SmallSize…ExtraLargeSize) are cells of the
27
+ // VariousSizes grid; AIActive is the size=40 cell of AIActiveVariousSizes.
28
+ // Keep them in Storybook for browsing but skip in Chromatic.
29
+ const skipInChromatic = { chromatic: { disableSnapshot: true } }
30
+
31
+ // Deterministic local avatar for the States grid so a pravatar network flake
32
+ // can't diff the snapshot. Individual stories keep their remote images (they're
33
+ // skipped in Chromatic anyway).
34
+ const LOCAL_AVATAR = '/image-thumbnail.jpg'
35
+
25
36
  export const Default: StoryFn<ComponentProps> = Template.bind({})
26
37
  Default.args = {
27
38
  id: 'user-1',
28
39
  name: 'Alice Johnson',
29
40
  image: 'https://i.pravatar.cc/150?img=1',
30
41
  }
31
-
32
- // Per-size stories (SmallSize…ExtraLargeSize) are each a single cell of the
33
- // VariousSizes grid; AIActive is the size=40 cell of AIActiveVariousSizes.
34
- // Keep them in Storybook for browsing but skip in Chromatic.
35
- const skipInChromatic = { chromatic: { disableSnapshot: true } }
42
+ Default.parameters = skipInChromatic
36
43
 
37
44
  export const AIActive: StoryFn<ComponentProps> = Template.bind({})
38
45
  AIActive.args = {
@@ -48,6 +55,7 @@ WithoutImage.args = {
48
55
  id: 'user-2',
49
56
  name: 'Bob Smith',
50
57
  }
58
+ WithoutImage.parameters = skipInChromatic
51
59
 
52
60
  export const Starred: StoryFn<ComponentProps> = Template.bind({})
53
61
  Starred.args = {
@@ -55,6 +63,37 @@ Starred.args = {
55
63
  name: 'Kaiya Korsgaard',
56
64
  starred: true,
57
65
  }
66
+ Starred.parameters = skipInChromatic
67
+
68
+ export const States: StoryFn = () => {
69
+ const cells: { label: string; props: ComponentProps }[] = [
70
+ {
71
+ label: 'Default (image)',
72
+ props: { id: 'user-1', name: 'Alice Johnson', image: LOCAL_AVATAR },
73
+ },
74
+ {
75
+ label: 'Without image',
76
+ props: { id: 'user-2', name: 'Bob Smith' },
77
+ },
78
+ {
79
+ label: 'Starred',
80
+ props: { id: 'user-9', name: 'Kaiya Korsgaard', starred: true },
81
+ },
82
+ ]
83
+
84
+ return (
85
+ <div className="p-12">
86
+ <div className="flex gap-8 items-center">
87
+ {cells.map(({ label, props }) => (
88
+ <div key={label} className="flex flex-col items-center gap-2">
89
+ <Avatar {...props} />
90
+ <span className="text-xs text-stone">{label}</span>
91
+ </div>
92
+ ))}
93
+ </div>
94
+ </div>
95
+ )
96
+ }
58
97
 
59
98
  export const SmallSize: StoryFn<ComponentProps> = Template.bind({})
60
99
  SmallSize.args = {
@@ -134,6 +134,17 @@ const Template: StoryFn<ComponentProps> = (args) => {
134
134
  )
135
135
  }
136
136
 
137
+ // Per-variant single-row stories stay in Storybook for browsing/design review
138
+ // but are individually covered by rows in the PreviewStates / MessageTypeStates
139
+ // / UrlAndAttachmentStates grids — skip in Chromatic so those grids are the
140
+ // single snapshot source of truth.
141
+ const skipInChromatic = { chromatic: { disableSnapshot: true } }
142
+
143
+ // Deterministic local avatar for the grids so a pravatar network flake can't
144
+ // diff a whole grid snapshot. Individual stories keep their remote images
145
+ // (they're skipped in Chromatic anyway).
146
+ const LOCAL_AVATAR = '/image-thumbnail.jpg'
147
+
137
148
  export const Default: StoryFn<ComponentProps> = Template.bind({})
138
149
  Default.args = {
139
150
  channel: createMockChannel({
@@ -145,6 +156,7 @@ Default.args = {
145
156
  lastMessageTime: now(),
146
157
  }),
147
158
  }
159
+ Default.parameters = skipInChromatic
148
160
 
149
161
  export const Selected: StoryFn<ComponentProps> = Template.bind({})
150
162
  Selected.args = {
@@ -163,6 +175,7 @@ Selected.args = {
163
175
  participantImage: 'https://i.pravatar.cc/150?img=3',
164
176
  }),
165
177
  }
178
+ Selected.parameters = skipInChromatic
166
179
 
167
180
  export const WithUnreadMessages: StoryFn<ComponentProps> = Template.bind({})
168
181
  WithUnreadMessages.args = {
@@ -176,6 +189,7 @@ WithUnreadMessages.args = {
176
189
  unreadCount: 3,
177
190
  }),
178
191
  }
192
+ WithUnreadMessages.parameters = skipInChromatic
179
193
 
180
194
  export const ManyUnreadMessages: StoryFn<ComponentProps> = Template.bind({})
181
195
  ManyUnreadMessages.args = {
@@ -189,6 +203,7 @@ ManyUnreadMessages.args = {
189
203
  unreadCount: 127,
190
204
  }),
191
205
  }
206
+ ManyUnreadMessages.parameters = skipInChromatic
192
207
 
193
208
  export const NoAvatar: StoryFn<ComponentProps> = Template.bind({})
194
209
  NoAvatar.args = {
@@ -200,6 +215,7 @@ NoAvatar.args = {
200
215
  lastMessageTime: minutesAgo(30),
201
216
  }),
202
217
  }
218
+ NoAvatar.parameters = skipInChromatic
203
219
 
204
220
  export const NoMessages: StoryFn<ComponentProps> = Template.bind({})
205
221
  NoMessages.args = {
@@ -211,6 +227,7 @@ NoMessages.args = {
211
227
  lastMessageText: '',
212
228
  }),
213
229
  }
230
+ NoMessages.parameters = skipInChromatic
214
231
 
215
232
  export const LongMessage: StoryFn<ComponentProps> = Template.bind({})
216
233
  LongMessage.args = {
@@ -224,6 +241,7 @@ LongMessage.args = {
224
241
  lastMessageTime: hoursAgo(2),
225
242
  }),
226
243
  }
244
+ LongMessage.parameters = skipInChromatic
227
245
 
228
246
  export const LongName: StoryFn<ComponentProps> = Template.bind({})
229
247
  LongName.args = {
@@ -235,6 +253,7 @@ LongName.args = {
235
253
  lastMessageTime: minutesAgo(45),
236
254
  }),
237
255
  }
256
+ LongName.parameters = skipInChromatic
238
257
 
239
258
  export const ChatbotMessage: StoryFn<ComponentProps> = Template.bind({})
240
259
  ChatbotMessage.args = {
@@ -248,6 +267,7 @@ ChatbotMessage.args = {
248
267
  lastMessageMetadata: { custom_type: 'MESSAGE_CHATBOT' },
249
268
  }),
250
269
  }
270
+ ChatbotMessage.parameters = skipInChromatic
251
271
 
252
272
  export const TipMessage: StoryFn<ComponentProps> = Template.bind({})
253
273
  TipMessage.args = {
@@ -261,6 +281,7 @@ TipMessage.args = {
261
281
  lastMessageMetadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.00' },
262
282
  }),
263
283
  }
284
+ TipMessage.parameters = skipInChromatic
264
285
 
265
286
  export const PaidMessage: StoryFn<ComponentProps> = Template.bind({})
266
287
  PaidMessage.args = {
@@ -274,6 +295,7 @@ PaidMessage.args = {
274
295
  lastMessageMetadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10.00' },
275
296
  }),
276
297
  }
298
+ PaidMessage.parameters = skipInChromatic
277
299
 
278
300
  export const PaidAttachment: StoryFn<ComponentProps> = Template.bind({})
279
301
  PaidAttachment.args = {
@@ -290,6 +312,7 @@ PaidAttachment.args = {
290
312
  },
291
313
  }),
292
314
  }
315
+ PaidAttachment.parameters = skipInChromatic
293
316
 
294
317
  const renderMessagePreviewWithMarkers = (
295
318
  message: LocalMessage | undefined,
@@ -396,6 +419,7 @@ SelectedWithUnread.args = {
396
419
  participantId: 'participant-9',
397
420
  }),
398
421
  }
422
+ SelectedWithUnread.parameters = skipInChromatic
399
423
 
400
424
  export const MultipleChannels: StoryFn = () => {
401
425
  const [selectedChannelId, _setSelectedChannelId] = React.useState<
@@ -468,6 +492,7 @@ WithUrl.args = {
468
492
  lastMessageTime: minutesAgo(8),
469
493
  }),
470
494
  }
495
+ WithUrl.parameters = skipInChromatic
471
496
 
472
497
  export const WithVeryLongUrl: StoryFn<ComponentProps> = Template.bind({})
473
498
  WithVeryLongUrl.args = {
@@ -481,6 +506,7 @@ WithVeryLongUrl.args = {
481
506
  lastMessageTime: minutesAgo(20),
482
507
  }),
483
508
  }
509
+ WithVeryLongUrl.parameters = skipInChromatic
484
510
 
485
511
  // Time-based stories
486
512
  //
@@ -488,7 +514,6 @@ WithVeryLongUrl.args = {
488
514
  // output matrix. The `TimeVariations` story further down already snapshots
489
515
  // all of them together in a labeled table — kept here for Storybook
490
516
  // browsing but skipped in Chromatic to avoid 9 redundant snapshots.
491
- const skipInChromatic = { chromatic: { disableSnapshot: true } }
492
517
 
493
518
  export const JustNow: StoryFn<ComponentProps> = Template.bind({})
494
519
  JustNow.args = {
@@ -722,6 +747,7 @@ WithAttachmentAssetUrl.args = {
722
747
  ],
723
748
  }),
724
749
  }
750
+ WithAttachmentAssetUrl.parameters = skipInChromatic
725
751
 
726
752
  export const WithAttachmentImageUrl: StoryFn<ComponentProps> = Template.bind({})
727
753
  WithAttachmentImageUrl.args = {
@@ -762,6 +788,7 @@ WithAttachmentOgScrapeUrl.args = {
762
788
  ],
763
789
  }),
764
790
  }
791
+ WithAttachmentOgScrapeUrl.parameters = skipInChromatic
765
792
 
766
793
  export const WithAttachmentThumbUrl: StoryFn<ComponentProps> = Template.bind({})
767
794
  WithAttachmentThumbUrl.args = {
@@ -799,3 +826,307 @@ WithLongAttachmentUrl.args = {
799
826
  }),
800
827
  }
801
828
  WithLongAttachmentUrl.parameters = skipInChromatic
829
+
830
+ interface GridRow {
831
+ label: string
832
+ channel: Channel
833
+ selectedChannel?: Channel
834
+ }
835
+
836
+ const StatesGrid: StoryFn<{ rows: GridRow[] }> = ({ rows }) => (
837
+ <div className="flex flex-col gap-4 p-6">
838
+ {rows.map(({ label, channel, selectedChannel }) => (
839
+ <div key={label} className="flex flex-col gap-1">
840
+ <span className="text-xs text-stone">{label}</span>
841
+ <div className="w-[360px] bg-chalk border border-sand rounded-lg overflow-hidden">
842
+ <CustomChannelPreview
843
+ channel={channel}
844
+ selectedChannel={selectedChannel}
845
+ onChannelSelect={(channel) => {
846
+ console.log('Channel selected:', channel.id)
847
+ }}
848
+ />
849
+ </div>
850
+ </div>
851
+ ))}
852
+ </div>
853
+ )
854
+
855
+ export const PreviewStates: StoryFn = () => {
856
+ const selected = createMockChannel({
857
+ id: 'channel-2',
858
+ participantName: 'Bob Smith',
859
+ participantId: 'participant-2',
860
+ participantImage: LOCAL_AVATAR,
861
+ lastMessageText: 'That sounds great!',
862
+ lastMessageTime: minutesAgo(5),
863
+ })
864
+ const selectedUnread = createMockChannel({
865
+ id: 'channel-9',
866
+ participantName: 'Helen Park',
867
+ participantId: 'participant-9',
868
+ participantImage: LOCAL_AVATAR,
869
+ lastMessageText: 'Important update!',
870
+ lastMessageTime: minutesAgo(10),
871
+ unreadCount: 5,
872
+ })
873
+
874
+ const rows: GridRow[] = [
875
+ {
876
+ label: 'Default',
877
+ channel: createMockChannel({
878
+ id: 'channel-1',
879
+ participantName: 'Alice Johnson',
880
+ participantId: 'participant-1',
881
+ participantImage: LOCAL_AVATAR,
882
+ lastMessageText: 'Hey! How are you doing?',
883
+ lastMessageTime: now(),
884
+ }),
885
+ },
886
+ {
887
+ label: 'Selected',
888
+ channel: selected,
889
+ selectedChannel: selected,
890
+ },
891
+ {
892
+ label: 'With unread messages',
893
+ channel: createMockChannel({
894
+ id: 'channel-3',
895
+ participantName: 'Carol Williams',
896
+ participantId: 'participant-3',
897
+ participantImage: LOCAL_AVATAR,
898
+ lastMessageText: 'Did you see my last message?',
899
+ lastMessageTime: minutesAgo(15),
900
+ unreadCount: 3,
901
+ }),
902
+ },
903
+ {
904
+ label: 'Many unread messages',
905
+ channel: createMockChannel({
906
+ id: 'channel-4',
907
+ participantName: 'David Brown',
908
+ participantId: 'participant-4',
909
+ participantImage: LOCAL_AVATAR,
910
+ lastMessageText: 'Please check this out!',
911
+ lastMessageTime: hoursAgo(1),
912
+ unreadCount: 127,
913
+ }),
914
+ },
915
+ {
916
+ label: 'Selected with unread',
917
+ channel: selectedUnread,
918
+ selectedChannel: selectedUnread,
919
+ },
920
+ {
921
+ label: 'No avatar',
922
+ channel: createMockChannel({
923
+ id: 'channel-5',
924
+ participantName: 'Emma Davis',
925
+ participantId: 'participant-5',
926
+ lastMessageText: 'Thanks for your help!',
927
+ lastMessageTime: minutesAgo(30),
928
+ }),
929
+ },
930
+ {
931
+ label: 'No messages',
932
+ channel: createMockChannel({
933
+ id: 'channel-6',
934
+ participantName: 'Frank Miller',
935
+ participantId: 'participant-6',
936
+ participantImage: LOCAL_AVATAR,
937
+ lastMessageText: '',
938
+ }),
939
+ },
940
+ {
941
+ label: 'Long message',
942
+ channel: createMockChannel({
943
+ id: 'channel-7',
944
+ participantName: 'Grace Lee',
945
+ participantId: 'participant-7',
946
+ participantImage: LOCAL_AVATAR,
947
+ lastMessageText:
948
+ 'This is a very long message that should be truncated because it contains way too much text to display in the preview. We want to make sure the component handles this gracefully.',
949
+ lastMessageTime: hoursAgo(2),
950
+ }),
951
+ },
952
+ {
953
+ label: 'Long name',
954
+ channel: createMockChannel({
955
+ id: 'channel-8',
956
+ participantName: 'Alexander Christopher Wellington-Montgomery III',
957
+ participantId: 'participant-8',
958
+ lastMessageText: 'Nice to meet you!',
959
+ lastMessageTime: minutesAgo(45),
960
+ }),
961
+ },
962
+ ]
963
+
964
+ return <StatesGrid rows={rows} />
965
+ }
966
+
967
+ export const MessageTypeStates: StoryFn = () => {
968
+ const rows: GridRow[] = [
969
+ {
970
+ label: 'Chatbot message',
971
+ channel: createMockChannel({
972
+ id: 'channel-chatbot',
973
+ participantName: 'Emily Watson',
974
+ participantId: 'participant-chatbot',
975
+ participantImage: LOCAL_AVATAR,
976
+ lastMessageText: 'Thanks for reaching out! How can I help you today?',
977
+ lastMessageTime: minutesAgo(2),
978
+ lastMessageMetadata: { custom_type: 'MESSAGE_CHATBOT' },
979
+ }),
980
+ },
981
+ {
982
+ label: 'Tip message',
983
+ channel: createMockChannel({
984
+ id: 'channel-tip',
985
+ participantName: 'Alice Johnson',
986
+ participantId: 'participant-tip',
987
+ participantImage: LOCAL_AVATAR,
988
+ lastMessageText: 'Take my money!',
989
+ lastMessageTime: minutesAgo(3),
990
+ lastMessageMetadata: {
991
+ custom_type: 'MESSAGE_TIP',
992
+ amount_text: '$5.00',
993
+ },
994
+ }),
995
+ },
996
+ {
997
+ label: 'Paid message',
998
+ channel: createMockChannel({
999
+ id: 'channel-paid',
1000
+ participantName: 'Bob Smith',
1001
+ participantId: 'participant-paid',
1002
+ participantImage: LOCAL_AVATAR,
1003
+ lastMessageText: 'I paid for this message!',
1004
+ lastMessageTime: minutesAgo(10),
1005
+ lastMessageMetadata: {
1006
+ custom_type: 'MESSAGE_PAID',
1007
+ amount_text: '$10.00',
1008
+ },
1009
+ }),
1010
+ },
1011
+ {
1012
+ label: 'Paid attachment',
1013
+ channel: createMockChannel({
1014
+ id: 'channel-paid-attachment',
1015
+ participantName: 'Carol Williams',
1016
+ participantId: 'participant-paid-attachment',
1017
+ participantImage: LOCAL_AVATAR,
1018
+ lastMessageText: 'Check out this exclusive photo!',
1019
+ lastMessageTime: minutesAgo(20),
1020
+ lastMessageMetadata: {
1021
+ custom_type: 'MESSAGE_ATTACHMENT',
1022
+ attachment_title: 'exclusive-photo.jpg',
1023
+ },
1024
+ }),
1025
+ },
1026
+ ]
1027
+
1028
+ return <StatesGrid rows={rows} />
1029
+ }
1030
+
1031
+ export const UrlAndAttachmentStates: StoryFn = () => {
1032
+ const rows: GridRow[] = [
1033
+ {
1034
+ label: 'With URL',
1035
+ channel: createMockChannel({
1036
+ id: 'channel-url',
1037
+ participantName: 'Ivan Rodriguez',
1038
+ participantId: 'participant-url',
1039
+ participantImage: LOCAL_AVATAR,
1040
+ lastMessageText: 'https://example.com/page',
1041
+ lastMessageTime: minutesAgo(8),
1042
+ }),
1043
+ },
1044
+ {
1045
+ label: 'With very long URL',
1046
+ channel: createMockChannel({
1047
+ id: 'channel-long-url',
1048
+ participantName: 'Julia Martinez',
1049
+ participantId: 'participant-long-url',
1050
+ participantImage: LOCAL_AVATAR,
1051
+ lastMessageText:
1052
+ 'https://example.com/very/long/path/with/many/segments/and/query/parameters?param1=value1&param2=value2&param3=value3&param4=value4&param5=very-long-value-that-makes-the-url-extremely-long',
1053
+ lastMessageTime: minutesAgo(20),
1054
+ }),
1055
+ },
1056
+ {
1057
+ label: 'Attachment (asset_url)',
1058
+ channel: createMockChannel({
1059
+ id: 'channel-attachment-asset',
1060
+ participantName: 'Tom Wilson',
1061
+ participantId: 'participant-attachment-asset',
1062
+ participantImage: LOCAL_AVATAR,
1063
+ lastMessageText: '',
1064
+ lastMessageTime: minutesAgo(10),
1065
+ lastMessageAttachments: [
1066
+ { asset_url: 'https://example.com/document.pdf' },
1067
+ ],
1068
+ }),
1069
+ },
1070
+ {
1071
+ label: 'Attachment (image_url)',
1072
+ channel: createMockChannel({
1073
+ id: 'channel-attachment-image',
1074
+ participantName: 'Uma Patel',
1075
+ participantId: 'participant-attachment-image',
1076
+ participantImage: LOCAL_AVATAR,
1077
+ lastMessageText: '',
1078
+ lastMessageTime: minutesAgo(15),
1079
+ lastMessageAttachments: [
1080
+ { image_url: 'https://example.com/image.jpg' },
1081
+ ],
1082
+ }),
1083
+ },
1084
+ {
1085
+ label: 'Attachment (og_scrape_url)',
1086
+ channel: createMockChannel({
1087
+ id: 'channel-attachment-og',
1088
+ participantName: 'Victor Zhang',
1089
+ participantId: 'participant-attachment-og',
1090
+ participantImage: LOCAL_AVATAR,
1091
+ lastMessageText: '',
1092
+ lastMessageTime: minutesAgo(7),
1093
+ lastMessageAttachments: [
1094
+ { og_scrape_url: 'https://example.com/article' },
1095
+ ],
1096
+ }),
1097
+ },
1098
+ {
1099
+ label: 'Attachment (thumb_url)',
1100
+ channel: createMockChannel({
1101
+ id: 'channel-attachment-thumb',
1102
+ participantName: 'Wendy Kim',
1103
+ participantId: 'participant-attachment-thumb',
1104
+ participantImage: LOCAL_AVATAR,
1105
+ lastMessageText: '',
1106
+ lastMessageTime: minutesAgo(12),
1107
+ lastMessageAttachments: [
1108
+ { thumb_url: 'https://example.com/thumb.jpg' },
1109
+ ],
1110
+ }),
1111
+ },
1112
+ {
1113
+ label: 'Long attachment URL',
1114
+ channel: createMockChannel({
1115
+ id: 'channel-attachment-long',
1116
+ participantName: 'Xavier Cruz',
1117
+ participantId: 'participant-attachment-long',
1118
+ participantImage: LOCAL_AVATAR,
1119
+ lastMessageText: '',
1120
+ lastMessageTime: minutesAgo(20),
1121
+ lastMessageAttachments: [
1122
+ {
1123
+ asset_url:
1124
+ 'https://example.com/very/long/path/to/file/with/many/segments/document-with-very-long-name-that-should-be-truncated.pdf',
1125
+ },
1126
+ ],
1127
+ }),
1128
+ },
1129
+ ]
1130
+
1131
+ return <StatesGrid rows={rows} />
1132
+ }
@@ -8,7 +8,7 @@ type ComponentProps = React.ComponentProps<typeof CloseButton>
8
8
  const meta: Meta<ComponentProps> = {
9
9
  title: 'CloseButton',
10
10
  component: CloseButton,
11
- parameters: { layout: 'centered' },
11
+ parameters: { layout: 'centered', chromatic: { disableSnapshot: true } },
12
12
  argTypes: {
13
13
  onClick: { action: 'clicked' },
14
14
  },
@@ -10,7 +10,7 @@ type ComponentProps = React.ComponentProps<typeof CustomDateSeparator>
10
10
  const meta: Meta<ComponentProps> = {
11
11
  title: 'CustomDateSeparator',
12
12
  component: CustomDateSeparator,
13
- parameters: { layout: 'fullscreen' },
13
+ parameters: { layout: 'fullscreen', chromatic: { disableSnapshot: true } },
14
14
  }
15
15
  export default meta
16
16
 
@@ -648,6 +648,11 @@ WithTypingIndicatorComparison.parameters = {
648
648
  'Renders a standard incoming message and the custom typing indicator in the same list for visual comparison.',
649
649
  },
650
650
  },
651
+ // The typing indicator is animation-dominant, so a snapshot mostly captures
652
+ // whichever frame the animation happened to be on — it flags a visual change
653
+ // on nearly every build. Matches CustomTypingIndicator.stories.tsx; the story
654
+ // stays browsable in Storybook for design review.
655
+ chromatic: { disableSnapshot: true },
651
656
  }
652
657
 
653
658
  // ─── Message Actions ──────────────────────────────────────────────────────────
@@ -179,9 +179,7 @@ export const AllVariants: StoryFn = () => {
179
179
  </div>
180
180
  )
181
181
  }
182
- // AllVariants is a design-review kitchen sink — every per-variant story
183
- // (Tip, Paid, TipStandalone, ChatbotReceiverText, ChatbotSenderText,
184
- // ChatbotSenderAttachment, NoTag) already covers its own row independently.
185
- // Skip in Chromatic to avoid a "any one row changes → whole snapshot diffs"
186
- // problem that masks which variant actually drifted.
187
- AllVariants.parameters = { chromatic: { disableSnapshot: true } }
182
+ // AllVariants is the single Chromatic snapshot for message tags: the per-variant
183
+ // stories above are skipped (kept for local browsing + controls) and this grid is
184
+ // the visual-regression guard — one snapshot covering every tag layout at once.
185
+ AllVariants.parameters = { chromatic: { disableSnapshot: false } }