Skip to main content
The WorkflowBuilder component is the main visual workflow editor powered by React Flow.

Component

function WorkflowBuilder(props: WorkflowBuilderProps): JSX.Element;

Props

nodes
Array<typeof Node>
Array of node classes to make available in the palette.
onWorkflowChange
(workflow: Workflow) => void
Callback when workflow changes (nodes added/removed, connections changed, etc.).
initialWorkflow
Workflow
Optional initial workflow to load.
showNodePalette
boolean
default:"true"
Show/hide the node palette sidebar.
showPropertyPanel
boolean
default:"true"
Show/hide the property panel sidebar.
showToolbar
boolean
default:"true"
Show/hide the toolbar.
showJsonEditor
boolean
default:"false"
Show/hide JSON editor (Monaco).
editorMode
'visual' | 'json' | 'split'
default:"'visual'"
Editor view mode.
onExecute
(result: ExecutionResult) => void
Callback when workflow execution completes.
onError
(error: Error) => void
Callback when an error occurs.
className
string
Additional CSS class for the container.
style
React.CSSProperties
Inline styles for the container.

Basic Usage

import React from 'react';
import { WorkflowBuilder } from '@crystalflow/react';
import { AddNode, MultiplyNode, DisplayNode } from './nodes';

function App() {
  const nodes = [AddNode, MultiplyNode, DisplayNode];

  const handleWorkflowChange = (workflow) => {
    console.log('Workflow changed:', workflow.toJSON());
  };

  const handleExecute = (result) => {
    console.log('Execution result:', result);
  };

  return (
    <WorkflowBuilder
      nodes={nodes}
      onWorkflowChange={handleWorkflowChange}
      onExecute={handleExecute}
    />
  );
}

With Initial Workflow

import { Workflow } from '@crystalflow/core';

const initialWorkflow = new Workflow('my-workflow', 'My Workflow');
// Add nodes and connections...

<WorkflowBuilder
  nodes={[AddNode, MultiplyNode]}
  initialWorkflow={initialWorkflow}
  onWorkflowChange={handleChange}
/>

Custom Configuration

<WorkflowBuilder
  nodes={[AddNode, MultiplyNode]}
  showNodePalette={true}
  showPropertyPanel={true}
  showToolbar={true}
  editorMode="visual"
  className="my-workflow-builder"
  style={{ height: '800px' }}
  onWorkflowChange={handleChange}
  onExecute={handleExecute}
  onError={handleError}
/>

JSON Editor Mode

Work in Progress: Monaco JSON editor integration is currently being implemented.
<WorkflowBuilder
  nodes={[AddNode, MultiplyNode]}
  showJsonEditor={true}
  editorMode="split"  // visual, json, or split
/>

Features

Drag & Drop

Drag nodes from palette to canvas

Visual Connections

Connect nodes by dragging between ports

Property Editing

Edit node properties in the sidebar

Execute Workflows

Run workflows and view results

Save/Load

Import and export workflows as JSON

Undo/Redo

Coming soon

Event Handlers

onWorkflowChange

Called whenever the workflow structure changes:
const handleChange = (workflow: Workflow) => {
  // Save to localStorage
  localStorage.setItem('workflow', JSON.stringify(workflow.toJSON()));
  
  // or send to server
  await saveWorkflow(workflow.toJSON());
};

onExecute

Called when workflow execution completes:
const handleExecute = (result: ExecutionResult) => {
  if (result.status === 'success') {
    console.log('Success! Duration:', result.duration, 'ms');
    
    // Access node results
    result.nodeResults.forEach((nodeResult, nodeId) => {
      console.log(`Node ${nodeId}:`, nodeResult.outputs);
    });
  } else {
    console.error('Execution failed:', result.error);
  }
};

onError

Called when errors occur in the UI:
const handleError = (error: Error) => {
  // Show error notification
  toast.error(error.message);
  
  // Log to error service
  errorLogger.log(error);
};

Styling

The WorkflowBuilder uses CSS modules and can be customized:
/* Your custom styles */
.my-workflow-builder {
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* Override internal styles */
.my-workflow-builder .react-flow__node {
  border: 2px solid #007bff;
}

Complete Example

import React, { useState, useCallback } from 'react';
import { WorkflowBuilder } from '@crystalflow/react';
import { Workflow, ExecutionResult } from '@crystalflow/core';
import {
  NumberInputNode,
  AddNode,
  MultiplyNode,
  DisplayNode
} from './nodes';

function WorkflowApp() {
  const [workflow, setWorkflow] = useState<Workflow | null>(null);
  const [result, setResult] = useState<ExecutionResult | null>(null);

  const nodes = [
    NumberInputNode,
    AddNode,
    MultiplyNode,
    DisplayNode
  ];

  const handleWorkflowChange = useCallback((newWorkflow: Workflow) => {
    setWorkflow(newWorkflow);
    
    // Auto-save to localStorage
    localStorage.setItem(
      'workflow',
      JSON.stringify(newWorkflow.toJSON())
    );
  }, []);

  const handleExecute = useCallback((executionResult: ExecutionResult) => {
    setResult(executionResult);
    
    if (executionResult.status === 'success') {
      console.log('Workflow executed successfully!');
    }
  }, []);

  const handleError = useCallback((error: Error) => {
    console.error('Error:', error);
    alert(`Error: ${error.message}`);
  }, []);

  return (
    <div className="app">
      <h1>My Workflow Editor</h1>
      
      <WorkflowBuilder
        nodes={nodes}
        onWorkflowChange={handleWorkflowChange}
        onExecute={handleExecute}
        onError={handleError}
        showNodePalette={true}
        showPropertyPanel={true}
        showToolbar={true}
        style={{ height: '600px' }}
      />
      
      {result && (
        <div className="results">
          <h2>Execution Result</h2>
          <pre>{JSON.stringify(result, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

export default WorkflowApp;