Newer
Older
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 https://medium.com/edonec/build-a-react-collapsible-component-from-scratch-using-react-hooks-typescript-73dfd02c9208
function Collapsible({
open,
header,
children,
color = "gray",
}: 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}
>
{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;