Skip to content

Commit 9f7e80b

Browse files
committed
feat: Context Panel for Flex Nodes
1 parent c451a9a commit 9f7e80b

6 files changed

Lines changed: 222 additions & 10 deletions

File tree

src/components/shared/ReactFlow/FlowCanvas/FlexNode/FlexNode.tsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,53 @@
11
import { type Node, type NodeProps } from "@xyflow/react";
2+
import { useEffect } from "react";
23

4+
import { InfoBox } from "@/components/shared/InfoBox";
35
import { Paragraph } from "@/components/ui/typography";
46
import { cn } from "@/lib/utils";
7+
import { useContextPanel } from "@/providers/ContextPanelProvider";
58

9+
import { StickyNoteEditor } from "./StickyNoteEditor";
610
import type { FlexNodeData } from "./types";
711

812
type FlexNodeProps = NodeProps<Node<FlexNodeData>>;
913

1014
const FlexNode = ({ data, id, selected }: FlexNodeProps) => {
11-
const { properties } = data;
15+
const { properties, readOnly, type } = data;
1216

13-
const color = properties.color || "yellow-200";
14-
const border = properties.border || "yellow-400";
15-
const zIndex = properties.zIndex || 1;
17+
const {
18+
setContent,
19+
clearContent,
20+
setOpen: setContextPanelOpen,
21+
} = useContextPanel();
22+
23+
useEffect(() => {
24+
if (selected) {
25+
switch (type) {
26+
case "sticky-note":
27+
setContent(
28+
<StickyNoteEditor stickyNote={data} readOnly={readOnly} />,
29+
);
30+
break;
31+
default:
32+
setContent(
33+
<InfoBox title="Context Panel Error" variant="error">
34+
Unknown node type: <span className="font-mono">{type}</span>
35+
</InfoBox>,
36+
);
37+
}
38+
setContextPanelOpen(true);
39+
}
40+
41+
return () => {
42+
if (selected) {
43+
clearContent();
44+
}
45+
};
46+
}, [selected]);
47+
48+
const color = properties.color;
49+
const border = properties.border;
50+
const zIndex = properties.zIndex;
1651

1752
return (
1853
<div
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock";
2+
import { KeyValueList } from "@/components/shared/ContextPanel/Blocks/KeyValueList";
3+
import { CopyText } from "@/components/shared/CopyText/CopyText";
4+
import { Input } from "@/components/ui/input";
5+
import { Label } from "@/components/ui/label";
6+
import { BlockStack, InlineStack } from "@/components/ui/layout";
7+
import { Textarea } from "@/components/ui/textarea";
8+
import { Paragraph, Text } from "@/components/ui/typography";
9+
10+
import type { FlexNodeData } from "./types";
11+
12+
interface StickyNoteEditorProps {
13+
stickyNote: FlexNodeData;
14+
readOnly?: boolean;
15+
}
16+
17+
export const StickyNoteEditor = ({
18+
stickyNote,
19+
readOnly = false,
20+
}: StickyNoteEditorProps) => {
21+
const { properties, size, position } = stickyNote;
22+
23+
return (
24+
<BlockStack gap="4" className="h-full px-2">
25+
<Text size="lg" weight="semibold" className="wrap-anywhere">
26+
Sticky Note
27+
</Text>
28+
29+
<ContentEditor properties={properties} readOnly={readOnly} />
30+
31+
<ColorEditor properties={properties} readOnly={readOnly} />
32+
33+
<KeyValueList
34+
title="Layout"
35+
items={[
36+
{
37+
label: "size",
38+
value: `${size.width} x ${size.height}`,
39+
copyable: true,
40+
},
41+
{
42+
label: "position",
43+
value: `${position.x}, ${position.y}`,
44+
copyable: true,
45+
},
46+
{
47+
label: "z-index",
48+
value: `${properties.zIndex}`,
49+
copyable: true,
50+
},
51+
]}
52+
/>
53+
</BlockStack>
54+
);
55+
};
56+
57+
const ContentEditor = ({
58+
properties,
59+
readOnly,
60+
}: {
61+
properties: FlexNodeData["properties"];
62+
readOnly: boolean;
63+
}) => {
64+
if (readOnly) {
65+
return (
66+
<KeyValueList
67+
title="Content"
68+
items={[
69+
{
70+
label: "Title",
71+
value: properties.title,
72+
copyable: true,
73+
},
74+
{
75+
value: properties.content,
76+
copyable: true,
77+
},
78+
]}
79+
/>
80+
);
81+
}
82+
83+
return (
84+
<ContentBlock title="Content">
85+
<BlockStack gap="2">
86+
<BlockStack>
87+
<Label
88+
htmlFor="flex-node-title"
89+
className="text-muted-foreground text-xs"
90+
>
91+
Title
92+
</Label>
93+
<Input
94+
id="flex-node-title"
95+
value={properties.title}
96+
className="text-sm"
97+
readOnly
98+
/>
99+
</BlockStack>
100+
<BlockStack>
101+
<Label
102+
htmlFor="flex-node-content"
103+
className="text-muted-foreground text-xs"
104+
>
105+
Note
106+
</Label>
107+
<Textarea
108+
id="flex-node-content"
109+
value={properties.content}
110+
className="text-xs"
111+
readOnly
112+
/>
113+
</BlockStack>
114+
</BlockStack>
115+
</ContentBlock>
116+
);
117+
};
118+
119+
const ColorEditor = ({
120+
properties,
121+
readOnly,
122+
}: {
123+
properties: FlexNodeData["properties"];
124+
readOnly: boolean;
125+
}) => {
126+
if (readOnly) {
127+
return (
128+
<KeyValueList
129+
title="Color"
130+
items={[
131+
{
132+
label: "Backgroud",
133+
value: properties.color,
134+
copyable: true,
135+
},
136+
{
137+
label: "Border",
138+
value: properties.border,
139+
copyable: true,
140+
},
141+
]}
142+
/>
143+
);
144+
}
145+
146+
return (
147+
<ContentBlock title="Color">
148+
<BlockStack gap="1">
149+
<InlineStack gap="4" blockAlign="center">
150+
<Paragraph size="xs">Background</Paragraph>
151+
<div
152+
className="aspect-square h-4 rounded-full border border-muted-foreground"
153+
style={{ backgroundColor: properties.color }}
154+
/>
155+
<CopyText className="text-xs font-mono">{properties.color}</CopyText>
156+
</InlineStack>
157+
<InlineStack gap="4" blockAlign="center">
158+
<Paragraph size="xs">Border</Paragraph>
159+
<div
160+
className="aspect-square h-4 rounded-full border border-muted-foreground"
161+
style={{ backgroundColor: properties.border }}
162+
/>
163+
<CopyText className="text-xs font-mono">{properties.border}</CopyText>
164+
</InlineStack>
165+
</BlockStack>
166+
</ContentBlock>
167+
);
168+
};

src/components/shared/ReactFlow/FlowCanvas/FlexNode/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface FlexNodeData extends Record<string, unknown> {
77
properties: StickyNoteProperties;
88
size: { width: number; height: number };
99
position: XYPosition;
10+
readOnly?: boolean;
1011
}
1112

1213
export type FlexNodeSpec = {

src/components/shared/ReactFlow/FlowCanvas/FlexNode/utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export const DEFAULT_STICKY_NOTE = {
1212

1313
export const DEFAULT_FLEX_NODE_SIZE = { width: 150, height: 100 };
1414

15-
export const createFlexNode = (flexNode: [string, FlexNodeSpec]) => {
15+
export const createFlexNode = (
16+
flexNode: [string, FlexNodeSpec],
17+
readOnly: boolean,
18+
) => {
1619
const [nodeId, flexSpec] = flexNode;
1720

1821
const flexNodeData = parseFlexNodeSpec(flexSpec);
@@ -21,7 +24,7 @@ export const createFlexNode = (flexNode: [string, FlexNodeSpec]) => {
2124

2225
return {
2326
id: nodeId,
24-
data: flexNodeData,
27+
data: { ...flexNodeData, readOnly },
2528
...size,
2629
position,
2730
type: "flex",

src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,11 @@ const FlowCanvas = ({
337337
currentSubgraphPath,
338338
notify,
339339
);
340-
const newNodes = createNodesFromComponentSpec(subgraphSpec, nodeData);
340+
const newNodes = createNodesFromComponentSpec(
341+
subgraphSpec,
342+
nodeData,
343+
readOnly,
344+
);
341345

342346
const updatedNewNodes = newNodes.map((node) => ({
343347
...node,

src/utils/nodes/createNodesFromComponentSpec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { createTaskNode } from "./createTaskNode";
1616
const createNodesFromComponentSpec = (
1717
componentSpec: ComponentSpec,
1818
nodeData: TaskNodeData,
19+
readOnly: boolean = false,
1920
): Node[] => {
2021
if (!isGraphImplementation(componentSpec.implementation)) {
2122
return [];
@@ -25,7 +26,7 @@ const createNodesFromComponentSpec = (
2526
const taskNodes = createTaskNodes(graphSpec, nodeData);
2627
const inputNodes = createInputNodes(componentSpec, nodeData);
2728
const outputNodes = createOutputNodes(componentSpec, nodeData);
28-
const flexNodes = createFlexNodes(componentSpec);
29+
const flexNodes = createFlexNodes(componentSpec, readOnly);
2930

3031
return [...taskNodes, ...inputNodes, ...outputNodes, ...flexNodes];
3132
};
@@ -54,10 +55,10 @@ const createOutputNodes = (
5455
);
5556
};
5657

57-
const createFlexNodes = (componentSpec: ComponentSpec) => {
58+
const createFlexNodes = (componentSpec: ComponentSpec, readOnly: boolean) => {
5859
return Object.entries(
5960
componentSpec.metadata?.annotations?.[FLEX_NODES_ANNOTATION] ?? [],
60-
).map((flexNode) => createFlexNode(flexNode));
61+
).map((flexNode) => createFlexNode(flexNode, readOnly));
6162
};
6263

6364
export default createNodesFromComponentSpec;

0 commit comments

Comments
 (0)