Fix color picker syncing, update components to tailwind ui, display all formats at once

This commit is contained in:
2025-02-10 11:45:17 -05:00
parent 4261e6f436
commit 7ae2594a66
5 changed files with 408 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
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
@@ -32,14 +33,30 @@ type OKLCH = {
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 [format, setFormat] = useState<ColorFormat>('hex')
const [displayValue, setDisplayValue] = useState(color)
const [closestTailwind, setClosestTailwind] = useState<{ name: string; hex: string; oklch: string }>({
const [colorValues, setColorValues] = useState<ColorValues>({
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)'
oklch: 'oklch(0.585 0.233 277.117)',
isExactMatch: true
})
useEffect(() => {
@@ -50,35 +67,22 @@ export function ColorPicker() {
const closest = findClosestTailwindColor(color)
setClosestTailwind(closest)
switch (format) {
case 'hex':
setDisplayValue(color)
break
case 'rgb':
setDisplayValue(`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`)
break
case 'hsl': {
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
setDisplayValue(`hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`)
break
}
case 'hsb': {
const hsb = rgbToHsb(rgb.r, rgb.g, rgb.b)
setDisplayValue(`hsb(${hsb.h}, ${hsb.s}%, ${hsb.b}%)`)
break
}
case 'oklch': {
const oklch = rgbToOklch(rgb.r, rgb.g, rgb.b)
setDisplayValue(oklchToString(oklch.l, oklch.c, oklch.h))
break
}
case 'tailwind':
setDisplayValue(closest.name)
break
}
}, [color, format])
// 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)
const handleInputChange = (value: string) => {
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)) {
@@ -118,68 +122,149 @@ export function ColorPicker() {
}
setColor(newHex)
setDisplayValue(value)
setColorValues(prev => ({
...prev,
[format]: value
}))
}
return (
<div className="w-full max-w-7xl mx-auto p-6">
<h1 className="text-3xl font-bold text-center mb-8">Color Picker</h1>
<div className="text-center space-y-2 mb-8">
<h1 className="text-4xl font-bold tracking-tight text-gray-900">Color Picker</h1>
<p className="text-lg text-gray-600">Find and convert colors in different formats</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-[1fr_2fr] gap-8">
{/* Left Column: Color Picker and Input */}
<div className="space-y-6">
<div className="flex flex-col items-center gap-4 p-6 bg-white rounded-xl shadow-sm">
{/* Color Preview */}
<div
className="w-32 h-32 rounded-lg shadow-lg border border-zinc-200"
style={{ backgroundColor: color }}
/>
<div className="text-sm text-zinc-600 text-center">
<div>Closest Tailwind: <span className="font-medium">{closestTailwind.name}</span></div>
<div className="text-xs mt-1 text-zinc-400">
{format === 'oklch' ? closestTailwind.oklch : closestTailwind.hex}
<div className="bg-white rounded-xl shadow-sm ring-1 ring-gray-900/5 p-6">
<div className="space-y-6">
{/* Color Preview */}
<div className="flex flex-col items-center gap-4">
<div
className="w-32 h-32 rounded-lg shadow-lg ring-1 ring-gray-900/5"
style={{ backgroundColor: color }}
/>
<div className="text-sm text-gray-600 text-center">
<div className="flex items-center justify-center gap-1.5">
<SwatchIcon className="h-4 w-4" />
{closestTailwind.isExactMatch ? (
<span>Tailwind: <span className="font-medium text-gray-900">{closestTailwind.name}</span></span>
) : (
<span>Closest Tailwind: <span className="font-medium text-gray-900">{closestTailwind.name}</span></span>
)}
</div>
</div>
</div>
</div>
{/* Color Picker */}
<div className="w-full max-w-[200px]">
<HexColorPicker color={color} onChange={setColor} />
</div>
{/* Color Picker */}
<div className="w-full max-w-[200px] mx-auto">
<HexColorPicker color={color} onChange={setColor} />
</div>
{/* Format Buttons */}
<div className="flex flex-wrap gap-2 justify-center w-full">
{(['hex', 'rgb', 'hsl', 'hsb', 'oklch', 'tailwind'] as const).map((f) => (
<button
key={f}
onClick={() => setFormat(f)}
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors
${format === f
? 'bg-indigo-500 text-white'
: 'bg-zinc-100 text-zinc-700 hover:bg-zinc-200'
}`}
>
{f.toUpperCase()}
</button>
))}
</div>
{/* Color Format Inputs */}
<div className="space-y-3">
<div>
<label htmlFor="hex-value" className="block text-sm font-medium text-gray-700 mb-1">
HEX
</label>
<input
type="text"
id="hex-value"
value={colorValues.hex}
onChange={(e) => 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"
/>
</div>
{/* Color Input */}
<div className="w-full">
<input
type="text"
value={displayValue}
onChange={(e) => handleInputChange(e.target.value)}
className="w-full px-4 py-2 rounded-md border border-zinc-300 focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="Enter color value..."
/>
<div>
<label htmlFor="rgb-value" className="block text-sm font-medium text-gray-700 mb-1">
RGB
</label>
<input
type="text"
id="rgb-value"
value={colorValues.rgb}
onChange={(e) => 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"
/>
</div>
<div>
<label htmlFor="hsl-value" className="block text-sm font-medium text-gray-700 mb-1">
HSL
</label>
<input
type="text"
id="hsl-value"
value={colorValues.hsl}
onChange={(e) => 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"
/>
</div>
<div>
<label htmlFor="hsb-value" className="block text-sm font-medium text-gray-700 mb-1">
HSB
</label>
<input
type="text"
id="hsb-value"
value={colorValues.hsb}
onChange={(e) => 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"
/>
</div>
<div>
<label htmlFor="oklch-value" className="block text-sm font-medium text-gray-700 mb-1">
OKLCH
</label>
<input
type="text"
id="oklch-value"
value={colorValues.oklch}
onChange={(e) => 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"
/>
</div>
<div>
<label htmlFor="tailwind-value" className="block text-sm font-medium text-gray-700 mb-1">
Tailwind
</label>
<input
type="text"
id="tailwind-value"
value={colorValues.tailwind}
readOnly
className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 bg-gray-50 shadow-sm ring-1 ring-inset ring-gray-300 sm:text-sm sm:leading-6"
/>
</div>
</div>
</div>
</div>
</div>
{/* Right Column: Tailwind Colors */}
<div className="bg-white rounded-xl shadow-sm p-4">
<TailwindColors onColorSelect={setColor} selectedColor={color} format={format} />
<div className="bg-white rounded-xl shadow-sm ring-1 ring-gray-900/5 p-4">
<TailwindColors
onColorSelect={(selectedColor) => {
// 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}
/>
</div>
</div>
</div>