From 1c06aca2319ff69bc6f15ffdbc5963a97b3e6178 Mon Sep 17 00:00:00 2001 From: louis phi Date: Tue, 20 Jan 2026 22:30:13 +0700 Subject: [PATCH 1/2] support flower color wheel mode option --- playground/src/App.tsx | 6 +- src/components/colorpicker/ColorPicker.tsx | 21 +- .../colorpicker/ColorWheelFlower.tsx | 216 ++++++++++++++++++ 3 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 src/components/colorpicker/ColorWheelFlower.tsx diff --git a/playground/src/App.tsx b/playground/src/App.tsx index d4f8bf3..4ee580f 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -204,17 +204,17 @@ export default function App() {
-

Color Wheel Mode

+

Flower Color Wheel Mode

- Starts in Wheel Mode: + Color Picker:

{color4()}

diff --git a/src/components/colorpicker/ColorPicker.tsx b/src/components/colorpicker/ColorPicker.tsx index 0179419..b6fc73a 100644 --- a/src/components/colorpicker/ColorPicker.tsx +++ b/src/components/colorpicker/ColorPicker.tsx @@ -14,6 +14,7 @@ import SaturationBrightness from "./SaturationBrightness"; import HueSlider from "./HueSlider"; import AlphaSlider from "./AlphaSlider"; import ColorWheel from "./ColorWheel"; +import ColorWheelFlower from "./ColorWheelFlower"; import LightnessSlider from "./LightnessSlider"; import ColorSwatches from "./ColorSwatches"; import ColorInput from "./ColorInput"; @@ -22,7 +23,7 @@ import { IComponentBaseProps } from "../types"; import { MotionDiv, motionPresets } from "../../motion"; export type { ColorFormat } from "./ColorUtils"; -export type ColorPickerMode = "picker" | "wheel"; +export type ColorPickerMode = "picker" | "wheel" | "flower"; export interface ColorPickerProps extends IComponentBaseProps { value?: string; @@ -189,6 +190,19 @@ const ColorPicker = (props: ColorPickerProps): JSX.Element => { > Wheel +
); @@ -213,6 +227,11 @@ const ColorPicker = (props: ColorPickerProps): JSX.Element => { + + + + + diff --git a/src/components/colorpicker/ColorWheelFlower.tsx b/src/components/colorpicker/ColorWheelFlower.tsx new file mode 100644 index 0000000..a1c2cad --- /dev/null +++ b/src/components/colorpicker/ColorWheelFlower.tsx @@ -0,0 +1,216 @@ +import { type JSX, createSignal, For } from "solid-js"; +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; +import { animate } from "popmotion"; +import { useColorPickerContext } from "./colorpickerContext"; +import { rgbToHsl, createColorFromHsl } from "./ColorUtils"; + +export interface ColorWheelFlowerProps { + class?: string; + className?: string; +} + +type ColorItem = { + rgb: string; + transform: string; + hue: number; + saturation: number; + lightness: number; +}; + + +const parseRgbToHsl = (rgbString: string) => { + const match = rgbString.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); + if (!match) return { hue: 0, saturation: 0, lightness: 100 }; + + const r = parseInt(match[1]); + const g = parseInt(match[2]); + const b = parseInt(match[3]); + + const hsl = rgbToHsl(r, g, b); + return { hue: hsl.h, saturation: hsl.s, lightness: hsl.l }; +}; + + +const COLORS: ColorItem[] = [ + { rgb: "rgb(245,245,61)", transform: "translateX(34.641px) translateY(-20px)", ...parseRgbToHsl("rgb(245,245,61)") }, + { rgb: "rgb(245,153,61)", transform: "translateX(20px) translateY(-34.641px)", ...parseRgbToHsl("rgb(245,153,61)") }, + { rgb: "rgb(245,61,61)", transform: "translateY(-40px)", ...parseRgbToHsl("rgb(245,61,61)") }, + { rgb: "rgb(245,61,153)", transform: "translateX(-20px) translateY(-34.641px)", ...parseRgbToHsl("rgb(245,61,153)") }, + { rgb: "rgb(245,61,245)", transform: "translateX(-34.641px) translateY(-20px)", ...parseRgbToHsl("rgb(245,61,245)") }, + { rgb: "rgb(153,61,245)", transform: "translateX(-40px)", ...parseRgbToHsl("rgb(153,61,245)") }, + { rgb: "rgb(61,61,245)", transform: "translateX(-34.641px) translateY(20px)", ...parseRgbToHsl("rgb(61,61,245)") }, + { rgb: "rgb(61,153,245)", transform: "translateX(-20px) translateY(34.641px)", ...parseRgbToHsl("rgb(61,153,245)") }, + { rgb: "rgb(61,245,245)", transform: "translateY(40px)", ...parseRgbToHsl("rgb(61,245,245)") }, + { rgb: "rgb(61,245,153)", transform: "translateX(20px) translateY(34.641px)", ...parseRgbToHsl("rgb(61,245,153)") }, + { rgb: "rgb(61,245,61)", transform: "translateX(34.641px) translateY(20px)", ...parseRgbToHsl("rgb(61,245,61)") }, + { rgb: "rgb(153,245,61)", transform: "translateX(40px)", ...parseRgbToHsl("rgb(153,245,61)") }, + { rgb: "rgb(240,217,194)", transform: "translateX(10px) translateY(-17.3205px)", ...parseRgbToHsl("rgb(240,217,194)") }, + { rgb: "rgb(240,194,217)", transform: "translateX(-10px) translateY(-17.3205px)", ...parseRgbToHsl("rgb(240,194,217)") }, + { rgb: "rgb(217,194,240)", transform: "translateX(-20px)", ...parseRgbToHsl("rgb(217,194,240)") }, + { rgb: "rgb(194,217,240)", transform: "translateX(-10px) translateY(17.3205px)", ...parseRgbToHsl("rgb(194,217,240)") }, + { rgb: "rgb(194,240,217)", transform: "translateX(10px) translateY(17.3205px)", ...parseRgbToHsl("rgb(194,240,217)") }, + { rgb: "rgb(217,240,194)", transform: "translateX(20px)", ...parseRgbToHsl("rgb(217,240,194)") }, + { rgb: "rgb(255,255,255)", transform: "none", ...parseRgbToHsl("rgb(255,255,255)") }, +]; + + +const RAINBOW_GRADIENT = `conic-gradient( + from 0deg, + rgb(245,61,61) 0deg, + rgb(245,153,61) 30deg, + rgb(245,245,61) 60deg, + rgb(61,245,61) 120deg, + rgb(61,245,245) 180deg, + rgb(61,61,245) 240deg, + rgb(245,61,245) 300deg, + rgb(245,61,61) 360deg +)`; + +const ColorWheelFlower = (props: ColorWheelFlowerProps): JSX.Element => { + const context = useColorPickerContext(); + const [selectedIndex, setSelectedIndex] = createSignal(null); + const [hoveredIndex, setHoveredIndex] = createSignal(null); + + + const handleDotClick = (index: number) => { + if (context.disabled()) return; + + if (selectedIndex() === index) { + setSelectedIndex(null); + const whiteColor = createColorFromHsl(0, 0, 100, context.color().hsl.a); + context.onChange(whiteColor); + } else { + setSelectedIndex(index); + const color = COLORS[index]; + + const newColor = createColorFromHsl( + color.hue, + color.saturation, + color.lightness, + context.color().hsl.a + ); + context.onChange(newColor); + } + }; + + + const handleDotHover = (el: HTMLElement, isEntering: boolean) => { + animate({ + from: { scale: isEntering ? 1 : 1.5 }, + to: { scale: isEntering ? 1.5 : 1 }, + type: "spring", + stiffness: 400, + damping: 25, + onUpdate: (latest: any) => { + if (el) { + el.style.transform = el.dataset.baseTransform + ` scale(${latest.scale})`; + } + }, + }); + }; + + const containerClasses = () => + twMerge( + "relative w-[140px] h-[140px] flex items-center justify-center", + clsx({ + "opacity-50 cursor-not-allowed": context.disabled(), + }), + props.class, + props.className + ); + + + const outerRingBackground = () => { + const idx = selectedIndex(); + return idx === null + ? RAINBOW_GRADIENT + : `conic-gradient(${COLORS[idx].rgb}, ${COLORS[idx].rgb})`; + }; + + return ( +
+
+
+
+
+ +
+ + {(item, index) => { + let buttonRef: HTMLButtonElement | undefined; + + return ( + + ); + }} + +
+
+ ); +}; + +export default ColorWheelFlower; From 8941546e1802697d26fdad8c20132245253b9751 Mon Sep 17 00:00:00 2001 From: louis phi Date: Tue, 20 Jan 2026 22:44:12 +0700 Subject: [PATCH 2/2] support flower color wheel mode option --- src/components/colorpicker/ColorWheelFlower.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/components/colorpicker/ColorWheelFlower.tsx b/src/components/colorpicker/ColorWheelFlower.tsx index a1c2cad..f9a82a8 100644 --- a/src/components/colorpicker/ColorWheelFlower.tsx +++ b/src/components/colorpicker/ColorWheelFlower.tsx @@ -1,7 +1,6 @@ import { type JSX, createSignal, For } from "solid-js"; import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -import { animate } from "popmotion"; import { useColorPickerContext } from "./colorpickerContext"; import { rgbToHsl, createColorFromHsl } from "./ColorUtils"; @@ -96,18 +95,7 @@ const ColorWheelFlower = (props: ColorWheelFlowerProps): JSX.Element => { const handleDotHover = (el: HTMLElement, isEntering: boolean) => { - animate({ - from: { scale: isEntering ? 1 : 1.5 }, - to: { scale: isEntering ? 1.5 : 1 }, - type: "spring", - stiffness: 400, - damping: 25, - onUpdate: (latest: any) => { - if (el) { - el.style.transform = el.dataset.baseTransform + ` scale(${latest.scale})`; - } - }, - }); + }; const containerClasses = () =>