import { useState, useEffect } from 'react' import { HexColorPicker } from 'react-colorful' import { SwatchIcon } from '@heroicons/react/24/outline' import { hexToRgb, rgbToHex, rgbToHsl, hslToRgb, rgbToHsb, hsbToRgb, findClosestTailwindColor, rgbToOklch, oklchToRgb, parseOklch, oklchToString } from '../utils/colors' import { TailwindColors } from './TailwindColors' type RGB = { r: number g: number b: number } type HSL = { h: number s: number l: number } type HSB = { h: number s: number b: number } type OKLCH = { l: number c: number h: number } type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'hsb' | 'oklch' | 'tailwind' type ColorValues = { hex: string rgb: string hsl: string hsb: string oklch: string tailwind: string } export function ColorPicker() { const [color, setColor] = useState('#6366F1') // Default to indigo-500 const [colorValues, setColorValues] = useState({ hex: '#6366F1', rgb: 'rgb(99, 102, 241)', hsl: 'hsl(239, 84%, 67%)', hsb: 'hsb(239, 59%, 95%)', oklch: 'oklch(0.585 0.233 277.117)', tailwind: 'indigo-500' }) const [closestTailwind, setClosestTailwind] = useState<{ name: string; hex: string; oklch: string; isExactMatch: boolean }>({ name: 'indigo-500', hex: '#6366F1', oklch: 'oklch(0.585 0.233 277.117)', isExactMatch: true }) useEffect(() => { const rgb = hexToRgb(color) if (!rgb) return // Always update closest Tailwind color const closest = findClosestTailwindColor(color) setClosestTailwind(closest) // Update all color format values const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b) const hsb = rgbToHsb(rgb.r, rgb.g, rgb.b) const oklch = rgbToOklch(rgb.r, rgb.g, rgb.b) setColorValues({ hex: color, rgb: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`, hsl: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`, hsb: `hsb(${hsb.h}, ${hsb.s}%, ${hsb.b}%)`, oklch: oklchToString(oklch.l, oklch.c, oklch.h), tailwind: closest.name }) }, [color]) const handleInputChange = (value: string, format: ColorFormat) => { let newHex = color if (format === 'hex' && /^#[0-9A-Fa-f]{6}$/.test(value)) { newHex = value } else if (format === 'rgb') { const match = value.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) if (match) { const [_, r, g, b] = match.map(Number) if (r <= 255 && g <= 255 && b <= 255) { newHex = rgbToHex(r, g, b) } } } else if (format === 'hsl') { const match = value.match(/^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/) if (match) { const [_, h, s, l] = match.map(Number) if (h <= 360 && s <= 100 && l <= 100) { const rgb = hslToRgb(h, s, l) newHex = rgbToHex(rgb.r, rgb.g, rgb.b) } } } else if (format === 'hsb') { const match = value.match(/^hsb\((\d+),\s*(\d+)%,\s*(\d+)%\)$/) if (match) { const [_, h, s, b] = match.map(Number) if (h <= 360 && s <= 100 && b <= 100) { const rgb = hsbToRgb(h, s, b) newHex = rgbToHex(rgb.r, rgb.g, rgb.b) } } } else if (format === 'oklch') { const oklch = parseOklch(value) if (oklch) { const rgb = oklchToRgb(oklch.l, oklch.c, oklch.h) newHex = rgbToHex(rgb.r, rgb.g, rgb.b) } } setColor(newHex) setColorValues(prev => ({ ...prev, [format]: value })) } return (

Color Picker

Find and convert colors in different formats

{/* Left Column: Color Picker and Input */}
{/* Color Preview */}
{closestTailwind.isExactMatch ? ( Tailwind: {closestTailwind.name} ) : ( Closest Tailwind: {closestTailwind.name} )}
{/* Color Picker */}
{/* Color Format Inputs */}
handleInputChange(e.target.value, 'hex')} className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
handleInputChange(e.target.value, 'rgb')} className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
handleInputChange(e.target.value, 'hsl')} className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
handleInputChange(e.target.value, 'hsb')} className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
handleInputChange(e.target.value, 'oklch')} className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
{/* Right Column: Tailwind Colors */}
{ // Always set the hex color for the color picker if (selectedColor.startsWith('oklch')) { const oklch = parseOklch(selectedColor) if (oklch) { const rgb = oklchToRgb(oklch.l, oklch.c, oklch.h) setColor(rgbToHex(rgb.r, rgb.g, rgb.b)) } } else { setColor(selectedColor) } }} selectedColor={color} />
) }