@bernierllc/generic-workflow-ui
Version:
Generic, reusable workflow UI components with linear and graph visualization
984 lines (806 loc) • 24 kB
Markdown
/*
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.