trilium/apps/client/src/widgets/react/Collapsible.tsx
Elian Doran 455dc5dc11
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
feat(layout): automatically collapse promoted attributes in full-height notes
2025-12-15 12:10:12 +02:00

59 lines
2.0 KiB
TypeScript

import "./Collapsible.css";
import clsx from "clsx";
import { ComponentChildren, HTMLAttributes } from "preact";
import { useRef, useState } from "preact/hooks";
import { useElementSize, useUniqueName } from "./hooks";
import Icon from "./Icon";
interface CollapsibleProps extends Pick<HTMLAttributes<HTMLDivElement>, "className"> {
title: string;
children: ComponentChildren;
initiallyExpanded?: boolean;
}
export default function Collapsible({ initiallyExpanded, ...restProps }: CollapsibleProps) {
const [ expanded, setExpanded ] = useState(initiallyExpanded);
return <ExternallyControlledCollapsible {...restProps} expanded={expanded} setExpanded={setExpanded} />;
}
export function ExternallyControlledCollapsible({ title, children, className, expanded, setExpanded }: Omit<CollapsibleProps, "initiallyExpanded"> & {
expanded: boolean | undefined;
setExpanded: (expanded: boolean) => void
}) {
const bodyRef = useRef<HTMLDivElement>(null);
const innerRef = useRef<HTMLDivElement>(null);
const { height } = useElementSize(innerRef) ?? {};
const contentId = useUniqueName();
return (
<div className={clsx("collapsible", className, expanded && "expanded")}>
<button
className="collapsible-title"
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
aria-controls={contentId}
>
<Icon className="arrow" icon="bx bx-chevron-right" />&nbsp;
{title}
</button>
<div
id={contentId}
ref={bodyRef}
className="collapsible-body"
style={{ height: expanded ? height : "0" }}
aria-hidden={!expanded}
>
<div
ref={innerRef}
className="collapsible-inner-body"
>
{children}
</div>
</div>
</div>
);
}