import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; import { DISPLAYNAME_PREFIX, IProps } from "../../common/props"; import { safeInvoke } from "../../common/utils"; import { Collapse } from "../collapse/collapse"; import { Icon, IconName } from "../icon/icon"; export interface ITreeNode extends IProps { /** * Child tree nodes of this node. */ childNodes?: Array>; /** * Whether the caret to expand/collapse a node should be shown. * If not specified, this will be true if the node has children and false otherwise. */ hasCaret?: boolean; /** * The name of a Blueprint icon (or an icon element) to render next to the node's label. */ icon?: IconName | JSX.Element; /** * A unique identifier for the node. */ id: string | number; /** */ isExpanded?: boolean; /** * Whether this node is selected. * @default false */ isSelected?: boolean; /** * The main label for the node. */ label: string | JSX.Element; /** * A secondary label/component that is displayed at the right side of the node. */ secondaryLabel?: string | JSX.Element; /** * An optional custom user object to associate with the node. * This property can then be used in the `onClick`, `onContextMenu` and `onDoubleClick` * event handlers for doing custom logic per node. */ nodeData?: T; } export interface ITreeNodeProps extends ITreeNode { children?: React.ReactNode; contentRef?: (node: TreeNode, element: HTMLDivElement | null) => void; depth: number; key?: string | number; onClick?: (node: TreeNode, e: React.MouseEvent) => void; onCollapse?: (node: TreeNode, e: React.MouseEvent) => void; onContextMenu?: (node: TreeNode, e: React.MouseEvent) => void; onDoubleClick?: (node: TreeNode, e: React.MouseEvent) => void; onExpand?: (node: TreeNode, e: React.MouseEvent) => void; path: number[]; } export class TreeNode extends React.Component, {}> { public static displayName = `${DISPLAYNAME_PREFIX}.TreeNode`; public static ofType() { return TreeNode as new (props: ITreeNodeProps) => TreeNode; } public render() { const { children, className, hasCaret, icon, isExpanded, isSelected, label } = this.props; const showCaret = hasCaret == null ? React.Children.count(children) > 0 : hasCaret; const caretStateClass = isExpanded ? Classes.TREE_NODE_CARET_OPEN : Classes.TREE_NODE_CARET_CLOSED; const caretClasses = showCaret ? classNames(Classes.TREE_NODE_CARET, caretStateClass) : Classes.TREE_NODE_CARET_NONE; const classes = classNames( Classes.TREE_NODE, { [Classes.TREE_NODE_SELECTED]: isSelected, [Classes.TREE_NODE_EXPANDED]: isExpanded, }, className, ); const contentClasses = classNames( Classes.TREE_NODE_CONTENT, `${Classes.TREE_NODE_CONTENT}-${this.props.depth}`, ); return (
  • {showCaret && } {label} {this.maybeRenderSecondaryLabel()}
    {children}
  • ); } private maybeRenderSecondaryLabel() { if (this.props.secondaryLabel != null) { return {this.props.secondaryLabel}; } else { return undefined; } } private handleCaretClick = (e: React.MouseEvent) => { e.stopPropagation(); const { isExpanded, onCollapse, onExpand } = this.props; safeInvoke(isExpanded ? onCollapse : onExpand, this, e); }; private handleClick = (e: React.MouseEvent) => { safeInvoke(this.props.onClick, this, e); }; private handleContentRef = (element: HTMLDivElement | null) => { safeInvoke(this.props.contentRef, this, element); }; private handleContextMenu = (e: React.MouseEvent) => { safeInvoke(this.props.onContextMenu, this, e); }; private handleDoubleClick = (e: React.MouseEvent) => { safeInvoke(this.props.onDoubleClick, this, e); }; }