Skip to content

Commit 3debe53

Browse files
committed
feat: Add Color Picker
1 parent c508724 commit 3debe53

4 files changed

Lines changed: 173 additions & 6 deletions

File tree

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"pyodide": "^0.29.3",
9292
"random-words": "^2.0.1",
9393
"react": "^19.2.4",
94+
"react-colorful": "^5.6.1",
9495
"react-dom": "^19.2.4",
9596
"react-error-boundary": "^6.1.0",
9697
"react-icons": "^5.5.0",

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

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type ChangeEvent, useEffect, useState } from "react";
33
import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock";
44
import { KeyValueList } from "@/components/shared/ContextPanel/Blocks/KeyValueList";
55
import { CopyText } from "@/components/shared/CopyText/CopyText";
6+
import { ColorPicker } from "@/components/ui/color";
67
import { Input } from "@/components/ui/input";
78
import { Label } from "@/components/ui/label";
89
import { BlockStack, InlineStack } from "@/components/ui/layout";
@@ -33,7 +34,7 @@ export const FlexNodeEditor = ({
3334

3435
<ContentEditor flexNode={flexNode} readOnly={readOnly} />
3536

36-
<ColorEditor properties={properties} readOnly={readOnly} />
37+
<ColorEditor flexNode={flexNode} readOnly={readOnly} />
3738

3839
<KeyValueList
3940
title="Layout"
@@ -172,12 +173,53 @@ const ContentEditor = ({
172173
};
173174

174175
const ColorEditor = ({
175-
properties,
176+
flexNode,
176177
readOnly,
177178
}: {
178-
properties: FlexNodeData["properties"];
179+
flexNode: FlexNodeData;
179180
readOnly: boolean;
180181
}) => {
182+
const {
183+
componentSpec,
184+
currentSubgraphSpec,
185+
currentSubgraphPath,
186+
setComponentSpec,
187+
} = useComponentSpec();
188+
189+
const { properties } = flexNode;
190+
191+
const [backgroundColor, setBackgroundColor] = useState(properties.color);
192+
193+
const handleBackgroundColorChange = (newColor: string) => {
194+
setBackgroundColor(newColor);
195+
saveColors(newColor);
196+
};
197+
198+
const saveColors = (newBackgroundColor: string) => {
199+
const updatedSubgraphSpec = updateFlexNodeInComponentSpec(
200+
currentSubgraphSpec,
201+
{
202+
...flexNode,
203+
properties: {
204+
...properties,
205+
color: newBackgroundColor,
206+
},
207+
},
208+
);
209+
210+
const newRootSpec = updateSubgraphSpec(
211+
componentSpec,
212+
currentSubgraphPath,
213+
updatedSubgraphSpec,
214+
);
215+
216+
setComponentSpec(newRootSpec);
217+
};
218+
219+
useEffect(() => {
220+
setBackgroundColor(properties.color);
221+
}, [properties]);
222+
181223
if (readOnly) {
182224
return (
183225
<KeyValueList
@@ -198,9 +240,10 @@ const ColorEditor = ({
198240
<BlockStack gap="1">
199241
<InlineStack gap="4" blockAlign="center">
200242
<Paragraph size="xs">Background</Paragraph>
201-
<div
202-
className="aspect-square h-4 rounded-full border border-muted-foreground"
203-
style={{ backgroundColor: properties.color }}
243+
<ColorPicker
244+
title="Background Color"
245+
color={backgroundColor}
246+
setColor={handleBackgroundColorChange}
204247
/>
205248
<CopyText className="text-xs font-mono">{properties.color}</CopyText>
206249
</InlineStack>

src/components/ui/color.tsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useState } from "react";
2+
import { HexColorPicker } from "react-colorful";
3+
4+
import { cn } from "@/lib/utils";
5+
6+
import { Button } from "./button";
7+
import {
8+
Collapsible,
9+
CollapsibleContent,
10+
CollapsibleTrigger,
11+
} from "./collapsible";
12+
import { Icon } from "./icon";
13+
import { Input } from "./input";
14+
import { BlockStack, InlineStack } from "./layout";
15+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
16+
import { Heading } from "./typography";
17+
18+
const PRESET_COLORS = [
19+
"#FFF9C4",
20+
"#C8E6C9",
21+
"#BBDEFB",
22+
"#D1C4E9",
23+
"#FFE0B2",
24+
"#EF9A9A",
25+
"#FFCCBC",
26+
"#D7CCC8",
27+
"#F5F5F5",
28+
"#CFD8DC",
29+
"#B0BEC5",
30+
"transparent",
31+
];
32+
33+
interface ColorPickerProps {
34+
title?: string;
35+
color: string;
36+
setColor: (color: string) => void;
37+
onClose?: () => void;
38+
}
39+
40+
export const ColorPicker = ({
41+
color,
42+
title,
43+
setColor,
44+
onClose,
45+
}: ColorPickerProps) => {
46+
const [open, setOpen] = useState(false);
47+
48+
const handleOpenChange = (isOpen: boolean) => {
49+
setOpen(isOpen);
50+
if (!isOpen) {
51+
onClose?.();
52+
}
53+
};
54+
55+
return (
56+
<Popover open={open} onOpenChange={handleOpenChange}>
57+
<PopoverTrigger>
58+
<div
59+
className="aspect-square h-4 rounded-full border border-muted-foreground cursor-pointer"
60+
style={{ backgroundColor: color }}
61+
/>
62+
</PopoverTrigger>
63+
<PopoverContent className="w-fit">
64+
<BlockStack gap="4" align="center">
65+
<Heading level={3}>{title ?? "Pick a color"}</Heading>
66+
<InlineStack gap="2" className="grid grid-cols-6 px-2">
67+
{PRESET_COLORS.map((preset) => (
68+
<div
69+
key={preset}
70+
className={cn(
71+
"aspect-square w-6 rounded-sm border border-muted-foreground cursor-pointer relative overflow-hidden",
72+
{
73+
"ring-2 ring-offset-2 ring-black": preset === color,
74+
},
75+
)}
76+
style={{ backgroundColor: preset }}
77+
onClick={() => setColor(preset)}
78+
>
79+
{preset === "transparent" && (
80+
<div className="absolute inset-0 flex items-center justify-center">
81+
<div className="h-px w-full bg-red-500 rotate-45 origin-center" />
82+
</div>
83+
)}
84+
</div>
85+
))}
86+
</InlineStack>
87+
<div className="relative w-full">
88+
<Input value={color} onChange={(e) => setColor(e.target.value)} />
89+
<Collapsible>
90+
<InlineStack blockAlign="center" gap="1">
91+
<CollapsibleTrigger asChild>
92+
<Button
93+
size="icon"
94+
variant="ghost"
95+
className="absolute right-2 top-0 hover:bg-transparent hover:text-black!"
96+
style={{ color }}
97+
>
98+
<Icon name="Palette" />
99+
<span className="sr-only">Toggle</span>
100+
</Button>
101+
</CollapsibleTrigger>
102+
</InlineStack>
103+
<CollapsibleContent className="mt-2">
104+
<HexColorPicker color={color} onChange={setColor} />
105+
</CollapsibleContent>
106+
</Collapsible>
107+
</div>
108+
</BlockStack>
109+
</PopoverContent>
110+
</Popover>
111+
);
112+
};

0 commit comments

Comments
 (0)