@linktr.ee/messaging-react 3.3.4 → 3.3.6-rc-1780987607

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 (56) hide show
  1. package/dist/{Card-DLmUSU4A.cjs → Card-BlviN8Fb.cjs} +2 -2
  2. package/dist/{Card-DLmUSU4A.cjs.map → Card-BlviN8Fb.cjs.map} +1 -1
  3. package/dist/{Card-DmPpcrSU.js → Card-C4ncqjxJ.js} +2 -2
  4. package/dist/{Card-DmPpcrSU.js.map → Card-C4ncqjxJ.js.map} +1 -1
  5. package/dist/{Card-0BgubwgM.cjs → Card-Cn7Zxc6U.cjs} +2 -2
  6. package/dist/{Card-0BgubwgM.cjs.map → Card-Cn7Zxc6U.cjs.map} +1 -1
  7. package/dist/{Card-DchJqvYq.js → Card-DE5bfj0l.js} +2 -2
  8. package/dist/{Card-DchJqvYq.js.map → Card-DE5bfj0l.js.map} +1 -1
  9. package/dist/{Card-B7AF5uOB.js → Card-IjOI7UXs.js} +3 -3
  10. package/dist/{Card-B7AF5uOB.js.map → Card-IjOI7UXs.js.map} +1 -1
  11. package/dist/{Card-CvBbAoUo.cjs → Card-KgQxeR-B.cjs} +2 -2
  12. package/dist/{Card-CvBbAoUo.cjs.map → Card-KgQxeR-B.cjs.map} +1 -1
  13. package/dist/{LockedThumbnail-BQjA4HaB.js → LockedThumbnail-4-54cyJG.js} +2 -2
  14. package/dist/{LockedThumbnail-BQjA4HaB.js.map → LockedThumbnail-4-54cyJG.js.map} +1 -1
  15. package/dist/{LockedThumbnail-D9fSb4N-.cjs → LockedThumbnail-DL5NZzWJ.cjs} +2 -2
  16. package/dist/{LockedThumbnail-D9fSb4N-.cjs.map → LockedThumbnail-DL5NZzWJ.cjs.map} +1 -1
  17. package/dist/{index-BcHUpyyw.js → index-C2wfgpUU.js} +855 -823
  18. package/dist/index-C2wfgpUU.js.map +1 -0
  19. package/dist/index-nanry0Io.cjs +2 -0
  20. package/dist/index-nanry0Io.cjs.map +1 -0
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.js +1 -1
  23. package/package.json +5 -2
  24. package/src/components/ActionButton/ActionButton.test.tsx +0 -25
  25. package/src/components/AttachmentCard/AttachmentCard.stories.tsx +226 -0
  26. package/src/components/Avatar/Avatar.stories.tsx +20 -0
  27. package/src/components/ChannelActionsMenu/ChannelActionsMenu.test.tsx +33 -8
  28. package/src/components/ChannelList/ChannelList.stories.tsx +5 -0
  29. package/src/components/ChannelList/CustomChannelPreview.stories.tsx +77 -47
  30. package/src/components/ChannelView.stories.tsx +8 -7
  31. package/src/components/ChannelView.test.tsx +12 -1
  32. package/src/components/ChannelView.tsx +34 -17
  33. package/src/components/CloseButton/CloseButton.stories.tsx +31 -0
  34. package/src/components/CustomDateSeparator/CustomDateSeparator.stories.tsx +33 -0
  35. package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.stories.tsx +63 -0
  36. package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.tsx +57 -0
  37. package/src/components/CustomLinkPreviewList/index.tsx +2 -54
  38. package/src/components/CustomMessage/CustomMessage.stories.tsx +3 -18
  39. package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +13 -0
  40. package/src/components/CustomMessage/MessageTag.stories.tsx +22 -2
  41. package/src/components/CustomMessage/MessageVoteButtons.stories.tsx +9 -0
  42. package/src/components/CustomMessageInput/index.tsx +14 -4
  43. package/src/components/CustomSystemMessage/CustomSystemMessage.stories.tsx +54 -0
  44. package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +7 -0
  45. package/src/components/LinkAttachment/LinkAttachment.stories.tsx +11 -1
  46. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +4 -0
  47. package/src/components/MediaMessage/MediaMessage.stories.tsx +4 -2
  48. package/src/components/MediaMessage/MediaMessage.test.tsx +0 -38
  49. package/src/components/MessageAttachment/MessageAttachment.test.tsx +25 -84
  50. package/src/components/SearchInput/SearchInput.test.tsx +0 -8
  51. package/src/hooks/useChannelModerationActions.ts +32 -14
  52. package/src/stories/decorators/storyTime.ts +31 -0
  53. package/src/utils/formatRelativeTime.test.ts +1 -32
  54. package/dist/index-BcHUpyyw.js.map +0 -1
  55. package/dist/index-DTZNltUC.cjs +0 -2
  56. package/dist/index-DTZNltUC.cjs.map +0 -1
