sample-ui-component-library 0.0.38-dev → 0.0.39-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/BehavioralGraphBuilder/BehavioralGraphBuilder.jsx +159 -0
- package/src/components/BehavioralGraphBuilder/BehavioralGraphBuilder.scss +0 -0
- package/src/components/BehavioralGraphBuilder/index.js +1 -0
- package/src/index.js +2 -1
- package/src/stories/BehavioralGraphBuilder.stories.js +42 -0
- package/src/stories/BehavioralGraphBuilderStories.scss +17 -0
- package/src/stories/components/ToolBar/ToolBar.js +57 -0
- package/src/stories/components/ToolBar/ToolBar.scss +29 -0
package/package.json
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ReactFlow,
|
|
4
|
+
ReactFlowProvider,
|
|
5
|
+
useNodesState,
|
|
6
|
+
useEdgesState,
|
|
7
|
+
Background,
|
|
8
|
+
Controls,
|
|
9
|
+
useReactFlow,
|
|
10
|
+
addEdge,
|
|
11
|
+
applyNodeChanges,
|
|
12
|
+
applyEdgeChanges
|
|
13
|
+
} from "@xyflow/react";
|
|
14
|
+
import "@xyflow/react/dist/style.css";
|
|
15
|
+
|
|
16
|
+
const initialNodes = [
|
|
17
|
+
{
|
|
18
|
+
id: "1",
|
|
19
|
+
position: { x: 250, y: 150 },
|
|
20
|
+
data: { label: "Behavior 1" },
|
|
21
|
+
draggable: true,
|
|
22
|
+
selectable: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "2",
|
|
26
|
+
position: { x: 250, y: 250 },
|
|
27
|
+
data: { label: "Behavior 2" },
|
|
28
|
+
draggable: true,
|
|
29
|
+
selectable: true,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const NODE_WIDTH = 150;
|
|
34
|
+
const NODE_HEIGHT = 40;
|
|
35
|
+
const Flow = ({ activeTool }) => {
|
|
36
|
+
const { screenToFlowPosition } = useReactFlow();
|
|
37
|
+
const [nodes, setNodes] = useState(initialNodes);
|
|
38
|
+
const [edges, setEdges] = useState([]);
|
|
39
|
+
const [ghostNode, setGhostNode] = useState(null);
|
|
40
|
+
|
|
41
|
+
// Callbacks to apply node changes
|
|
42
|
+
const onNodesChange = useCallback((changes) => {
|
|
43
|
+
setNodes((nds) => applyNodeChanges(changes, nds));
|
|
44
|
+
}, []);
|
|
45
|
+
const onEdgesChange = useCallback((changes) => {
|
|
46
|
+
setEdges((eds) => applyEdgeChanges(changes, eds));
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Console messages for debugging
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
console.log("Active Tool:", activeTool);
|
|
52
|
+
}, [activeTool]);
|
|
53
|
+
|
|
54
|
+
// Callback for mouse pane move
|
|
55
|
+
const onPaneMouseMove = useCallback( (event) => {
|
|
56
|
+
const pos = screenToFlowPosition({
|
|
57
|
+
x: event.clientX,
|
|
58
|
+
y: event.clientY,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (activeTool === "drop") {
|
|
62
|
+
setGhostNode({
|
|
63
|
+
id: "ghost",
|
|
64
|
+
position: {
|
|
65
|
+
x: pos.x - NODE_WIDTH / 2,
|
|
66
|
+
y: pos.y - NODE_HEIGHT / 2,
|
|
67
|
+
},
|
|
68
|
+
data: { label: "Behavior" },
|
|
69
|
+
draggable: false,
|
|
70
|
+
selectable: false,
|
|
71
|
+
style: {
|
|
72
|
+
opacity: 0.5,
|
|
73
|
+
pointerEvents: "none",
|
|
74
|
+
width: NODE_WIDTH,
|
|
75
|
+
height: NODE_HEIGHT,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}, [activeTool]);
|
|
80
|
+
|
|
81
|
+
// Callback for when mouse leaves pane
|
|
82
|
+
const onPaneMouseLeave = useCallback(() => {
|
|
83
|
+
setGhostNode(null);
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
// Callback for pane click
|
|
87
|
+
const onPaneClick = useCallback((event, node) => {
|
|
88
|
+
if (activeTool === "drop") {
|
|
89
|
+
if (!ghostNode) return;
|
|
90
|
+
setNodes((nds) => [
|
|
91
|
+
...nds,
|
|
92
|
+
{
|
|
93
|
+
...ghostNode,
|
|
94
|
+
draggable: true,
|
|
95
|
+
selectable: true,
|
|
96
|
+
id: crypto.randomUUID(),
|
|
97
|
+
style: {},
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
}, [ghostNode, activeTool]);
|
|
102
|
+
|
|
103
|
+
// Callback for node click to delete it (if tool is active)
|
|
104
|
+
const onNodeClick = useCallback((event, node) => {
|
|
105
|
+
if (activeTool === "delete") {
|
|
106
|
+
setNodes((nds) => nds.filter((n) => n.id !== node.id));
|
|
107
|
+
setEdges((eds) =>
|
|
108
|
+
eds.filter((e) => e.source !== node.id && e.target !== node.id)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}, [activeTool]);
|
|
112
|
+
|
|
113
|
+
// Callback for edge click to delete it (if tool is active)
|
|
114
|
+
const onEdgeClick = useCallback((event, edge) => {
|
|
115
|
+
if (activeTool === "delete") {
|
|
116
|
+
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
|
117
|
+
}
|
|
118
|
+
}, [activeTool]);
|
|
119
|
+
|
|
120
|
+
// Callback for when edge is connected, nodesConnectable={activeTool === "connect"}
|
|
121
|
+
// is used to determine connectability
|
|
122
|
+
const onConnect = useCallback((connection) => {
|
|
123
|
+
const edge = {
|
|
124
|
+
...connection,
|
|
125
|
+
id: crypto.randomUUID(),
|
|
126
|
+
type: "smoothstep",
|
|
127
|
+
animated: true,
|
|
128
|
+
};
|
|
129
|
+
setEdges((eds) => [...eds, edge]);
|
|
130
|
+
}, [activeTool]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<ReactFlow
|
|
134
|
+
nodes={ghostNode ? [...nodes, ghostNode] : nodes}
|
|
135
|
+
edges={edges}
|
|
136
|
+
onPaneMouseMove={onPaneMouseMove}
|
|
137
|
+
onPaneMouseLeave={onPaneMouseLeave}
|
|
138
|
+
onPaneClick={onPaneClick}
|
|
139
|
+
onConnect={onConnect}
|
|
140
|
+
onNodesChange={onNodesChange}
|
|
141
|
+
onEdgesChange={onEdgesChange}
|
|
142
|
+
onNodeClick={onNodeClick}
|
|
143
|
+
onEdgeClick={onEdgeClick}
|
|
144
|
+
colorMode={"dark"}
|
|
145
|
+
fitView
|
|
146
|
+
>
|
|
147
|
+
<Background />
|
|
148
|
+
<Controls />
|
|
149
|
+
</ReactFlow>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const BehavioralGraphBuilder = ({ activeTool }) => {
|
|
154
|
+
return (
|
|
155
|
+
<ReactFlowProvider>
|
|
156
|
+
<Flow activeTool={activeTool} />
|
|
157
|
+
</ReactFlowProvider>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./BehavioralGraphBuilder.jsx"
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect } from "react";
|
|
2
|
+
import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder";
|
|
3
|
+
import "./BehavioralGraphBuilderStories.scss"
|
|
4
|
+
import { ToolBar } from "./components/ToolBar/ToolBar";
|
|
5
|
+
import { useArgs } from "@storybook/preview-api";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'BehavioralGraphBuilder',
|
|
9
|
+
component: BehavioralGraphBuilder,
|
|
10
|
+
argTypes: {
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Template = (args) => {
|
|
15
|
+
const [, updateArgs] = useArgs();
|
|
16
|
+
|
|
17
|
+
const [activeTool, setActiveTool] = useState("select");
|
|
18
|
+
|
|
19
|
+
const selectTool = useCallback((tool) => {
|
|
20
|
+
updateArgs({activeTool : tool});
|
|
21
|
+
}, [activeTool, setActiveTool]);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
updateArgs({activeTool : activeTool});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="graphBuilderRootContainer">
|
|
29
|
+
<div className="toolbar">
|
|
30
|
+
<ToolBar onSelectTool={selectTool}/>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="flow">
|
|
33
|
+
<BehavioralGraphBuilder {...args}/>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Default = Template.bind({})
|
|
40
|
+
|
|
41
|
+
Default.args = {
|
|
42
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Cursor,
|
|
5
|
+
Square,
|
|
6
|
+
NodePlus,
|
|
7
|
+
Trash
|
|
8
|
+
} from "react-bootstrap-icons";
|
|
9
|
+
|
|
10
|
+
import "./ToolBar.scss";
|
|
11
|
+
|
|
12
|
+
ToolBar.propTypes = {};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Toolbar Component
|
|
16
|
+
* @return {JSX.Element}
|
|
17
|
+
*/
|
|
18
|
+
export function ToolBar({ onSelectTool }) {
|
|
19
|
+
const [selectedTool, setSelectedTool] = useState("select");
|
|
20
|
+
|
|
21
|
+
const selectTool = (tool) => {
|
|
22
|
+
setSelectedTool(tool);
|
|
23
|
+
onSelectTool(tool);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="toolbarWrapper">
|
|
28
|
+
<div className="toolbarContainer">
|
|
29
|
+
<Cursor
|
|
30
|
+
onClick={(e) => selectTool("select")}
|
|
31
|
+
style={{color: selectedTool === "select" ? "white": "grey"}}
|
|
32
|
+
title="Select"
|
|
33
|
+
className="icon"
|
|
34
|
+
/>
|
|
35
|
+
<Square
|
|
36
|
+
onClick={(e) => selectTool("drop")}
|
|
37
|
+
style={{color: selectedTool === "drop" ? "white": "grey"}}
|
|
38
|
+
title="Add Node"
|
|
39
|
+
className="icon"
|
|
40
|
+
/>
|
|
41
|
+
<NodePlus
|
|
42
|
+
onClick={(e) => selectTool("connect")}
|
|
43
|
+
style={{color: selectedTool === "connect" ? "white": "grey"}}
|
|
44
|
+
title="Connect Node"
|
|
45
|
+
className="icon"
|
|
46
|
+
/>
|
|
47
|
+
<Trash
|
|
48
|
+
onClick={(e) => selectTool("delete")}
|
|
49
|
+
style={{color: selectedTool === "delete" ? "white": "grey"}}
|
|
50
|
+
title="Delete Node"
|
|
51
|
+
className="icon"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="toolbarContainer bottom"></div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.toolbarWrapper {
|
|
2
|
+
display:flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
justify-content:space-around;
|
|
5
|
+
height: 100%;
|
|
6
|
+
background-color: #333333;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.toolbarContainer {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 15px;
|
|
13
|
+
width:100%;
|
|
14
|
+
align-items: center;
|
|
15
|
+
padding: 10px 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.toolbarWrapper > .bottom {
|
|
19
|
+
margin-top: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.toolbarContainer > .icon {
|
|
23
|
+
color:grey;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.toolbarContainer > .icon:hover {
|
|
27
|
+
color:white;
|
|
28
|
+
cursor:pointer;
|
|
29
|
+
}
|