TreeView
A hierarchical tree view component for displaying nested data structures with support for search, selection, and custom actions.
Basic Usage
The TreeView component displays hierarchical data in a collapsible tree structure. Data is provided as a flat array with parent-child relationships defined by parentId.
import {File, Folder} from "lucide-react";
import {TreeView, useTreeState} from "@vuer-ai/vuer-uikit";
import {useState} from "react";
function FileExplorer() {
const [hoveredId, setHoveredId] = useState(null);
// Flat data structure with parentId references
const fileSystemData = [
{id: "1", parentId: null, label: "src", etype: "folder", isCollapsible: true},
{id: "2", parentId: "1", label: "components", etype: "folder", isCollapsible: true},
{id: "3", parentId: "2", label: "TreeView.tsx", etype: "file"},
{id: "4", parentId: "2", label: "Button.tsx", etype: "file"},
{id: "5", parentId: "1", label: "hooks", etype: "folder", isCollapsible: true},
{id: "6", parentId: "5", label: "useTreeState.ts", etype: "file"},
{id: "7", parentId: null, label: "package.json", etype: "file"},
];
// Use the tree state hook
const {visibleData, expandedItems, toggleItem, hasDescendants} = useTreeState({
data: fileSystemData,
defaultExpanded: true,
});
const getFileIcon = (item) => {
if (item.etype === "folder") {
return <Folder className="size-4 shrink-0" />;
}
return <File className="size-4 shrink-0" />;
};
return (
<TreeView
data={visibleData}
getIcon={getFileIcon}
className="h-[400px]"
hoveredId={hoveredId}
onItemHover={setHoveredId}
expandedItems={expandedItems}
onToggleItem={toggleItem}
hasDescendants={hasDescendants}
/>
);
}
With Search
Add search functionality using the TreeSearchBar component and useTreeSearch hook. The search supports regular expressions and case sensitivity options.
function SearchableFileExplorer() {
const [searchQuery, setSearchQuery] = useState("");
const [isCaseSensitive, setIsCaseSensitive] = useState(false);
const [isRegex, setIsRegex] = useState(false);
const fileSystemData = [/* ... same as above */];
// Filter data based on search
const {filteredData, searchResultsCount, isRegexValid} = useTreeSearch({
data: fileSystemData,
searchQuery,
isCaseSensitive,
isRegex,
});
// Process filtered data for display
const {visibleData, expandedItems, toggleItem, hasDescendants} = useTreeState({
data: filteredData,
defaultExpanded: true,
});
return (
<div className="h-[500px] flex flex-col">
<div className="p-2">
<TreeSearchBar
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
isCaseSensitive={isCaseSensitive}
setIsCaseSensitive={setIsCaseSensitive}
isRegex={isRegex}
setIsRegex={setIsRegex}
isRegexValid={isRegexValid}
searchResultsCount={searchResultsCount}
/>
</div>
<TreeView
data={visibleData}
getIcon={getFileIcon}
className="flex-1"
expandedItems={expandedItems}
onToggleItem={toggleItem}
hasDescendants={hasDescendants}
/>
</div>
);
}
Selectable Items
Enable item selection by setting isSelectable={true}. Use selectedItemId and onSelectChange to control the selected state.
function SelectableFileExplorer() {
const [selectedId, setSelectedId] = useState(null);
return (
<div>
<div className="mb-2 text-sm">
Selected: {selectedId ? fileSystemData.find(item => item.id === selectedId)?.label : "None"}
</div>
<TreeView
data={visibleData}
getIcon={getFileIcon}
isSelectable={true}
selectedItemId={selectedId}
onSelectChange={setSelectedId}
expandedItems={expandedItems}
onToggleItem={toggleItem}
hasDescendants={hasDescendants}
/>
</div>
);
}
With Actions
Add custom action buttons to tree items using the actions property. Actions can be always visible or appear on hover.
function FileExplorerWithActions() {
const [hoveredId, setHoveredId] = useState(null);
const fileSystemData = [
{
id: "1",
parentId: null,
label: "src",
etype: "folder",
isCollapsible: true,
actions: (
<div className="flex gap-1">
<Plus className="size-3"/>
<MoreHorizontal className="size-3"/>
</div>,
),
}, {
id: "2",
parentId: "1",
label: "TreeView.tsx",
etype: "file",
actions: (
<div className="flex gap-1">
<Edit className="size-3"/>
<Copy className="size-3"/>
<Trash2 className="size-3"/>
</div>
),
},
];
return (
<TreeView
data={visibleData}
getIcon={getFileIcon}
hoveredId={hoveredId}
onItemHover={setHoveredId}
expandedItems={expandedItems}
onToggleItem={toggleItem}
hasDescendants={hasDescendants}
/>
);
}
Disabled Items
Items can be disabled by setting disable: true. Disabled items have muted colors and limited interaction.
const fileSystemData = [
{
id: "1",
label: "src",
etype: "folder",
isCollapsible: true,
}, {
id: "2",
label: "deprecated-utils.ts",
etype: "file",
disable: true, // Disabled file
}, {
id: "3",
label: "legacy",
etype: "folder",
isCollapsible: true,
disable: true, // Disabled but still collapsible
},
];
External Search Bar
The TreeSearchBar can be used separately for custom layouts and styling.
function ExternalSearchExample() {
const [searchQuery, setSearchQuery] = useState("");
const [isCaseSensitive, setIsCaseSensitive] = useState(false);
const [isRegex, setIsRegex] = useState(false);
return (
<div className="flex flex-col">
{/* Custom positioned search bar */}
<div className="bg-gray-50 dark:bg-gray-900 p-2">
<TreeSearchBar
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
isCaseSensitive={isCaseSensitive}
setIsCaseSensitive={setIsCaseSensitive}
isRegex={isRegex}
setIsRegex={setIsRegex}
isRegexValid={true}
searchResultsCount={0}
/>
</div>
{/* Tree without internal search */}
<TreeView
data={visibleData}
getIcon={getFileIcon}
expandedItems={expandedItems}
onToggleItem={toggleItem}
hasDescendants={hasDescendants}
/>
</div>
);
}
Hide Expand Button
Control the visibility of expand/collapse buttons with the hideExpand prop. When included, the expand button is hidden until you hover over the item. By default (without this prop), the expand button is always visible.
<TreeView
data={visibleData}
getIcon={getFileIcon}
hideExpand // Hide expand button until hover
expandedItems={expandedItems}
onToggleItem={toggleItem}
hasDescendants={hasDescendants}
/>
Data Structure
The TreeView uses a flat data structure with parent-child relationships:
type TreeDataItem = {
id: string; // Unique identifier
parentId: string | null; // Parent's id (null for root)
label: string; // Display text
isCollapsible?: boolean; // Can expand/collapse
actions?: ReactNode; // Custom actions
disable?: boolean; // Disabled state
[key: string]: any; // Additional properties
}
API Reference
TreeView Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | TreeDataItem[] | - | Processed data from useTreeState |
getIcon | (item) => ReactNode | - | Icon renderer function |
className | string | - | Additional CSS classes |
isSelectable | boolean | false | Enable selection |
selectedItemId | string | null | - | Selected item id |
onSelectChange | (id) => void | - | Selection change handler |
hoveredId | string | null | - | Hovered item id |
onItemHover | (id) => void | - | Hover change handler |
hideExpand | boolean | false | Hide expand buttons |
expandedItems | Set<string> | - | Expanded item ids |
onToggleItem | (id) => void | - | Toggle item handler |
hasDescendants | (id) => boolean | - | Check for children |
TreeSearchBar Props
| Prop | Type | Description |
|---|---|---|
searchQuery | string | Current search text |
setSearchQuery | (query) => void | Update search text |
isCaseSensitive | boolean | Case sensitive search |
setIsCaseSensitive | (value) => void | Toggle case sensitivity |
isRegex | boolean | Use regex search |
setIsRegex | (value) => void | Toggle regex mode |
isRegexValid | boolean | Is regex valid |
searchResultsCount | number | Number of results |
Hooks
useTreeState
Processes flat data into a tree structure for rendering.
const {visibleData, expandedItems, toggleItem, hasDescendants, expandAll, collapseAll} = useTreeState({
data: TreeDataItem[],
defaultExpanded?: boolean
});
useTreeSearch
Filters tree data based on search criteria.
const {filteredData, searchResultsCount, isRegexValid, renderLabel} = useTreeSearch({
data: TreeDataItem[],
searchQuery: string,
isCaseSensitive?: boolean,
isRegex?: boolean
});