@@ -7,6 +7,14 @@ import {
7
7
  StreamChat,
8
8
  } from 'stream-chat'
9
9
 
10
+ import {
11
+ daysAgo,
12
+ hoursAgo,
13
+ minutesAgo,
14
+ now,
15
+ secondsAgo,
16
+ } from '../../stories/decorators/storyTime'
17
+
10
18
  import CustomChannelPreview from './CustomChannelPreview'
11
19
 
12
20
  type ComponentProps = React.ComponentProps<typeof CustomChannelPreview>
@@ -54,7 +62,7 @@ const createMockChannel = (options: {
54
62
  participantId,
55
63
  participantImage,
56
64
  lastMessageText = 'Hey! How are you doing?',
57
- lastMessageTime = new Date(),
65
+ lastMessageTime = now(),
58
66
  unreadCount = 0,
59
67
  lastMessageAttachments,
60
68
  lastMessageMetadata,
@@ -134,7 +142,7 @@ Default.args = {
134
142
  participantId: 'participant-1',
135
143
  participantImage: 'https://i.pravatar.cc/150?img=2',
136
144
  lastMessageText: 'Hey! How are you doing?',
137
- lastMessageTime: new Date(),
145
+ lastMessageTime: now(),
138
146
  }),
139
147
  }
140
148
 
@@ -146,7 +154,7 @@ Selected.args = {
146
154
  participantId: 'participant-2',
147
155
  participantImage: 'https://i.pravatar.cc/150?img=3',
148
156
  lastMessageText: 'That sounds great!',
149
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
157
+ lastMessageTime: minutesAgo(5),
150
158
  }),
151
159
  selectedChannel: createMockChannel({
152
160
  id: 'channel-2',
@@ -164,7 +172,7 @@ WithUnreadMessages.args = {
164
172
  participantId: 'participant-3',
165
173
  participantImage: 'https://i.pravatar.cc/150?img=4',
166
174
  lastMessageText: 'Did you see my last message?',
167
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 15), // 15 minutes ago
175
+ lastMessageTime: minutesAgo(15),
168
176
  unreadCount: 3,
169
177
  }),
170
178
  }
@@ -177,7 +185,7 @@ ManyUnreadMessages.args = {
177
185
  participantId: 'participant-4',
178
186
  participantImage: 'https://i.pravatar.cc/150?img=5',
179
187
  lastMessageText: 'Please check this out!',
180
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60), // 1 hour ago
188
+ lastMessageTime: hoursAgo(1),
181
189
  unreadCount: 127,
182
190
  }),
183
191
  }
@@ -189,7 +197,7 @@ NoAvatar.args = {
189
197
  participantName: 'Emma Davis',
190
198
  participantId: 'participant-5',
191
199
  lastMessageText: 'Thanks for your help!',
192
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 30), // 30 minutes ago
200
+ lastMessageTime: minutesAgo(30),
193
201
  }),
194
202
  }
195
203
 
@@ -213,7 +221,7 @@ LongMessage.args = {
213
221
  participantImage: 'https://i.pravatar.cc/150?img=7',
214
222
  lastMessageText:
215
223
  '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.',
216
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
224
+ lastMessageTime: hoursAgo(2),
217
225
  }),
218
226
  }
219
227
 
@@ -224,7 +232,7 @@ LongName.args = {
224
232
  participantName: 'Alexander Christopher Wellington-Montgomery III',
225
233
  participantId: 'participant-8',
226
234
  lastMessageText: 'Nice to meet you!',
227
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 45), // 45 minutes ago
235
+ lastMessageTime: minutesAgo(45),
228
236
  }),
229
237
  }
230
238
 
@@ -236,7 +244,7 @@ ChatbotMessage.args = {
236
244
  participantId: 'participant-chatbot',
237
245
  participantImage: 'https://i.pravatar.cc/150?img=9',
238
246
  lastMessageText: 'Thanks for reaching out! How can I help you today?',
239
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 2), // 2 minutes ago
247
+ lastMessageTime: minutesAgo(2),
240
248
  lastMessageMetadata: { custom_type: 'MESSAGE_CHATBOT' },
241
249
  }),
242
250
  }
