@linktr.ee/messaging-react 4.1.1-rc-1786774403 → 4.1.1-rc-1786776179

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "4.1.1-rc-1786774403",
3
+ "version": "4.1.1-rc-1786776179",
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-1786774403",
53
+ "@linktr.ee/messaging-core": "2.4.0-rc-1786776179",
54
54
  "@linktr.ee/messaging-taxonomy": "^0.10.0",
55
55
  "@phosphor-icons/react": "^2.1.10"
56
56
  },
@@ -10,7 +10,6 @@ const meta: Meta<ComponentProps> = {
10
10
  component: ActionButton,
11
11
  parameters: {
12
12
  layout: 'centered',
13
- chromatic: { disableSnapshot: true },
14
13
  },
15
14
  argTypes: {
16
15
  onClick: { action: 'clicked' },
@@ -8,7 +8,7 @@ type ComponentProps = React.ComponentProps<typeof AttachmentCard>
8
8
  const meta: Meta<ComponentProps> = {
9
9
  title: 'AttachmentCard',
10
10
  component: AttachmentCard,
11
- parameters: { layout: 'centered', chromatic: { disableSnapshot: true } },
11
+ parameters: { layout: 'centered' },
12
12
  argTypes: {
13
13
  variant: { control: { type: 'inline-radio' }, options: ['light', 'dark'] },
14
14
  },
@@ -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', chromatic: { disableSnapshot: true } },
11
+ parameters: { layout: 'centered' },
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', chromatic: { disableSnapshot: true } },
13
+ parameters: { layout: 'fullscreen' },
14
14
  }
15
15
  export default meta
16
16
 
@@ -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 } }
@@ -22,6 +22,15 @@ const Template: StoryFn<ComponentProps> = (args) => {
22
22
  )
23
23
  }
24
24
 
25
+ // Per-variant stories stay in Storybook for browsing/design review but are
26
+ // individually covered by cells in AllStates — skip in Chromatic.
27
+ const skipInChromatic = { chromatic: { disableSnapshot: true } }
28
+
29
+ // Deterministic local avatar for the grid so a pravatar network flake can't
30
+ // diff the whole AllStates snapshot. Individual stories keep their remote
31
+ // images (they're skipped in Chromatic anyway).
32
+ const LOCAL_AVATAR = '/image-thumbnail.jpg'
33
+
25
34
  export const Default: StoryFn<ComponentProps> = Template.bind({})
