import type { AriaAttributes, HTMLAttributes, ReactElement } from 'react'
import React, {
cloneElement,
createContext,
forwardRef,
useContext,
} from 'react'
interface AccordionContext {
indices: Set
onChange: (index: number) => void
numberOfItems: number
}
const AccordionContext = createContext(undefined)
export interface AccordionProps
extends Omit, 'onChange'> {
/**
* ID to find this component in testing tools (e.g.: cypress,
* testing-library, and jest).
*/
testId?: string
/**
* Indices that indicate which accordion items are opened.
*/
indices: Iterable
/**
* Function that is triggered when an accordion item is opened/closed.
*/
onChange: (index: number) => void
/**
* For accessibility purposes, define a string that labels the current accodion.
*/
'aria-label'?: AriaAttributes['aria-label']
}
const Accordion = forwardRef(function Accordion(
{
testId = 'fs-accordion',
indices,
onChange,
children,
'aria-label': ariaLabel,
...otherProps
},
ref
) {
const childrenWithIndex = React.Children.map(
children as ReactElement,
(child, index) => {
if (!child) return
return cloneElement(child, { index: child.props.index ?? index })
}
)
const context = {
indices: new Set(indices),
onChange,
numberOfItems: childrenWithIndex.length,
}
return (
{childrenWithIndex}
)
})
export function useAccordion() {
const context = useContext(AccordionContext)
if (context === undefined) {
throw new Error(
'Do not use Accordion components outside the Accordion context.'
)
}
return context
}
export default Accordion