@@ -249,7 +257,7 @@ TipMessage.args = {
249
257
  participantId: 'participant-tip',
250
258
  participantImage: 'https://i.pravatar.cc/150?img=2',
251
259
  lastMessageText: 'Take my money!',
252
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 3),
260
+ lastMessageTime: minutesAgo(3),
253
261
  lastMessageMetadata: { custom_type: 'MESSAGE_TIP', amount_text: '$5.00' },
254
262
  }),
255
263
  }
@@ -262,7 +270,7 @@ PaidMessage.args = {
262
270
  participantId: 'participant-paid',
263
271
  participantImage: 'https://i.pravatar.cc/150?img=3',
264
272
  lastMessageText: 'I paid for this message!',
265
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 10),
273
+ lastMessageTime: minutesAgo(10),
266
274
  lastMessageMetadata: { custom_type: 'MESSAGE_PAID', amount_text: '$10.00' },
267
275
  }),
268
276
  }
@@ -275,7 +283,7 @@ PaidAttachment.args = {
275
283
  participantId: 'participant-paid-attachment',
276
284
  participantImage: 'https://i.pravatar.cc/150?img=4',
277
285
  lastMessageText: 'Check out this exclusive photo!',
278
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 20),
286
+ lastMessageTime: minutesAgo(20),
279
287
  lastMessageMetadata: {
280
288
  custom_type: 'MESSAGE_ATTACHMENT',
281
289
  attachment_title: 'exclusive-photo.jpg',
@@ -322,7 +330,7 @@ export const WithCustomMessagePreview: StoryFn = () => {
322
330
  participantImage: 'https://i.pravatar.cc/150?img=36',
323
331
  lastMessageText:
324
332
  'Critical issue needs immediate attention! Please review the server logs for more details and escalate to the on-call engineer. This system outage is affecting multiple users, and we need to resolve it as soon as possible. Impacted services include messaging, notifications, and real-time updates. All hands are required to investigate and remediate the situation. If you have any questions or need additional resources, contact the incident commander immediately!',
325
- lastMessageTime: new Date(Date.now() - 1000 * 60), // 1 minute ago
333
+ lastMessageTime: minutesAgo(1),
326
334
  lastMessageMetadata: { custom_type: 'MESSAGE_URGENT' },
327
335
  unreadCount: 3,
328
336
  }),
@@ -332,7 +340,7 @@ export const WithCustomMessagePreview: StoryFn = () => {
332
340
  participantId: 'participant-priority',
333
341
  participantImage: 'https://i.pravatar.cc/150?img=37',
334
342
  lastMessageText: 'High priority message',
335
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
343
+ lastMessageTime: minutesAgo(5),
336
344
  lastMessageMetadata: { is_priority: true },
337
345
  unreadCount: 1,
338
346
  }),
@@ -342,7 +350,7 @@ export const WithCustomMessagePreview: StoryFn = () => {
342
350
  participantId: 'participant-vip',
343
351
  participantImage: 'https://i.pravatar.cc/150?img=38',
344
352
  lastMessageText: 'Thanks for the support!',
345
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 10), // 10 minutes ago
353
+ lastMessageTime: minutesAgo(10),
346
354
  lastMessageMetadata: { is_vip: true },
347
355
  }),
348
356
  createMockChannel({
@@ -351,7 +359,7 @@ export const WithCustomMessagePreview: StoryFn = () => {
351
359
  participantId: 'participant-normal',
352
360
  participantImage: 'https://i.pravatar.cc/150?img=39',
353
361
  lastMessageText: 'Hey, how are you?',
354
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 30), // 30 minutes ago
362
+ lastMessageTime: minutesAgo(30),
355
363
  }),
356
364
  ]
357
365
 
@@ -379,7 +387,7 @@ SelectedWithUnread.args = {
379
387
  participantId: 'participant-9',
380
388
  participantImage: 'https://i.pravatar.cc/150?img=8',
381
389
  lastMessageText: 'Important update!',
382
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 10), // 10 minutes ago
390
+ lastMessageTime: minutesAgo(10),
383
391
  unreadCount: 5,
384
392
  }),
