70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
import React, { useState, useRef } from "react";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
interface Props {
|
|
children: React.ReactNode;
|
|
defaultCollapsed?: boolean;
|
|
className?: string;
|
|
contentClassName?: string;
|
|
title?: React.ReactNode;
|
|
}
|
|
|
|
export default function ExpandableCell({
|
|
children,
|
|
defaultCollapsed = true,
|
|
className,
|
|
contentClassName = "expandable-cell__content_defaults",
|
|
title,
|
|
}: Props) {
|
|
const [open, setOpen] = useState(!defaultCollapsed);
|
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
const contentHeight = contentRef.current
|
|
? contentRef.current.scrollHeight
|
|
: 0;
|
|
const calcMaxHeight = open ? `${contentHeight}px` : "0px";
|
|
|
|
const classes = ["expandable-cell", className, open ? "is-open" : ""]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
|
|
return (
|
|
<div className={classes}>
|
|
<button
|
|
type="button"
|
|
aria-expanded={open}
|
|
onClick={() => setOpen((s) => !s)}
|
|
className="expandable-cell__button"
|
|
aria-label={
|
|
typeof title === "string"
|
|
? `${open ? "Hide" : "Show"} ${title}`
|
|
: undefined
|
|
}
|
|
>
|
|
{title ? <span className="expandable-cell__title">{title}</span> : null}
|
|
<FontAwesomeIcon
|
|
className="expandable-cell__icon"
|
|
icon={faChevronRight}
|
|
/>
|
|
</button>
|
|
|
|
<div
|
|
className={["expandable-cell__content", contentClassName]
|
|
.filter(Boolean)
|
|
.join(" ")}
|
|
style={{ height: calcMaxHeight }}
|
|
aria-hidden={!open}
|
|
>
|
|
<div
|
|
ref={contentRef}
|
|
className="expandable-cell__content-inner"
|
|
style={{ height: "100%" }}
|
|
>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|