Styled React Flow - Copy this React, Mui Component to your project
Import React, { useState, useCallback } from "react"; import ReactFlow, { Background, Controls, MiniMap, addEdge, applyEdgeChanges, applyNodeChanges } from "reactflow"; import "reactflow/dist/style.css"; import { Box, Tab, Tabs, TextField, Button, Card, CardContent, Grid, Typography, InputAdornment, ThemeProvider, createTheme, } from "@mui/material"; import { styled } from "@mui/system"; import { FiSearch, FiPlus, FiEdit, FiUpload, FiSave } from "react icons/fi"; const darkTheme = createTheme({ palette: { mode: "dark", }, }); const StyledReactFlow = styled(Box)(({ theme }) => ({ height: "60vh", background: theme.palette.background.paper, border: `1px solid ${theme.palette.divider}`, borderRadius: theme.shape.borderRadius, })); const initialNodes = [ { id: "1", type: "input", data: { label: "Start Node" }, position: { x: 250, y: 5 }, }, ]; const ProcessManager = () => { const [currentTab, setCurrentTab] = useState(0); const [nodes, setNodes] = useState(initialNodes); const [edges, setEdges] = useState([]); const [processName, setProcessName] = useState(""); const [processDescription, setProcessDescription] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const [selectedProcess, setSelectedProcess] = useState(null); const [processes, setProcesses] = useState([ { id: 1, name: "Process A", description: "Description for Process A", updatedAt: "2024 01 20", }, { id: 2, name: "Process B", description: "Description for Process B", updatedAt: "2024 01 19", }, ]); const handleTabChange = (event, newValue) => { setCurrentTab(newValue); }; const onNodesChange = useCallback( (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), [] ); const onEdgesChange = useCallback( (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), [] ); const onConnect = useCallback( (params) => setEdges((eds) => addEdge(params, eds)), [] ); const addNode = () => { const newNode = { id: (nodes.length + 1).toString(), data: { label: `Node ${nodes.length + 1}` }, position: { x: Math.random() * 500, y: Math.random() * 300 }, }; setNodes([...nodes, newNode]); }; const handleSave = () => { if (selectedProcess) { // Update existing process const updatedProcesses = processes.map((proc) => proc.id === selectedProcess.id ? { ...proc, name: processName, description: processDescription, updatedAt: new Date().toISOString().split("T")[0], } : proc ); setProcesses(updatedProcesses); } else { // Create new process const newProcess = { id: processes.length + 1, name: processName, description: processDescription, updatedAt: new Date().toISOString().split("T")[0], }; setProcesses([...processes, newProcess]); } setCurrentTab(1); resetForm(); }; const resetForm = () => { setProcessName(""); setProcessDescription(""); setNodes(initialNodes); setEdges([]); setSelectedProcess(null); }; const handleEdit = (process) => { setSelectedProcess(process); setProcessName(process.name); setProcessDescription(process.description); setCurrentTab(0); }; const handleImport = () => { console.log("Importing process"); }; const CreateTab = () => ( <Box sx={{ p: 2 }}> <Grid container spacing={3}> <Grid item xs={12} md={6}> <TextField fullWidth label="Process Name" value={processName} onChange={(e) => setProcessName(e.target.value)} margin="normal" /> </Grid> <Grid item xs={12} md={6}> <TextField fullWidth label="Description" multiline rows={2} value={processDescription} onChange={(e) => setProcessDescription(e.target.value)} margin="normal" /> </Grid> <Grid item xs={12}> <Button variant="contained" onClick={addNode} sx={{ mb: 2 }}> Add Node </Button> <StyledReactFlow> <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} fitView > <Background /> <Controls /> <MiniMap /> </ReactFlow> </StyledReactFlow> </Grid> <Grid item xs={12}> <Box sx={{ display: "flex", gap: 2, justifyContent: "flex end" }}> <Button variant="outlined" onClick={resetForm}> Cancel </Button> <Button variant="contained" startIcon={<FiSave />} onClick={handleSave} disabled={!processName.trim() || !processDescription.trim()} > {selectedProcess ? "Update Process" : "Save Process"} </Button> </Box> </Grid> </Grid> </Box> ); const ProcessHub = () => ( <Box sx={{ p: 2 }}> <Grid container spacing={3}> <Grid item xs={12}> <Box sx={{ display: "flex", gap: 2, mb: 3 }}> <TextField fullWidth placeholder="Search processes..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} InputProps={{ startAdornment: ( <InputAdornment position="start"> <FiSearch /> </InputAdornment> ), }} /> <Button variant="contained" startIcon={<FiUpload />} onClick={handleImport} > Import </Button> </Box> </Grid> <Grid item xs={12}> <Grid container spacing={2}> {processes .filter( (process) => process.name .toLowerCase() .includes(searchQuery.toLowerCase()) || process.description .toLowerCase() .includes(searchQuery.toLowerCase()) ) .map((process) => ( <Grid item xs={12} sm={6} md={4} key={process.id}> <Card> <CardContent> <Typography variant="h6" gutterBottom> {process.name} </Typography> <Typography variant="body2" color="textSecondary" gutterBottom > {process.description} </Typography> <Typography variant="caption" color="textSecondary"> Last updated: {process.updatedAt} </Typography> <Box sx={{ display: "flex", justifyContent: "flex end", mt: 2, }} > <Button startIcon={<FiEdit />} onClick={() => handleEdit(process)} > Edit </Button> </Box> </CardContent> </Card> </Grid> ))} </Grid> </Grid> </Grid> </Box> ); return ( <ThemeProvider theme={darkTheme}> <Box sx={{ width: "100%", bgcolor: "background.paper" }}> <Tabs value={currentTab} onChange={handleTabChange} centered sx={{ borderBottom: 1, borderColor: "divider" }} > <Tab icon={<FiPlus />} iconPosition="start" label="Create" sx={{ textTransform: "none" }} /> <Tab icon={<FiEdit />} iconPosition="start" label="Process Hub" sx={{ textTransform: "none" }} /> </Tabs> {currentTab === 0 && <CreateTab />} {currentTab === 1 && <ProcessHub />} </Box> </ThemeProvider> ); }; export default ProcessManager;
