Add react color picker, show tailwind colors all the time

This commit is contained in:
2025-02-10 10:29:00 -05:00
parent db5fb7ca61
commit 1d03a56c70
5 changed files with 393 additions and 72 deletions

View File

@@ -1,4 +1,7 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { HexColorPicker } from 'react-colorful'
import { hexToRgb, rgbToHex, rgbToHsl, hslToRgb, rgbToHsb, hsbToRgb, findClosestTailwindColor } from '../utils/colors'
import { TailwindColors } from './TailwindColors'
type RGB = {
r: number
@@ -23,52 +26,134 @@ type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'hsb' | 'tailwind'
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 }>({ name: 'indigo-500', hex: '#6366F1' })
useEffect(() => {
const rgb = hexToRgb(color)
if (!rgb) return
// Always update closest Tailwind color
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 'tailwind':
setDisplayValue(closest.name)
break
}
}, [color, format])
const handleInputChange = (value: string) => {
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)
}
}
}
setColor(newHex)
setDisplayValue(value)
}
return (
<div className="w-full max-w-2xl mx-auto p-6 space-y-6">
<div className="flex flex-col items-center gap-6">
<h1 className="text-3xl font-bold">Color Picker</h1>
{/* Color Preview */}
<div
className="w-32 h-32 rounded-lg shadow-lg border border-zinc-200"
style={{ backgroundColor: color }}
/>
{/* Color Input */}
<div className="w-full max-w-md">
<div className="flex gap-2 mb-4">
{(['hex', 'rgb', 'hsl', 'hsb', '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>
<div className="relative">
<input
type="text"
value={color}
onChange={(e) => setColor(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..."
/>
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
className="absolute right-2 top-1/2 -translate-y-1/2 w-8 h-8 p-0 border-0 rounded-md cursor-pointer"
<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="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">
Closest Tailwind: <span className="font-medium">{closestTailwind.name}</span>
</div>
{/* Color Picker */}
<div className="w-full max-w-[200px]">
<HexColorPicker color={color} onChange={setColor} />
</div>
{/* Format Buttons */}
<div className="flex flex-wrap gap-2 justify-center w-full">
{(['hex', 'rgb', 'hsl', 'hsb', '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 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>
</div>
</div>
{/* Right Column: Tailwind Colors */}
<div className="bg-white rounded-xl shadow-sm p-4">
<TailwindColors onColorSelect={setColor} selectedColor={color} />
</div>
</div>
</div>
)