webui/src/components/common/ExpandableCell.tsx

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>
);
}