npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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 searchQuery to 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-component

Using yarn

yarn add @e-llm-studio/creative-workspace-ui-component

Import 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 — pass null to render nothing
  • sectionsConfig: Defines which section tabs appear under each expanded node
  • nodeTypesConfig: Defines node labels and add-child behavior per depth level
  • onSectionSelect: Fired when a section tab is clicked — returns the selected nodeId and sectionId

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 searchQuery with onSearchCountChange to 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

  • TreeSidebar is 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 nodeTypesConfig array maps to tree depth — index 0 applies to the root node, index 1 to 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 data changes.
  • Search expansion is automatic — all ancestor nodes of matching results are expanded.