mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-10-30 18:22:59 +00:00
381 lines
8.6 KiB
Plaintext
381 lines
8.6 KiB
Plaintext
---
|
|
title: Visual Workflows
|
|
description: Build visual workflows and node-based editors with React Flow
|
|
---
|
|
|
|
## Overview
|
|
|
|
Create interactive node-based editors, workflow builders, and visual diagrams using [React Flow](https://reactflow.dev/). Perfect for building no-code tools, data pipelines, and process automation interfaces.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pnpm add @xyflow/react
|
|
```
|
|
|
|
## Basic Example
|
|
|
|
```tsx
|
|
"use client"
|
|
|
|
import { useCallback } from "react"
|
|
import {
|
|
addEdge,
|
|
Background,
|
|
Controls,
|
|
MiniMap,
|
|
ReactFlow,
|
|
useEdgesState,
|
|
useNodesState,
|
|
} from "@xyflow/react"
|
|
|
|
import "@xyflow/react/dist/style.css"
|
|
|
|
const initialNodes = [
|
|
{ id: "1", position: { x: 0, y: 0 }, data: { label: "Start" } },
|
|
{ id: "2", position: { x: 0, y: 100 }, data: { label: "Process" } },
|
|
{ id: "3", position: { x: 0, y: 200 }, data: { label: "End" } },
|
|
]
|
|
|
|
const initialEdges = [
|
|
{ id: "e1-2", source: "1", target: "2" },
|
|
{ id: "e2-3", source: "2", target: "3" },
|
|
]
|
|
|
|
export function WorkflowBuilder() {
|
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
|
|
|
const onConnect = useCallback(
|
|
(params) => setEdges((eds) => addEdge(params, eds)),
|
|
[setEdges]
|
|
)
|
|
|
|
return (
|
|
<div className="h-[600px] w-full">
|
|
<ReactFlow
|
|
nodes={nodes}
|
|
edges={edges}
|
|
onNodesChange={onNodesChange}
|
|
onEdgesChange={onEdgesChange}
|
|
onConnect={onConnect}
|
|
>
|
|
<Controls />
|
|
<MiniMap />
|
|
<Background variant="dots" gap={12} size={1} />
|
|
</ReactFlow>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Custom Nodes
|
|
|
|
Create custom node types that match your UI design:
|
|
|
|
```tsx
|
|
import { Handle, Position } from '@xyflow/react'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
function CustomNode({ data }) {
|
|
return (
|
|
<Card className="w-64">
|
|
<CardHeader>
|
|
<CardTitle className="text-sm">{data.label}</CardTitle>
|
|
{data.badge && <Badge variant="outline">{data.badge}</Badge>}
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-xs text-muted-foreground">{data.description}</p>
|
|
</CardContent>
|
|
|
|
<Handle type="target" position={Position.Top} />
|
|
<Handle type="source" position={Position.Bottom} />
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
const nodeTypes = {
|
|
custom: CustomNode,
|
|
}
|
|
|
|
// Use in ReactFlow
|
|
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} />
|
|
```
|
|
|
|
## Use Cases
|
|
|
|
### 1. AI Agent Workflows
|
|
|
|
Build visual AI agent orchestration:
|
|
|
|
```tsx
|
|
const aiNodes = [
|
|
{
|
|
id: "input",
|
|
type: "custom",
|
|
position: { x: 0, y: 0 },
|
|
data: { label: "User Input", badge: "Start" },
|
|
},
|
|
{
|
|
id: "llm",
|
|
type: "custom",
|
|
position: { x: 0, y: 100 },
|
|
data: { label: "LLM Agent", description: "Claude 3.5 Sonnet" },
|
|
},
|
|
{
|
|
id: "tool",
|
|
type: "custom",
|
|
position: { x: 200, y: 100 },
|
|
data: { label: "Tool Use", description: "Execute functions" },
|
|
},
|
|
{
|
|
id: "output",
|
|
type: "custom",
|
|
position: { x: 0, y: 200 },
|
|
data: { label: "Response", badge: "End" },
|
|
},
|
|
]
|
|
```
|
|
|
|
### 2. Data Pipeline Builder
|
|
|
|
Create ETL pipelines visually:
|
|
|
|
```tsx
|
|
const pipelineNodes = [
|
|
{ id: "source", data: { label: "Data Source", type: "database" } },
|
|
{ id: "transform", data: { label: "Transform", type: "function" } },
|
|
{ id: "filter", data: { label: "Filter", type: "condition" } },
|
|
{ id: "sink", data: { label: "Data Sink", type: "storage" } },
|
|
]
|
|
```
|
|
|
|
### 3. UI Component Tree
|
|
|
|
Visualize component hierarchies:
|
|
|
|
```tsx
|
|
const componentTree = [
|
|
{ id: "app", data: { label: "App", component: "Layout" } },
|
|
{ id: "header", data: { label: "Header", component: "Navigation" } },
|
|
{ id: "main", data: { label: "Main", component: "Content" } },
|
|
{ id: "footer", data: { label: "Footer", component: "Footer" } },
|
|
]
|
|
```
|
|
|
|
## Styling with Hanzo UI
|
|
|
|
React Flow integrates seamlessly with Hanzo UI theming:
|
|
|
|
```tsx
|
|
import { ReactFlow } from "@xyflow/react"
|
|
|
|
import "@xyflow/react/dist/style.css"
|
|
|
|
export function ThemedFlow() {
|
|
return (
|
|
<div className="h-[600px] rounded-lg border bg-background">
|
|
<ReactFlow
|
|
nodes={nodes}
|
|
edges={edges}
|
|
className="bg-background"
|
|
style={{
|
|
background: "hsl(var(--background))",
|
|
}}
|
|
>
|
|
<Controls className="border-border bg-card" />
|
|
<MiniMap className="border-border bg-card" nodeBorderRadius={8} />
|
|
<Background color="hsl(var(--muted-foreground))" gap={16} />
|
|
</ReactFlow>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Advanced Features
|
|
|
|
### Edge Types
|
|
|
|
Create custom edge connections:
|
|
|
|
```tsx
|
|
import { BaseEdge, EdgeLabelRenderer, getStraightPath } from "@xyflow/react"
|
|
|
|
import { Badge } from "@/components/ui/badge"
|
|
|
|
function CustomEdge({ id, sourceX, sourceY, targetX, targetY, data }) {
|
|
const [edgePath, labelX, labelY] = getStraightPath({
|
|
sourceX,
|
|
sourceY,
|
|
targetX,
|
|
targetY,
|
|
})
|
|
|
|
return (
|
|
<>
|
|
<BaseEdge path={edgePath} />
|
|
<EdgeLabelRenderer>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
}}
|
|
>
|
|
<Badge>{data.label}</Badge>
|
|
</div>
|
|
</EdgeLabelRenderer>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Interactive Controls
|
|
|
|
Add custom controls using Hanzo UI components:
|
|
|
|
```tsx
|
|
import { Controls } from "@xyflow/react"
|
|
import { Maximize, ZoomIn, ZoomOut } from "lucide-react"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
;<Controls>
|
|
<Button size="icon" variant="ghost">
|
|
<ZoomIn className="h-4 w-4" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost">
|
|
<ZoomOut className="h-4 w-4" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost">
|
|
<Maximize className="h-4 w-4" />
|
|
</Button>
|
|
</Controls>
|
|
```
|
|
|
|
### Persistence
|
|
|
|
Save and load workflows:
|
|
|
|
```tsx
|
|
function WorkflowEditor() {
|
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
|
|
|
const saveWorkflow = () => {
|
|
const workflow = { nodes, edges }
|
|
localStorage.setItem("workflow", JSON.stringify(workflow))
|
|
}
|
|
|
|
const loadWorkflow = () => {
|
|
const saved = localStorage.getItem("workflow")
|
|
if (saved) {
|
|
const { nodes, edges } = JSON.parse(saved)
|
|
setNodes(nodes)
|
|
setEdges(edges)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex gap-2">
|
|
<Button onClick={saveWorkflow}>Save</Button>
|
|
<Button onClick={loadWorkflow} variant="outline">
|
|
Load
|
|
</Button>
|
|
</div>
|
|
<ReactFlow nodes={nodes} edges={edges} />
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Integration with Hanzo UI Components
|
|
|
|
### Node Toolbar
|
|
|
|
Add actions to nodes:
|
|
|
|
```tsx
|
|
import { NodeToolbar, Position } from "@xyflow/react"
|
|
import { Copy, Settings, Trash2 } from "lucide-react"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
function NodeWithToolbar({ data }) {
|
|
return (
|
|
<>
|
|
<NodeToolbar position={Position.Top}>
|
|
<div className="flex gap-1">
|
|
<Button size="icon" variant="ghost">
|
|
<Settings className="h-4 w-4" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost">
|
|
<Copy className="h-4 w-4" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost">
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</NodeToolbar>
|
|
<Card>{/* Node content */}</Card>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Context Menu
|
|
|
|
Right-click context menu:
|
|
|
|
```tsx
|
|
import { useReactFlow } from "@xyflow/react"
|
|
|
|
import {
|
|
ContextMenu,
|
|
ContextMenuContent,
|
|
ContextMenuItem,
|
|
ContextMenuTrigger,
|
|
} from "@/components/ui/context-menu"
|
|
|
|
function FlowWithContextMenu() {
|
|
const { addNodes, deleteElements } = useReactFlow()
|
|
|
|
return (
|
|
<ContextMenu>
|
|
<ContextMenuTrigger>
|
|
<ReactFlow nodes={nodes} edges={edges} />
|
|
</ContextMenuTrigger>
|
|
<ContextMenuContent>
|
|
<ContextMenuItem onClick={() => addNodes(newNode)}>
|
|
Add Node
|
|
</ContextMenuItem>
|
|
<ContextMenuItem onClick={() => deleteElements({ nodes: [{ id }] })}>
|
|
Delete
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Resources
|
|
|
|
### dnd-kit
|
|
|
|
- [Documentation](https://docs.dndkit.com/)
|
|
- [Examples](https://docs.dndkit.com/presets/sortable)
|
|
- [Storybook](https://master--5fc05e08a4a65d0021ae0bf2.chromatic.com/)
|
|
|
|
### React Flow
|
|
|
|
- [Documentation](https://reactflow.dev/learn)
|
|
- [Examples](https://reactflow.dev/examples)
|
|
- [API Reference](https://reactflow.dev/api-reference)
|
|
|
|
## See Also
|
|
|
|
- [Blocks](/blocks) - Browse all available blocks
|
|
- [Page Builder](/builder) - Build pages visually
|
|
- [Components](/docs/components) - Individual UI components
|