// eslint-disable-next-line import/no-duplicates import * as React from 'react'; // eslint-disable-next-line import/no-duplicates import {useContext, useEffect, useRef, useState} from 'react'; import {generateId} from '../utils'; import cx from 'classnames'; import Box from '../box/Box'; import Flex from '../flex/Flex'; import Icon, {ICON_COLOR} from '../icons/Icon'; import Link from '../text/Link'; import Text from '../text/Text'; import {AccordionContext} from './Accordion'; import type {ResponsivePropType} from '../utils/responsive-props'; type PaddingType = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl'; export type AccordionItemPropsType = Readonly<{ title: React.ReactNode; titleSize?: ResponsivePropType<'small' | 'large'>; children?: React.ReactNode; className?: string; padding?: ResponsivePropType; tabIndex?: number; id?: string; ariaHeadingLevel?: number; }>; const AccordionItem = ({ title, titleSize = 'large', children, className = '', padding = 'm', tabIndex = 0, id: customId, ariaHeadingLevel = 2, }: AccordionItemPropsType) => { const contentRef = useRef(null); const {current: id} = useRef( customId ?? `AccordionItem_${generateId()}` ); const contentId = `Section_${id}`; const { noGapBetweenElements, expanded, focusedElementId, dispatch, reduceMotion, onItemSelect, } = useContext(AccordionContext); const hasRenderedInitially = useRef(false); const [isHovered, setIsHovered] = useState(false); const isCollapsed = !expanded.includes(id); const isFocused = focusedElementId === id; const isHighlighted = isHovered || isFocused; const isBorderHighlighted = isHighlighted && !noGapBetweenElements; const isTitleString = typeof title === 'string'; const toggleOpen = React.useCallback(() => { onItemSelect(id, isCollapsed); }, [id, isCollapsed, onItemSelect]); const handleFocus = React.useCallback(() => { dispatch({ type: 'accordion/SET_FOCUSED', payload: { id, }, }); }, [dispatch, id]); const handleBlur = React.useCallback(() => { dispatch({ type: 'accordion/SET_FOCUSED', payload: { id: '', }, }); }, [dispatch]); useEffect(() => { const content = contentRef.current; function collapse() { if (!contentRef.current) { return; } if (reduceMotion || !hasRenderedInitially.current) { contentRef.current.style.height = `${0}px`; contentRef.current.hidden = true; contentRef.current.style.overflow = 'hidden'; hasRenderedInitially.current = true; } else { const sectionHeight = contentRef.current.scrollHeight; requestAnimationFrame(function () { if (!contentRef.current) { return; } contentRef.current.style.height = `${sectionHeight}px`; requestAnimationFrame(function () { if (!contentRef.current) { return; } contentRef.current.style.height = `0px`; contentRef.current.style.overflow = `hidden`; contentRef.current.addEventListener( 'transitionend', onTransitionEnd ); }); }); } } function expand() { if (!contentRef.current) { return; } contentRef.current.hidden = false; const sectionHeight = contentRef.current.scrollHeight; if (reduceMotion || !hasRenderedInitially.current) { contentRef.current.style.height = 'auto'; contentRef.current.style.overflow = 'visible'; hasRenderedInitially.current = true; } else { contentRef.current.style.height = `${sectionHeight}px`; contentRef.current.addEventListener('transitionend', onTransitionEnd); } } function onTransitionEnd() { if (!contentRef.current) { return; } if (contentRef.current.style.height === '0px') { // we set hidden in order to prevent gaining focus inside collapsed content contentRef.current.hidden = true; } else { contentRef.current.style.height = 'auto'; contentRef.current.style.overflow = 'visible'; } contentRef.current.removeEventListener('transitionend', onTransitionEnd); } isCollapsed ? collapse() : expand(); return () => { if (!content) { return; } content.removeEventListener('transitionend', onTransitionEnd); }; }, [isCollapsed, reduceMotion]); return React.useMemo( () => (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onFocus={handleFocus} onBlur={handleBlur} aria-expanded={!isCollapsed} aria-controls={contentId} role="button" tabIndex={tabIndex} > {isTitleString ? ( {title} ) : ( {title} )}
), [ isBorderHighlighted, noGapBetweenElements, ariaHeadingLevel, children, className, contentId, handleBlur, handleFocus, id, isCollapsed, isHighlighted, isTitleString, padding, reduceMotion, tabIndex, title, titleSize, toggleOpen, ] ); }; export default AccordionItem;