Skip to content
Snippets Groups Projects
collapsible.tsx 2.27 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[];
    
        color?: string;
    
        heightTransition?: boolean;
    
    // Implementation details partially from https://medium.com/edonec/build-a-react-collapsible-component-from-scratch-using-react-hooks-typescript-73dfd02c9208
    
    function Collapsible({
        open,
        header,
        children,
        color = "gray",
    
        heightTransition = true,
    
    }: 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]);
    
    
        const borderBottom = "1px solid " + color;
    
    
        return (
            <>
                <div className={"collapsible-card"}>
                    <div>
                        <button
                            type="button"
                            className={`collapsible-button ${
                                isOpen ? "" : "collapsed"
                            }`}
                            onClick={toggleOpen}
    
                            style={{ borderBottom }}
    
                        >
                            {header}
                        </button>
                    </div>
    
                    <div className={`collapsible-content ${heightTransition ? "collapsible-transition-height" : ""}`} style={{ height }}>
    
                        <div ref={ref}>
                            <div className={"collapsible-content-padding"}>
                                {children}
                            </div>
                        </div>
                    </div>
                </div>
            </>
        );
    }
    
    export default Collapsible;