@ceed/ads 1.20.0 → 1.20.1

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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
+ Accordions is a component that displays a vertically stacked list of collapsible sections, each with a header that users can click to expand or collapse the content below. It efficiently organizes content by allowing users to focus on one section at a time while keeping other sections collapsed, reducing visual clutter and improving content navigation. Accordions are commonly used for FAQs, settings panels, navigation menus, and any content that benefits from progressive disclosure.
6
+
5
7
  ```tsx
6
8
  <Accordions {...args} />
7
9
  ```
@@ -10,7 +12,55 @@
10
12
  | ----- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
11
13
  | items | Array of accordion summaries and details | \[\{ summary: 'First header', details: \<h3>Content of the first accordion.\</h3> }, \{ summary: 'Second header', details: \<h3>Content of the second accordion.\</h3> }, \{ summary: 'Third header', details: \<h3>Content of the third accordion.\</h3> }] |
12
14
 
13
- ## Basics
15
+ > ⚠️ **Usage Warning** ⚠️
16
+ >
17
+ > Consider these guidelines when using Accordions:
18
+ >
19
+ > - **Don't hide critical content**: Important information should be visible by default
20
+ > - **Limit nesting**: Avoid nesting accordions within accordions
21
+ > - **Keep headers concise**: Users should understand content without expanding
22
+ > - **Consider Tabs**: Use Tabs for content that users need to compare side-by-side
23
+
24
+ ## Usage
25
+
26
+ ```tsx
27
+ import { Accordions } from '@ceed/ads';
28
+
29
+ function FAQSection() {
30
+ return (
31
+ <Accordions
32
+ items={[
33
+ {
34
+ summary: 'What is your return policy?',
35
+ details: <p>We offer a 30-day return policy for all unused items.</p>,
36
+ },
37
+ {
38
+ summary: 'How long does shipping take?',
39
+ details: <p>Standard shipping takes 3-5 business days.</p>,
40
+ },
41
+ {
42
+ summary: 'Do you ship internationally?',
43
+ details: <p>Yes, we ship to over 50 countries worldwide.</p>,
44
+ },
45
+ ]}
46
+ />
47
+ );
48
+ }
49
+ ```
50
+
51
+ ## Examples
52
+
53
+ ### Playground
54
+
55
+ Interactive example with all controls.
56
+
57
+ ```tsx
58
+ <Accordions {...args} />
59
+ ```
60
+
61
+ ### Basics
62
+
63
+ Basic accordion with multiple collapsible sections.
14
64
 
15
65
  ```tsx
16
66
  <Accordions
@@ -27,10 +77,10 @@
27
77
  />
28
78
  ```
29
79
 
30
- ## Customization
31
-
32
80
  ### Sizes
33
81
 
82
+ Available size options: `sm`, `md`, `lg`.
83
+
34
84
  ```tsx
35
85
  <>
36
86
  <Accordions {...args} size="sm" />
@@ -41,6 +91,8 @@
41
91
 
42
92
  ### Variants
43
93
 
94
+ Style variants: `plain`, `outlined`, `soft`, `solid`.
95
+
44
96
  ```tsx
45
97
  <>
46
98
  <div>
@@ -64,6 +116,8 @@
64
116
 
65
117
  ### Colors
66
118
 
119
+ Color options: `primary`, `neutral`, `danger`, `success`, `warning`.
120
+
67
121
  ```tsx
68
122
  <>
69
123
  <div>
@@ -91,6 +145,896 @@
91
145
 
92
146
  ### Removing Divider
93
147
 
148
+ Use `disableDivider` to remove the divider lines between accordion items.
149
+
94
150
  ```tsx
95
151
  <Accordions {...args} disableDivider />