385
393
  selectedChannel: createMockChannel({
@@ -401,7 +409,7 @@ export const MultipleChannels: StoryFn = () => {
401
409
  participantId: 'participant-1',
402
410
  participantImage: 'https://i.pravatar.cc/150?img=2',
403
411
  lastMessageText: 'Hey! How are you doing?',
404
- lastMessageTime: new Date(),
412
+ lastMessageTime: now(),
405
413
  unreadCount: 2,
406
414
  }),
407
415
  createMockChannel({
@@ -410,7 +418,7 @@ export const MultipleChannels: StoryFn = () => {
410
418
  participantId: 'participant-2',
411
419
  participantImage: 'https://i.pravatar.cc/150?img=3',
412
420
  lastMessageText: 'That sounds great!',
413
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
421
+ lastMessageTime: minutesAgo(5),
414
422
  }),
415
423
  createMockChannel({
416
424
  id: 'channel-3',
@@ -418,7 +426,7 @@ export const MultipleChannels: StoryFn = () => {
418
426
  participantId: 'participant-3',
419
427
  participantImage: 'https://i.pravatar.cc/150?img=4',
420
428
  lastMessageText: 'See you tomorrow',
421
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60), // 1 hour ago
429
+ lastMessageTime: hoursAgo(1),
422
430
  unreadCount: 15,
423
431
  }),
424
432
  createMockChannel({
@@ -426,7 +434,7 @@ export const MultipleChannels: StoryFn = () => {
426
434
  participantName: 'David Brown',
427
435
  participantId: 'participant-4',
428
436
  lastMessageText: 'Thanks!',
429
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
437
+ lastMessageTime: hoursAgo(2),
430
438
  }),
431
439
  ]
432
440
 
@@ -457,7 +465,7 @@ WithUrl.args = {
457
465
  participantId: 'participant-url',
458
466
  participantImage: 'https://i.pravatar.cc/150?img=10',
459
467
  lastMessageText: 'https://example.com/page',
460
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 8), // 8 minutes ago
468
+ lastMessageTime: minutesAgo(8),
461
469
  }),
462
470
  }
463
471
 
@@ -470,11 +478,18 @@ WithVeryLongUrl.args = {
470
478
  participantImage: 'https://i.pravatar.cc/150?img=11',
471
479
  lastMessageText:
472
480
  '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',
473
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 20), // 20 minutes ago
481
+ lastMessageTime: minutesAgo(20),
474
482
  }),
475
483
  }
476
484
 
477
485
  // Time-based stories
486
+ //
487
+ // Each story below covers exactly one row of the `formatRelativeTime`
488
+ // output matrix. The `TimeVariations` story further down already snapshots
489
+ // all of them together in a labeled table — kept here for Storybook
490
+ // browsing but skipped in Chromatic to avoid 9 redundant snapshots.
491
+ const skipInChromatic = { chromatic: { disableSnapshot: true } }
492
+
478
493
  export const JustNow: StoryFn<ComponentProps> = Template.bind({})
479
494
  JustNow.args = {
480
495
  channel: createMockChannel({
@@ -483,9 +498,10 @@ JustNow.args = {
483
498
  participantId: 'participant-just-now',
484
499
  participantImage: 'https://i.pravatar.cc/150?img=12',
485
500
  lastMessageText: 'I just sent this!',
486
- lastMessageTime: new Date(Date.now() - 1000 * 30), // 30 seconds ago
501
+ lastMessageTime: secondsAgo(30),
487
502
  }),
488
503
  }
504
+ JustNow.parameters = skipInChromatic
489
505
 
490
506
  export const FiveMinutesAgo: StoryFn<ComponentProps> = Template.bind({})
491
507
  FiveMinutesAgo.args = {
@@ -495,9 +511,10 @@ FiveMinutesAgo.args = {
495
511
  participantId: 'participant-5m',
496
512
  participantImage: 'https://i.pravatar.cc/150?img=13',
497
513
  lastMessageText: 'Be right back',
498
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
514
+ lastMessageTime: minutesAgo(5),
499
515
  }),
500
516
  }
517
+ FiveMinutesAgo.parameters = skipInChromatic
501
518
 
502
519
  export const ThreeHoursAgo: StoryFn<ComponentProps> = Template.bind({})
503
520
  ThreeHoursAgo.args = {
@@ -507,9 +524,10 @@ ThreeHoursAgo.args = {
507
524
  participantId: 'participant-3h',
508
525
  participantImage: 'https://i.pravatar.cc/150?img=14',
509
526
  lastMessageText: 'Got it, thanks!',
510
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 3), // 3 hours ago
527
+ lastMessageTime: hoursAgo(3),
511
528
  }),
512
529
  }
530
+ ThreeHoursAgo.parameters = skipInChromatic
513
531
 
514
532
  export const Yesterday: StoryFn<ComponentProps> = Template.bind({})
515
533
  Yesterday.args = {
@@ -519,9 +537,10 @@ Yesterday.args = {
519
537
  participantId: 'participant-yesterday',
520
538
  participantImage: 'https://i.pravatar.cc/150?img=15',
521
539
  lastMessageText: 'See you tomorrow',
522
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 30), // 30 hours ago
540
+ lastMessageTime: hoursAgo(30),
523
541
  }),
