Skip to main content
Version: Next

Tree Component

The Tree component provides a hierarchical tree view with support for expand/collapse, checkboxes, drag-and-drop, and context menus.

Installation

npm install @abpjs/components

Basic Usage

import { Tree, TreeAdapter, BaseNode } from '@abpjs/components';

interface OrgUnit extends BaseNode {
id: string;
parentId: string | null;
displayName: string;
}

const units: OrgUnit[] = [
{ id: '1', parentId: null, displayName: 'Company' },
{ id: '2', parentId: '1', displayName: 'Engineering' },
{ id: '3', parentId: '2', displayName: 'Frontend Team' },
{ id: '4', parentId: '2', displayName: 'Backend Team' },
{ id: '5', parentId: '1', displayName: 'Marketing' },
];

function OrgUnitTree() {
const adapter = new TreeAdapter(units);

return (
<Tree
nodes={adapter.getTree()}
onSelectedNodeChange={(unit) => console.log('Selected:', unit.displayName)}
/>
);
}

BaseNode Interface

All tree node entities must implement the BaseNode interface:

interface BaseNode {
/** Unique identifier for the node */
id: string;
/** Parent node's id, null for root nodes */
parentId: string | null;
/** Optional name for the node */
name?: string;
/** Optional display name (takes precedence over name) */
displayName?: string;
}

TreeAdapter

The TreeAdapter class converts flat lists to tree structures and provides tree manipulation methods:

import { TreeAdapter, BaseNode } from '@abpjs/components';

const adapter = new TreeAdapter(flatList);

// Get the tree structure
const tree = adapter.getTree();

// Get the flat list
const list = adapter.getList();

// Find a node by key
const node = adapter.findNode('node-id');

// Add a new node
adapter.addNode({ id: '6', parentId: '2', name: 'New Team' });

// Update a node
adapter.updateNode({ id: '6', parentId: '2', name: 'Updated Team' });

// Handle drop (updates parent relationships)
adapter.handleDrop(droppedNode);

// Handle remove (removes node and descendants)
adapter.handleRemove(nodeToRemove);

// Get/set expanded keys
const expanded = adapter.getExpandedKeys();
adapter.setExpandedKeys(['1', '2']);

// Get/set checked keys
const checked = adapter.getCheckedKeys();
adapter.setCheckedKeys(['3', '4']);

Custom Name Resolver

By default, TreeAdapter uses displayName or name for the node title. You can provide a custom resolver:

const adapter = new TreeAdapter(
items,
(entity) => `${entity.code} - ${entity.name}`
);

Tree Props

PropTypeDefaultDescription
nodesTreeNodeData[][]Tree nodes data
checkedKeysstring[][]Keys of checked nodes
expandedKeysstring[][]Keys of expanded nodes
selectedNodeT-Currently selected node entity
checkablebooleanfalseWhether nodes have checkboxes
draggablebooleanfalseWhether nodes are draggable
checkStrictlybooleanfalseCheck strictly (parent/children not associated)
isNodeSelected(node) => boolean-Custom selection check
beforeDrop(event) => boolean-Called before drop to allow/prevent
menu(node) => ReactNode-Context menu render function
onCheckedKeysChange(keys) => void-Called when checked keys change
onExpandedKeysChange(keys) => void-Called when expanded keys change
onSelectedNodeChange(entity) => void-Called when selection changes
onDrop(event) => void-Called when a node is dropped
classNamestring-Custom class name
styleCSSProperties-Custom styles

Checkable Tree

Enable checkboxes with the checkable prop:

import { useState } from 'react';
import { Tree, TreeAdapter } from '@abpjs/components';

function PermissionTree() {
const adapter = new TreeAdapter(permissions);
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);

const handleSave = () => {
console.log('Selected permissions:', checkedKeys);
};

return (
<>
<Tree
nodes={adapter.getTree()}
checkable
checkedKeys={checkedKeys}
onCheckedKeysChange={setCheckedKeys}
/>
<button onClick={handleSave}>Save Permissions</button>
</>
);
}

Check Strictly Mode

By default, checking a parent affects children. Use checkStrictly to disable this:

<Tree
nodes={nodes}
checkable
checkStrictly
checkedKeys={checkedKeys}
onCheckedKeysChange={setCheckedKeys}
/>

Drag and Drop

Enable drag and drop with the draggable prop:

import { Tree, TreeAdapter, DropEvent } from '@abpjs/components';

function DraggableOrgTree() {
const [items, setItems] = useState(initialItems);
const adapter = new TreeAdapter(items);

const handleDrop = (event: DropEvent) => {
// Update the node's parent
adapter.handleDrop(event.node);

// Get the updated list
setItems([...adapter.getList()]);
};

return (
<Tree
nodes={adapter.getTree()}
draggable
onDrop={handleDrop}
/>
);
}

Prevent Drop

Use beforeDrop to conditionally allow/prevent drops:

<Tree
nodes={nodes}
draggable
beforeDrop={({ dragNode, node, pos }) => {
// Prevent dropping on root
if (node.parentId === null && pos === 0) {
return false;
}
// Prevent dropping on disabled nodes
if (node.disabled) {
return false;
}
return true;
}}
onDrop={handleDrop}
/>

Context Menu

Add context menus with the menu prop:

<Tree
nodes={nodes}
menu={(node) => (
<div className="context-menu">
<button onClick={() => handleEdit(node.entity)}>Edit</button>
<button onClick={() => handleDelete(node.entity)}>Delete</button>
<button onClick={() => handleAddChild(node.entity)}>Add Child</button>
</div>
)}
/>

Controlled Expansion

Control which nodes are expanded:

function ControlledTree() {
const adapter = new TreeAdapter(items);
const [expandedKeys, setExpandedKeys] = useState<string[]>(['1']);

const expandAll = () => {
const allKeys = items.map(item => item.id);
setExpandedKeys(allKeys);
};

const collapseAll = () => {
setExpandedKeys([]);
};

return (
<>
<button onClick={expandAll}>Expand All</button>
<button onClick={collapseAll}>Collapse All</button>
<Tree
nodes={adapter.getTree()}
expandedKeys={expandedKeys}
onExpandedKeysChange={setExpandedKeys}
/>
</>
);
}

Utility Functions

createTreeFromList

Convert a flat list to a tree structure:

import { createTreeFromList, BaseNode } from '@abpjs/components';

const flatList: BaseNode[] = [
{ id: '1', parentId: null, name: 'Root' },
{ id: '2', parentId: '1', name: 'Child' },
];

const tree = createTreeFromList(flatList);

createListFromTree

Flatten a tree back to a list:

import { createListFromTree } from '@abpjs/components';

const flatList = createListFromTree(tree);

createMapFromList

Create a lookup map for efficient node access:

import { createMapFromList } from '@abpjs/components';

const map = createMapFromList(flatList);
const node = map.get('node-id');

TreeNodeData Interface

The tree node wrapper structure:

interface TreeNodeData<T extends BaseNode> {
entity: T; // Original entity
key: string; // Unique key (same as id)
title: string; // Display title
icon: string | null; // Optional icon
children: TreeNodeData<T>[];
isLeaf: boolean;
checked: boolean;
selected: boolean;
expanded: boolean;
selectable: boolean;
disabled: boolean;
disableCheckbox: boolean;
parentNode?: TreeNodeData<T>;
id: string;
parentId: string | null;
}