import React, { useEffect, useRef, useState } from "react"; import "./collapsible.css"; interface CollapsibleProps { open?: boolean; header: string | React.ReactNode; children?: React.ReactNode | React.ReactNode[]; } // Implementation details at nhttps://medium.com/edonec/build-a-react-collapsible-component-from-scratch-using-react-hooks-typescript-73dfd02c9208 function Collapsible({ open, header, children }: CollapsibleProps) { const [isOpen, setIsOpen] = useState(open); const [height, setHeight] = useState<number | undefined>( open ? undefined : 0 ); const toggleOpen = () => { setIsOpen((prev) => !prev); }; const ref = useRef<HTMLDivElement>(null); useEffect(() => { if (!height || !isOpen || !ref.current) return undefined; const resizeObserver = new ResizeObserver((el) => { setHeight(el[0].contentRect.height); }); resizeObserver.observe(ref.current); return () => { resizeObserver.disconnect(); }; }, [height, isOpen]); useEffect(() => { if (isOpen) setHeight(ref.current?.getBoundingClientRect().height); else setHeight(0); }, [isOpen]); return ( <> <div className={"collapsible-card"}> <div> <button type="button" className={`collapsible-button ${ isOpen ? "" : "collapsed" }`} onClick={toggleOpen} > {header} </button> </div> <div className={"collapsible-content"} style={{ height }}> <div ref={ref}> <div className={"collapsible-content-padding"}> {children} </div> </div> </div> </div> </> ); } export default Collapsible;