524
542
  }
543
+ Yesterday.parameters = skipInChromatic
525
544
 
526
545
  export const ThreeDaysAgo: StoryFn<ComponentProps> = Template.bind({})
527
546
  ThreeDaysAgo.args = {
@@ -531,9 +550,10 @@ ThreeDaysAgo.args = {
531
550
  participantId: 'participant-3d',
532
551
  participantImage: 'https://i.pravatar.cc/150?img=16',
533
552
  lastMessageText: 'Have a great weekend!',
534
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3), // 3 days ago
553
+ lastMessageTime: daysAgo(3),
535
554
  }),
536
555
  }
556
+ ThreeDaysAgo.parameters = skipInChromatic
537
557
 
538
558
  export const LastWeek: StoryFn<ComponentProps> = Template.bind({})
539
559
  LastWeek.args = {
@@ -543,9 +563,10 @@ LastWeek.args = {
543
563
  participantId: 'participant-1w',
544
564
  participantImage: 'https://i.pravatar.cc/150?img=17',
545
565
  lastMessageText: 'Talk to you next week',
546
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7), // 1 week ago
566
+ lastMessageTime: daysAgo(7),
547
567
  }),
548
568
  }
569
+ LastWeek.parameters = skipInChromatic
549
570
 
550
571
  export const TwoWeeksAgo: StoryFn<ComponentProps> = Template.bind({})
551
572
  TwoWeeksAgo.args = {
@@ -555,9 +576,10 @@ TwoWeeksAgo.args = {
555
576
  participantId: 'participant-2w',
556
577
  participantImage: 'https://i.pravatar.cc/150?img=18',
557
578
  lastMessageText: 'Sounds good',
558
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14), // 2 weeks ago
579
+ lastMessageTime: daysAgo(14),
559
580
  }),
560
581
  }
582
+ TwoWeeksAgo.parameters = skipInChromatic
561
583
 
562
584
  export const OneMonthAgo: StoryFn<ComponentProps> = Template.bind({})
563
585
  OneMonthAgo.args = {
@@ -567,9 +589,10 @@ OneMonthAgo.args = {
567
589
  participantId: 'participant-1m',
568
590
  participantImage: 'https://i.pravatar.cc/150?img=19',
569
591
  lastMessageText: 'Happy New Year!',
570
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), // 1 month ago
592
+ lastMessageTime: daysAgo(30),
571
593
  }),
572
594
  }
595
+ OneMonthAgo.parameters = skipInChromatic
573
596
 
574
597
  export const SixMonthsAgo: StoryFn<ComponentProps> = Template.bind({})
575
598
  SixMonthsAgo.args = {
@@ -579,9 +602,10 @@ SixMonthsAgo.args = {
579
602
  participantId: 'participant-6m',
580
603
  participantImage: 'https://i.pravatar.cc/150?img=20',
581
604
  lastMessageText: 'Long time no talk!',
582
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 24 * 180), // 6 months ago
605
+ lastMessageTime: daysAgo(180),
583
606
  }),
584
607
  }
608
+ SixMonthsAgo.parameters = skipInChromatic
585
609
 
586
610
  export const TimeVariations: StoryFn = () => {
587
611
  const [selectedChannelId, _setSelectedChannelId] = React.useState<
@@ -592,61 +616,61 @@ export const TimeVariations: StoryFn = () => {
592
616
  {
593
617
  id: 'time-just-now',
594
618
  name: 'Just Now',
595
- time: new Date(Date.now() - 1000 * 30), // 30 seconds
619
+ time: secondsAgo(30),
596
620
  message: 'Just sent',
597
621
  },
598
622
  {
599
623
  id: 'time-5m',
600
624
  name: '5 Minutes',
601
- time: new Date(Date.now() - 1000 * 60 * 5),
625
+ time: minutesAgo(5),
602
626
  message: '5 minutes ago',
603
627
  },
604
628
  {
605
629
  id: 'time-30m',
606
630
  name: '30 Minutes',
607
- time: new Date(Date.now() - 1000 * 60 * 30),
631
+ time: minutesAgo(30),
608
632
  message: '30 minutes ago',
609
633
  },
610
634
  {
611
635
  id: 'time-3h',
612
636
  name: '3 Hours',
613
- time: new Date(Date.now() - 1000 * 60 * 60 * 3),
637
+ time: hoursAgo(3),
614
638
  message: '3 hours ago',
615
639
  },
616
640
  {
617
641
  id: 'time-yesterday',
618
642
  name: 'Yesterday',
619
- time: new Date(Date.now() - 1000 * 60 * 60 * 30),
643
+ time: hoursAgo(30),
620
644
  message: 'Yesterday',
621
645
  },
622
646
  {
623
647
  id: 'time-3d',
624
648
  name: '3 Days',
625
- time: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3),
649
+ time: daysAgo(3),
626
650
  message: '3 days ago',
627
651
  },
628
652
  {
629
653
  id: 'time-1w',
630
654
  name: '1 Week',
631
- time: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
655
+ time: daysAgo(7),
632
656
  message: '1 week ago',
633
657
  },
634
658
  {
635
659
  id: 'time-2w',
636
660
  name: '2 Weeks',
637
- time: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14),
661
+ time: daysAgo(14),
638
662
  message: '2 weeks ago',
639
663
  },
