@bernierllc/generic-workflow-ui
v1.1.0
Published
Generic, reusable workflow UI components with linear and graph visualization
Readme
/* 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
npm install @bernierllc/generic-workflow-uiPeer Dependencies
This package requires React 19:
npm install react@^19.0.0 react-dom@^19.0.0Optional Dependencies
For advanced graph layout algorithms:
npm install dagre elkjs # For automatic graph layoutOverview
@bernierllc/generic-workflow-ui provides two complementary visualization modes:
Linear Mode (Tamagui): Sequential, step-based workflow visualization
- Best for: Simple, linear workflows with clear progression
- Components: Stepper, Timeline, Progress Bar, Status Indicator
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)
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)
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
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.
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.
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.
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.
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.
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.
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.
interface GenericNodeData<StageMetadata = any> {
stage: GenericStage<StageMetadata>;
isActive: boolean;
isCompleted: boolean;
workflowId: string;
}
// Used internally by GenericWorkflowCanvas
// Custom rendering available via canvas configGenericWorkflowEdge
Custom react-flow edge component for workflow transitions.
interface GenericEdgeData<TransitionMetadata = any> {
transition: GenericTransition<TransitionMetadata>;
isAvailable: boolean;
workflowId: string;
}
// Used internally by GenericWorkflowCanvas
// Custom rendering available via canvas configWorkflow Converters
linearToGraph
Converts linear workflow to graph representation with auto-layout.
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.
function graphToLinear(
reactFlowWorkflow: ReactFlowWorkflow
): GenericWorkflow;
// Example usage
import { graphToLinear } from '@bernierllc/generic-workflow-ui';
const linearWorkflow = graphToLinear(graphWorkflow);Hooks
useWorkflowCanvas
Canvas state management hook.
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.
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.
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.
<GenericWorkflowStageEditor
stage={stage}
stages={workflow.stages}
onSave={(updatedStage) => console.log('Saved:', updatedStage)}
onDelete={(stageId) => console.log('Deleted:', stageId)}
/>GenericWorkflowTransitionEditor
Transition management interface.
<GenericWorkflowTransitionEditor
transitions={workflow.transitions}
stages={workflow.stages}
onSave={(updatedTransitions) => console.log('Saved:', updatedTransitions)}
/>GenericWorkflowAdminConfig
Complete admin configuration interface.
<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)
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
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
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
Update package version:
npm install @bernierllc/generic-workflow-ui@^1.1.0Test existing functionality: Run existing tests to ensure backward compatibility
Opt-in to new features: Gradually adopt react-flow components where graph visualization is beneficial
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
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
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
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
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:
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:
npm test # Watch mode
npm run test:run # Single run
npm run test:coverage # With coverage reportTypeScript Support
Full generic type support for custom metadata:
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: '[email protected]',
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-specific workflow wrapper
- @bernierllc/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.
