From 06e3d0ca2aba3d804a7abe58e6a72f07dbb69806 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 27 Aug 2025 17:45:13 +0200 Subject: [PATCH] fix: color picker --- apps/web/src/components/ui/color-picker.tsx | 55 ++++++++++++--------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/apps/web/src/components/ui/color-picker.tsx b/apps/web/src/components/ui/color-picker.tsx index 6a309d5a..b94c1f98 100644 --- a/apps/web/src/components/ui/color-picker.tsx +++ b/apps/web/src/components/ui/color-picker.tsx @@ -37,7 +37,10 @@ const hexToHsv = (hex: string) => { } } - return [h * 60, s, v]; + h = (h * 60 + 360) % 360; + if (isNaN(h)) h = 0; + + return [h, s, v]; }; const hsvToHex = (h: number, s: number, v: number) => { @@ -92,12 +95,20 @@ const ColorPicker = React.forwardRef( right: 0, bottom: 0, }); + const [internalHue, setInternalHue] = React.useState(0); + const [inputValue, setInputValue] = React.useState(value); + const pickerRef = React.useRef(null); const saturationRef = React.useRef(null); const hueRef = React.useRef(null); const triggerRef = React.useRef(null); const [h, s, v] = hexToHsv(value); + const displayHue = s > 0 ? h : internalHue; + + React.useEffect(() => { + setInputValue(value); + }, [value]); React.useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -130,10 +141,9 @@ const ColorPicker = React.forwardRef( 0, Math.min(1, (e.clientY - rect.top) / rect.height) ); - const newS = x; const newV = 1 - y; - const newHex = hsvToHex(h, newS, newV); + const newHex = hsvToHex(displayHue, newS, newV); onChange?.(newHex); } @@ -144,8 +154,11 @@ const ColorPicker = React.forwardRef( Math.min(1, (e.clientX - rect.left) / rect.width) ); const newH = x * 360; - const newHex = hsvToHex(newH, s, v); - onChange?.(newHex); + setInternalHue(newH); + if (s > 0) { + const newHex = hsvToHex(newH, s, v); + onChange?.(newHex); + } } }; @@ -161,37 +174,33 @@ const ColorPicker = React.forwardRef( document.removeEventListener("mouseup", handleMouseUp); }; } - }, [isDragging, h, s, v, onChange]); + }, [isDragging, displayHue, s, v, onChange]); const handleSaturationMouseDown = (e: React.MouseEvent) => { e.preventDefault(); setIsDragging("saturation"); - const rect = saturationRef.current!.getBoundingClientRect(); const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height)); const newS = x; const newV = 1 - y; - const newHex = hsvToHex(h, newS, newV); + const newHex = hsvToHex(displayHue, newS, newV); onChange?.(newHex); }; const handleHueMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); setIsDragging("hue"); - const rect = hueRef.current!.getBoundingClientRect(); const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); const newH = x * 360; - const newHex = hsvToHex(newH, s, v); - onChange?.(newHex); + setInternalHue(newH); + if (s > 0) { + const newHex = hsvToHex(newH, s, v); + onChange?.(newHex); + } }; - const [inputValue, setInputValue] = React.useState(value); - - React.useEffect(() => { - setInputValue(value); - }, [value]); - const handleInputChange = (e: React.ChangeEvent) => { const hex = e.target.value.replace("#", ""); setInputValue(hex); @@ -209,7 +218,7 @@ const ColorPicker = React.forwardRef( }; const saturationStyle = { - background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${h}, 100%, 50%))`, + background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${displayHue}, 100%, 50%))`, }; const hueStyle = { @@ -235,7 +244,6 @@ const ColorPicker = React.forwardRef( if (!isOpen && triggerRef.current && containerRef?.current) { const containerRect = containerRef.current.getBoundingClientRect(); - setPickerPosition({ right: window.innerWidth - containerRect.left - 8, bottom: window.innerHeight - containerRect.bottom, @@ -246,7 +254,7 @@ const ColorPicker = React.forwardRef( />
( >
@@ -310,7 +321,7 @@ const ColorCircle = ({ color: string; }) => (