@sproutsocial/seeds-react-list 0.0.2 → 0.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.
@@ -0,0 +1,659 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import DataList from "./DataList";
4
+ import ListItemContent from "./ListItemContent";
5
+ import ListItemButton from "./ListItemButton";
6
+ import DataItemSkeleton from "./DataItemSkeleton";
7
+ import { Button } from "@sproutsocial/seeds-react-button";
8
+ import { Icon } from "@sproutsocial/seeds-react-icon";
9
+ import type { TypeIconName } from "@sproutsocial/seeds-react-icon";
10
+ import {
11
+ ActionMenu,
12
+ MenuToggleButton,
13
+ MenuContent,
14
+ MenuItem,
15
+ } from "@sproutsocial/seeds-react-menu";
16
+ import Box from "@sproutsocial/seeds-react-box";
17
+
18
+ const meta = {
19
+ title: "Components/DataList",
20
+ component: DataList,
21
+ decorators: [
22
+ (Story) => (
23
+ <Box maxWidth="500px" height="100%">
24
+ <Story />
25
+ </Box>
26
+ ),
27
+ ],
28
+ } satisfies Meta<typeof DataList>;
29
+
30
+ export default meta;
31
+ type Story<T = unknown> = StoryObj<Meta<typeof DataList<T>>>;
32
+
33
+ interface SimpleItem {
34
+ id: string;
35
+ name: string;
36
+ }
37
+
38
+ const simpleData: SimpleItem[] = [
39
+ { id: "1", name: "First item" },
40
+ { id: "2", name: "Second item" },
41
+ { id: "3", name: "Third item" },
42
+ ];
43
+
44
+ export const Basic: Story = {
45
+ name: "Basic",
46
+ render: () => (
47
+ <DataList
48
+ data={simpleData}
49
+ renderItem={(item) => <div key={item.id}>{item.name}</div>}
50
+ />
51
+ ),
52
+ };
53
+
54
+ interface ListItemData {
55
+ id: string;
56
+ text: string;
57
+ details?: string;
58
+ aside?: string;
59
+ iconName?: TypeIconName;
60
+ }
61
+
62
+ const listItemData: ListItemData[] = [
63
+ {
64
+ id: "1",
65
+ text: "Negative Spikes – Snouts Paws & Tails",
66
+ details:
67
+ "Are there any negative sentiment spikes in Snouts Paws & Tails Brand?",
68
+ aside: "Nov 5",
69
+ },
70
+ {
71
+ id: "2",
72
+ text: "Another Item",
73
+ details: "This is a detail about the item",
74
+ aside: "Dec 10",
75
+ },
76
+ {
77
+ id: "3",
78
+ text: "Another Item",
79
+ details: "This is a detail about the item",
80
+ aside: "Dec 10",
81
+ },
82
+ {
83
+ id: "4",
84
+ text: "Another Item",
85
+ details: "This is a detail about the item",
86
+ aside: "Dec 10",
87
+ },
88
+ {
89
+ id: "5",
90
+ text: "Another Item",
91
+ details: "This is a detail about the item",
92
+ aside: "Dec 10",
93
+ },
94
+ ];
95
+
96
+ export const WithListItems: Story<ListItemData> = {
97
+ name: "With List Items",
98
+ args: {
99
+ data: listItemData,
100
+ renderItem: (item) => (
101
+ <ListItemContent
102
+ key={item.id}
103
+ text={item.text}
104
+ details={item.details}
105
+ aside={item.aside}
106
+ />
107
+ ),
108
+ },
109
+ };
110
+
111
+ const listItemWithIconsData: ListItemData[] = [
112
+ {
113
+ id: "1",
114
+ text: "Negative Spikes – Snouts Paws & Tails",
115
+ details: "Are there any negative sentiment spikes in Snouts Paws Brand?",
116
+ aside: "Nov 5",
117
+ iconName: "tag-solid",
118
+ },
119
+ {
120
+ id: "2",
121
+ text: "Another Item",
122
+ details: "This is a detail about the item",
123
+ aside: "Dec 10",
124
+ iconName: "tag-outline",
125
+ },
126
+ {
127
+ id: "3",
128
+ text: "Another Item",
129
+ details: "This is a detail about the item",
130
+ aside: "Dec 10",
131
+ iconName: "tag-outline",
132
+ },
133
+ {
134
+ id: "4",
135
+ text: "Another Item",
136
+ details: "This is a detail about the item",
137
+ aside: "Dec 10",
138
+ iconName: "tag-outline",
139
+ },
140
+ {
141
+ id: "5",
142
+ text: "Another Item",
143
+ details: "This is a detail about the item",
144
+ aside: "Dec 10",
145
+ iconName: "tag-outline",
146
+ },
147
+ {
148
+ id: "6",
149
+ text: "Another Item",
150
+ details: "This is a detail about the item",
151
+ aside: "Dec 10",
152
+ iconName: "tag-outline",
153
+ },
154
+ {
155
+ id: "7",
156
+ text: "Another Item",
157
+ details: "This is a detail about the item",
158
+ aside: "Dec 10",
159
+ iconName: "tag-outline",
160
+ },
161
+ {
162
+ id: "8",
163
+ text: "Another Item",
164
+ details: "This is a detail about the item",
165
+ aside: "Dec 10",
166
+ iconName: "tag-outline",
167
+ },
168
+ {
169
+ id: "9",
170
+ text: "Another Item",
171
+ details: "This is a detail about the item",
172
+ aside: "Dec 10",
173
+ iconName: "tag-outline",
174
+ },
175
+ {
176
+ id: "10",
177
+ text: "Another Item",
178
+ details: "This is a detail about the item",
179
+ aside: "Dec 10",
180
+ iconName: "tag-outline",
181
+ },
182
+ {
183
+ id: "11",
184
+ text: "Another Item",
185
+ details: "This is a detail about the item",
186
+ aside: "Dec 10",
187
+ iconName: "tag-outline",
188
+ },
189
+ {
190
+ id: "12",
191
+ text: "Another Item",
192
+ details: "This is a detail about the item",
193
+ aside: "Dec 10",
194
+ iconName: "tag-outline",
195
+ },
196
+ {
197
+ id: "13",
198
+ text: "Another Item",
199
+ details: "This is a detail about the item",
200
+ aside: "Dec 10",
201
+ iconName: "tag-outline",
202
+ },
203
+ {
204
+ id: "14",
205
+ text: "Another Item",
206
+ details: "This is a detail about the item",
207
+ aside: "Dec 10",
208
+ iconName: "tag-outline",
209
+ },
210
+ {
211
+ id: "15",
212
+ text: "Another Item",
213
+ details: "This is a detail about the item",
214
+ aside: "Dec 10",
215
+ iconName: "tag-outline",
216
+ },
217
+ {
218
+ id: "16",
219
+ text: "Another Item",
220
+ details: "This is a detail about the item",
221
+ aside: "Dec 10",
222
+ iconName: "tag-outline",
223
+ },
224
+ {
225
+ id: "17",
226
+ text: "Another Item",
227
+ details: "This is a detail about the item",
228
+ aside: "Dec 10",
229
+ iconName: "tag-outline",
230
+ },
231
+ {
232
+ id: "18",
233
+ text: "Another Item",
234
+ details: "This is a detail about the item",
235
+ aside: "Dec 10",
236
+ iconName: "tag-outline",
237
+ },
238
+ {
239
+ id: "19",
240
+ text: "Another Item",
241
+ details: "This is a detail about the item",
242
+ aside: "Dec 10",
243
+ iconName: "tag-outline",
244
+ },
245
+ {
246
+ id: "20",
247
+ text: "Another Item",
248
+ details: "This is a detail about the item",
249
+ aside: "Dec 10",
250
+ iconName: "tag-outline",
251
+ },
252
+ ];
253
+
254
+ export const WithIcons: Story<ListItemData> = {
255
+ name: "With Icons",
256
+ args: {
257
+ data: listItemWithIconsData,
258
+ renderItem: (item) => (
259
+ <ListItemContent
260
+ key={item.id}
261
+ text={item.text}
262
+ details={item.details}
263
+ aside={item.aside}
264
+ iconName={item.iconName}
265
+ />
266
+ ),
267
+ },
268
+ };
269
+
270
+ export const WithBordered: Story<ListItemData> = {
271
+ name: "With Bordered",
272
+ args: {
273
+ data: listItemWithIconsData,
274
+ listItemProps: {
275
+ isBordered: true,
276
+ },
277
+ renderItem: (item) => (
278
+ <ListItemContent
279
+ key={item.id}
280
+ text={item.text}
281
+ details={item.details}
282
+ aside={item.aside}
283
+ iconName={item.iconName}
284
+ />
285
+ ),
286
+ },
287
+ };
288
+
289
+ export const WithListItemButtons: Story<ListItemData> = {
290
+ name: "With List Item Buttons",
291
+ args: {
292
+ data: listItemWithIconsData,
293
+ renderItem: (item) => (
294
+ <ListItemButton onClick={() => console.log("Button clicked", item.id)}>
295
+ <ListItemContent
296
+ key={item.id}
297
+ text={item.text}
298
+ details={item.details}
299
+ aside={item.aside}
300
+ iconName={item.iconName}
301
+ actions={
302
+ <Button appearance="unstyled" aria-label="Reconnect">
303
+ <Icon name="plug-outline" size="small" aria-hidden="true" />
304
+ </Button>
305
+ }
306
+ />
307
+ </ListItemButton>
308
+ ),
309
+ },
310
+ };
311
+
312
+ interface ListItemWithActionsData extends ListItemData {
313
+ hasReconnect?: boolean;
314
+ }
315
+
316
+ const listItemWithActionsData: ListItemWithActionsData[] = [
317
+ {
318
+ id: "1",
319
+ text: "Groupon Babies is disconnected",
320
+ details:
321
+ "It looks like your Facebook profile needs reauthorization due to the...",
322
+ aside: "2 days ago",
323
+ iconName: "tag-solid",
324
+ hasReconnect: true,
325
+ },
326
+ {
327
+ id: "2",
328
+ text: "New secure forms submission received in a message.",
329
+ aside: "Nov 3, 2025",
330
+ iconName: "tag-outline",
331
+ hasReconnect: false,
332
+ },
333
+ {
334
+ id: "3",
335
+ text: "Another Item",
336
+ details: "This is a detail about the item",
337
+ aside: "Dec 10",
338
+ iconName: "tag-outline",
339
+ hasReconnect: false,
340
+ },
341
+ {
342
+ id: "4",
343
+ text: "Another Item",
344
+ details: "This is a detail about the item",
345
+ aside: "Dec 10",
346
+ iconName: "tag-outline",
347
+ hasReconnect: false,
348
+ },
349
+ {
350
+ id: "5",
351
+ text: "Another Item",
352
+ details: "This is a detail about the item",
353
+ aside: "Dec 10",
354
+ iconName: "tag-outline",
355
+ hasReconnect: false,
356
+ },
357
+ {
358
+ id: "6",
359
+ text: "Another Item",
360
+ details: "This is a detail about the item",
361
+ aside: "Dec 10",
362
+ iconName: "tag-outline",
363
+ hasReconnect: false,
364
+ },
365
+ {
366
+ id: "7",
367
+ text: "Another Item",
368
+ details: "This is a detail about the item",
369
+ aside: "Dec 10",
370
+ iconName: "tag-outline",
371
+ hasReconnect: false,
372
+ },
373
+ {
374
+ id: "8",
375
+ text: "Another Item",
376
+ details: "This is a detail about the item",
377
+ aside: "Dec 10",
378
+ iconName: "tag-outline",
379
+ hasReconnect: false,
380
+ },
381
+ {
382
+ id: "9",
383
+ text: "Another Item",
384
+ details: "This is a detail about the item",
385
+ aside: "Dec 10",
386
+ iconName: "tag-outline",
387
+ hasReconnect: false,
388
+ },
389
+ {
390
+ id: "10",
391
+ text: "Another Item",
392
+ details: "This is a detail about the item",
393
+ aside: "Dec 10",
394
+ iconName: "tag-outline",
395
+ hasReconnect: false,
396
+ },
397
+ ];
398
+
399
+ export const WithActions: Story<ListItemWithActionsData> = {
400
+ name: "With Actions",
401
+ args: {
402
+ data: listItemWithActionsData,
403
+ renderItem: (item) => (
404
+ <ListItemContent
405
+ key={item.id}
406
+ text={item.text}
407
+ details={item.details}
408
+ aside={item.aside}
409
+ iconName={item.iconName}
410
+ actions={
411
+ item.hasReconnect ? (
412
+ <Button appearance="unstyled" aria-label="Reconnect">
413
+ <Icon name="plug-outline" size="small" aria-hidden="true" />
414
+ </Button>
415
+ ) : (
416
+ <ActionMenu
417
+ menuToggleElement={
418
+ <MenuToggleButton
419
+ appearance="unstyled"
420
+ aria-label="More actions"
421
+ >
422
+ <Icon
423
+ name="ellipsis-horizontal-outline"
424
+ size="small"
425
+ aria-hidden="true"
426
+ />
427
+ </MenuToggleButton>
428
+ }
429
+ >
430
+ <MenuContent>
431
+ <MenuItem
432
+ id="edit"
433
+ onClick={() => console.log("Edit clicked", item.id)}
434
+ >
435
+ Edit
436
+ </MenuItem>
437
+ <MenuItem
438
+ id="delete"
439
+ onClick={() => console.log("Delete clicked", item.id)}
440
+ >
441
+ Delete
442
+ </MenuItem>
443
+ </MenuContent>
444
+ </ActionMenu>
445
+ )
446
+ }
447
+ />
448
+ ),
449
+ },
450
+ };
451
+
452
+ const Header = () => {
453
+ return (
454
+ <Box
455
+ backgroundColor="container.background.base"
456
+ border="1px solid"
457
+ borderColor="container.border.base"
458
+ borderWidth={500}
459
+ style={{ height: "60px" }}
460
+ p={300}
461
+ >
462
+ Header
463
+ </Box>
464
+ );
465
+ };
466
+
467
+ export const WithHeader: Story<ListItemData> = {
468
+ name: "With Header",
469
+ args: {
470
+ data: listItemWithIconsData,
471
+ Header: <Header />,
472
+ renderItem: (item) => (
473
+ <ListItemContent
474
+ key={item.id}
475
+ text={item.text}
476
+ details={item.details}
477
+ aside={item.aside}
478
+ iconName={item.iconName}
479
+ />
480
+ ),
481
+ },
482
+ };
483
+
484
+ export const Loading: Story<ListItemData> = {
485
+ name: "Loading",
486
+ render: () => {
487
+ const [isLoading, setIsLoading] = useState(true);
488
+
489
+ useEffect(() => {
490
+ const timer = setTimeout(() => {
491
+ setIsLoading(false);
492
+ }, 3000);
493
+
494
+ return () => clearTimeout(timer);
495
+ }, []);
496
+
497
+ return (
498
+ <DataList
499
+ data={listItemWithIconsData}
500
+ isLoading={isLoading}
501
+ renderItem={(item) => (
502
+ <ListItemContent key={item.id} text={item.text} />
503
+ )}
504
+ />
505
+ );
506
+ },
507
+ };
508
+
509
+ export const LoadingWithIconAndAside: Story<ListItemData> = {
510
+ name: "Loading with Icon and Aside",
511
+ render: () => {
512
+ const [isLoading, setIsLoading] = useState(true);
513
+
514
+ useEffect(() => {
515
+ const timer = setTimeout(() => {
516
+ setIsLoading(false);
517
+ }, 3000);
518
+
519
+ return () => clearTimeout(timer);
520
+ }, []);
521
+
522
+ return (
523
+ <DataList
524
+ data={listItemWithIconsData}
525
+ isLoading={isLoading}
526
+ LoadingComponent={(props) => (
527
+ <DataItemSkeleton {...props} hasIcon hasAside />
528
+ )}
529
+ renderItem={(item) => (
530
+ <ListItemContent
531
+ key={item.id}
532
+ text={item.text}
533
+ aside={item.aside}
534
+ iconName={item.iconName}
535
+ />
536
+ )}
537
+ />
538
+ );
539
+ },
540
+ };
541
+
542
+ export const LoadingWithAllContent: Story<ListItemData> = {
543
+ name: "Loading with All Content",
544
+ render: () => {
545
+ const [isLoading, setIsLoading] = useState(true);
546
+
547
+ useEffect(() => {
548
+ const timer = setTimeout(() => {
549
+ setIsLoading(false);
550
+ }, 3000);
551
+
552
+ return () => clearTimeout(timer);
553
+ }, []);
554
+
555
+ return (
556
+ <DataList
557
+ data={listItemWithIconsData}
558
+ isLoading={isLoading}
559
+ LoadingComponent={(props) => (
560
+ <DataItemSkeleton hasIcon hasAside hasSubtext />
561
+ )}
562
+ renderItem={(item) => (
563
+ <ListItemContent
564
+ key={item.id}
565
+ text={item.text}
566
+ details={item.details}
567
+ aside={item.aside}
568
+ iconName={item.iconName}
569
+ />
570
+ )}
571
+ />
572
+ );
573
+ },
574
+ };
575
+
576
+ export const LoadingWithBorder: Story<ListItemData> = {
577
+ name: "Loading with Border",
578
+ render: () => {
579
+ const [isLoading, setIsLoading] = useState(true);
580
+
581
+ useEffect(() => {
582
+ const timer = setTimeout(() => {
583
+ setIsLoading(false);
584
+ }, 3000);
585
+
586
+ return () => clearTimeout(timer);
587
+ }, []);
588
+
589
+ return (
590
+ <DataList
591
+ data={listItemWithIconsData}
592
+ isLoading={isLoading}
593
+ LoadingComponent={(props) => (
594
+ <DataItemSkeleton hasIcon hasAside hasSubtext />
595
+ )}
596
+ listItemProps={{
597
+ isBordered: true,
598
+ }}
599
+ renderItem={(item) => (
600
+ <ListItemContent
601
+ key={item.id}
602
+ text={item.text}
603
+ details={item.details}
604
+ aside={item.aside}
605
+ iconName={item.iconName}
606
+ />
607
+ )}
608
+ />
609
+ );
610
+ },
611
+ };
612
+
613
+ // Story showing two DataLists side by side, one loading and one loaded
614
+ // to demonstrate consistent layout
615
+ export const LoadingAlwaysWithAllFeatures: Story<ListItemData> = {
616
+ name: "Loading Always",
617
+ render: () => {
618
+ return (
619
+ <Box width="1000px" height="100%" display="flex">
620
+ <Box width="500px">
621
+ <DataList
622
+ data={listItemWithIconsData}
623
+ isLoading={true}
624
+ LoadingComponent={(props) => (
625
+ <DataItemSkeleton {...props} hasIcon hasAside hasSubtext />
626
+ )}
627
+ renderItem={(item) => (
628
+ <ListItemContent
629
+ key={item.id}
630
+ text={item.text}
631
+ details={item.details}
632
+ aside={item.aside}
633
+ iconName={item.iconName}
634
+ />
635
+ )}
636
+ />
637
+ </Box>
638
+ <Box width="500px">
639
+ <DataList
640
+ data={listItemWithIconsData.slice(0, 5)}
641
+ isLoading={false}
642
+ LoadingComponent={(props) => (
643
+ <DataItemSkeleton {...props} hasIcon hasAside hasSubtext />
644
+ )}
645
+ renderItem={(item) => (
646
+ <ListItemContent
647
+ key={item.id}
648
+ text={item.text}
649
+ details={item.details}
650
+ aside={item.aside}
651
+ iconName={item.iconName}
652
+ />
653
+ )}
654
+ />
655
+ </Box>
656
+ </Box>
657
+ );
658
+ },
659
+ };