640
664
  {
641
665
  id: 'time-1m',
642
666
  name: '1 Month',
643
- time: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30),
667
+ time: daysAgo(30),
644
668
  message: '1 month ago',
645
669
  },
646
670
  {
647
671
  id: 'time-6m',
648
672
  name: '6 Months',
649
- time: new Date(Date.now() - 1000 * 60 * 60 * 24 * 180),
673
+ time: daysAgo(180),
650
674
  message: '6 months ago',
651
675
  },
652
676
  ]
@@ -690,7 +714,7 @@ WithAttachmentAssetUrl.args = {
690
714
  participantId: 'participant-attachment-asset',
691
715
  participantImage: 'https://i.pravatar.cc/150?img=30',
692
716
  lastMessageText: '', // No text, only attachment
693
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 10), // 10 minutes ago
717
+ lastMessageTime: minutesAgo(10),
694
718
  lastMessageAttachments: [
695
719
  {
696
720
  asset_url: 'https://example.com/document.pdf',
@@ -707,7 +731,7 @@ WithAttachmentImageUrl.args = {
707
731
  participantId: 'participant-attachment-image',
708
732
  participantImage: 'https://i.pravatar.cc/150?img=31',
709
733
  lastMessageText: '', // No text, only attachment
710
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 15), // 15 minutes ago
734
+ lastMessageTime: minutesAgo(15),
711
735
  lastMessageAttachments: [
712
736
  {
713
737
  image_url: 'https://example.com/image.jpg',
@@ -715,6 +739,10 @@ WithAttachmentImageUrl.args = {
715
739
  ],
716
740
  }),
717
741
  }
742
+ // Same fallback render as WithAttachmentAssetUrl ("📎 Sent an attachment") —
743
+ // kept as a separate story to document the field, skipped in Chromatic to
744
+ // avoid a redundant snapshot.
745
+ WithAttachmentImageUrl.parameters = skipInChromatic
718
746
 
719
747
  export const WithAttachmentOgScrapeUrl: StoryFn<ComponentProps> = Template.bind(
720
748
  {}
@@ -726,7 +754,7 @@ WithAttachmentOgScrapeUrl.args = {
726
754
  participantId: 'participant-attachment-og',
727
755
  participantImage: 'https://i.pravatar.cc/150?img=32',
728
756
  lastMessageText: '', // No text, only attachment
729
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 7), // 7 minutes ago
757
+ lastMessageTime: minutesAgo(7),
730
758
  lastMessageAttachments: [
731
759
  {
732
760
  og_scrape_url: 'https://example.com/article',
@@ -743,7 +771,7 @@ WithAttachmentThumbUrl.args = {
743
771
  participantId: 'participant-attachment-thumb',
744
772
  participantImage: 'https://i.pravatar.cc/150?img=33',
745
773
  lastMessageText: '', // No text, only attachment
746
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 12), // 12 minutes ago
774
+ lastMessageTime: minutesAgo(12),
747
775
  lastMessageAttachments: [
748
776
  {
749
777
  thumb_url: 'https://example.com/thumb.jpg',
@@ -751,6 +779,7 @@ WithAttachmentThumbUrl.args = {
751
779
  ],
752
780
  }),
753
781
  }
782
+ WithAttachmentThumbUrl.parameters = skipInChromatic
754
783
 
755
784
  export const WithLongAttachmentUrl: StoryFn<ComponentProps> = Template.bind({})
756
785
  WithLongAttachmentUrl.args = {
@@ -760,7 +789,7 @@ WithLongAttachmentUrl.args = {
760
789
  participantId: 'participant-attachment-long',
761
790
  participantImage: 'https://i.pravatar.cc/150?img=34',
762
791
  lastMessageText: '', // No text, only attachment
763
- lastMessageTime: new Date(Date.now() - 1000 * 60 * 20), // 20 minutes ago
792
+ lastMessageTime: minutesAgo(20),
764
793
  lastMessageAttachments: [
765
794
  {
766
795
  asset_url:
@@ -769,3 +798,4 @@ WithLongAttachmentUrl.args = {
769
798
  ],
770
799
  }),
771
800
  }
801
+ WithLongAttachmentUrl.parameters = skipInChromatic
@@ -9,6 +9,7 @@ import {
9
9
  } from 'stream-chat'
10
10
  import { Chat } from 'stream-chat-react'
11
11
 
12
+ import { hoursAgo, minutesAgo, now } from '../stories/decorators/storyTime'
12
13
  import { mockParticipants } from '../stories/mocks'
13
14
 
14
15
  import { ChannelView } from './ChannelView'
@@ -50,8 +51,8 @@ const createMockChannel = async (
50
51
  id: 'msg-1',
51
52
  text: 'Hey! How are you doing?',
52
53
  type: 'regular' as const,
53
- created_at: new Date(Date.now() - 1000 * 60 * 60),
54
- updated_at: new Date(Date.now() - 1000 * 60 * 60),
54
+ created_at: hoursAgo(1),
55
+ updated_at: hoursAgo(1),
55
56
  user: participant,
56
57
  html: '<p>Hey! How are you doing?</p>',
57
58
  attachments: [],
@@ -68,8 +69,8 @@ const createMockChannel = async (
68
69
  id: 'msg-2',
69
70
  text: "I'm doing great, thanks! How about you?",
70
71
  type: 'regular' as const,
71
- created_at: new Date(Date.now() - 1000 * 60 * 50),
72
- updated_at: new Date(Date.now() - 1000 * 60 * 50),
72
+ created_at: minutesAgo(50),
73
+ updated_at: minutesAgo(50),
73
74
  user: mockUser,
74
75
  html: "<p>I'm doing great, thanks! How about you?</p>",
75
76
  attachments: [],
@@ -86,8 +87,8 @@ const createMockChannel = async (
86
87
  id: 'msg-3',
87
88
  text: 'Pretty good! Just working on some exciting stuff.',
88
89
  type: 'regular' as const,
89
- created_at: new Date(Date.now() - 1000 * 60 * 30),
90
- updated_at: new Date(Date.now() - 1000 * 60 * 30),
90
+ created_at: minutesAgo(30),
91
+ updated_at: minutesAgo(30),
91
92
  user: participant,
92
93
  html: '<p>Pretty good! Just working on some exciting stuff.</p>',
93
94
  attachments: [],
@@ -429,7 +430,7 @@ const WithStarButtonTemplate: StoryFn<ComponentProps> = (args) => {
429
430
  const member: ChannelMemberResponse = {
430
431
  user: mockUser,
431
432
  user_id: mockUser.id,
432
- pinned_at: isStarred ? new Date().toISOString() : null,
433
+ pinned_at: isStarred ? now().toISOString() : null,
433
434
  }
434
435
 
435
436
  client.dispatchEvent({
@@ -22,7 +22,18 @@ vi.mock('stream-chat-react', () => ({
22
22
  Window: ({ children }: { children: React.ReactNode }) => (
23
23
  <div data-testid="window">{children}</div>
24
24
  ),
25
- MessageList: () => <div data-testid="message-list" />,
25
+ MessageList: ({
26
+ renderMessages,
27
+ }: {
28
+ renderMessages?: (options: Record<string, unknown>) => React.ReactNode[]
29
+ }) => (
30
+ <ul data-testid="message-list">
31
+ {renderMessages?.({ messages: [] }) ?? null}
32
+ </ul>
33
+ ),
34
+ defaultRenderMessages: () => [
35
+ <li key="message-list-item" data-testid="message-list-item" />,
36
+ ],
26
37
  WithComponents: ({ children }: { children: React.ReactNode }) => (
27
38
  <>{children}</>
28
39
  ),
@@ -10,6 +10,7 @@ import {
10
10
  useChannelStateContext,
11
11
  WithComponents,
12
12
  MessageUIComponentProps,
13
+ defaultRenderMessages,
13
14
  } from 'stream-chat-react'
14
15
 
15
16
  import { useChannelStar } from '../hooks/useChannelStar'
@@ -399,25 +400,41 @@ const ChannelViewInner: React.FC<{
399
400
  key="lt-channel-message-list"
400
401
  className="flex-1 overflow-hidden relative"
401
402
  >
402
- <MessageList hideDeletedMessages hideNewMessageSeparator={false} />
403
- </div>
403
+ <MessageList
404
+ hideDeletedMessages
405
+ hideNewMessageSeparator={false}
406
+ renderMessages={(options) => {
407
+ const elements = defaultRenderMessages(options)
404
408
 
405
- {renderConversationFooter ? (
406
- <React.Fragment key="lt-channel-conversation-footer">
407
- {renderConversationFooter(channel)}
408
- </React.Fragment>
409
- ) : null}
409
+ if (renderConversationFooter) {
410
+ elements.push(
411
+ <li key="lt-channel-conversation-footer">
412
+ {renderConversationFooter(channel)}
413
+ </li>
414
+ )
415
+ }
410
416
 
411
- {/* Message Input */}
412
- <CustomMessageInput
413
- key="lt-channel-message-input"
414
- {...(renderMessageInputActions && {
415
- renderActions: () => renderMessageInputActions?.(channel),
416
- })}
417
- renderFooter={() => renderMessageInputFooter?.(channel)}
418
- disabled={composerDisabled}
419
- disabledReason={composerDisabledReason}
420
- />
417
+ elements.push(
418
+ // Message Input
419
+ <li
420
+ key="lt-channel-message-input"
421
+ className="flex sticky bottom-0 p-4 pt-1 inset-x-0 mx-[calc(-1*var(--str-chat\_\_spacing-2))] md:mx-[calc(-1*min(var(--str-chat\_\_spacing-10),4%))]"
422
+ >
423
+ <div className="absolute bottom-0 inset-x-0 w-full h-4/5 backdrop-blur-[16px] [mask-image:linear-gradient(to_top,black_0%,transparent_100%)] [-webkit-mask-image:linear-gradient(to_top,black_0%,transparent_100%)]" />
424
+ <CustomMessageInput
425
+ className="isolate w-full"
426
+ renderActions={() => renderMessageInputActions?.(channel)}
427
+ renderFooter={() => renderMessageInputFooter?.(channel)}
428
+ disabled={composerDisabled}
429
+ disabledReason={composerDisabledReason}
430
+ />
431
+ </li>
432
+ )
433
+
434
+ return elements
435
+ }}
436
+ />
437
+ </div>
421
438
  </Window>
422
439
  </WithComponents>
423
440
  </>
@@ -0,0 +1,31 @@
1
+ import type { Meta, StoryFn } from '@storybook/react'
2
+ import React from 'react'
3
+
4
+ import { CloseButton } from '.'
5
+
6
+ type ComponentProps = React.ComponentProps<typeof CloseButton>
7
+
8
+ const meta: Meta<ComponentProps> = {
9
+ title: 'CloseButton',
10
+ component: CloseButton,
11
+ parameters: { layout: 'centered' },
12
+ argTypes: {
13
+ onClick: { action: 'clicked' },
14
+ },
15
+ }
16
+ export default meta
17
+
18
+ const Template: StoryFn<ComponentProps> = (args) => (
19
+ <div className="p-12">
20
+ <CloseButton {...args} />
21
+ </div>
22
+ )
23
+
24
+ export const Default: StoryFn<ComponentProps> = Template.bind({})
25
+ Default.args = {}
26
+
27
+ export const OnDarkSurface: StoryFn<ComponentProps> = () => (
28
+ <div className="bg-[#121110] p-12">
29
+ <CloseButton onClick={() => undefined} />
30
+ </div>
31
+ )
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryFn } from '@storybook/react'
2
+ import React from 'react'
3
+
4
+ import { daysAgo, hoursAgo, now } from '../../stories/decorators/storyTime'
5
+
6
+ import { CustomDateSeparator } from '.'
7
+
8
+ type ComponentProps = React.ComponentProps<typeof CustomDateSeparator>
9
+
10
+ const meta: Meta<ComponentProps> = {
11
+ title: 'CustomDateSeparator',
12
+ component: CustomDateSeparator,
13
+ parameters: { layout: 'fullscreen' },
14
+ }
15
+ export default meta
16
+
17
+ const Template: StoryFn<ComponentProps> = (args) => (
18
+ <div className="w-[640px] bg-background-primary p-6">
19
+ <CustomDateSeparator {...args} />
20
+ </div>
21
+ )
22
+
23
+ export const Today: StoryFn<ComponentProps> = Template.bind({})
24
+ Today.args = { date: hoursAgo(2) }
25
+
26
+ export const Yesterday: StoryFn<ComponentProps> = Template.bind({})
27
+ Yesterday.args = { date: hoursAgo(30) }
28
+
29
+ export const LastWeek: StoryFn<ComponentProps> = Template.bind({})
30
+ LastWeek.args = { date: daysAgo(6) }
31
+
32
+ export const Unread: StoryFn<ComponentProps> = Template.bind({})
33
+ Unread.args = { date: now(), unread: true }