Skip to content
Snippets Groups Projects
collapsible.tsx 1.99 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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;