Skip to content

Commit 36e7fe5

Browse files
authored
Merge pull request #2580 from cry4pt/main
2 parents b51f8a3 + 6715ccd commit 36e7fe5

File tree

1 file changed

+88
-66
lines changed

1 file changed

+88
-66
lines changed

src/components/color-picker.tsx

Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"use client";
22

33
import type React from "react";
4-
5-
import { useState, useEffect, useRef } from "react";
4+
import { useState, useEffect, useRef, useCallback } from "react";
65
import { Input } from "@/components/ui/input";
76
import { Button } from "@/components/ui/button";
87
import {
@@ -16,73 +15,87 @@ interface ColorPickerProps {
1615
onChange: (color: string) => void;
1716
}
1817

18+
/** Discord-themed preset colors */
19+
const discordColors = [
20+
"#5865F2", // Blurple
21+
"#57F287", // Green
22+
"#FEE75C", // Yellow
23+
"#EB459E", // Fuchsia
24+
"#ED4245", // Red
25+
"#000000", // Black
26+
"#FFFFFF", // White
27+
"#1E1F22", // Dark
28+
"#2B2D31", // Dark but not as dark
29+
"#313338", // Even less dark
30+
"#F2F3F5", // Light
31+
"#E3E5E8", // Less light
32+
];
33+
1934
export function ColorPicker({ color, onChange }: ColorPickerProps) {
2035
const [inputValue, setInputValue] = useState(color);
21-
const canvasRef = useRef<HTMLCanvasElement>(null);
2236
const [isOpen, setIsOpen] = useState(false);
37+
const canvasRef = useRef<HTMLCanvasElement>(null);
2338

24-
// Discord colors
25-
const discordColors = [
26-
"#5865F2", // Blurple
27-
"#57F287", // Green
28-
"#FEE75C", // Yellow
29-
"#EB459E", // Fuchsia
30-
"#ED4245", // Red
31-
"#000000", // Black
32-
"#FFFFFF", // White
33-
"#1E1F22", // Dark
34-
"#2B2D31", // Dark but not as dark
35-
"#313338", // Even less dark
36-
"#F2F3F5", // Light
37-
"#E3E5E8", // Less light
38-
];
39-
40-
useEffect(() => {
41-
setInputValue(color);
42-
}, [color]);
43-
44-
useEffect(() => {
45-
if (isOpen) {
46-
setTimeout(() => {
47-
drawColorGradient();
48-
}, 50);
49-
}
50-
}, [isOpen]);
51-
52-
const drawColorGradient = () => {
39+
/* --------------------------------------------------------------------- *
40+
* Draw the HSV-style gradient on the canvas
41+
* --------------------------------------------------------------------- */
42+
const drawColorGradient = useCallback(() => {
5343
const canvas = canvasRef.current;
5444
if (!canvas) return;
5545

5646
const ctx = canvas.getContext("2d");
5747
if (!ctx) return;
5848

59-
// Clear canvas
49+
// Clear
6050
ctx.clearRect(0, 0, canvas.width, canvas.height);
6151

62-
// Draw color gradient (horizontal - hue)
63-
const gradientH = ctx.createLinearGradient(0, 0, canvas.width, 0);
64-
gradientH.addColorStop(0, "#FF0000");
65-
gradientH.addColorStop(1 / 6, "#FFFF00");
66-
gradientH.addColorStop(2 / 6, "#00FF00");
67-
gradientH.addColorStop(3 / 6, "#00FFFF");
68-
gradientH.addColorStop(4 / 6, "#0000FF");
69-
gradientH.addColorStop(5 / 6, "#FF00FF");
70-
gradientH.addColorStop(1, "#FF0000");
71-
72-
ctx.fillStyle = gradientH;
52+
// Horizontal hue gradient
53+
const gradH = ctx.createLinearGradient(0, 0, canvas.width, 0);
54+
gradH.addColorStop(0, "#FF0000");
55+
gradH.addColorStop(1 / 6, "#FFFF00");
56+
gradH.addColorStop(2 / 6, "#00FF00");
57+
gradH.addColorStop(3 / 6, "#00FFFF");
58+
gradH.addColorStop(4 / 6, "#0000FF");
59+
gradH.addColorStop(5 / 6, "#FF00FF");
60+
gradH.addColorStop(1, "#FF0000");
61+
62+
ctx.fillStyle = gradH;
7363
ctx.fillRect(0, 0, canvas.width, canvas.height);
7464

75-
// Draw white to black gradient overlay (vertical - saturation/value)
76-
const gradientV = ctx.createLinearGradient(0, 0, 0, canvas.height);
77-
gradientV.addColorStop(0, "rgba(255, 255, 255, 1)");
78-
gradientV.addColorStop(0.5, "rgba(255, 255, 255, 0)");
79-
gradientV.addColorStop(0.5, "rgba(0, 0, 0, 0)");
80-
gradientV.addColorStop(1, "rgba(0, 0, 0, 1)");
65+
// Vertical white→transparent→black overlay (saturation/value)
66+
const gradV = ctx.createLinearGradient(0, 0, 0, canvas.height);
67+
gradV.addColorStop(0, "rgba(255,255,255,1)");
68+
gradV.addColorStop(0.5, "rgba(255,255,255,0)");
69+
gradV.addColorStop(0.5, "rgba(0,0,0,0)");
70+
gradV.addColorStop(1, "rgba(0,0,0,1)");
8171

82-
ctx.fillStyle = gradientV;
72+
ctx.fillStyle = gradV;
8373
ctx.fillRect(0, 0, canvas.width, canvas.height);
84-
};
74+
}, []);
75+
76+
/* --------------------------------------------------------------------- *
77+
* Redraw when the popover opens (give the canvas time to mount)
78+
* --------------------------------------------------------------------- */
79+
useEffect(() => {
80+
if (!isOpen) return;
8581

82+
const timer = setTimeout(() => {
83+
drawColorGradient();
84+
}, 50);
85+
86+
return () => clearTimeout(timer);
87+
}, [isOpen, drawColorGradient]);
88+
89+
/* --------------------------------------------------------------------- *
90+
* Keep the input in sync with the prop
91+
* --------------------------------------------------------------------- */
92+
useEffect(() => {
93+
setInputValue(color);
94+
}, [color]);
95+
96+
/* --------------------------------------------------------------------- *
97+
* Click → sample color from canvas
98+
* --------------------------------------------------------------------- */
8699
const handleCanvasClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
87100
const canvas = canvasRef.current;
88101
if (!canvas) return;
@@ -94,19 +107,24 @@ export function ColorPicker({ color, onChange }: ColorPickerProps) {
94107
const ctx = canvas.getContext("2d");
95108
if (!ctx) return;
96109

97-
const imageData = ctx.getImageData(x, y, 1, 1).data;
98-
const color = `#${[imageData[0], imageData[1], imageData[2]].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
110+
const [r, g, b] = ctx.getImageData(x, y, 1, 1).data;
111+
const hex = `#${[r, g, b]
112+
.map((c) => c.toString(16).padStart(2, "0"))
113+
.join("")}`;
99114

100-
onChange(color);
101-
setInputValue(color);
115+
onChange(hex);
116+
setInputValue(hex);
102117
};
103118

119+
/* --------------------------------------------------------------------- *
120+
* Manual hex input
121+
* --------------------------------------------------------------------- */
104122
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
105-
setInputValue(e.target.value);
123+
const val = e.target.value;
124+
setInputValue(val);
106125

107-
// Validate if it's a proper hex color
108-
if (/^#[0-9A-F]{6}$/i.test(e.target.value)) {
109-
onChange(e.target.value);
126+
if (/^#[0-9A-F]{6}$/i.test(val)) {
127+
onChange(val);
110128
}
111129
};
112130

@@ -122,8 +140,10 @@ export function ColorPicker({ color, onChange }: ColorPickerProps) {
122140
<span className="sr-only">Pick a color</span>
123141
</Button>
124142
</PopoverTrigger>
143+
125144
<PopoverContent className="w-64">
126145
<div className="space-y-4">
146+
{/* Canvas picker */}
127147
<div className="relative w-full h-40 rounded-md overflow-hidden border border-input">
128148
<canvas
129149
ref={canvasRef}
@@ -134,26 +154,28 @@ export function ColorPicker({ color, onChange }: ColorPickerProps) {
134154
/>
135155
</div>
136156

157+
{/* Discord preset swatches */}
137158
<div className="grid grid-cols-6 gap-2">
138-
{discordColors.map((discordColor, index) => (
159+
{discordColors.map((c) => (
139160
<Button
140-
key={index}
161+
key={c}
141162
variant="outline"
142163
className="w-8 h-8 p-0 rounded-md border"
143-
style={{ backgroundColor: discordColor }}
164+
style={{ backgroundColor: c }}
144165
onClick={() => {
145-
onChange(discordColor);
146-
setInputValue(discordColor);
166+
onChange(c);
167+
setInputValue(c);
147168
}}
148169
>
149-
<span className="sr-only">Select color {discordColor}</span>
170+
<span className="sr-only">Select {c}</span>
150171
</Button>
151172
))}
152173
</div>
153174
</div>
154175
</PopoverContent>
155176
</Popover>
156177

178+
{/* Hex input */}
157179
<Input
158180
value={inputValue}
159181
onChange={handleInputChange}

0 commit comments

Comments
 (0)