Generic, reusable workflow UI components with linear and graph visualization
npm install @bernierllc/generic-workflow-ui/*
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.
*/
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
``bash`
npm install @bernierllc/generic-workflow-ui
This package requires React 19:
`bash`
npm install react@^19.0.0 react-dom@^19.0.0
For advanced graph layout algorithms:
`bash`
npm install dagre elkjs # For automatic graph layout
@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.
- 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
`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 (
currentStageId={status.currentStageId}
config={{ orientation: 'horizontal' }}
/>
);
}
`
`tsx
import { GenericWorkflowCanvas } from '@bernierllc/generic-workflow-ui';
// Use the same workflow from above
function MyGraphWorkflow() {
const [currentWorkflow, setCurrentWorkflow] = useState(workflow);
return (
currentStageId="review"
config={{
minimap: true,
controls: true,
background: 'dots',
fitView: true,
snapToGrid: true
}}
onWorkflowChange={(updated) => setCurrentWorkflow(updated)}
onNodeClick={(stageId) => console.log('Clicked stage:', stageId)}
/>
);
}
`
`tsx
import { GenericWorkflowBuilderV2 } from '@bernierllc/generic-workflow-ui';
function MyWorkflowBuilder() {
return (
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);
}}
/>
);
}
`
#### GenericWorkflowStepper
Visual progress through workflow stages.
`tsx
interface GenericWorkflowStepperProps
workflow: GenericWorkflow
currentStageId: string;
config?: {
orientation?: 'horizontal' | 'vertical';
showDescriptions?: boolean;
showIcons?: boolean;
size?: 'small' | 'medium' | 'large';
};
onStageChange?: (stageId: string) => void;
customStageRenderer?: (
stage: GenericStage
isActive: boolean,
isCompleted: boolean
) => React.ReactNode;
}
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';
};
}
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;
};
}
config={{
showTimestamps: true,
showUsers: true,
maxItems: 5
}}
/>
`
#### GenericWorkflowProgressBar
Progress bar with stage indicators.
`tsx
interface GenericWorkflowProgressBarProps
workflow: GenericWorkflow
currentStageId: string;
config?: {
showStageLabels?: boolean;
showPercentage?: boolean;
height?: number;
color?: string;
};
}
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;
};
}
config={{
size: 'medium',
showText: true,
showTransitions: true
}}
/>
`
#### GenericWorkflowCanvas
Graph-based workflow visualization with drag-and-drop editing.
`tsx
interface GenericWorkflowCanvasProps<
StageMetadata = any,
TransitionMetadata = any
> {
workflow: GenericWorkflow
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
}
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
stage: GenericStage
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
transition: GenericTransition
isAvailable: boolean;
workflowId: string;
}
// Used internally by GenericWorkflowCanvas
// Custom rendering available via canvas config
`
#### 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);
`
#### 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 (
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
/>
);
}
`
#### 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;
}
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;
}
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)}
/>
`
#### GenericWorkflowStageEditor
Stage management interface.
`tsx`
stages={workflow.stages}
onSave={(updatedStage) => console.log('Saved:', updatedStage)}
onDelete={(stageId) => console.log('Deleted:', stageId)}
/>
#### GenericWorkflowTransitionEditor
Transition management interface.
`tsx`
stages={workflow.stages}
onSave={(updatedTransitions) => console.log('Saved:', updatedTransitions)}
/>
#### GenericWorkflowAdminConfig
Complete admin configuration interface.
`tsx`
config={{
showStageEditor: true,
showTransitionEditor: true,
allowCreation: true,
allowDeletion: true
}}
onSave={(updatedWorkflow) => console.log('Saved:', updatedWorkflow)}
/>
NONE - This is a backward-compatible update. All v1.0.2 APIs remain unchanged.
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
`
#### Option 2: Use New Graph-Based Canvas
`typescript
import { GenericWorkflowCanvas } from '@bernierllc/generic-workflow-ui';
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';
initialMode="graph"
config={{ allowModeToggle: true }}
onSave={({ generic, json }) => console.log('Saved:', generic)}
/>
`
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
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)
`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 (
currentStageId="review"
config={{
orientation: 'horizontal',
showDescriptions: true
}}
/>
);
}
`
`tsx
import { GenericWorkflowCanvas } from '@bernierllc/generic-workflow-ui';
function VisualWorkflowEditor() {
const [workflow, setWorkflow] = useState
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 (
$3
`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 (
initialMode="graph"
config={{
allowJSONImport: true,
allowJSONExport: true,
showJSONView: true,
allowModeToggle: true,
canvasConfig: {
minimap: true,
controls: true,
background: 'dots',
snapToGrid: true
}
}}
onSave={handleSave}
/>
);
}
`$3
`tsx
import {
GenericWorkflowCanvas,
workflowToJSON,
jsonToWorkflow,
validateWorkflowJSON
} from '@bernierllc/generic-workflow-ui';function WorkflowWithJSONSupport() {
const [workflow, setWorkflow] = useState(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 (
handleImport(e.target.files[0])} /> workflow={workflow}
config={{ minimap: true, controls: true }}
onWorkflowChange={setWorkflow}
/>
);
}
`Configuration
$3
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 = {
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
}
}
]
};
``- 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
- @bernierllc/content-workflow-ui - Content-specific workflow wrapper
- @bernierllc/workflow-service - Workflow orchestration service
- 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
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.