Skip to content

Commit 5ed8e47

Browse files
committed
feat: make sidebar width with scalible and handle drag and drop
1 parent e13ae33 commit 5ed8e47

File tree

11 files changed

+184
-117
lines changed

11 files changed

+184
-117
lines changed

library/lib/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { defaultEdges, defaultNodes } from "./initialElements"
2121
import "@/styles/app.css"
2222
import { Sidebar } from "@/components"
2323
import { useCallback } from "react"
24-
import { nodeTypes } from "./nodes"
24+
import { diagramNodeTypes } from "./nodes"
2525
import { useDragDrop } from "./hooks"
2626

2727
const defaultEdgeOptions: DefaultEdgeOptions = {
@@ -47,7 +47,7 @@ function App({ onReactFlowInit }: AppProps) {
4747
<div style={{ display: "flex", width: "100vw", height: "100vh" }}>
4848
<Sidebar />
4949
<ReactFlow
50-
nodeTypes={nodeTypes}
50+
nodeTypes={diagramNodeTypes}
5151
defaultEdgeOptions={defaultEdgeOptions}
5252
nodes={nodes}
5353
edges={edges}

library/lib/components/Divider.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from "react"
2+
3+
interface DividerProps {
4+
style?: React.CSSProperties
5+
}
6+
7+
export const Divider: React.FC<DividerProps> = ({ style }) => {
8+
return (
9+
<div
10+
style={{
11+
height: "2px",
12+
width: "100%",
13+
backgroundColor: "#000",
14+
...style,
15+
}}
16+
/>
17+
)
18+
}

library/lib/components/Sidebar.tsx

Lines changed: 34 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,54 @@
1-
import { DragEvent } from "react"
2-
import { v4 as uuidv4 } from "uuid"
3-
import { ClassType, DropNodeData } from "@/types"
4-
import { ClassSVG, ColorDescriptionSVG, PackageSVG } from "@/svgs"
5-
6-
const SideBarElementWidth = 128
7-
const SideBarElementHeight = 88
8-
const SideBarElementScale = 0.8
1+
import React, { DragEvent } from "react"
2+
import { dropElementConfig, transformScale } from "@/constant"
3+
import { Divider } from "./Divider"
4+
import { DropNodeData } from "@/types"
95

106
const onDragStart = (event: DragEvent, { type, data }: DropNodeData) => {
117
event.dataTransfer.setData("text/plain", JSON.stringify({ type, data }))
128
event.dataTransfer.effectAllowed = "move"
139
}
1410

15-
// Common configuration for sidebar elements
16-
const sideBarElements = [
17-
{
18-
name: "Class",
19-
type: "class",
20-
stereotype: undefined,
21-
},
22-
{
23-
name: "Abstract",
24-
type: "class",
25-
stereotype: ClassType.Abstract,
26-
},
27-
{
28-
name: "Enumeration",
29-
type: "class",
30-
stereotype: ClassType.Enumeration,
31-
},
32-
{
33-
name: "Interface",
34-
type: "class",
35-
stereotype: ClassType.Interface,
36-
},
37-
]
38-
3911
export const Sidebar = () => {
4012
return (
4113
<aside style={{ height: "100%", backgroundColor: "#f0f0f0" }}>
4214
<div
4315
style={{
4416
display: "flex",
4517
flexDirection: "column",
46-
gap: "10px",
18+
gap: "8px",
4719
margin: "10px",
4820
}}
4921
>
50-
<div
51-
onDragStart={(event: DragEvent) =>
52-
onDragStart(event, {
53-
type: "package",
54-
data: {
55-
name: "Package",
56-
},
57-
})
58-
}
59-
draggable
60-
style={{ width: SideBarElementWidth, height: SideBarElementHeight }}
61-
>
62-
<PackageSVG
63-
width={SideBarElementWidth / SideBarElementScale}
64-
height={SideBarElementHeight / SideBarElementScale}
65-
name="Package"
66-
svgAttributes={{ transform: `scale(${SideBarElementScale})` }}
67-
/>
68-
</div>
69-
{sideBarElements.map(({ name, type, stereotype }) => (
70-
<div
71-
key={name}
72-
style={{ width: SideBarElementWidth, height: SideBarElementHeight }}
73-
onDragStart={(event: DragEvent) =>
74-
onDragStart(event, {
75-
type,
76-
data: {
77-
name,
78-
methods: [{ id: uuidv4(), name: "+ method()" }],
79-
attributes: [{ id: uuidv4(), name: "+ attribute: Type" }],
80-
stereotype,
81-
},
82-
})
83-
}
84-
draggable
85-
>
86-
<ClassSVG
87-
width={SideBarElementWidth / SideBarElementScale}
88-
height={SideBarElementHeight / SideBarElementScale}
89-
methods={[{ id: uuidv4(), name: "+ method()" }]}
90-
attributes={[{ id: uuidv4(), name: "+ attribute: Type" }]}
91-
name={name}
92-
stereotype={stereotype}
93-
svgAttributes={{ transform: `scale(${SideBarElementScale})` }}
94-
/>
95-
</div>
22+
{dropElementConfig.map((config) => (
23+
<React.Fragment key={`${config.type}_${config.name}`}>
24+
{/* Add separator before the Color Description */}
25+
{config.type === "colorDescription" && (
26+
<Divider style={{ margin: "3px 0" }} />
27+
)}
28+
<div
29+
style={{
30+
width: config.width * transformScale,
31+
height: config.height * transformScale,
32+
overflow: "hidden",
33+
zIndex: 2,
34+
}}
35+
draggable
36+
onDragStart={(event: DragEvent) =>
37+
onDragStart(event, {
38+
type: config.type,
39+
data: config.defaultData,
40+
})
41+
}
42+
>
43+
{React.createElement(config.svg, {
44+
width: config.width,
45+
height: config.height,
46+
...config.defaultData,
47+
transformScale,
48+
})}
49+
</div>
50+
</React.Fragment>
9651
))}
97-
<div style={{ height: 2, width: "auto", backgroundColor: "black" }} />
98-
<div
99-
style={{ width: SideBarElementWidth, height: SideBarElementHeight }}
100-
onDragStart={(event: DragEvent) =>
101-
onDragStart(event, {
102-
type: "colorDescription",
103-
data: {
104-
description: "Color Description",
105-
},
106-
})
107-
}
108-
draggable
109-
>
110-
<ColorDescriptionSVG
111-
width={SideBarElementWidth / SideBarElementScale}
112-
height={48 / SideBarElementScale}
113-
description="Color Description"
114-
svgAttributes={{ transform: `scale(${SideBarElementScale})` }}
115-
/>
116-
</div>
11752
</div>
11853
</aside>
11954
)

library/lib/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./Text"
22
export * from "./ThemedElements"
33
export * from "./Sidebar"
4+
export * from "./Divider"

library/lib/constant/index.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { ClassSVG, PackageSVG, ColorDescriptionSVG } from "@/svgs"
3+
import { generateUUID } from "@/utils"
4+
import { ClassType } from "@/types"
5+
import { DiagramNodeTypeKeys } from "@/nodes"
6+
7+
export const transformScale = 0.8
8+
const droppedElementWidth = 160
9+
const droppedElementHeight = 110
10+
11+
export const dropElementConfig: {
12+
type: DiagramNodeTypeKeys
13+
name: string
14+
width: number
15+
height: number
16+
defaultData: Record<string, unknown>
17+
svg: React.FC<any>
18+
}[] = [
19+
{
20+
type: "package",
21+
name: "Package",
22+
width: droppedElementWidth,
23+
height: droppedElementHeight,
24+
defaultData: { name: "Package" },
25+
svg: (props) => <PackageSVG {...props} />,
26+
},
27+
{
28+
type: "class",
29+
name: "Class",
30+
width: droppedElementWidth,
31+
height: droppedElementHeight,
32+
defaultData: {
33+
name: "Class",
34+
methods: [{ id: generateUUID(), name: "+ method()" }],
35+
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
36+
},
37+
svg: (props) => <ClassSVG {...props} />,
38+
},
39+
{
40+
type: "class",
41+
name: "Abstract",
42+
width: droppedElementWidth,
43+
height: droppedElementHeight,
44+
defaultData: {
45+
name: "Abstract",
46+
stereotype: ClassType.Abstract,
47+
methods: [{ id: generateUUID(), name: "+ method()" }],
48+
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
49+
},
50+
svg: (props) => <ClassSVG {...props} />,
51+
},
52+
{
53+
type: "class",
54+
name: "Enumeration",
55+
width: droppedElementWidth,
56+
height: droppedElementHeight,
57+
defaultData: {
58+
name: "Enumeration",
59+
stereotype: ClassType.Enumeration,
60+
methods: [{ id: generateUUID(), name: "+ method()" }],
61+
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
62+
},
63+
svg: (props) => <ClassSVG {...props} />,
64+
},
65+
{
66+
type: "class",
67+
name: "Interface",
68+
width: droppedElementWidth,
69+
height: droppedElementHeight,
70+
defaultData: {
71+
name: "Interface",
72+
stereotype: ClassType.Interface,
73+
methods: [{ id: generateUUID(), name: "+ method()" }],
74+
attributes: [{ id: generateUUID(), name: "+ attribute: Type" }],
75+
},
76+
svg: (props) => <ClassSVG {...props} />,
77+
},
78+
{
79+
type: "colorDescription",
80+
name: "Color Description",
81+
width: droppedElementWidth,
82+
height: 48,
83+
defaultData: { description: "Color Description" },
84+
svg: (props) => <ColorDescriptionSVG {...props} />,
85+
},
86+
]

library/lib/hooks/useDragDrop.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { dropElementConfig } from "@/constant"
12
import { DropNodeData } from "@/types"
23
import { generateUUID } from "@/utils"
34
import { useReactFlow, type Node } from "@xyflow/react"
@@ -9,31 +10,36 @@ export const useDragDrop = () => {
910
const onDrop = useCallback(
1011
(event: DragEvent) => {
1112
event.preventDefault()
12-
const data = JSON.parse(
13+
const dropData = JSON.parse(
1314
event.dataTransfer.getData("text/plain")
1415
) as DropNodeData
1516

16-
// check if the dropped element is valid
17-
if (!data.type) {
17+
const config = dropElementConfig.find(
18+
(config) => config.type === dropData.type
19+
)
20+
// Validate the dropped element type
21+
if (!config) {
22+
console.warn(`Unknown drop element type: ${dropData.type}`)
1823
return
1924
}
2025

2126
const position = screenToFlowPosition({
2227
x: event.clientX,
2328
y: event.clientY,
2429
})
30+
2531
const newNode: Node = {
26-
width: 200,
27-
height: 110,
32+
width: config.width,
33+
height: config.height,
2834
id: generateUUID(),
29-
type: data.type,
35+
type: dropData.type,
3036
position,
31-
data: data.data,
37+
data: { ...config.defaultData, ...dropData.data },
3238
}
3339

3440
setNodes((nds) => nds.concat(newNode))
3541
},
36-
[screenToFlowPosition]
42+
[screenToFlowPosition, setNodes]
3743
)
3844

3945
const onDragOver = useCallback((event: DragEvent) => {

library/lib/nodes/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { NodeTypes } from "@xyflow/react"
1+
import { NodeTypes } from "@xyflow/react" // Explicitly differentiate imported type
22
import { Class, ColorDescription, Package } from "./classDiagram"
33

4-
export const nodeTypes: NodeTypes = {
4+
export const diagramNodeTypes = {
55
package: Package,
66
class: Class,
77
colorDescription: ColorDescription,
8-
}
8+
} satisfies NodeTypes
9+
10+
export type DiagramNodeTypeKeys = keyof typeof diagramNodeTypes

library/lib/svgs/classDiagram/ClassSVG.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type ClassSVGProps = {
1010
attributes: ExtraElements[]
1111
stereotype?: ClassType
1212
name: string
13+
transformScale?: number
1314
svgAttributes?: SVGAttributes<SVGElement>
1415
}
1516

@@ -20,6 +21,7 @@ export function ClassSVG({
2021
attributes,
2122
stereotype,
2223
name,
24+
transformScale,
2325
svgAttributes,
2426
}: ClassSVGProps) {
2527
const headerHeight = 50
@@ -32,7 +34,12 @@ export function ClassSVG({
3234
<svg
3335
width={width}
3436
height={height}
35-
style={{ transformOrigin: "0 0" }}
37+
z={2}
38+
style={{
39+
transformOrigin: "0 0",
40+
transformBox: "content-box",
41+
transform: transformScale ? `scale(${transformScale})` : undefined,
42+
}}
3643
{...svgAttributes}
3744
>
3845
<g>

0 commit comments

Comments
 (0)