Skip to content

Commit d95d4e4

Browse files
committed
feat: Context Panel for Flex Nodes
1 parent bb8141c commit d95d4e4

2 files changed

Lines changed: 198 additions & 1 deletion

File tree

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

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

34
import { BlockStack } from "@/components/ui/layout";
45
import { Paragraph } from "@/components/ui/typography";
56
import { cn } from "@/lib/utils";
7+
import { useContextPanel } from "@/providers/ContextPanelProvider";
68

9+
import { FlexNodeEditor } from "./FlexNodeEditor";
710
import type { FlexNodeData } from "./types";
811

912
type FlexNodeProps = NodeProps<Node<FlexNodeData>>;
1013

1114
const FlexNode = ({ data, id, selected }: FlexNodeProps) => {
12-
const { properties } = data;
15+
const { properties, readOnly } = data;
1316
const { title, content, color } = properties;
1417

18+
const {
19+
setContent,
20+
clearContent,
21+
setOpen: setContextPanelOpen,
22+
} = useContextPanel();
23+
24+
useEffect(() => {
25+
if (selected) {
26+
setContent(<FlexNodeEditor flexNode={data} readOnly={readOnly} />);
27+
setContextPanelOpen(true);
28+
}
29+
30+
return () => {
31+
if (selected) {
32+
clearContent();
33+
}
34+
};
35+
}, [data, readOnly, selected]);
36+
1537
const isTransparent = color === "transparent";
1638

1739
return (
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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 FlexNodeEditorProps {
13+
flexNode: FlexNodeData;
14+
readOnly?: boolean;
15+
}
16+
17+
export const FlexNodeEditor = ({
18+
flexNode,
19+
readOnly = false,
20+
}: FlexNodeEditorProps) => {
21+
const { metadata, zIndex, size, position } = flexNode;
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 flexNode={flexNode} readOnly={readOnly} />
30+
31+
<ColorEditor flexNode={flexNode} 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: `${zIndex}`,
49+
copyable: true,
50+
},
51+
]}
52+
/>
53+
54+
<KeyValueList
55+
title="Metadata"
56+
items={[
57+
{
58+
label: "Created",
59+
value: new Date(metadata.createdAt).toLocaleString(),
60+
copyable: true,
61+
},
62+
{
63+
label: "Author",
64+
value: metadata.createdBy,
65+
copyable: true,
66+
},
67+
]}
68+
/>
69+
</BlockStack>
70+
);
71+
};
72+
73+
const ContentEditor = ({
74+
flexNode,
75+
readOnly,
76+
}: {
77+
flexNode: FlexNodeData;
78+
readOnly: boolean;
79+
}) => {
80+
const { properties } = flexNode;
81+
82+
if (readOnly) {
83+
return (
84+
<KeyValueList
85+
title="Content"
86+
items={[
87+
{
88+
label: "Title",
89+
value: properties.title,
90+
copyable: true,
91+
},
92+
{
93+
value: properties.content,
94+
copyable: true,
95+
},
96+
]}
97+
/>
98+
);
99+
}
100+
101+
return (
102+
<ContentBlock title="Content">
103+
<BlockStack gap="2">
104+
<BlockStack>
105+
<Label
106+
htmlFor="flex-node-title"
107+
className="text-muted-foreground text-xs"
108+
>
109+
Title
110+
</Label>
111+
<Input
112+
id="flex-node-title"
113+
value={properties.title}
114+
className="text-sm"
115+
readOnly
116+
/>
117+
</BlockStack>
118+
<BlockStack>
119+
<Label
120+
htmlFor="flex-node-content"
121+
className="text-muted-foreground text-xs"
122+
>
123+
Note
124+
</Label>
125+
<Textarea
126+
id="flex-node-content"
127+
value={properties.content}
128+
className="text-xs"
129+
readOnly
130+
/>
131+
</BlockStack>
132+
</BlockStack>
133+
</ContentBlock>
134+
);
135+
};
136+
137+
const ColorEditor = ({
138+
flexNode,
139+
readOnly,
140+
}: {
141+
flexNode: FlexNodeData;
142+
readOnly: boolean;
143+
}) => {
144+
const { properties } = flexNode;
145+
146+
if (readOnly) {
147+
return (
148+
<KeyValueList
149+
title="Color"
150+
items={[
151+
{
152+
label: "Backgroud",
153+
value: properties.color,
154+
copyable: true,
155+
},
156+
]}
157+
/>
158+
);
159+
}
160+
161+
return (
162+
<ContentBlock title="Color">
163+
<BlockStack gap="1">
164+
<InlineStack gap="4" blockAlign="center">
165+
<Paragraph size="xs">Background</Paragraph>
166+
<div
167+
className="aspect-square h-4 rounded-full border border-muted-foreground"
168+
style={{ backgroundColor: properties.color }}
169+
/>
170+
<CopyText className="text-xs font-mono">{properties.color}</CopyText>
171+
</InlineStack>
172+
</BlockStack>
173+
</ContentBlock>
174+
);
175+
};

0 commit comments

Comments
 (0)