96
152
  ```
153
+
154
+ ## When to Use
155
+
156
+ ### ✅ Good Use Cases
157
+
158
+ - **FAQs**: Frequently asked questions with expandable answers
159
+ - **Settings panels**: Grouped settings that can be expanded individually
160
+ - **Product details**: Specifications, reviews, shipping info sections
161
+ - **Documentation**: Collapsible sections for detailed explanations
162
+ - **Navigation menus**: Expandable category menus
163
+ - **Form sections**: Long forms grouped into logical sections
164
+ - **Help content**: Step-by-step instructions or troubleshooting guides
165
+
166
+ ### ❌ When Not to Use
167
+
168
+ - **Critical information**: Don't hide must-read content in accordions
169
+ - **Short content**: Use a list or cards if content is minimal
170
+ - **Comparison content**: Use Tabs when users need to compare sections
171
+ - **Primary navigation**: Use Navigator or Tabs for main navigation
172
+ - **Single item**: Don't use accordion for a single collapsible section
173
+ - **Deeply nested content**: Avoid nesting accordions within accordions
174
+
175
+ ## Common Use Cases
176
+
177
+ ### FAQ Section
178
+
179
+ ```tsx
180
+ function FAQAccordion() {
181
+ const faqs = [
182
+ {
183
+ summary: 'What payment methods do you accept?',
184
+ details: (
185
+ <Typography>
186
+ We accept Visa, MasterCard, American Express, and PayPal.
187
+ All payments are processed securely through our payment gateway.
188
+ </Typography>
189
+ ),
190
+ },
191
+ {
192
+ summary: 'How can I track my order?',
193
+ details: (
194
+ <Typography>
195
+ Once your order ships, you'll receive an email with a tracking
196
+ number. You can use this number on our website or the carrier's
197
+ site to track your package.
198
+ </Typography>
199
+ ),
200
+ },
201
+ {
202
+ summary: 'What is your refund policy?',
203
+ details: (
204
+ <Stack gap={1}>
205
+ <Typography>Our refund policy includes:</Typography>
206
+ <ul>
207
+ <li>30-day money-back guarantee</li>
208
+ <li>Full refund for unused items</li>
209
+ <li>Refunds processed within 5-7 business days</li>
210
+ </ul>
211
+ </Stack>
212
+ ),
213
+ },
214
+ ];
215
+
216
+ return (
217
+ <Box sx={{ maxWidth: 600 }}>
218
+ <Typography level="h2" sx={{ mb: 2 }}>
219
+ Frequently Asked Questions
220
+ </Typography>
221
+ <Accordions items={faqs} variant="outlined" />
222
+ </Box>
223
+ );
224
+ }
225
+ ```
226
+
227
+ ### Settings Panel
228
+
229
+ ```tsx
230
+ function SettingsAccordion({ settings, onSettingChange }) {
231
+ const settingsItems = [
232
+ {
233
+ summary: 'Account Settings',
234
+ details: (
235
+ <Stack gap={2}>
236
+ <FormControl>
237
+ <FormLabel>Display Name</FormLabel>
238
+ <Input
239
+ value={settings.displayName}
240
+ onChange={(e) => onSettingChange('displayName', e.target.value)}
241
+ />
242
+ </FormControl>
243
+ <FormControl>
244
+ <FormLabel>Email</FormLabel>
245
+ <Input
246
+ type="email"
247
+ value={settings.email}
248
+ onChange={(e) => onSettingChange('email', e.target.value)}
249
+ />
250
+ </FormControl>
251
+ </Stack>
252
+ ),
253
+ },
254
+ {
255
+ summary: 'Notification Preferences',
256
+ details: (
257
+ <Stack gap={1}>
258
+ <Checkbox
259
+ label="Email notifications"
260
+ checked={settings.emailNotifications}
261
+ onChange={(e) =>
262
+ onSettingChange('emailNotifications', e.target.checked)
263
+ }
264
+ />
265
+ <Checkbox
266
+ label="Push notifications"
267
+ checked={settings.pushNotifications}
268
+ onChange={(e) =>
269
+ onSettingChange('pushNotifications', e.target.checked)
270
+ }
271
+ />
272
+ <Checkbox
273
+ label="Weekly digest"
274
+ checked={settings.weeklyDigest}
275
+ onChange={(e) => onSettingChange('weeklyDigest', e.target.checked)}
276
+ />
277
+ </Stack>
278
+ ),
279
+ },
280
+ {
281
+ summary: 'Privacy Settings',
282
+ details: (
283
+ <Stack gap={1}>
284
+ <Checkbox
285
+ label="Make profile public"
286
+ checked={settings.publicProfile}
287
+ onChange={(e) => onSettingChange('publicProfile', e.target.checked)}
288
+ />
289
+ <Checkbox
290
+ label="Show activity status"
291
+ checked={settings.showActivity}
292
+ onChange={(e) => onSettingChange('showActivity', e.target.checked)}
293
+ />
294
+ </Stack>
295
+ ),
296
+ },
297
+ ];
298
+
299
+ return (
300
+ <Box sx={{ maxWidth: 500 }}>
301
+ <Typography level="h3" sx={{ mb: 2 }}>
302
+ Settings
303
+ </Typography>
304
+ <Accordions items={settingsItems} variant="soft" color="neutral" />
305
+ </Box>
306
+ );
307
+ }
308
+ ```
309
+
310
+ ### Product Details
311
+
312
+ ```tsx
313
+ function ProductAccordion({ product }) {
314
+ const productDetails = [
315
+ {
316
+ summary: 'Description',
317
+ details: (
318
+ <Typography>{product.description}</Typography>
319
+ ),
320
+ },
321
+ {
322
+ summary: 'Specifications',
323
+ details: (
324
+ <Table>
325
+ <tbody>
326
+ {Object.entries(product.specs).map(([key, value]) => (
327
+ <tr key={key}>
328
+ <td>
329
+ <Typography level="body-sm" fontWeight="md">
330
+ {key}
331
+ </Typography>
332
+ </td>
333
+ <td>
334
+ <Typography level="body-sm">{value}</Typography>
335
+ </td>
336
+ </tr>
337
+ ))}
338
+ </tbody>
339
+ </Table>
340
+ ),
341
+ },
342
+ {
343
+ summary: 'Shipping & Returns',
344
+ details: (
345
+ <Stack gap={2}>
346
+ <Box>
347
+ <Typography level="title-sm">Shipping</Typography>
348
+ <Typography level="body-sm">
349
+ Free shipping on orders over $50. Standard delivery 3-5 days.
350
+ </Typography>
351
+ </Box>
352
+ <Box>
353
+ <Typography level="title-sm">Returns</Typography>
354
+ <Typography level="body-sm">
355
+ 30-day return policy. Items must be unused with original tags.
356
+ </Typography>
357
+ </Box>
358
+ </Stack>
359
+ ),
360
+ },
361
+ {
362
+ summary: `Reviews (${product.reviewCount})`,
363
+ details: (
364
+ <Stack gap={2}>
365
+ {product.reviews.slice(0, 3).map((review) => (
366
+ <Card key={review.id} variant="outlined">
367
+ <CardContent>
368
+ <Stack direction="row" justifyContent="space-between">
369
+ <Typography level="title-sm">{review.author}</Typography>
370
+ <Typography level="body-xs" color="neutral">
371
+ {review.date}
372
+ </Typography>
373
+ </Stack>
374
+ <Typography level="body-sm">{review.content}</Typography>
375
+ </CardContent>
376
+ </Card>
377
+ ))}
378
+ <Button variant="plain" size="sm">
379
+ View all reviews
380
+ </Button>
381
+ </Stack>
382
+ ),
383
+ },
384
+ ];
385
+
386
+ return <Accordions items={productDetails} variant="plain" />;
387
+ }
388
+ ```
389
+
390
+ ### Navigation Menu
391
+
392
+ ```tsx
393
+ function NavigationAccordion() {
394
+ const menuItems = [
395
+ {
396
+ summary: 'Products',
397
+ details: (
398
+ <List size="sm">
399
+ <ListItem>
400
+ <ListItemButton onClick={() => navigate('/products/electronics')}>
401
+ Electronics
402
+ </ListItemButton>
403
+ </ListItem>
404
+ <ListItem>
405
+ <ListItemButton onClick={() => navigate('/products/clothing')}>
406
+ Clothing
407
+ </ListItemButton>
408
+ </ListItem>
409
+ <ListItem>
410
+ <ListItemButton onClick={() => navigate('/products/home')}>
411
+ Home & Garden
412
+ </ListItemButton>
413
+ </ListItem>
414
+ </List>
415
+ ),
416
+ },
417
+ {
418
+ summary: 'Services',
419
+ details: (
420
+ <List size="sm">
421
+ <ListItem>
422
+ <ListItemButton onClick={() => navigate('/services/consulting')}>
423
+ Consulting
424
+ </ListItemButton>
425
+ </ListItem>
426
+ <ListItem>
427
+ <ListItemButton onClick={() => navigate('/services/support')}>
428
+ Support
429
+ </ListItemButton>
430
+ </ListItem>
431
+ </List>
432
+ ),
433
+ },
434
+ {
435
+ summary: 'Resources',
436
+ details: (
437
+ <List size="sm">
438
+ <ListItem>
439
+ <ListItemButton onClick={() => navigate('/docs')}>
440
+ Documentation
441
+ </ListItemButton>
442
+ </ListItem>
443
+ <ListItem>
444
+ <ListItemButton onClick={() => navigate('/blog')}>
445
+ Blog
446
+ </ListItemButton>
447
+ </ListItem>
448
+ <ListItem>
449
+ <ListItemButton onClick={() => navigate('/tutorials')}>
450
+ Tutorials
451
+ </ListItemButton>
452
+ </ListItem>
453
+ </List>
454
+ ),
455
+ },
456
+ ];
457
+
458
+ return (
459
+ <Box sx={{ width: 250 }}>
460
+ <Accordions items={menuItems} variant="plain" size="sm" disableDivider />
461
+ </Box>
462
+ );
463
+ }
464
+ ```
465
+
466
+ ### Help Documentation
467
+
468
+ ```tsx
469
+ function HelpAccordion() {
470
+ const helpTopics = [
471
+ {
472
+ summary: 'Getting Started',
473
+ details: (
474
+ <Stack gap={2}>
475
+ <Typography level="title-sm">Step 1: Create an Account</Typography>
476
+ <Typography level="body-sm">
477
+ Click the "Sign Up" button and fill in your details.
478
+ </Typography>
479
+ <Typography level="title-sm">Step 2: Verify Email</Typography>
480
+ <Typography level="body-sm">
481
+ Check your inbox for a verification email and click the link.
482
+ </Typography>
483
+ <Typography level="title-sm">Step 3: Complete Profile</Typography>
484
+ <Typography level="body-sm">
485
+ Add your profile picture and bio to personalize your account.
486
+ </Typography>
487
+ </Stack>
488
+ ),
489
+ },
490
+ {
491
+ summary: 'Troubleshooting',
492
+ details: (
493
+ <Stack gap={2}>
494
+ <Box>
495
+ <Typography level="title-sm" color="danger">
496
+ Can't log in?
497
+ </Typography>
498
+ <Typography level="body-sm">
499
+ Try resetting your password or clearing your browser cache.
500
+ </Typography>
501
+ </Box>
502
+ <Box>
503
+ <Typography level="title-sm" color="danger">
504
+ Page not loading?
505
+ </Typography>
506
+ <Typography level="body-sm">
507
+ Check your internet connection and try refreshing the page.
508
+ </Typography>
509
+ </Box>
510
+ </Stack>
511
+ ),
512
+ },
513
+ {
514
+ summary: 'Contact Support',
515
+ details: (
516
+ <Stack gap={2}>
517
+ <Typography level="body-sm">
518
+ Need more help? Reach out to our support team:
519
+ </Typography>
520
+ <Stack gap={1}>
521
+ <Typography level="body-sm">
522
+ 📧 Email: support@example.com
523
+ </Typography>
524
+ <Typography level="body-sm">
525
+ 💬 Live Chat: Available 9am-5pm EST
526
+ </Typography>
527
+ <Typography level="body-sm">
528
+ 📞 Phone: 1-800-EXAMPLE
529
+ </Typography>
530
+ </Stack>
531
+ </Stack>
532
+ ),
533
+ },
534
+ ];
535
+
536
+ return (
537
+ <Box sx={{ maxWidth: 600 }}>
538
+ <Typography level="h2" sx={{ mb: 2 }}>
539
+ Help Center
540
+ </Typography>
541
+ <Accordions items={helpTopics} variant="outlined" color="primary" />
542
+ </Box>
543
+ );
544
+ }
545
+ ```
546
+
547
+ ### Collapsible Form Sections
548
+
549
+ ```tsx
550
+ function MultiSectionForm({ onSubmit }) {
551
+ const [formData, setFormData] = useState({
552
+ personal: { firstName: '', lastName: '', email: '' },
553
+ address: { street: '', city: '', zipCode: '' },
554
+ payment: { cardNumber: '', expiry: '', cvv: '' },
555
+ });
556
+
557
+ const updateField = (section, field, value) => {
558
+ setFormData((prev) => ({
559
+ ...prev,
560
+ [section]: { ...prev[section], [field]: value },
561
+ }));
562
+ };
563
+
564
+ const formSections = [
565
+ {
566
+ summary: 'Personal Information',
567
+ details: (
568
+ <Stack gap={2}>
569
+ <Stack direction="row" gap={2}>
570
+ <FormControl sx={{ flex: 1 }}>
571
+ <FormLabel>First Name</FormLabel>
572
+ <Input
573
+ value={formData.personal.firstName}
574
+ onChange={(e) =>
575
+ updateField('personal', 'firstName', e.target.value)
576
+ }
577
+ />
578
+ </FormControl>
579
+ <FormControl sx={{ flex: 1 }}>
580
+ <FormLabel>Last Name</FormLabel>
581
+ <Input
582
+ value={formData.personal.lastName}
583
+ onChange={(e) =>
584
+ updateField('personal', 'lastName', e.target.value)
585
+ }
586
+ />
587
+ </FormControl>
588
+ </Stack>
589
+ <FormControl>
590
+ <FormLabel>Email</FormLabel>
591
+ <Input
592
+ type="email"
593
+ value={formData.personal.email}
594
+ onChange={(e) =>
595
+ updateField('personal', 'email', e.target.value)
596
+ }
597
+ />
598
+ </FormControl>
599
+ </Stack>
600
+ ),
601
+ },
602
+ {
603
+ summary: 'Shipping Address',
604
+ details: (
605
+ <Stack gap={2}>
606
+ <FormControl>
607
+ <FormLabel>Street Address</FormLabel>
608
+ <Input
609
+ value={formData.address.street}
610
+ onChange={(e) =>
611
+ updateField('address', 'street', e.target.value)
612
+ }
613
+ />
614
+ </FormControl>
615
+ <Stack direction="row" gap={2}>
616
+ <FormControl sx={{ flex: 2 }}>
617
+ <FormLabel>City</FormLabel>
618
+ <Input
619
+ value={formData.address.city}
620
+ onChange={(e) =>
621
+ updateField('address', 'city', e.target.value)
622
+ }
623
+ />
624
+ </FormControl>
625
+ <FormControl sx={{ flex: 1 }}>
626
+ <FormLabel>ZIP Code</FormLabel>
627
+ <Input
628
+ value={formData.address.zipCode}
629
+ onChange={(e) =>
630
+ updateField('address', 'zipCode', e.target.value)
631
+ }
632
+ />
633
+ </FormControl>
634
+ </Stack>
635
+ </Stack>
636
+ ),
637
+ },
638
+ {
639
+ summary: 'Payment Details',
640
+ details: (
641
+ <Stack gap={2}>
642
+ <FormControl>
643
+ <FormLabel>Card Number</FormLabel>
644
+ <Input
645
+ placeholder="1234 5678 9012 3456"
646
+ value={formData.payment.cardNumber}
647
+ onChange={(e) =>
648
+ updateField('payment', 'cardNumber', e.target.value)
649
+ }
650
+ />
651
+ </FormControl>
652
+ <Stack direction="row" gap={2}>
653
+ <FormControl sx={{ flex: 1 }}>
654
+ <FormLabel>Expiry Date</FormLabel>
655
+ <Input
656
+ placeholder="MM/YY"
657
+ value={formData.payment.expiry}
658
+ onChange={(e) =>
659
+ updateField('payment', 'expiry', e.target.value)
660
+ }
661
+ />
662
+ </FormControl>
663
+ <FormControl sx={{ flex: 1 }}>
664
+ <FormLabel>CVV</FormLabel>
665
+ <Input
666
+ type="password"
667
+ placeholder="123"
668
+ value={formData.payment.cvv}
669
+ onChange={(e) =>
670
+ updateField('payment', 'cvv', e.target.value)
671
+ }
672
+ />
673
+ </FormControl>
674
+ </Stack>
675
+ </Stack>
676
+ ),
677
+ },
678
+ ];
679
+
680
+ return (
681
+ <Box sx={{ maxWidth: 500 }}>
682
+ <Accordions items={formSections} variant="outlined" />
683
+ <Button sx={{ mt: 2 }} fullWidth onClick={() => onSubmit(formData)}>
684
+ Submit Order
685
+ </Button>
686
+ </Box>
687
+ );
688
+ }
689
+ ```
690
+
691
+ ## Props and Customization
692
+
693
+ ### Key Props
694
+
695
+ | Prop | Type | Default | Description |
696
+ | ---------------- | -------------------------------------------------------------- | ----------- | ------------------------------------------------- |
697
+ | `items` | `Array<{ summary: string; details: ReactNode }>` | - | Array of accordion items with headers and content |
698
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the accordion |
699
+ | `variant` | `'plain' \| 'outlined' \| 'soft' \| 'solid'` | `'plain'` | Visual style variant |
700
+ | `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color theme |
701
+ | `disableDivider` | `boolean` | `false` | Remove divider lines between items |
702
+
703
+ ### Items Structure
704
+
705
+ ```tsx
706
+ interface AccordionItem {
707
+ summary: string; // Header text displayed when collapsed
708
+ details: ReactNode; // Content shown when expanded
709
+ }
710
+
711
+ // Example items array
712
+ const items = [
713
+ {
714
+ summary: 'Section Title',
715
+ details: <Typography>Section content...</Typography>,
716
+ },
717
+ {
718
+ summary: 'Another Section',
719
+ details: (
720
+ <Stack gap={2}>
721
+ <Typography>Multiple elements...</Typography>
722
+ <Button>Action</Button>
723
+ </Stack>
724
+ ),
725
+ },
726
+ ];
727
+ ```
728
+
729
+ ### Variant Options
730
+
731
+ ```tsx
732
+ // Plain - minimal styling, no background
733
+ <Accordions items={items} variant="plain" />
734
+
735
+ // Outlined - bordered container
736
+ <Accordions items={items} variant="outlined" />
737
+
738
+ // Soft - subtle background color
739
+ <Accordions items={items} variant="soft" />
740
+
741
+ // Solid - full background color
742
+ <Accordions items={items} variant="solid" />
743
+ ```
744
+
745
+ ### Size Options
746
+
747
+ ```tsx
748
+ // Small - compact for sidebars/menus
749
+ <Accordions items={items} size="sm" />
750
+
751
+ // Medium - default size
752
+ <Accordions items={items} size="md" />
753
+
754
+ // Large - spacious for main content
755
+ <Accordions items={items} size="lg" />
756
+ ```
757
+
758
+ ### Color Options
759
+
760
+ ```tsx
761
+ // Primary - brand color emphasis
762
+ <Accordions items={items} color="primary" />
763
+
764
+ // Neutral - subtle, default
765
+ <Accordions items={items} color="neutral" />
766
+
767
+ // Status colors
768
+ <Accordions items={items} color="success" />
769
+ <Accordions items={items} color="warning" />
770
+ <Accordions items={items} color="danger" />
771
+ ```
772
+
773
+ ### Without Dividers
774
+
775
+ ```tsx
776
+ // Clean look without divider lines
777
+ <Accordions items={items} disableDivider />
778
+ ```
779
+
780
+ ## Accessibility
781
+
782
+ Accordions includes built-in accessibility features:
783
+
784
+ ### ARIA Attributes
785
+
786
+ - Accordion headers have `role="button"` with `aria-expanded`
787
+ - Content regions have `role="region"` with `aria-labelledby`
788
+ - Proper focus management between sections
789
+
790
+ ### Keyboard Navigation
791
+
792
+ - **Tab**: Move focus between accordion headers
793
+ - **Enter/Space**: Expand or collapse focused accordion
794
+ - **Arrow Down**: Move focus to next accordion header
795
+ - **Arrow Up**: Move focus to previous accordion header
796
+ - **Home**: Move focus to first accordion header
797
+ - **End**: Move focus to last accordion header
798
+
799
+ ### Screen Reader Support
800
+
801
+ ```tsx
802
+ // Headers announce: "Section Title, collapsed/expanded, button"
803
+ <Accordions
804
+ items={[
805
+ {
806
+ summary: 'Shipping Information', // Announced as header
807
+ details: <Typography>Content...</Typography>,
808
+ },
809
+ ]}
810
+ />
811
+ ```
812
+
813
+ ### Focus Visibility
814
+
815
+ - Clear focus indicators on headers
816
+ - Focus remains on header after expand/collapse
817
+ - Tab navigation works within expanded content
818
+
819
+ ## Best Practices
820
+
821
+ ### ✅ Do
822
+
823
+ 1. **Use descriptive headers**: Make content clear without expanding
824
+
825
+ ```tsx
826
+ // ✅ Good: Descriptive headers
827
+ <Accordions
828
+ items={[
829
+ { summary: 'Return Policy (30 days)', details: '...' },
830
+ { summary: 'Shipping Options & Costs', details: '...' },
831
+ ]}
832
+ />
833
+ ```
834
+
835
+ 2. **Group related content**: Keep sections logically organized
836
+
837
+ ```tsx
838
+ // ✅ Good: Logical grouping
839
+ <Accordions
840
+ items={[
841
+ { summary: 'Account Settings', details: accountSettings },
842
+ { summary: 'Notification Settings', details: notificationSettings },
843
+ { summary: 'Privacy Settings', details: privacySettings },
844
+ ]}
845
+ />
846
+ ```
847
+
848
+ 3. **Keep content scannable**: Use lists and headings inside accordions
849
+
850
+ ```tsx
851
+ // ✅ Good: Scannable content
852
+ <Accordions
853
+ items={[
854
+ {
855
+ summary: 'Features',
856
+ details: (
857
+ <ul>
858
+ <li>Feature one</li>
859
+ <li>Feature two</li>
860
+ <li>Feature three</li>
861
+ </ul>
862
+ ),
863
+ },
864
+ ]}
865
+ />
866
+ ```
867
+
868
+ 4. **Match variant to context**: Use appropriate styling
869
+
870
+ ```tsx
871
+ // ✅ Good: Outlined for forms, plain for navigation
872
+ <Accordions items={formSections} variant="outlined" />
873
+ <Accordions items={menuItems} variant="plain" size="sm" />
874
+ ```
875
+
876
+ ### ❌ Don't
877
+
878
+ 1. **Don't hide critical information**: Important content should be visible
879
+
880
+ ```tsx
881
+ // ❌ Bad: Hiding important warnings
882
+ <Accordions
883
+ items={[
884
+ { summary: 'Important Safety Information', details: criticalWarning },
885
+ ]}
886
+ />
887
+
888
+ // ✅ Good: Show warnings prominently
889
+ <Alert color="warning">{criticalWarning}</Alert>
890
+ ```
891
+
892
+ 2. **Don't nest accordions**: Avoid complex hierarchies
893
+
894
+ ```tsx
895
+ // ❌ Bad: Nested accordions
896
+ <Accordions
897
+ items={[
898
+ {
899
+ summary: 'Parent',
900
+ details: (
901
+ <Accordions items={[{ summary: 'Child', details: '...' }]} />
902
+ ),
903
+ },
904
+ ]}
905
+ />
906
+
907
+ // ✅ Good: Flat structure or use alternative
908
+ <Accordions items={flattenedItems} />
909
+ ```
910
+
911
+ 3. **Don't use for single items**: Unnecessary complexity
912
+
913
+ ```tsx
914
+ // ❌ Bad: Single accordion item
915
+ <Accordions items={[{ summary: 'Details', details: content }]} />
916
+
917
+ // ✅ Good: Use simple disclosure or always show content
918
+ <Card>
919
+ <CardContent>{content}</CardContent>
920
+ </Card>
921
+ ```
922
+
923
+ 4. **Don't overload with too many sections**: Keep it manageable
924
+
925
+ ```tsx
926
+ // ❌ Bad: Too many accordion items (15+)
927
+ <Accordions items={arrayOf15Items} />
928
+
929
+ // ✅ Good: Group or paginate if needed
930
+ <Accordions items={arrayOf5To7Items} />
931
+ ```
932
+
933
+ ## Performance Considerations
934
+
935
+ ### Lazy Load Heavy Content
936
+
937
+ For accordions with heavy content, render only when expanded:
938
+
939
+ ```tsx
940
+ function LazyAccordionContent({ isExpanded, children }) {
941
+ const [hasBeenExpanded, setHasBeenExpanded] = useState(false);
942
+
943
+ useEffect(() => {
944
+ if (isExpanded) setHasBeenExpanded(true);
945
+ }, [isExpanded]);
946
+
947
+ // Render placeholder until first expansion
948
+ if (!hasBeenExpanded) {
949
+ return <Typography color="neutral">Loading...</Typography>;
950
+ }
951
+
952
+ return children;
953
+ }
954
+ ```
955
+
956
+ ### Memoize Items Array
957
+
958
+ Prevent unnecessary re-renders by memoizing items:
959
+
960
+ ```tsx
961
+ const accordionItems = useMemo(
962
+ () => [
963
+ {
964
+ summary: 'Section 1',
965
+ details: <ExpensiveComponent data={data1} />,
966
+ },
967
+ {
968
+ summary: 'Section 2',
969
+ details: <ExpensiveComponent data={data2} />,
970
+ },
971
+ ],
972
+ [data1, data2]
973
+ );
974
+
975
+ <Accordions items={accordionItems} />
976
+ ```
977
+
978
+ ### Avoid Inline Functions in Items
979
+
980
+ ```tsx
981
+ // ❌ Bad: Creates new objects on every render
982
+ <Accordions
983
+ items={data.map((item) => ({
984
+ summary: item.title,
985
+ details: <Content data={item} />,
986
+ }))}
987
+ />
988
+
989
+ // ✅ Good: Memoized transformation
990
+ const items = useMemo(
991
+ () =>
992
+ data.map((item) => ({
993
+ summary: item.title,
994
+ details: <Content data={item} />,
995
+ })),
996
+ [data]
997
+ );
998
+ <Accordions items={items} />
999
+ ```
1000
+
1001
+ ### Virtualize Long Lists
1002
+
1003
+ For many accordion items, consider virtualization:
1004
+
1005
+ ```tsx
1006
+ // For very long lists (20+ items), consider:
1007
+ // 1. Pagination with limited items per page
1008
+ // 2. Search/filter to reduce visible items
1009
+ // 3. Grouping into categories
1010
+
1011
+ function FilteredAccordions({ allItems }) {
1012
+ const [search, setSearch] = useState('');
1013
+
1014
+ const filteredItems = useMemo(
1015
+ () =>
1016
+ allItems.filter((item) =>
1017
+ item.summary.toLowerCase().includes(search.toLowerCase())
1018
+ ),
1019
+ [allItems, search]
1020
+ );
1021
+
1022
+ return (
1023
+ <Stack gap={2}>
1024
+ <Input
1025
+ placeholder="Search..."
1026
+ value={search}
1027
+ onChange={(e) => setSearch(e.target.value)}
1028
+ />
1029
+ <Accordions items={filteredItems.slice(0, 10)} />
1030
+ {filteredItems.length > 10 && (
1031
+ <Typography level="body-sm" color="neutral">
1032
+ Showing 10 of {filteredItems.length} results
1033
+ </Typography>
1034
+ )}
1035
+ </Stack>
1036
+ );
1037
+ }
1038
+ ```
1039
+
1040
+ Accordions provides an efficient way to organize and present collapsible content sections. Use descriptive headers, keep content scannable, and choose appropriate variants based on your context. For critical information, consider displaying it prominently rather than hiding it within an accordion.