npm.io
0.0.62 • Published 1 week ago

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

Licence
MIT
Version
0.0.62
Deps
12
Size
9.4 MB
Vulns
1
Weekly
0

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.
searchSections boolean false Whether search query also matches section item labels alongside node names.
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.
isDraftMode boolean false Whether to display the draft status chip on section items when drafts are available.
enableEllipsis boolean false Enables text truncation with ellipsis for long section labels.
draftChipClassName string — Custom CSS class name to style the draft chip element.
draftChipDotClassName string — Custom CSS class name to style the indicator dot inside the draft chip.
draftChipStyle React.CSSProperties — Inline style overrides for the draft chip element.
showChildrenCount boolean true Whether to show the children count badge on parent node headers (including top assistant name node).
showSectionBadge boolean true Whether to show the metric badge on section items.
showNodeOptions boolean true Whether to show the 3-dot menu options on node headers.
hideEmptyNodes boolean false If true, nodes with no children and no sections are hidden.
showEmptyState boolean false Whether to show an empty state UI when an expanded node has no children/sections.
emptyStateText string 'No data present' Custom text to display in the empty state UI when showEmptyState is true.

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;
  isDraftMode?: boolean;
  enableEllipsis?: boolean;
  draftChipClassName?: string;
  draftChipDotClassName?: string;
  draftChipStyle?: React.CSSProperties;
}

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

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.

Assign Reminder & Approval Manager

A configurable modal workflow for assigning users to approval levels, managing due dates and reminders, and tracking review status.

Features

  • Multi-tab interface (Assignees, Reminders, Review Status)
  • Multi-level approval workflows
  • Controlled and uncontrolled state support
  • Async user search and assignment
  • Due date management
  • Final approval workflow support
  • Visual approval progress tracking
  • Customizable metadata, tabs, and priority badges
  • Reusable UI components (Dropdowns, Date Pickers, Checkboxes)

Installation

npm install @e-llm-studio/creative-workspace-ui-component

Public exports

import {
  AssignReminderWrapper,
  AssignReminderLevels,
} from "@e-llm-studio/creative-workspace-ui-component";
  • AssignReminderWrapper provides the complete modal, tabs, validation, reminders, review status, and footer actions.
  • AssignReminderLevels provides the controlled approval-level editor for applications that supply their own surrounding UI.

Internal reminder components, utilities, constants, and AssignReminders types are not currently exported from the package root.

Props

Prop Type Description
metadata MetaData Customizes modal title, priority badge, tabs, and display settings.
reviewData Record<number, Record<string, boolean>> Approval status mapping used by Review Status.
levels Level[] Approval workflow levels.
setLevels Dispatch Controlled state setter for levels.
requireFinalApproval boolean Enables final approval step.
finalApprovalStatus boolean Status for final approval.
setRequireFinalApproval Dispatch Controlled setter for final approval state.
approvalDueDate Date | null Final approval due date.
setApprovalDueDate Dispatch Controlled setter for approval due date.
fetchUsers FetchUsersFunction Async user lookup function.
finalApprovalDueDate Date | null Required by AssignReminderLevels. Sets the final approval deadline used to constrain level due dates.
onClose () => void Called when modal closes.
onSave (payload) => void Returns configured workflow data.
onValidationError (message) => void Handles validation errors.

Core Types

MetaData
interface MetaData {
  wrapperTitle?: string;
  wrapperSubTitle?: string;
  wrapperPriority?: string;
  wrapperPriorityStyles?: React.CSSProperties;
  tabData?: TabData[];
  showFinalApprovalDueDate?: boolean;
}
Level
interface Level {
  headerName: string;
  title: string;
  isMandatory: boolean;
  hasDueDate: boolean;
  dueDate: Date | null;
  assignees: AssigneeUser[];
}
AssigneeUser
interface AssigneeUser {
  id: any;
  name: string;
  email: string;
  accessLevel?: string;
}
SavePayload
interface SavePayload {
  levels: Level[];
  requireFinalApproval: boolean;
  finalApprovalDueDate: Date | null;
  activeTab: "assignees" | "reminders" | "review";
}

Included Components

AssignReminderLevels

Manages approval levels, assignees, due dates, and final approval configuration.

ReviewStatus

Displays approval progress and assignee review status using reviewData.

LevelData

Handles assignee search, selection, and access-level assignment.

UI Components
  • CustomDropdown
  • CustomDatePicker
  • AssignLevelDatePicker
  • CustomCheckbox

Basic usage

import { useState } from "react";
import {
  AssignReminderWrapper,
} from "@e-llm-studio/creative-workspace-ui-component";

export function ContractReview() {
  const [open, setOpen] = useState(false);

  const fetchUsers = async (query: string) => {
    const response = await fetch(
      `/api/users?query=${encodeURIComponent(query)}`,
    );
    return response.json();
  };

  return (
    <>
      <button onClick={() => setOpen(true)}>Assign reviewers</button>

      {open && (
        <AssignReminderWrapper
          fetchUsers={fetchUsers}
          requireFinalApproval
          finalApprovalStatus="approved"
          metadata={{
            wrapperTitle: "Contract Review",
            wrapperPriority: "High",
          }}
          onClose={() => setOpen(false)}
          onValidationError={(message) => console.error(message)}
          onSave={(payload) => {
            console.log(payload);
            setOpen(false);
          }}
        />
      )}
    </>
  );
}

fetchUsers is required and must return users containing id, name, and email. The wrapper may be used with its local state or connected to parent state through levels, requireFinalApproval, approvalDueDate, and their setter props.

Key capabilities

  • Debounced asynchronous assignee search
  • Sequential approval levels and access roles
  • Per-level and final approval due-date validation
  • Optional owner final sign-off
  • Dedicated final approval status
  • Manual, scheduled, and escalation reminders
  • Approval and rejection progress tracking
  • Configurable tabs, labels, colors, visibility, and inline style overrides
  • Editing restrictions for read-only or partially locked workflows

On a valid save, onSave receives the normalized visible levels, final approval setting, final due date, active tab, schedules, and reminder owners. The host application remains responsible for persistence and closing the modal.

For the complete props, validation rules, reminder configuration, styling slots, examples, and type reference, see the AssignReminders component guide.

Keywords