@e-llm-studio/creative-workspace-ui-component
v0.0.45
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
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. |
| 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
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.
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-componentPublic exports
import {
AssignReminderWrapper,
AssignReminderLevels,
} from "@e-llm-studio/creative-workspace-ui-component";AssignReminderWrapperprovides the complete modal, tabs, validation, reminders, review status, and footer actions.AssignReminderLevelsprovides 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
CustomDropdownCustomDatePickerAssignLevelDatePickerCustomCheckbox
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.
