UNPKG

@bernierllc/generic-workflow-ui

Version:

Generic, reusable workflow UI components with linear and graph visualization

984 lines (806 loc) 24 kB
/* Copyright (c) 2025 Bernier LLC This file is licensed to the client under a limited-use license. The client may use and modify this code *only within the scope of the project it was delivered for*. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC. */ # @bernierllc/generic-workflow-ui Generic, reusable workflow UI components with dual visualization modes: linear (Tamagui-based stepper) and graph (react-flow-based canvas). This package provides domain-agnostic React components for visualizing and managing workflows using TypeScript. **Version**: 1.1.0 ## Installation ```bash npm install @bernierllc/generic-workflow-ui ``` ### Peer Dependencies This package requires React 19: ```bash npm install react@^19.0.0 react-dom@^19.0.0 ``` ### Optional Dependencies For advanced graph layout algorithms: ```bash npm install dagre elkjs # For automatic graph layout ``` ## Overview @bernierllc/generic-workflow-ui provides two complementary visualization modes: 1. **Linear Mode** (Tamagui): Sequential, step-based workflow visualization - Best for: Simple, linear workflows with clear progression - Components: Stepper, Timeline, Progress Bar, Status Indicator 2. **Graph Mode** (react-flow): Complex, graph-based workflow visualization with drag-and-drop - Best for: Branching workflows, complex state machines, visual workflow design - Components: Canvas, Node Editor, Edge Editor, Graph Builder Both modes work with the same underlying `GenericWorkflow` data structure, allowing seamless conversion between visualization styles. ## Features - **Dual Visualization**: Linear stepper AND graph-based canvas - **Generic TypeScript Types**: Works with any workflow domain - **n8n-Style JSON**: Compatible with n8n workflow definitions - **React 19 Support**: Built for the latest React version - **Tamagui & react-flow**: Combines the best of both libraries - **Drag-and-Drop**: Visual workflow editing in graph mode - **JSON Import/Export**: Serialize workflows to/from JSON - **Admin Components**: Stage and transition editors - **Dark/Light Themes**: Full theme support - **Accessibility**: WCAG 2.1 AA compliant - **Test Coverage**: 77% coverage with comprehensive tests ## Quick Start ### Linear Mode (Tamagui Stepper) ```tsx import { GenericWorkflowStepper, GenericActionButtons, GenericWorkflow, GenericWorkflowStatus, GenericActionButton } from '@bernierllc/generic-workflow-ui'; // Define your workflow const workflow: GenericWorkflow = { id: 'my-workflow', name: 'My Workflow', stages: [ { id: 'draft', name: 'Draft', order: 0 }, { id: 'review', name: 'Review', order: 1 }, { id: 'published', name: 'Published', order: 2 } ], transitions: [ { id: 't1', from: 'draft', to: 'review' }, { id: 't2', from: 'review', to: 'published' } ] }; // Define workflow status const status: GenericWorkflowStatus = { workflowId: 'my-workflow', currentStageId: 'review', availableTransitions: ['t2'] }; // Use the linear stepper function MyLinearWorkflow() { return ( <GenericWorkflowStepper workflow={workflow} currentStageId={status.currentStageId} config={{ orientation: 'horizontal' }} /> ); } ``` ### Graph Mode (react-flow Canvas) ```tsx import { GenericWorkflowCanvas } from '@bernierllc/generic-workflow-ui'; // Use the same workflow from above function MyGraphWorkflow() { const [currentWorkflow, setCurrentWorkflow] = useState(workflow); return ( <GenericWorkflowCanvas workflow={currentWorkflow} currentStageId="review" config={{ minimap: true, controls: true, background: 'dots', fitView: true, snapToGrid: true }} onWorkflowChange={(updated) => setCurrentWorkflow(updated)} onNodeClick={(stageId) => console.log('Clicked stage:', stageId)} /> ); } ``` ### Enhanced Builder with Mode Toggle ```tsx import { GenericWorkflowBuilderV2 } from '@bernierllc/generic-workflow-ui'; function MyWorkflowBuilder() { return ( <GenericWorkflowBuilderV2 initialWorkflow={workflow} initialMode="graph" config={{ allowJSONImport: true, allowJSONExport: true, showJSONView: true, allowModeToggle: true // Toggle between linear and graph }} onSave={({ generic, json }) => { console.log('Saved workflow:', generic); console.log('JSON representation:', json); }} /> ); } ``` ## API Reference ### Linear Components (Tamagui) #### GenericWorkflowStepper Visual progress through workflow stages. ```tsx interface GenericWorkflowStepperProps<StageMetadata = any> { workflow: GenericWorkflow<StageMetadata>; currentStageId: string; config?: { orientation?: 'horizontal' | 'vertical'; showDescriptions?: boolean; showIcons?: boolean; size?: 'small' | 'medium' | 'large'; }; onStageChange?: (stageId: string) => void; customStageRenderer?: ( stage: GenericStage<StageMetadata>, isActive: boolean, isCompleted: boolean ) => React.ReactNode; } <GenericWorkflowStepper workflow={workflow} currentStageId="review" config={{ orientation: 'horizontal', showDescriptions: true, showIcons: true }} onStageChange={(stageId) => console.log('Changed to:', stageId)} /> ``` #### GenericActionButtons Context-aware action buttons. ```tsx interface GenericActionButtonsProps { workflowStatus: GenericWorkflowStatus; actions: GenericActionButton[]; config?: { size?: 'small' | 'medium' | 'large'; variant?: 'primary' | 'secondary' | 'outline'; showIcons?: boolean; layout?: 'horizontal' | 'vertical'; }; } <GenericActionButtons workflowStatus={status} actions={[ { id: 'approve', label: 'Approve', visible: true, onClick: () => handleApprove() } ]} config={{ size: 'medium', variant: 'primary' }} /> ``` #### GenericWorkflowTimeline Historical workflow activity timeline. ```tsx interface GenericWorkflowTimelineProps { items: GenericTimelineItem[]; config?: { showTimestamps?: boolean; showUsers?: boolean; maxItems?: number; dateFormat?: string; }; } <GenericWorkflowTimeline items={timelineItems} config={{ showTimestamps: true, showUsers: true, maxItems: 5 }} /> ``` #### GenericWorkflowProgressBar Progress bar with stage indicators. ```tsx interface GenericWorkflowProgressBarProps<StageMetadata = any> { workflow: GenericWorkflow<StageMetadata>; currentStageId: string; config?: { showStageLabels?: boolean; showPercentage?: boolean; height?: number; color?: string; }; } <GenericWorkflowProgressBar workflow={workflow} currentStageId="review" config={{ showStageLabels: true, showPercentage: true }} /> ``` #### GenericWorkflowStatusIndicator Compact workflow status display. ```tsx interface GenericWorkflowStatusIndicatorProps { status: GenericWorkflowStatus; config?: { size?: 'small' | 'medium' | 'large'; showText?: boolean; showTransitions?: boolean; }; } <GenericWorkflowStatusIndicator status={status} config={{ size: 'medium', showText: true, showTransitions: true }} /> ``` ### Graph Components (react-flow) #### GenericWorkflowCanvas Graph-based workflow visualization with drag-and-drop editing. ```tsx interface GenericWorkflowCanvasProps< StageMetadata = any, TransitionMetadata = any > { workflow: GenericWorkflow<StageMetadata, TransitionMetadata>; currentStageId?: string; config?: CanvasConfig; onWorkflowChange?: (workflow: GenericWorkflow) => void; onNodeClick?: (stageId: string) => void; onEdgeClick?: (transitionId: string) => void; readOnly?: boolean; } interface CanvasConfig { nodeStyle?: { width?: number; height?: number; borderRadius?: number; backgroundColor?: string; }; edgeStyle?: { strokeWidth?: number; strokeColor?: string; animated?: boolean; }; minimap?: boolean; // Show minimap navigation controls?: boolean; // Show zoom/pan controls background?: 'dots' | 'lines' | 'cross' | 'none'; fitView?: boolean; // Auto-fit workflow to viewport snapToGrid?: boolean; // Snap nodes to grid gridSize?: number; // Grid size in pixels } <GenericWorkflowCanvas workflow={workflow} currentStageId="review" config={{ minimap: true, controls: true, background: 'dots', fitView: true, snapToGrid: true, gridSize: 15 }} onWorkflowChange={(updated) => setWorkflow(updated)} onNodeClick={(stageId) => console.log('Clicked:', stageId)} readOnly={false} /> ``` #### GenericWorkflowNode Custom react-flow node component for workflow stages. ```tsx interface GenericNodeData<StageMetadata = any> { stage: GenericStage<StageMetadata>; isActive: boolean; isCompleted: boolean; workflowId: string; } // Used internally by GenericWorkflowCanvas // Custom rendering available via canvas config ``` #### GenericWorkflowEdge Custom react-flow edge component for workflow transitions. ```tsx interface GenericEdgeData<TransitionMetadata = any> { transition: GenericTransition<TransitionMetadata>; isAvailable: boolean; workflowId: string; } // Used internally by GenericWorkflowCanvas // Custom rendering available via canvas config ``` ### Workflow Converters #### linearToGraph Converts linear workflow to graph representation with auto-layout. ```tsx interface WorkflowLayoutOptions { algorithm: 'dagre' | 'elk' | 'manual'; direction: 'TB' | 'LR' | 'BT' | 'RL'; // Top-Bottom, Left-Right, etc. nodeSpacing: number; rankSpacing: number; } function linearToGraph( workflow: GenericWorkflow, layoutOptions?: WorkflowLayoutOptions ): ReactFlowWorkflow; // Example usage import { linearToGraph } from '@bernierllc/generic-workflow-ui'; const graphWorkflow = linearToGraph(workflow, { algorithm: 'dagre', direction: 'TB', nodeSpacing: 50, rankSpacing: 100 }); ``` #### graphToLinear Converts graph workflow back to linear representation. ```tsx function graphToLinear( reactFlowWorkflow: ReactFlowWorkflow ): GenericWorkflow; // Example usage import { graphToLinear } from '@bernierllc/generic-workflow-ui'; const linearWorkflow = graphToLinear(graphWorkflow); ``` ### Hooks #### useWorkflowCanvas Canvas state management hook. ```tsx function useWorkflowCanvas( initialWorkflow: GenericWorkflow, config?: CanvasConfig ): { nodes: Node[]; edges: Edge[]; onNodesChange: (changes: NodeChange[]) => void; onEdgesChange: (changes: EdgeChange[]) => void; onConnect: (connection: Connection) => void; workflow: GenericWorkflow; updateWorkflow: (workflow: GenericWorkflow) => void; }; // Example usage import { useWorkflowCanvas } from '@bernierllc/generic-workflow-ui'; function MyCanvas() { const { nodes, edges, onNodesChange, onEdgesChange, onConnect, workflow } = useWorkflowCanvas(initialWorkflow, { fitView: true, snapToGrid: true }); return ( <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} /> ); } ``` ### Builder Components #### GenericWorkflowBuilder (v1.0.2 - Linear Only) Visual workflow builder with JSON import/export. ```tsx interface GenericWorkflowBuilderProps { initialWorkflow?: GenericWorkflow; config?: { allowJSONImport?: boolean; allowJSONExport?: boolean; showJSONView?: boolean; }; onSave?: (result: { generic: GenericWorkflow; json: WorkflowJSONDefinition; }) => void; } <GenericWorkflowBuilder initialWorkflow={workflow} config={{ allowJSONImport: true, allowJSONExport: true, showJSONView: true }} onSave={({ generic, json }) => { console.log('Generic workflow:', generic); console.log('JSON workflow:', json); }} /> ``` #### GenericWorkflowBuilderV2 (v1.1.0 - Dual Mode) Enhanced builder with linear/graph mode toggle. ```tsx interface GenericWorkflowBuilderV2Props { initialWorkflow?: GenericWorkflow; initialMode?: 'linear' | 'graph'; config?: { allowJSONImport?: boolean; allowJSONExport?: boolean; showJSONView?: boolean; allowModeToggle?: boolean; // NEW: Toggle between modes canvasConfig?: CanvasConfig; // NEW: Graph canvas settings }; onSave?: (result: { generic: GenericWorkflow; json: WorkflowJSONDefinition; }) => void; } <GenericWorkflowBuilderV2 initialWorkflow={workflow} initialMode="graph" config={{ allowJSONImport: true, allowJSONExport: true, showJSONView: true, allowModeToggle: true, canvasConfig: { minimap: true, controls: true, background: 'dots' } }} onSave={({ generic, json }) => console.log('Saved:', generic)} /> ``` ### Admin Components #### GenericWorkflowStageEditor Stage management interface. ```tsx <GenericWorkflowStageEditor stage={stage} stages={workflow.stages} onSave={(updatedStage) => console.log('Saved:', updatedStage)} onDelete={(stageId) => console.log('Deleted:', stageId)} /> ``` #### GenericWorkflowTransitionEditor Transition management interface. ```tsx <GenericWorkflowTransitionEditor transitions={workflow.transitions} stages={workflow.stages} onSave={(updatedTransitions) => console.log('Saved:', updatedTransitions)} /> ``` #### GenericWorkflowAdminConfig Complete admin configuration interface. ```tsx <GenericWorkflowAdminConfig workflow={workflow} config={{ showStageEditor: true, showTransitionEditor: true, allowCreation: true, allowDeletion: true }} onSave={(updatedWorkflow) => console.log('Saved:', updatedWorkflow)} /> ``` ## Migration Guide (v1.0.2 → v1.1.0) ### Breaking Changes **NONE** - This is a backward-compatible update. All v1.0.2 APIs remain unchanged. ### New Features Available Consumers can opt-in to new react-flow features: #### Option 1: Continue Using Existing Linear Components (No Changes) ```typescript import { GenericWorkflowStepper } from '@bernierllc/generic-workflow-ui'; // Your existing code works exactly as before <GenericWorkflowStepper workflow={workflow} currentStageId="review" /> ``` #### Option 2: Use New Graph-Based Canvas ```typescript import { GenericWorkflowCanvas } from '@bernierllc/generic-workflow-ui'; <GenericWorkflowCanvas workflow={workflow} currentStageId="review" config={{ minimap: true, controls: true }} onWorkflowChange={(updated) => setWorkflow(updated)} /> ``` #### Option 3: Use Enhanced Builder with Mode Toggle ```typescript import { GenericWorkflowBuilderV2 } from '@bernierllc/generic-workflow-ui'; <GenericWorkflowBuilderV2 initialWorkflow={workflow} initialMode="graph" config={{ allowModeToggle: true }} onSave={({ generic, json }) => console.log('Saved:', generic)} /> ``` ### Recommended Upgrade Path 1. **Update package version**: ```bash npm install @bernierllc/generic-workflow-ui@^1.1.0 ``` 2. **Test existing functionality**: Run existing tests to ensure backward compatibility 3. **Opt-in to new features**: Gradually adopt react-flow components where graph visualization is beneficial 4. **Update documentation**: Document which workflows use linear vs graph visualization ### When to Use Linear vs Graph **Use Linear Mode** when: - Workflow is sequential with clear progression - Users need simple, easy-to-understand visualization - Mobile-first design is required - Workflow has few branches or decision points **Use Graph Mode** when: - Workflow has complex branching or parallel paths - Visual workflow design is required (drag-and-drop editing) - Users need to see the entire workflow structure at once - Advanced workflow analysis is needed (cycle detection, path finding) ## Examples ### Example 1: Linear Workflow Visualization ```tsx import { GenericWorkflowStepper } from '@bernierllc/generic-workflow-ui'; function ContentApprovalWorkflow() { const workflow: GenericWorkflow = { id: 'content-approval', name: 'Content Approval', stages: [ { id: 'draft', name: 'Draft', order: 0 }, { id: 'review', name: 'Review', order: 1 }, { id: 'approved', name: 'Approved', order: 2 }, { id: 'published', name: 'Published', order: 3 } ], transitions: [ { id: 't1', from: 'draft', to: 'review' }, { id: 't2', from: 'review', to: 'approved' }, { id: 't3', from: 'approved', to: 'published' } ] }; return ( <GenericWorkflowStepper workflow={workflow} currentStageId="review" config={{ orientation: 'horizontal', showDescriptions: true }} /> ); } ``` ### Example 2: Graph Workflow Visualization with Drag-and-Drop ```tsx import { GenericWorkflowCanvas } from '@bernierllc/generic-workflow-ui'; function VisualWorkflowEditor() { const [workflow, setWorkflow] = useState<GenericWorkflow>({ id: 'order-processing', name: 'Order Processing', stages: [ { id: 'order-received', name: 'Order Received', order: 0 }, { id: 'payment', name: 'Payment', order: 1 }, { id: 'fulfillment', name: 'Fulfillment', order: 2 }, { id: 'shipped', name: 'Shipped', order: 3 } ], transitions: [ { id: 't1', from: 'order-received', to: 'payment' }, { id: 't2', from: 'payment', to: 'fulfillment' }, { id: 't3', from: 'fulfillment', to: 'shipped' } ] }); return ( <div style={{ width: '100%', height: '600px' }}> <GenericWorkflowCanvas workflow={workflow} currentStageId="payment" config={{ minimap: true, controls: true, background: 'dots', fitView: true, snapToGrid: true, gridSize: 15 }} onWorkflowChange={(updated) => { setWorkflow(updated); console.log('Workflow updated:', updated); }} onNodeClick={(stageId) => console.log('Stage clicked:', stageId)} onEdgeClick={(transitionId) => console.log('Transition clicked:', transitionId)} readOnly={false} /> </div> ); } ``` ### Example 3: Mode Toggle in Builder ```tsx import { GenericWorkflowBuilderV2 } from '@bernierllc/generic-workflow-ui'; function WorkflowDesigner() { const handleSave = ({ generic, json }) => { // Save to backend fetch('/api/workflows', { method: 'POST', body: JSON.stringify({ workflow: generic, json }) }); }; return ( <GenericWorkflowBuilderV2 initialMode="graph" config={{ allowJSONImport: true, allowJSONExport: true, showJSONView: true, allowModeToggle: true, canvasConfig: { minimap: true, controls: true, background: 'dots', snapToGrid: true } }} onSave={handleSave} /> ); } ``` ### Example 4: JSON Import/Export in Graph Mode ```tsx import { GenericWorkflowCanvas, workflowToJSON, jsonToWorkflow, validateWorkflowJSON } from '@bernierllc/generic-workflow-ui'; function WorkflowWithJSONSupport() { const [workflow, setWorkflow] = useState<GenericWorkflow>(initialWorkflow); const handleExport = () => { const json = workflowToJSON(workflow); const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'workflow.json'; a.click(); }; const handleImport = async (file: File) => { const text = await file.text(); const json = JSON.parse(text); const validation = validateWorkflowJSON(json); if (!validation.valid) { alert('Invalid workflow JSON: ' + validation.errors.join(', ')); return; } const importedWorkflow = jsonToWorkflow(json); setWorkflow(importedWorkflow); }; return ( <div> <button onClick={handleExport}>Export Workflow</button> <input type="file" onChange={(e) => handleImport(e.target.files[0])} /> <GenericWorkflowCanvas workflow={workflow} config={{ minimap: true, controls: true }} onWorkflowChange={setWorkflow} /> </div> ); } ``` ## Configuration ### CanvasConfig Options Complete configuration options for graph-based canvas: ```typescript interface CanvasConfig { // Node styling nodeStyle?: { width?: number; // Default: 150 height?: number; // Default: 80 borderRadius?: number; // Default: 8 backgroundColor?: string; // Default: from theme borderColor?: string; // Default: from theme fontSize?: number; // Default: 14 }; // Edge styling edgeStyle?: { strokeWidth?: number; // Default: 2 strokeColor?: string; // Default: from theme animated?: boolean; // Default: false type?: 'default' | 'straight' | 'step' | 'smoothstep'; }; // Canvas features minimap?: boolean; // Default: false controls?: boolean; // Default: true background?: 'dots' | 'lines' | 'cross' | 'none'; // Default: 'dots' // Layout options fitView?: boolean; // Default: true snapToGrid?: boolean; // Default: false gridSize?: number; // Default: 15 // Interaction readOnly?: boolean; // Default: false nodesDraggable?: boolean; // Default: true nodesConnectable?: boolean; // Default: true elementsSelectable?: boolean; // Default: true } ``` ## Testing The package includes comprehensive test coverage: - **Coverage**: 77% (statements, branches, lines, functions) - **Test Types**: Unit tests, integration tests, component tests - **Test Framework**: Jest with React Testing Library - **Test Files**: 15 test suites, 166 tests total Run tests: ```bash npm test # Watch mode npm run test:run # Single run npm run test:coverage # With coverage report ``` ## TypeScript Support Full generic type support for custom metadata: ```tsx interface MyStageMetadata { assignee?: string; dueDate?: Date; priority?: 'low' | 'medium' | 'high'; } interface MyTransitionMetadata { permissions: string[]; requiresApproval?: boolean; } const workflow: GenericWorkflow<MyStageMetadata, MyTransitionMetadata> = { id: 'my-workflow', name: 'My Workflow', stages: [ { id: 'review', name: 'Review', order: 0, metadata: { assignee: 'user@example.com', priority: 'high' } } ], transitions: [ { id: 't1', from: 'draft', to: 'review', metadata: { permissions: ['editor'], requiresApproval: true } } ] }; ``` ## Integration Status - **Logger**: not-applicable - Pure UI package with no backend operations - **Docs-Suite**: ready - Full TypeDoc documentation available - **NeverHub**: not-applicable - UI components do not require service discovery ## See Also - [@bernierllc/content-workflow-ui](../content-workflow-ui) - Content-specific workflow wrapper - [@bernierllc/workflow-service](../../service/workflow-service) - Workflow orchestration service ## Package Information - **Type**: UI Package - **Category**: Generic Workflow UI - **Version**: 1.1.0 - **Test Coverage**: 77% - **React Version**: 19.0.0 - **Tamagui Version**: 1.135.1 - **react-flow Version**: @xyflow/react 12.3.5 ## License Copyright (c) 2025 Bernier LLC. All rights reserved. This file is licensed to the client under a limited-use license. The client may use and modify this code *only within the scope of the project it was delivered for*. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.