All files / src/shared/components/collapse Collapse.tsx

100% Statements 57/57
100% Branches 14/14
100% Functions 2/2
100% Lines 57/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 841x 1x 1x 1x 1x                                     1x 1x 1x 1x 1x 1x   1x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x   88x 88x   88x 88x 88x 87x 87x 87x 87x 87x 87x 87x     88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x 88x   88x 88x 88x   88x   1x  
import React, { useMemo, useState, useId } from 'react';
import { motion } from 'framer-motion';
import classNames from 'classnames';
import CollapseHeader from './CollapseHeader';
import { useFallbackTranslation } from '@/hooks/useFallbackTranslation';
 
interface IPadding {
  paddingTop?: number | null;
  paddingRight?: number | null;
  paddingBottom?: number | null;
  paddingLeft?: number | null;
}
 
interface CollapseProps {
  children: React.ReactNode;
  duration?: number;
  ease?: 'easeInOut' | 'easeIn' | 'easeOut' | 'linear';
  padding?: IPadding | null;
  className?: string;
  iconArrowLast?: boolean;
  header?: string;
}
 
const defaultPadding: Required<IPadding> = {
  paddingTop: 8,
  paddingRight: 8,
  paddingBottom: 8,
  paddingLeft: 8,
};
 
const Collapse: React.FC<CollapseProps> = ({
  className,
  children,
  duration = 0.35,
  ease = 'easeInOut',
  padding = {},
  iconArrowLast = false,
  header,
}) => {
  const [collapsed, setCollapsed] = useState<boolean>(false);
  const { t } = useFallbackTranslation();
  const collapseId = useId();
 
  const mergedPadding: Required<IPadding> = useMemo(() => ({ ...defaultPadding, ...padding }), [padding]);
  const containerClass = useMemo(() => classNames('collapsed', className), [className]);
 
  return (
    <div className={containerClass}>
      {header && (
        <CollapseHeader
          id={collapseId}
          isOpen={collapsed}
          iconArrowLast={iconArrowLast}
          label={t(header)}
          onClick={() => setCollapsed((prev) => !prev)}
        />
      )}
 
      <motion.div
        id={`${collapseId}-content`}
        role="region"
        aria-labelledby={collapseId}
        initial={false}
        animate={{
          height: collapsed ? 'auto' : 0,
          opacity: collapsed ? 1 : 0,
          paddingLeft: `${mergedPadding.paddingLeft}px`,
          paddingRight: `${mergedPadding.paddingRight}px`,
          paddingTop: collapsed ? `${mergedPadding.paddingTop}px` : '0',
          paddingBottom: collapsed ? `${mergedPadding.paddingBottom}px` : '0',
        }}
        style={{ overflow: 'hidden' }}
        className="d-block"
        transition={{ duration, ease }}
        aria-hidden={!collapsed}
      >
        {children}
      </motion.div>
    </div>
  );
};
 
export default Collapse;