26
35
  Default.args = {
27
36
  faqs: [
@@ -52,6 +61,7 @@ Default.args = {
52
61
  avatarName: 'Brie Parson',
53
62
  avatarImage: 'https://i.pravatar.cc/150?img=47',
54
63
  }
64
+ Default.parameters = skipInChromatic
55
65
 
56
66
  export const WithoutHeader: StoryFn<ComponentProps> = Template.bind({})
57
67
  WithoutHeader.args = {
@@ -73,6 +83,7 @@ WithoutHeader.args = {
73
83
  ],
74
84
  onFaqClick: (id) => console.log('FAQ clicked:', id),
75
85
  }
86
+ WithoutHeader.parameters = skipInChromatic
76
87
 
77
88
  export const WithDisabledFaq: StoryFn<ComponentProps> = Template.bind({})
78
89
  WithDisabledFaq.args = {
@@ -101,6 +112,7 @@ WithDisabledFaq.args = {
101
112
  ],
102
113
  onFaqClick: (id) => console.log('FAQ clicked:', id),
103
114
  }
115
+ WithDisabledFaq.parameters = skipInChromatic
104
116
 
105
117
  export const WithLoading: StoryFn<ComponentProps> = Template.bind({})
106
118
  WithLoading.args = {
@@ -131,12 +143,14 @@ WithLoading.args = {
131
143
  loadingFaqId: '2',
132
144
  headerText: 'Tap to send a suggested question:',
133
145
  }
146
+ WithLoading.parameters = skipInChromatic
134
147
 
135
148
  export const Empty: StoryFn<ComponentProps> = Template.bind({})
136
149
  Empty.args = {
137
150
  faqs: [],
138
151
  onFaqClick: (id) => console.log('FAQ clicked:', id),
139
152
  }
153
+ Empty.parameters = skipInChromatic
140
154
 
141
155
  export const WithAvatarOnly: StoryFn<ComponentProps> = Template.bind({})
142
156
  WithAvatarOnly.args = {
@@ -160,6 +174,7 @@ WithAvatarOnly.args = {
160
174
  headerText: 'Frequently Asked Questions',
161
175
  avatarName: 'Account Owner',
162
176
  }
177
+ WithAvatarOnly.parameters = skipInChromatic
163
178
 
164
179
  export const WithAvatarAndImage: StoryFn<ComponentProps> = Template.bind({})
165
180
  WithAvatarAndImage.args = {
@@ -184,3 +199,192 @@ WithAvatarAndImage.args = {
184
199
  avatarName: 'Sarah Johnson',
185
200
  avatarImage: 'https://i.pravatar.cc/150?img=28',
186
201
  }
202
+ WithAvatarAndImage.parameters = skipInChromatic
203
+
204
+ export const AllStates: StoryFn = () => {
205
+ const states: { label: string; props: ComponentProps }[] = [
206
+ {
207
+ label: 'Default (header + avatar + image)',
208
+ props: {
209
+ faqs: [
210
+ {
211
+ id: '1',
212
+ question: 'Book club slots',
213
+ answer: 'We ship worldwide...',
214
+ enabled: true,
215
+ order: 1,
216
+ },
217
+ {
218
+ id: '2',
219
+ question: 'Refunds',
220
+ answer: 'You can track...',
221
+ enabled: true,
222
+ order: 2,
223
+ },
224
+ {
225
+ id: '3',
226
+ question: 'Brand collabs',
227
+ answer: 'Returns are accepted...',
228
+ enabled: true,
229
+ order: 3,
230
+ },
231
+ ],
232
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
233
+ headerText: 'Tap to send a suggested question by Brie Parson:',
234
+ avatarName: 'Brie Parson',
235
+ avatarImage: LOCAL_AVATAR,
236
+ },
237
+ },
238
+ {
239
+ label: 'Without header',
240
+ props: {
241
+ faqs: [
242
+ {
243
+ id: '1',
244
+ question: 'Book club slots',
245
+ answer: 'We ship worldwide...',
246
+ enabled: true,
247
+ order: 1,
248
+ },
249
+ {
250
+ id: '2',
251
+ question: 'Refunds',
252
+ answer: 'You can track...',
253
+ enabled: true,
254
+ order: 2,
255
+ },
256
+ ],
257
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
258
+ },
259
+ },
260
+ {
261
+ label: 'With avatar only',
262
+ props: {
263
+ faqs: [
264
+ {
265
+ id: '1',
266
+ question: 'Book club slots',
267
+ answer: 'We ship worldwide...',
268
+ enabled: true,
269
+ order: 1,
270
+ },
271
+ {
272
+ id: '2',
273
+ question: 'Refunds',
274
+ answer: 'You can track...',
275
+ enabled: true,
276
+ order: 2,
277
+ },
278
+ ],
279
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
280
+ headerText: 'Frequently Asked Questions',
281
+ avatarName: 'Account Owner',
282
+ },
283
+ },
284
+ {
285
+ label: 'With avatar and image',
286
+ props: {
287
+ faqs: [
288
+ {
289
+ id: '1',
290
+ question: 'How do I contact support?',
291
+ answer: 'You can reach us...',
292
+ enabled: true,
293
+ order: 1,
294
+ },
295
+ {
296
+ id: '2',
297
+ question: 'What are your hours?',
298
+ answer: 'We are available...',
299
+ enabled: true,
300
+ order: 2,
301
+ },
302
+ ],
303
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
304
+ headerText: 'Questions for Sarah',
305
+ avatarName: 'Sarah Johnson',
306
+ avatarImage: LOCAL_AVATAR,
307
+ },
308
+ },
309
+ {
310
+ label: 'With disabled FAQ',
311
+ props: {
312
+ faqs: [
313
+ {
314
+ id: '1',
315
+ question: 'Enabled question',
316
+ answer: 'Answer...',
317
+ enabled: true,
318
+ order: 1,
319
+ },
320
+ {
321
+ id: '2',
322
+ question: 'Disabled question',
323
+ answer: 'Answer...',
324
+ enabled: false,
325
+ order: 2,
326
+ },
327
+ {
328
+ id: '3',
329
+ question: 'Another enabled',
330
+ answer: 'Answer...',
331
+ enabled: true,
332
+ order: 3,
333
+ },
334
+ ],
335
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
336
+ },
337
+ },
338
+ {
339
+ label: 'With loading',
340
+ props: {
341
+ faqs: [
342
+ {
343
+ id: '1',
344
+ question: 'Book club slots',
345
+ answer: 'We ship worldwide...',
346
+ enabled: true,
347
+ order: 1,
348
+ },
349
+ {
350
+ id: '2',
351
+ question: 'Refunds',
352
+ answer: 'You can track...',
353
+ enabled: true,
354
+ order: 2,
355
+ },
356
+ {
357
+ id: '3',
358
+ question: 'Brand collabs',
359
+ answer: 'Returns are accepted...',
360
+ enabled: true,
361
+ order: 3,
362
+ },
363
+ ],
364
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
365
+ loadingFaqId: '2',
366
+ headerText: 'Tap to send a suggested question:',
367
+ },
368
+ },
369
+ {
370
+ label: 'Empty (renders nothing)',
371
+ props: {
372
+ faqs: [],
373
+ onFaqClick: (id) => console.log('FAQ clicked:', id),
374
+ },
375
+ },
376
+ ]
377
+
378
+ return (
379
+ <div className="flex flex-col gap-6 bg-white p-6">
380
+ {states.map(({ label, props }) => (
381
+ <div key={label} className="flex flex-col gap-1">
382
+ <span className="text-xs text-stone">{label}</span>
383
+ <div className="w-96">
384
+ <FaqList {...props} />
385
+ </div>
386
+ </div>
387
+ ))}
388
+ </div>
389
+ )
390
+ }
@@ -9,7 +9,6 @@ const meta: Meta<ComponentProps> = {
9
9
  component: FaqListItem,
10
10
  parameters: {
11
11
  layout: 'centered',
12
- chromatic: { disableSnapshot: true },
13
12
  },
14
13
  }
15
14
  export default meta
@@ -11,7 +11,6 @@ const meta: Meta<ComponentProps> = {
11
11
  component: IconButton,
12
12
  parameters: {
13
13
  layout: 'centered',
14
- chromatic: { disableSnapshot: true },
15
14
  },
16
15
  argTypes: {
17
16
  onClick: { action: 'clicked' },
@@ -10,7 +10,6 @@ const meta: Meta<ComponentProps> = {
10
10
  component: Loading,
11
11
  parameters: {
12
12
  layout: 'centered',
13
- chromatic: { disableSnapshot: true },
14
13
  },
15
14
  }
16
15
  export default meta
@@ -10,7 +10,6 @@ const meta: Meta<ComponentProps> = {
10
10
  component: ErrorState,
11
11
  parameters: {
12
12
  layout: 'fullscreen',
13
- chromatic: { disableSnapshot: true },
14
13
  },
15
14
  }
16
15
 
@@ -10,7 +10,6 @@ const meta: Meta<ComponentProps> = {
10
10
  component: LoadingState,
11
11
  parameters: {
12
12
  layout: 'fullscreen',
13
- chromatic: { disableSnapshot: true },
14
13
  },
15
14
  }
16
15
 
@@ -10,7 +10,6 @@ const meta: Meta<ComponentProps> = {
10
10
  component: SearchInput,
11
11
  parameters: {
12
12
  layout: 'centered',
13
- chromatic: { disableSnapshot: true },
14
13
  },
15
14
  }
16
15
  export default meta
@@ -14,7 +14,7 @@ import { createMockMessagingClient } from './createMockMessagingClient'
14
14
  */
15
15
  const meta: Meta = {
16
16
  title: 'testing/createMockMessagingClient',
17
- parameters: { layout: 'fullscreen', chromatic: { disableSnapshot: true } },
17
+ parameters: { layout: 'fullscreen' },
18
18
  }
19
19
  export default meta
20
20