import { Icon } from "@yaakapp-internal/ui"; import classNames from "classnames"; import type { ReactNode } from "react"; import { useState } from "react"; import type { CheckboxProps } from "./Checkbox"; import { Checkbox } from "./Checkbox"; export interface CheckboxTreeNode { key: string; data: T; children: CheckboxTreeNode[]; } interface Props { node: CheckboxTreeNode; depth?: number; /** Return "hidden" to render row alignment space instead of a checkbox */ checked: (node: CheckboxTreeNode) => CheckboxProps["checked"] | "hidden"; onCheck: (node: CheckboxTreeNode, checked: boolean) => void; checkboxTitle?: (node: CheckboxTreeNode) => string; isCheckboxDisabled?: (node: CheckboxTreeNode) => boolean; /** An irrelevant row is hidden unless one of its descendants is relevant */ isRelevant: (node: CheckboxTreeNode) => boolean; renderRow: (node: CheckboxTreeNode) => ReactNode; onSelectRow?: (node: CheckboxTreeNode) => void; canSelectRow?: (node: CheckboxTreeNode) => boolean; isRowSelected?: (node: CheckboxTreeNode) => boolean; } export function CheckboxTree(props: Props) { const { node, depth = 0 } = props; const [collapsed, setCollapsed] = useState(false); if (!hasRelevantNode(node, props.isRelevant)) return null; const checked = props.checked(node); const selected = props.isRowSelected?.(node) ?? false; const selectable = props.onSelectRow != null && (props.canSelectRow?.(node) ?? true); const hasVisibleChildren = node.children.some((c) => hasRelevantNode(c, props.isRelevant)); const rowContent = (
{props.renderRow(node)}
); return (
0 && "pl-4 ml-2 border-l border-dashed border-border-subtle relative", )} >
{selected && (
)} {hasVisibleChildren ? ( ) : ( )} {checked === "hidden" ? ( ) : ( props.onCheck(node, checked)} /> )} {selectable ? ( ) : ( rowContent )}
{!collapsed && node.children.map((child) => ( ))}
); } function hasRelevantNode( node: CheckboxTreeNode, isRelevant: (node: CheckboxTreeNode) => boolean, ): boolean { return isRelevant(node) || node.children.some((c) => hasRelevantNode(c, isRelevant)); }