@e-llm-studio/creative-workspace-ui-component
v0.0.1
Published
A hierarchical tree navigation component for rendering nested scope/node structures with expandable sections, search, and configurable actions. The component is part of the `@e-llm-studio/creative-workspace-ui-component` package and can be used independen
Downloads
105
Maintainers
Keywords
Readme
TreeSidebar Component
A hierarchical tree navigation component for rendering nested scope/node structures with expandable sections, search, and configurable actions. The component is part of the @e-llm-studio/creative-workspace-ui-component package and can be used independently in any React application.
Introduction
TreeSidebar is a standalone component from the @e-llm-studio/creative-workspace-ui-component package designed to provide a fully generic, hierarchical tree navigation panel. Its primary purpose is to render a tree of nodes (e.g. Global → Scope → Subscope) with expandable sections, search filtering, and optional action handlers. The consuming team controls what actions are available by passing or omitting handler props — if a handler is not passed, the corresponding button is not rendered.
Features
- Hierarchical tree rendering — Supports unlimited nesting levels (Global → Scope → Subscope → ...)
- Expand/collapse — Each node is independently expandable; root node auto-expands on load
- Section items — Each node can show configurable section tabs (e.g. Learnings, Stations) below it when expanded
- Search filtering — Pass a
searchQueryto filter nodes by name; non-matching subtrees are hidden - Configurable node types — Control labels, add-child labels and child types via
nodeTypesConfig - Configurable sections — Control which sections appear per node via
sectionsConfig - Conditional actions — Add, Rename, Delete buttons only render when the corresponding handler prop is passed
- Children count badge — Optional badge showing number of direct children per node
- Section badge — Optional badge on section items showing metrics (e.g. learnings count)
- Inline styles only — No Tailwind or MUI dependency, works in any consuming app regardless of styling setup
Installation
The TreeSidebar component is part of the @e-llm-studio/creative-workspace-ui-component package.
Using npm
npm install @e-llm-studio/creative-workspace-ui-componentUsing yarn
yarn add @e-llm-studio/creative-workspace-ui-componentImport Statement
import { TreeSidebar } from "@e-llm-studio/creative-workspace-ui-component";
// or via subpath
import { TreeSidebar } from "@e-llm-studio/creative-workspace-ui-component/TreeSidebar";Usage
Basic Example
Here's a simple example demonstrating how to use TreeSidebar with minimal props:
import { TreeSidebar } from "@e-llm-studio/creative-workspace-ui-component";
import { BookOpen } from "lucide-react";
const treeData = {
id: "root",
name: "Global Scope",
type: "GLOBAL" as const,
metrics: { learningsCount: 10 },
children: [
{
id: "scope-1",
name: "Scope 1",
type: "SCOPE" as const,
metrics: { learningsCount: 5 },
children: [],
},
],
};
export default function App() {
return (
<TreeSidebar
data={treeData}
sectionsConfig={[
{
id: "learnings",
label: "Learnings",
icon: BookOpen,
badgeKey: "learningsCount",
},
]}
nodeTypesConfig={[
{ type: "GLOBAL", label: "Global", addChildLabel: "Add Scope", addChildType: "SCOPE" },
{ type: "SCOPE", label: "Scope" },
]}
onSectionSelect={(nodeId, sectionId) => console.log(nodeId, sectionId)}
/>
);
}Key Props Explained:
data: The root node of the tree — passnullto render nothingsectionsConfig: Defines which section tabs appear under each expanded nodenodeTypesConfig: Defines node labels and add-child behavior per depth levelonSectionSelect: Fired when a section tab is clicked — returns the selectednodeIdandsectionId
Advanced Example
This example demonstrates all action handlers and badge visibility controls:
import { TreeSidebar } from "@e-llm-studio/creative-workspace-ui-component";
import { BookOpen } from "lucide-react";
export default function AdvancedExample() {
return (
<TreeSidebar
data={treeData}
sectionsConfig={[
{
id: "learnings",
label: "Learnings",
icon: BookOpen,
badgeKey: "learningsCount",
},
]}
nodeTypesConfig={[
{ type: "GLOBAL", label: "Global", addChildLabel: "Add Scope", addChildType: "SCOPE" },
{ type: "SCOPE", label: "Scope" },
]}
onSectionSelect={(nodeId, sectionId) => handleSelect(nodeId, sectionId)}
onAddChild={(parentId, childType, parentName) => handleAdd(parentId, childType, parentName)}
onRenameChild={(scopeId, initialName) => handleRename(scopeId, initialName)}
onDeleteChild={(scopeId, scopeName, learningsCount) => handleDelete(scopeId, scopeName, learningsCount)}
showChildrenCount={true}
showSectionBadge={true}
/>
);
}Customization Tips:
- Only pass the action handlers (
onAddChild,onRenameChild,onDeleteChild) your use case needs — unneeded buttons won't render - Use
showChildrenCount={false}to hide the children count badge on node headers - Use
showSectionBadge={false}to hide the metric badge on section items - Use
searchQuerywithonSearchCountChangeto implement a search bar above the tree
API Reference
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| data | ScopeTreeNode \| null | ✅ | — | Root node of the tree. Pass null to render nothing. |
| sectionsConfig | TreeSectionConfig[] | ❌ | [] | Defines section tabs shown under each expanded node. |
| nodeTypesConfig | NodeTypeConfig[] | ❌ | [] | Defines node labels and add-child behavior per depth level. |
| defaultExpandedIds | string[] | ❌ | [] | Node IDs to expand by default on first render. |
| searchQuery | string | ❌ | '' | Filters nodes by name. Non-matching subtrees are hidden. |
| onSearchCountChange | (count: number) => void | ❌ | — | Fired when the search result count changes. |
| onSectionSelect | (nodeId: string, sectionId: string) => void | ❌ | — | Fired when a section tab is clicked. Returns the node ID and section ID. |
| onAddChild | (parentId: string, childType: 'SCOPE' \| 'SUBSCOPE', parentName?: string) => void | ❌ | — | If passed, an Add button appears in the node menu. |
| onRenameChild | (scopeId: string, initialName: string) => void | ❌ | — | If passed, a Rename button appears in the node menu. |
| onDeleteChild | (scopeId: string, scopeName: string, learningsCount: number) => void | ❌ | — | If passed, a Delete button appears in the node menu. |
| selectedRuleSetId | string \| null | ❌ | — | ID of the node to auto-select on load. |
| activeSectionId | string | ❌ | — | Section ID to auto-activate alongside selectedRuleSetId. |
| showChildrenCount | boolean | ❌ | true | Whether to show the children count badge on node headers. |
| showSectionBadge | boolean | ❌ | true | Whether to show the metric badge on section items. |
TreeSectionConfig
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | string | ✅ | Unique section identifier. |
| label | string | ✅ | Display label for the section tab. |
| icon | ElementType | ✅ | Lucide icon component to show next to the label. |
| badgeKey | keyof NodeMetrics | ❌ | Maps to a key in node.metrics to show as a badge count. |
| isVisible | (node: ScopeTreeNode, depth: number) => boolean | ❌ | Optional visibility check per node. If omitted, section is visible for all nodes. |
NodeTypeConfig
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| type | string | ✅ | Node type identifier (e.g. 'GLOBAL', 'SCOPE', 'SUBSCOPE'). |
| label | string | ✅ | Display label used in delete confirmation text. |
| addChildLabel | string | ❌ | Label for the add child button (e.g. 'Add Scope'). Required if addChildType is set. |
| addChildType | string | ❌ | The type of child node to create. If omitted, no add button is shown for this node type. |
ScopeTreeNode
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | string | ✅ | Unique identifier for the node. |
| name | string | ✅ | Display name of the node. |
| type | 'GLOBAL' \| 'SCOPE' \| 'SUBSCOPE' \| 'Custom' | ✅ | Node type. |
| metrics | NodeMetrics | ✅ | Metric values used by section badges. |
| children | ScopeTreeNode[] | ✅ | Child nodes. Pass [] for leaf nodes. |
Type Definitions
export interface TreeSidebarProps {
data: ScopeTreeNode | null;
defaultExpandedIds?: string[];
onSectionSelect?: (nodeId: string, sectionId: string) => void;
onNodeOptionsClick?: (nodeId: string) => void;
onAddChild?: (parentId: string, childType: 'SCOPE' | 'SUBSCOPE', parentName?: string) => void;
onRenameChild?: (scopeId: string, initialName: string) => void;
onDeleteChild?: (scopeId: string, scopeName: string, learningsCount: number) => void;
searchQuery?: string;
onSearchCountChange?: (count: number) => void;
sectionsConfig?: TreeSectionConfig[];
nodeTypesConfig?: NodeTypeConfig[];
selectedRuleSetId?: string | null;
activeSectionId?: string;
showChildrenCount?: boolean;
showSectionBadge?: boolean;
}
export interface TreeSectionConfig {
id: string;
label: string;
icon: any;
badgeKey?: keyof NodeMetrics;
isVisible?: (node: ScopeTreeNode, depth: number) => boolean;
}
export interface NodeTypeConfig {
type: string;
label: string;
addChildLabel?: string;
addChildType?: string;
}
export interface ScopeTreeNode {
id: string;
name: string;
type: 'GLOBAL' | 'SCOPE' | 'SUBSCOPE' | 'Custom';
metrics: NodeMetrics;
children: ScopeTreeNode[];
}
export interface NodeMetrics {
learningsCount: number;
stationsCount?: number;
}Examples
With search
const [searchQuery, setSearchQuery] = useState('');
const [searchCount, setSearchCount] = useState(0);
<>
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search scopes..."
/>
<span>{searchCount} results</span>
<TreeSidebar
data={treeData}
sectionsConfig={sectionsConfig}
nodeTypesConfig={nodeTypesConfig}
searchQuery={searchQuery}
onSearchCountChange={setSearchCount}
/>
</>With only rename and delete (no add)
<TreeSidebar
data={treeData}
sectionsConfig={sectionsConfig}
nodeTypesConfig={nodeTypesConfig}
onRenameChild={(scopeId, name) => handleRename(scopeId, name)}
onDeleteChild={(scopeId, name, count) => handleDelete(scopeId, name, count)}
/>With badges hidden
<TreeSidebar
data={treeData}
sectionsConfig={sectionsConfig}
nodeTypesConfig={nodeTypesConfig}
showChildrenCount={false}
showSectionBadge={false}
/>With auto-selected node
<TreeSidebar
data={treeData}
sectionsConfig={sectionsConfig}
nodeTypesConfig={nodeTypesConfig}
selectedRuleSetId="scope-1"
activeSectionId="learnings"
/>With multiple sections
import { BookOpen, RadioTower } from 'lucide-react';
<TreeSidebar
data={treeData}
sectionsConfig={[
{ id: 'learnings', label: 'Learnings', icon: BookOpen, badgeKey: 'learningsCount' },
{
id: 'stations',
label: 'Stations',
icon: RadioTower,
badgeKey: 'stationsCount',
isVisible: (node, depth) => depth > 0, // Only show for non-root nodes
},
]}
nodeTypesConfig={nodeTypesConfig}
/>Notes
TreeSidebaris display-only — no internal data fetching. All state and data management lives in the consuming app.- Action buttons (Add, Rename, Delete) are conditionally rendered — only shown when the corresponding handler prop is passed.
- The
nodeTypesConfigarray maps to tree depth — index0applies to the root node, index1to its children, and so on. If the tree is deeper than the config array, the last config entry is reused. - Delete is automatically disabled for nodes that have children, with a tooltip explaining why.
- The root node auto-expands on load and whenever
datachanges. - Search expansion is automatic — all ancestor nodes of matching results are expanded.
