|
1 | | -import React, { ReactNode } from "react"; |
| 1 | +import React, { ReactNode, useMemo } from "react"; |
2 | 2 | import { useElementSize } from "../../hooks/useElementSize"; |
3 | 3 | import Plus from "../../assets/svgs/accordion/plus.svg"; |
4 | 4 | import Minus from "../../assets/svgs/accordion/minus.svg"; |
5 | 5 |
|
6 | 6 | import { Button } from "react-aria-components"; |
7 | 7 | import { cn } from "../../utils"; |
8 | 8 |
|
9 | | -interface AccordionItemProps { |
| 9 | +export interface AccordionItemProps { |
10 | 10 | setExpanded: React.Dispatch<React.SetStateAction<number>>; |
11 | 11 | index: number; |
| 12 | + /** |
| 13 | + * The title displayed in the accordion header. |
| 14 | + * |
| 15 | + * This is usually text, but can be any ReactNode. |
| 16 | + */ |
12 | 17 | title: ReactNode; |
| 18 | + /** |
| 19 | + * The body/content of the accordion section. |
| 20 | + * This is only visible when the item is expanded. |
| 21 | + */ |
13 | 22 | body: ReactNode; |
14 | | - expanded?: boolean; |
| 23 | + /** |
| 24 | + * Custom render function for the expand/collapse button. |
| 25 | + * |
| 26 | + * This function receives: |
| 27 | + * - `expanded`: boolean => whether the item is currently expanded |
| 28 | + * - `toggle`: function => expands/collapses this item when called |
| 29 | + * |
| 30 | + * Example: |
| 31 | + * ``` |
| 32 | + * expandButton={({ expanded, toggle }) => ( |
| 33 | + * <button onClick={toggle}> |
| 34 | + * {expanded ? "-" : "+"} |
| 35 | + * </button> |
| 36 | + * )} |
| 37 | + * ``` |
| 38 | + * |
| 39 | + * If not provided, a default + or − icon will be shown. |
| 40 | + * |
| 41 | + * This button does NOT need to manage its own state — calling `toggle()` |
| 42 | + * properly expands/collapses the item. |
| 43 | + */ |
| 44 | + expandButton?: (options: { |
| 45 | + expanded: boolean; |
| 46 | + toggle: () => void; |
| 47 | + }) => ReactNode; |
| 48 | + expanded: boolean; |
15 | 49 | } |
16 | 50 |
|
17 | 51 | const AccordionItem: React.FC<AccordionItemProps> = ({ |
18 | 52 | title, |
19 | 53 | body, |
| 54 | + expandButton, |
20 | 55 | index, |
21 | 56 | expanded, |
22 | 57 | setExpanded, |
23 | 58 | }) => { |
24 | 59 | const [ref, { height }] = useElementSize(); |
| 60 | + const ExpandButton = useMemo(() => { |
| 61 | + if (expandButton) { |
| 62 | + return expandButton({ |
| 63 | + expanded, |
| 64 | + toggle: () => setExpanded(expanded ? -1 : index), |
| 65 | + }); |
| 66 | + } |
| 67 | + const IconComponent = expanded ? Minus : Plus; |
| 68 | + return ( |
| 69 | + <IconComponent className="fill-klerosUIComponentsPrimaryText size-4 shrink-0" /> |
| 70 | + ); |
| 71 | + }, [expanded, expandButton, index, setExpanded]); |
| 72 | + |
25 | 73 | return ( |
26 | 74 | <div className="my-2"> |
27 | 75 | <Button |
28 | 76 | id="expand-button" |
| 77 | + aria-expanded={expanded} |
29 | 78 | className={cn( |
30 | 79 | "bg-klerosUIComponentsWhiteBackground border-klerosUIComponentsStroke border", |
31 | 80 | "hover-medium-blue hover-short-transition hover:cursor-pointer", |
32 | 81 | "rounded-[3px] px-4 py-[11.5px] md:px-8", |
33 | | - "flex w-full items-center justify-between", |
| 82 | + "flex w-full items-center justify-between gap-4", |
34 | 83 | )} |
35 | 84 | onPress={() => setExpanded(expanded ? -1 : index)} |
36 | 85 | > |
37 | 86 | {title} |
38 | | - {expanded ? ( |
39 | | - <Minus |
40 | | - className={cn("fill-klerosUIComponentsPrimaryText size-4 shrink-0")} |
41 | | - /> |
42 | | - ) : ( |
43 | | - <Plus |
44 | | - className={cn("fill-klerosUIComponentsPrimaryText size-4 shrink-0")} |
45 | | - /> |
46 | | - )} |
| 87 | + {ExpandButton} |
47 | 88 | </Button> |
48 | 89 | <div |
49 | 90 | style={{ height: expanded ? `${height.toString()}px` : 0 }} |
50 | | - className={cn( |
51 | | - expanded ? `overflow-visible` : "overflow-hidden", |
52 | | - "transition-[height] duration-(--klerosUIComponentsTransitionSpeed) ease-initial", |
53 | | - )} |
| 91 | + className="overflow-hidden transition-[height] duration-(--klerosUIComponentsTransitionSpeed) ease-in-out" |
54 | 92 | > |
55 | 93 | <div className="p-4 md:p-8" id="body-wrapper" ref={ref}> |
56 | 94 | {body} |
|
0 commit comments