Tree
Tree components display hierarchical data in a collapsible structure, allowing users to explore nested relationships while keeping the interface organized and navigable.
import {Tree} from "@qualcomm-ui/react/tree"Overview
- The tree relies on the
TreeCollectionclass to manage its items. Refer to the API below for details. - Trees are composed of nodes, which are objects that describe the tree data. There are two types of nodes:
- A
branchnode is a node that has children. - A
leafnode is a node that does not have children.
- A
- Each node has a
value(unique identifier used for selection/expansion) and atext(display text).
Default object shape:
value(required): unique identifier for expansion/selectiontext(required): display textnodes(optional): child nodes for branchesdisabled(optional): prevents interaction whentrue
These defaults can be overridden in the TreeCollection constructor.
Examples
Node Shorthand
We expose the <Tree.Nodes> shorthand for rendering Branch and Leaf nodes. Use its renderBranch and renderLeaf render props to customize the content of each tree item.
Note that <Tree.Nodes> automatically renders child nodes for branches, so you only have to customize the content of the node itself.
import type {ReactElement} from "react"
import {FileText, FolderIcon} from "lucide-react"
import {createTreeCollection} from "@qualcomm-ui/core/tree"
import {Tree} from "@qualcomm-ui/react/tree"
interface Node {
id: string
name: string
nodes?: Node[]
}
export function TreeNodeShorthandDemo(): ReactElement {
return (
<Tree.Root className="w-full max-w-sm" collection={collection}>
{collection.rootNode.nodes?.map((parentNode, index) => {
return (
<Tree.Nodes
key={parentNode.id}
indexPath={[index]}
node={parentNode}
renderBranch={({node}) => (
<Tree.BranchNode>
<Tree.NodeIndicator />
<Tree.BranchTrigger />
<Tree.NodeIcon icon={FolderIcon} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.BranchNode>
)}
renderLeaf={({node}) => (
<Tree.LeafNode>
<Tree.NodeIndicator />
<Tree.NodeIcon icon={FileText} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.LeafNode>
)}
/>
)
})}
</Tree.Root>
)
}
const collection = createTreeCollection<Node>({
nodeChildren: "nodes",
nodeText: (node) => node.name,
nodeValue: (node) => node.id,
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "node_modules",
name: "node_modules",
nodes: [
{
id: "@qui",
name: "@qui",
nodes: [
{id: "node_modules/@qualcomm-ui/core", name: "@qualcomm-ui/core"},
{
id: "node_modules/@qualcomm-ui/react",
name: "@qualcomm-ui/react",
},
{
id: "node_modules/@qualcomm-ui/react-core",
name: "@qualcomm-ui/react-core",
},
],
},
{
id: "node_modules/@types",
name: "@types",
nodes: [
{id: "node_modules/@types/react", name: "react"},
{id: "node_modules/@types/react-dom", name: "react-dom"},
],
},
],
},
{
id: "src",
name: "src",
nodes: [
{id: "src/app.tsx", name: "app.tsx"},
{id: "src/index.ts", name: "index.ts"},
],
},
{id: "prettier.config.js", name: "prettier.config.js"},
{id: "package.json", name: "package.json"},
{id: "tsconfig.json", name: "tsconfig.json"},
],
},
})Nodes
You can bring your own TreeNodes to create your own abstraction for the tree.
NOTE
This approach is recommended only for advanced use cases. Most users should use the shorthand <Tree.Nodes> instead.
import type {ReactElement} from "react"
import {FileText, FolderIcon} from "lucide-react"
import {createTreeCollection} from "@qualcomm-ui/core/tree"
import {Tree, type TreeNodeProviderProps} from "@qualcomm-ui/react/tree"
interface Node {
id: string
name: string
nodes?: Node[]
}
export function TreeNodesDemo(): ReactElement {
return (
<Tree.Root className="w-full max-w-sm" collection={collection}>
{collection.rootNode.nodes?.map((node, index) => {
return <TreeNodes key={node.id} indexPath={[index]} node={node} />
})}
</Tree.Root>
)
}
function TreeNodes(props: TreeNodeProviderProps<Node>): ReactElement {
const {indexPath, node} = props
const childNodes = collection.getNodeChildren(node)
return (
<Tree.NodeProvider {...props}>
{childNodes.length ? (
<Tree.Branch>
<Tree.BranchNode>
<Tree.NodeIndicator />
<Tree.BranchTrigger />
<Tree.NodeIcon icon={FolderIcon} />
<Tree.NodeText>{collection.stringifyNode(node)}</Tree.NodeText>
</Tree.BranchNode>
<Tree.BranchContent>
<Tree.BranchIndentGuide />
{collection.getNodeChildren(node).map((childNode, index) => (
<TreeNodes
key={collection.getNodeValue(childNode)}
indexPath={[...indexPath, index]}
node={childNode}
/>
))}
</Tree.BranchContent>
</Tree.Branch>
) : (
<Tree.LeafNode>
<Tree.NodeIndicator />
<Tree.NodeIcon icon={FileText} />
<Tree.NodeText>{collection.stringifyNode(node)}</Tree.NodeText>
</Tree.LeafNode>
)}
</Tree.NodeProvider>
)
}
const collection = createTreeCollection<Node>({
nodeChildren: "nodes",
nodeText: (node) => node.name,
nodeValue: (node) => node.id,
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "node_modules",
name: "node_modules",
nodes: [
{
id: "@qui",
name: "@qui",
nodes: [
{id: "node_modules/@qualcomm-ui/core", name: "@qualcomm-ui/core"},
{
id: "node_modules/@qualcomm-ui/react",
name: "@qualcomm-ui/react",
},
{
id: "node_modules/@qualcomm-ui/react-core",
name: "@qualcomm-ui/react-core",
},
],
},
{
id: "node_modules/@types",
name: "@types",
nodes: [
{id: "node_modules/@types/react", name: "react"},
{id: "node_modules/@types/react-dom", name: "react-dom"},
],
},
],
},
{
id: "src",
name: "src",
nodes: [
{id: "src/app.tsx", name: "app.tsx"},
{id: "src/index.ts", name: "index.ts"},
],
},
{id: "prettier.config.js", name: "prettier.config.js"},
{id: "package.json", name: "package.json"},
{id: "tsconfig.json", name: "tsconfig.json"},
],
},
})Default Expanded
Expand nodes by default using the defaultExpandedValue prop. Or use expandedValue and onExpandedChange to control the expansion manually. These props follow our controlled state pattern.
<Tree.Root
className="w-full max-w-sm"
collection={collection}
defaultExpandedValue={["src"]}
>
Checkbox Trees
Use the Tree.NodeCheckbox to add a checkbox to each node. The checked state of the tree can be controlled using the checkedValue, onCheckedChange, and defaultCheckedValue props, which follow our controlled state pattern.
<Tree.Nodes
key={node.id}
indexPath={[index]}
node={node}
renderBranch={({node}) => (
<Tree.BranchNode>
<Tree.BranchTrigger />
<Tree.NodeCheckbox />
<Tree.NodeText>{node.text}</Tree.NodeText>
</Tree.BranchNode>
)}
renderLeaf={({node}) => (
<Tree.LeafNode>
<Tree.NodeCheckbox />
<Tree.NodeText>{node.text}</Tree.NodeText>
</Tree.LeafNode>
)}
/>
Checkbox selection state
The Tree handles nested checkbox selection automatically:
- If all of a node's children are checked, the node will also be checked.
- If only some of a node's children are checked, the node will appear indeterminate to indicate partial selection.
When you supply the checkedValue or defaultCheckedValue props, you must account for the above logic.
Consider the following tree:
const collection = createTreeCollection<Node>({
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "qualcomm",
name: "Qualcomm",
nodes: [
{
id: "sdx",
name: "Snapdragon X",
nodes: [
{id: "elite", name: "Snapdragon X Elite"},
{id: "plus", name: "Snapdragon X Plus"},
],
},
],
},
],
},
})Let's say that we want to select the sdx node and its children by default.
function Component() {
return (
// won't work, we need to supply the child nodes instead
<Tree.Root defaultCheckedValue={["sdx"]} collection={collection}>
...
</Tree.Root>
)
}Instead, supply all the child nodes to indicate that the parent is selected:
<Tree.Root
className="w-full max-w-sm"
collection={collection}
defaultCheckedValue={["elite", "plus"]}
defaultExpandedValue={["qualcomm", "sdx"]}
>
The following demo shows checked state below the tree. Interact with it to see state updates:
[
"X1E-00-1DE",
"X1E-84-100"
]
<Tree.Nodes
key={node.id}
indexPath={[index]}
node={node}
renderBranch={({node}) => (
<Tree.BranchNode>
<Tree.BranchTrigger />
<Tree.NodeCheckbox />
<Tree.NodeText>{node.id}</Tree.NodeText>
</Tree.BranchNode>
)}
renderLeaf={({node}) => (
<Tree.LeafNode>
<Tree.NodeCheckbox />
<Tree.NodeText>{node.id}</Tree.NodeText>
</Tree.LeafNode>
)}
/>
Disabled Nodes
You can disable nodes by setting the disabled property on the node object.
import type {ReactElement} from "react"
import {FileText, FolderIcon} from "lucide-react"
import {createTreeCollection} from "@qualcomm-ui/core/tree"
import {Tree} from "@qualcomm-ui/react/tree"
interface Node {
disabled?: boolean
id: string
name: string
nodes?: Node[]
}
export function TreeDisabledNodeDemo(): ReactElement {
return (
<Tree.Root className="w-full max-w-sm" collection={collection}>
{collection.rootNode.nodes?.map((parentNode, index) => {
return (
<Tree.Nodes
key={parentNode.id}
indexPath={[index]}
node={parentNode}
renderBranch={({node}) => (
<Tree.BranchNode>
<Tree.NodeIndicator />
<Tree.BranchTrigger />
<Tree.NodeIcon icon={FolderIcon} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.BranchNode>
)}
renderLeaf={({node}) => (
<Tree.LeafNode>
<Tree.NodeIndicator />
<Tree.NodeIcon icon={FileText} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.LeafNode>
)}
/>
)
})}
</Tree.Root>
)
}
const collection = createTreeCollection<Node>({
nodeChildren: "nodes",
nodeText: (node) => node.name,
nodeValue: (node) => node.id,
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "node_modules",
name: "node_modules",
nodes: [
{
id: "@qui",
name: "@qui",
nodes: [
{id: "node_modules/@qualcomm-ui/core", name: "@qualcomm-ui/core"},
{
id: "node_modules/@qualcomm-ui/react",
name: "@qualcomm-ui/react",
},
{
id: "node_modules/@qualcomm-ui/react-core",
name: "@qualcomm-ui/react-core",
},
],
},
{
id: "node_modules/@types",
name: "@types",
nodes: [
{id: "node_modules/@types/react", name: "react"},
{id: "node_modules/@types/react-dom", name: "react-dom"},
],
},
],
},
{
id: "src",
name: "src",
nodes: [
{id: "src/app.tsx", name: "app.tsx"},
{id: "src/index.ts", name: "index.ts"},
],
},
{id: "prettier.config.js", name: "prettier.config.js"},
{id: "package.json", name: "package.json"},
{disabled: true, id: "renovate.json", name: "renovate.json"},
{id: "tsconfig.json", name: "tsconfig.json"},
],
},
})Filtering
Here's an example that filters the nodes using matchSorter.
import {type ReactElement, useState} from "react"
import {FileText, FolderIcon, Search} from "lucide-react"
import {createTreeCollection} from "@qualcomm-ui/core/tree"
import {TextInput} from "@qualcomm-ui/react/text-input"
import {Tree} from "@qualcomm-ui/react/tree"
import type {TreeCollection} from "@qualcomm-ui/utils/collection"
import {matchSorter} from "@qualcomm-ui/utils/match-sorter"
interface Node {
id: string
name: string
nodes?: Node[]
}
export function TreeFilteringDemo(): ReactElement {
const [collection, setCollection] =
useState<TreeCollection<Node>>(initialCollection)
const [expanded, setExpanded] = useState<string[]>([])
const [query, setQuery] = useState<string>("")
const search = (value: string) => {
setQuery(value)
if (!value) {
setCollection(initialCollection)
return
}
const nodes = matchSorter(initialCollection.getDescendantNodes(), value, {
keys: ["name"],
})
const nextCollection = initialCollection.filter((node) =>
nodes.some((n) => n.id === node.id),
)
setCollection(nextCollection)
setExpanded(nextCollection.getBranchValues())
}
return (
<Tree.Root
className="w-full max-w-sm"
collection={initialCollection}
expandedValue={expanded}
onExpandedValueChange={({expandedValue}) => setExpanded(expandedValue)}
>
<TextInput
aria-label="Search for files"
className="mb-1"
onValueChange={search}
placeholder="Search for files: 'react'"
size="sm"
startIcon={Search}
value={query}
/>
{collection.rootNode.nodes?.map((parentNode, index) => {
return (
<Tree.Nodes
key={parentNode.id}
indexPath={[index]}
node={parentNode}
renderBranch={({node}) => (
<Tree.BranchNode>
<Tree.NodeIndicator />
<Tree.BranchTrigger />
<Tree.NodeIcon icon={FolderIcon} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.BranchNode>
)}
renderLeaf={({node}) => (
<Tree.LeafNode>
<Tree.NodeIndicator />
<Tree.NodeIcon icon={FileText} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.LeafNode>
)}
/>
)
})}
</Tree.Root>
)
}
const initialCollection = createTreeCollection<Node>({
nodeChildren: "nodes",
nodeText: (node) => node.name,
nodeValue: (node) => node.id,
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "node_modules",
name: "node_modules",
nodes: [
{
id: "@qui",
name: "@qui",
nodes: [
{id: "node_modules/@qualcomm-ui/core", name: "@qualcomm-ui/core"},
{
id: "node_modules/@qualcomm-ui/react",
name: "@qualcomm-ui/react",
},
{
id: "node_modules/@qualcomm-ui/react-core",
name: "@qualcomm-ui/react-core",
},
],
},
{
id: "node_modules/@types",
name: "@types",
nodes: [
{id: "node_modules/@types/react", name: "react"},
{id: "node_modules/@types/react-dom", name: "react-dom"},
],
},
],
},
{
id: "src",
name: "src",
nodes: [
{id: "src/app.tsx", name: "app.tsx"},
{id: "src/index.ts", name: "index.ts"},
],
},
{id: "prettier.config.js", name: "prettier.config.js"},
{id: "package.json", name: "package.json"},
{id: "tsconfig.json", name: "tsconfig.json"},
],
},
})Links
Tree nodes can be links using polymorphic composition. Use the render prop on the <Tree.BranchNode> or <Tree.LeafNode> to specify the element type.
Sizes
Tree item sizes are controlled using the size prop on the root of the tree.
import type {ReactElement} from "react"
import {FileText, FolderIcon} from "lucide-react"
import {createTreeCollection} from "@qualcomm-ui/core/tree"
import {Tree} from "@qualcomm-ui/react/tree"
interface Node {
id: string
name: string
nodes?: Node[]
}
export function TreeSizeDemo(): ReactElement {
return (
<div className="flex w-full flex-col gap-4">
<Tree.Root className="w-full max-w-sm" collection={collection} size="sm">
<Tree.Label>Small (sm)</Tree.Label>
<TreeContent />
</Tree.Root>
<Tree.Root className="w-full max-w-sm" collection={collection} size="md">
<Tree.Label>Medium (md)</Tree.Label>
<TreeContent />
</Tree.Root>
</div>
)
}
function TreeContent() {
return (
<>
{collection.rootNode.nodes?.map((parentNode, index) => {
return (
<Tree.Nodes
key={parentNode.id}
indexPath={[index]}
node={parentNode}
renderBranch={({node}) => (
<Tree.BranchNode>
<Tree.NodeIndicator />
<Tree.BranchTrigger />
<Tree.NodeIcon icon={FolderIcon} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.BranchNode>
)}
renderLeaf={({node}) => (
<Tree.LeafNode>
<Tree.NodeIndicator />
<Tree.NodeIcon icon={FileText} />
<Tree.NodeText>{node.name}</Tree.NodeText>
</Tree.LeafNode>
)}
/>
)
})}
</>
)
}
const collection = createTreeCollection<Node>({
nodeChildren: "nodes",
nodeText: (node) => node.name,
nodeValue: (node) => node.id,
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "node_modules",
name: "node_modules",
nodes: [
{
id: "@qui",
name: "@qui",
nodes: [
{id: "node_modules/@qualcomm-ui/core", name: "@qualcomm-ui/core"},
{
id: "node_modules/@qualcomm-ui/react",
name: "@qualcomm-ui/react",
},
{
id: "node_modules/@qualcomm-ui/react-core",
name: "@qualcomm-ui/react-core",
},
],
},
{
id: "node_modules/@types",
name: "@types",
nodes: [
{id: "node_modules/@types/react", name: "react"},
{id: "node_modules/@types/react-dom", name: "react-dom"},
],
},
],
},
{
id: "src",
name: "src",
nodes: [
{id: "src/app.tsx", name: "app.tsx"},
{id: "src/index.ts", name: "index.ts"},
],
},
{id: "prettier.config.js", name: "prettier.config.js"},
{id: "package.json", name: "package.json"},
{id: "tsconfig.json", name: "tsconfig.json"},
],
},
})Add / Remove nodes
The TreeCollection class exposes methods to handle the addition and removal of nodes. Here's an example of how to use them.
import {type ReactElement, useState} from "react"
import {FileText, FolderIcon, Plus, Trash} from "lucide-react"
import {createTreeCollection} from "@qualcomm-ui/core/tree"
import {Tree, type TreeNodeProviderProps} from "@qualcomm-ui/react/tree"
import {useTreeContext} from "@qualcomm-ui/react-core/tree"
interface Node {
id: string
name: string
nodes?: Node[]
}
export function TreeAddRemoveDemo(): ReactElement {
const [collection, setCollection] = useState(initialCollection)
const removeNode = (nodeProps: TreeNodeActionsProps) => {
setCollection(collection.remove([nodeProps.indexPath]))
}
const addNode = (nodeProps: TreeNodeActionsProps) => {
const {indexPath, node} = nodeProps
if (!collection.isBranchNode(node)) {
return
}
const nodes = [
{
id: `untitled-${Date.now()}`,
name: `untitled-${node.nodes?.length}.tsx`,
},
...(node.nodes || []),
]
setCollection(collection.replace(indexPath, {...node, nodes}))
}
return (
<Tree.Root className="w-full max-w-sm" collection={collection}>
{collection.getNodeChildren(collection.rootNode).map((node, index) => {
return (
<Tree.Nodes
key={node.id}
indexPath={[index]}
node={node}
renderBranch={({indexPath, node}) => (
<Tree.BranchNode role="treeitem">
<Tree.NodeIndicator />
<Tree.BranchTrigger />
<Tree.NodeIcon icon={FolderIcon} />
<Tree.NodeText>{collection.stringifyNode(node)}</Tree.NodeText>
<TreeNodeActions
indexPath={indexPath}
node={node}
onAdd={addNode}
onRemove={removeNode}
/>
</Tree.BranchNode>
)}
renderLeaf={({indexPath, node}) => (
<Tree.LeafNode>
<Tree.NodeIndicator />
<Tree.NodeIcon icon={FileText} />
<Tree.NodeText>{collection.stringifyNode(node)}</Tree.NodeText>
<TreeNodeActions
indexPath={indexPath}
node={node}
onAdd={addNode}
onRemove={removeNode}
/>
</Tree.LeafNode>
)}
/>
)
})}
</Tree.Root>
)
}
interface TreeNodeActionsProps extends TreeNodeProviderProps<Node> {
onAdd?: (props: TreeNodeProviderProps<Node>) => void
onRemove?: (props: TreeNodeProviderProps<Node>) => void
}
function TreeNodeActions(props: TreeNodeActionsProps) {
const {node, onAdd, onRemove} = props
const tree = useTreeContext<Node>()
const isBranch = tree.collection.isBranchNode(node)
return (
<>
<Tree.NodeAction
aria-label="Remove node"
icon={Trash}
onClick={() => {
onRemove?.(props)
}}
size="sm"
/>
{isBranch && (
<Tree.NodeAction
aria-label="Add node"
icon={Plus}
onClick={() => {
onAdd?.(props)
tree.expand([node.id])
}}
size="sm"
/>
)}
</>
)
}
const initialCollection = createTreeCollection<Node>({
nodeChildren: "nodes",
nodeText: "name",
nodeValue: "id",
rootNode: {
id: "ROOT",
name: "",
nodes: [
{
id: "node_modules",
name: "node_modules",
nodes: [
{
id: "@qui",
name: "@qui",
nodes: [
{id: "node_modules/@qualcomm-ui/core", name: "@qualcomm-ui/core"},
{
id: "node_modules/@qualcomm-ui/react",
name: "@qualcomm-ui/react",
},
{
id: "node_modules/@qualcomm-ui/react-core",
name: "@qualcomm-ui/react-core",
},
],
},
{
id: "node_modules/@types",
name: "@types",
nodes: [
{id: "node_modules/@types/react", name: "react"},
{id: "node_modules/@types/react-dom", name: "react-dom"},
],
},
],
},
{
id: "src",
name: "src",
nodes: [
{id: "src/app.tsx", name: "app.tsx"},
{id: "src/index.ts", name: "index.ts"},
],
},
{id: "prettier.config.js", name: "prettier.config.js"},
{id: "package.json", name: "package.json"},
{id: "tsconfig.json", name: "tsconfig.json"},
],
},
})API
<Tree.Root>
| Prop | Type | Default |
|---|---|---|
The controlled checked node value | string[] | |
The tree collection data | ||
The initial checked node value when rendered.
Use when you don't need to control the checked node value. | string[] | |
The initial expanded node ids when rendered.
Use when you don't need to control the expanded node value. | string[] | |
The initial focused node value when rendered.
Use when you don't need to control the focused node value. | string | |
The initial selected node value when rendered.
Use when you don't need to control the selected node value. | string[] | |
The document's text/writing direction. | 'ltr' | 'rtl' | 'ltr' |
The controlled expanded node ids | string[] | |
Whether clicking on a branch should open it or not | boolean | true |
The value of the focused node | string | |
A root node to correctly resolve document in custom environments. i.e.,
Iframes, Electron. | () => | |
id attribute. If
omitted, a unique identifier will be automatically generated for accessibility. | string | |
When true, the component will not be rendered in the DOM until it becomes
visible or active. | boolean | false |
Function to load children for a node asynchronously.
When provided, branches will wait for this promise to resolve before expanding. | (details: { | |
Called when the checked value changes | (details: { | |
Called when the tree is opened or closed | (details: { | |
Called when the focused node changes | (details: { | |
Called when a node finishes loading children | (details: { | |
Called when loading children fails for one or more nodes | (details: { | |
Called when the selection changes | (details: { | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement | |
The controlled selected node value | string[] | |
Whether the tree supports multiple selection | | 'multiple' | 'single' |
Callback function that determines whether a node should be hidden. | ( | |
The size of the tree and its elements. Governs properties like font size,
item padding, and icon sizes. | 'sm' | 'md' | 'md' |
Whether the tree supports typeahead search | boolean | true |
When true, the component will be completely removed from the DOM when it
becomes inactive or hidden, rather than just being hidden with CSS. | boolean | false |
string[]
string[]
string[]
stringstring[]
'ltr' | 'rtl'
string[]
booleanstring() =>
| Node
| ShadowRoot
| Document
stringboolean(details: {
indexPath: number[]
node: T
signal: AbortSignal
valuePath: string[]
}) => Promise<Array<T>>
(details: {
checkedNodes: Array<T>
checkedValue: string[]
}) => void
(details: {
expandedNodes: Array<T>
expandedValue: string[]
focusedValue: string
}) => void
(details: {
focusedNode: T
focusedValue: string
}) => void
(details: {
collection: TreeCollection<T>
}) => void
(details: {
nodes: NodeWithError[]
}) => void
(details: {
focusedValue: string
selectedNodes: Array<T>
selectedValue: string[]
}) => void
| ReactElement
| ((
props: object,
) => ReactElement)
string[]
| 'multiple'
| 'single'
(
state: NodeState<T>,
) => boolean
'sm' | 'md'
booleanboolean| Attribute / Property | Value |
|---|---|
className | 'qui-tree__root' |
data-part | 'root' |
data-scope | 'tree' |
data-size | 'sm' | 'md' |
tabIndex | -1 |
className'qui-tree__root'data-part'root'data-scope'tree'data-size'sm' | 'md'
tabIndex-1
<Tree.Nodes>
| Prop | Type | Default |
|---|---|---|
The index path of the tree node | number[] | |
node * The tree node to apply context from. | T | |
Render Prop
for the tree's branch items. Use this prop to supply the content of the branch
item like the text, indicator, or icon. | ||
Render Prop
for the tree's leaf items. Use this prop to supply the content of the leaf item
like the text, indicator, or icon. | ||
Props passed to the branch indent guide component. Only applicable when showIndentGuide is true. | ||
Whether to render the indent guide for branch child nodes. | boolean | false |
number[]
TbooleanTreeCollection
Note that the TreeCollection accepts a single generic type parameter, T, which is the object type of the node used in the collection.
Constructor
The constructor of the TreeCollection class accepts the following options:
| Prop | Type | Default |
|---|---|---|
Property key for accessing a node's children. | keyof T | "nodes" |
Function to determine the count of a node's children. | ( | |
Property key or function to determine if a node is disabled. When a string key
is provided, the value of node[key] determines the disabled state. | | keyof T | "disabled" |
Property key or function for getting a node's text. When a string key
is provided, the value of node[key] is used. | | keyof T | "text" |
Property key or function for getting a node's value. When a string key
is provided, the value of node[key] is used. | | keyof T | "value" |
The root node of the tree | T |
keyof T
(
node: T,
) => number
| keyof T
| ((node: T) => boolean)
| keyof T
| ((node: T) => string)
| keyof T
| ((node: T) => string)
T| Prop | Type |
|---|---|
Gets the node at the specified index path.
| ( |
Checks if a parent index path contains a child index path.
| ( |
Creates a new tree collection with the same options but different root node.
| ( |
Filters the tree keeping only nodes that match the predicate.
| ( |
Finds the first node with the specified value.
| ( |
( | |
Finds all nodes with values matching the provided array.
| ( |
Flattens the tree into an array with parent/child relationships.
| ( |
Gets all branch node values with optional depth filtering.
| ( |
Gets the depth of a node with the specified value.
| ( |
Gets all descendant nodes of the specified node.
| ( |
Gets all descendant values of the specified node.
| ( |
Gets the first non-disabled node in the tree.
| ( |
Gets the index path for a node with the specified value.
| ( |
Gets the last non-disabled node in the tree.
| ( |
Gets the next node after the one with the specified value.
| ( |
Gets the next non-disabled sibling of the node at the index path.
| ( |
Returns all child nodes for this node.
Uses options.nodeToChildren if provided, otherwise falls back to default behavior. | ( |
Gets the number of children for a node, supporting lazy loading scenarios.
Uses options.nodeToChildrenCount if provided, otherwise falls back to default behavior.
| ( |
Checks if a node is disabled.
Uses options.isNodeDisabled if provided, otherwise falls back to default behavior.
| ( |
Gets the string value for a node.
Uses options.nodeValue if provided, otherwise falls back to default behavior.
| ( |
Gets the parent node of the specified node.
| ( |
Gets all parent nodes from root to the specified node.
| ( |
Gets the previous node before the one with the specified value.
| ( |
Gets the previous non-disabled sibling of the node at the index path.
| ( |
Gets all sibling nodes of the node at the index path.
| ( |
Gets the value of the node at the specified index path.
| ( |
Gets the path of values from root to the specified index path.
| ( |
Gets all values in the tree, excluding the root node.
| ( |
Groups children of a parent node by a specified key.
| ( |
// Group root-level children | |
Inserts nodes after the node at the specified index path.
| ( |
Inserts nodes before the node at the specified index path.
| ( |
Checks if a node is a branch node (has children or can have children).
| ( |
Compares this tree collection with another for deep equality.
| ( |
Checks if a node is the root node.
| ( |
Checks if two nodes are the same by comparing their values.
| ( |
Moves nodes from one location to another in the tree.
| ( |
Removes nodes at the specified index paths.
| ( |
Replaces the node at the specified index path.
| ( |
The root tree node. | T |
Sorts values according to their tree order.
| ( |
Converts a node value to its string representation.
| ( |
Converts a node to its string representation.
Uses options.nodeLabel if provided, otherwise falls back to default behavior: uses node.text, or node.value if node.text is not available.
| ( |
Serializes the tree to a JSON-compatible array of values. | () => string[] |
Visits all nodes in the tree with optional skip functionality.
| (opts: { |
(
indexPath: number[],
) => T
- indexPath:Array of indices representing the path to the node
(
parentIndexPath: number[],
valueIndexPath: number[],
) => boolean
- parentIndexPath:The parent path
- valueIndexPath:The child path to check
(
rootNode: T,
) => any
- rootNode:The new root node for the copied collection
(
predicate: (
node: T,
indexPath: number[],
) => boolean,
) => any
- predicate:Function to test each node
(
value: string,
rootNode?: T,
) => T
- value:The value to search for
- rootNode:The root node to start searching from
(
predicate: (
node: T,
indexPath: number[],
) => boolean,
rootNode?: T,
) => T
(
values: string[],
rootNode?: T,
) => Array<T>
- values:Array of values to search for
- rootNode:The root node to start searching from
(
rootNode?: T,
) => Array<
T & {
_children: number[]
_index: number
_parent: number
}
>
- rootNode:The root node to start flattening from
(
rootNode?: T,
opts?: {
skip?: (args: {
indexPath: number[]
node: T
value: string
}) => boolean | void
} & {
depth?:
| number
| ((
nodeDepth: number,
) => boolean)
},
) => string[]
- rootNode:The root node to start from
- opts:Options for skipping nodes and filtering by depth
(
value: string,
) => number
- value:The value to find the depth for
(
valueOrIndexPath?:
| string
| number[],
options?: T & {
disabled?: boolean
id?: string
onUnregister?: (
index: number,
) => void
requireContext?: boolean
},
) => Array<T>
- valueOrIndexPath:Either a node value or index path
- options:Options for controlling which descendants to include
(
valueOrIndexPath:
| string
| number[],
options?: T & {
disabled?: boolean
id?: string
onUnregister?: (
index: number,
) => void
requireContext?: boolean
},
) => string[]
- valueOrIndexPath:Either a node value or index path
- options:Options for controlling which descendants to include
(
rootNode?: T,
) => T
- rootNode:The root node to start searching from
(
value: string,
) => number[]
- value:The value to find the index path for
(
rootNode?: T,
opts?: {
skip?: (args: {
indexPath: number[]
node: T
value: string
}) => boolean | void
},
) => T
- rootNode:The root node to start searching from
- opts:Options for skipping nodes during traversal
(
value: string,
opts?: {
skip?: (args: {
indexPath: number[]
node: T
value: string
}) => boolean | void
},
) => T
- value:The value to find the next node from
- opts:Options for skipping nodes during traversal
(
indexPath: number[],
) => T
- indexPath:Array of indices representing the path to the node
(
node: T,
) => Array<T>
(
node: T,
) => number
- node:The node to get children count for
(
node: T,
) => boolean
- node:The node to check
(
node: T,
) => string
- node:The node to get the value from
(
valueOrIndexPath:
| string
| number[],
) => T
- valueOrIndexPath:Either a node value or index path
(
valueOrIndexPath:
| string
| number[],
) => Array<T>
- valueOrIndexPath:Either a node value or index path
(
value: string,
opts?: {
skip?: (args: {
indexPath: number[]
node: T
value: string
}) => boolean | void
},
) => T
- value:The value to find the previous node from
- opts:Options for skipping nodes during traversal
(
indexPath: number[],
) => T
- indexPath:Array of indices representing the path to the node
(
indexPath: number[],
) => Array<T>
- indexPath:Array of indices representing the path to the node
(
indexPath: number[],
) => string
- indexPath:Array of indices representing the path to the node
(
indexPath: number[],
) => string[]
- indexPath:Array of indices representing the path to the node
(
rootNode?: T,
) => string[]
- rootNode:The root node to start from
(
parentIndexPath: IndexPath,
groupBy: (
node: T,
index: number,
) => string,
sortGroups?:
| string[]
| ((
a: {
items: Array<{
indexPath: IndexPath
node: T
}>
key: string
},
b: {
items: Array<{
indexPath: IndexPath
node: T
}>
key: string
},
) => number),
) => GroupedTreeNode<T>[]
- parentIndexPath:Index path of the parent node whose children to group. Pass
[]for root-level children. - groupBy:Function that determines the group key for each child node
- sortGroups:Optional array of group keys defining order, or comparator function to sort the groups. By default, groups are sorted by first occurrence in the tree (insertion order)
// Group root-level children
const groups = collection.groupChildren([], (node) => node.group ?? 'default')
// Group with explicit order
const groups = collection.groupChildren(
[],
(node) => node.group,
['primary', 'secondary', 'tertiary']
)
// Group with custom sorter
const groups = collection.groupChildren(
[],
(node) => node.group,
(a, b) => String(a.key).localeCompare(String(b.key))
)
(
indexPath: number[],
nodes: Array<T>,
) => any
- indexPath:Array of indices representing the insertion point
- nodes:Array of nodes to insert
(
indexPath: number[],
nodes: Array<T>,
) => any
- indexPath:Array of indices representing the insertion point
- nodes:Array of nodes to insert
(
node: T,
) => boolean
- node:The node to check
(
other: TreeCollection<T>,
) => boolean
- other:The other tree collection to compare with
(
node: T,
) => boolean
- node:The node to check
(
node: T,
other: T,
) => boolean
- node:First node to compare
- other:Second node to compare
(
fromIndexPaths: Array<
number[]
>,
toIndexPath: number[],
) => any
- fromIndexPaths:Array of index paths to move from
- toIndexPath:Index path to move to
(
indexPaths: Array<number[]>,
) => any
- indexPaths:Array of index paths to remove
(
indexPath: number[],
node: T,
) => any
- indexPath:Array of indices representing the path to the node
- node:The new node to replace with
T(
values: string[],
) => string[]
- values:Array of values to sort
(
value: string,
) => string
- value:The value to stringify
(
T,
) => string
node.text, or node.value if node.text is not available.- node:The node to stringify
() => string[]
(opts: {
onEnter?: (
node: T,
indexPath: number[],
) => void | 'skip' | 'stop'
onLeave?: (
node: T,
indexPath: number[],
) => void | 'stop'
reuseIndexPath?: boolean
skip?: (args: {
indexPath: number[]
node: T
value: string
}) => boolean | void
}) => void
- opts:Options for visiting nodes, including skip predicate
Element API
<Tree.Label>
<div> element by default.| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be automatically generated for accessibility. | string |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
string| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__label' |
data-part | 'label' |
data-scope | 'tree' |
data-size | 'sm' | 'md' |
className'qui-tree__label'data-part'label'data-scope'tree'data-size'sm' | 'md'
<Tree.Branch>
<div> element by
default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__branch-root' |
data-branch | string |
data-depth | number |
data-disabled | |
data-loading | |
data-ownedby | string |
data-part | 'branch' |
data-path | string |
data-scope | 'tree' |
data-selected | |
data-state | | 'open' |
data-value | string |
hidden | boolean |
style |
className'qui-tree__branch-root'data-branchstringdata-depthnumberdata-disableddata-loadingdata-ownedbystringdata-part'branch'data-pathstringdata-scope'tree'data-selecteddata-state| 'open'
| 'closed'
data-valuestringhiddenbooleanstyle<Tree.BranchNode>
<div> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__node-root' |
data-depth | number |
data-disabled | |
data-focus | |
data-loading | |
data-part | 'branch-node' |
data-path | string |
data-scope | 'tree' |
data-selected | |
data-size | 'sm' | 'md' |
data-state | | 'open' |
data-value | string |
tabIndex | -1 | 0 |
className'qui-tree__node-root'data-depthnumberdata-disableddata-focusdata-loadingdata-part'branch-node'data-pathstringdata-scope'tree'data-selecteddata-size'sm' | 'md'
data-state| 'open'
| 'closed'
data-valuestringtabIndex-1 | 0
<Tree.BranchTrigger>
<div>
element by default.| Prop | Type | Default |
|---|---|---|
| LucideIcon | ChevronRight | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| LucideIcon
| ReactNode
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__branch-trigger' |
data-disabled | |
data-loading | |
data-part | 'branch-trigger' |
data-scope | 'tree' |
data-state | | 'open' |
data-value | string |
className'qui-tree__branch-trigger'data-disableddata-loadingdata-part'branch-trigger'data-scope'tree'data-state| 'open'
| 'closed'
data-valuestring<Tree.BranchContent>
<div> element by
default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__branch-content' |
data-depth | number |
data-part | 'branch-content' |
data-path | string |
data-scope | 'tree' |
data-value | string |
className'qui-tree__branch-content'data-depthnumberdata-part'branch-content'data-pathstringdata-scope'tree'data-valuestring<Tree.BranchIndentGuide>
<div> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
data-depth | number |
data-part | 'branch-indent-guide' |
data-scope | 'tree' |
style |
data-depthnumberdata-part'branch-indent-guide'data-scope'tree'style<Tree.LeafNode>
<div> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__node-root' |
data-depth | number |
data-disabled | |
data-focus | |
data-ownedby | string |
data-part | 'leaf-node' |
data-path | string |
data-scope | 'tree' |
data-selected | |
data-size | 'sm' | 'md' |
data-value | string |
hidden | boolean |
style | |
tabIndex | -1 | 0 |
className'qui-tree__node-root'data-depthnumberdata-disableddata-focusdata-ownedbystringdata-part'leaf-node'data-pathstringdata-scope'tree'data-selecteddata-size'sm' | 'md'
data-valuestringhiddenbooleanstyletabIndex-1 | 0
<Tree.NodeCheckbox>
<span> element by default.| Prop | Type | Default |
|---|---|---|
React Node rendered when the node is checked. | CheckmarkCheckedIcon | |
React Node rendered when the node is indeterminate. | CheckmarkIndeterminateIcon | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement | |
React Node rendered when the node is unchecked. |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-checkbox__control qui-checkmark__root' |
data-disabled | |
data-part | 'node-checkbox' |
data-scope | 'tree' |
data-state | | 'checked' |
tabIndex | -1 |
className'qui-checkbox__control qui-checkmark__root'data-disableddata-part'node-checkbox'data-scope'tree'data-state| 'checked'
| 'indeterminate'
| 'unchecked'
tabIndex-1
<Tree.NodeAction>
<button>
element by default.| Prop | Type | Default |
|---|---|---|
icon * Lucide icon to display inside the button. | | LucideIcon | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement | |
The size of the button and its icon. | | 'sm' | 'md' |
| LucideIcon
| ReactNode
| ReactElement
| ((
props: object,
) => ReactElement)
| 'sm'
| 'md'
| 'lg'
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__node-action' |
data-disabled | |
data-focus | |
data-part | 'node-action' |
data-scope | 'tree' |
data-selected |
className'qui-tree__node-action'data-disableddata-focusdata-part'node-action'data-scope'tree'data-selected<Tree.NodeIndicator>
<div> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__node-indicator' |
data-disabled | |
data-focus | |
data-part | 'node-indicator' |
data-scope | 'tree' |
data-selected | |
hidden | boolean |
className'qui-tree__node-indicator'data-disableddata-focusdata-part'node-indicator'data-scope'tree'data-selectedhiddenboolean<Tree.NodeIcon>
<span> element by default.| Prop | Type |
|---|---|
icon * lucide-react icon or JSX Element. | | LucideIcon |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| LucideIcon
| ReactNode
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-tree__node-icon' |
data-disabled | |
data-focus | |
data-part | 'node-icon' |
data-scope | 'tree' |
data-selected | |
data-size | 'sm' | 'md' |
className'qui-tree__node-icon'data-disableddata-focusdata-part'node-icon'data-scope'tree'data-selecteddata-size'sm' | 'md'
<Tree.NodeProvider>
<Tree.NodeText>
<span> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
TreeNodeText Bindings, QdsTreeNodeTextBindings
<div>element by default.