refactor: refactor color tool to match calculate blueprint

Rewrites all color components to use the glass panel design language,
fixed-height two-panel layout, and tab-based navigation.

- ColorManipulation: lg:grid-cols-5 split — left 2/5 shows ColorPicker
  + ColorInfo always; right 3/5 has Info/Adjust/Harmony/Gradient tabs;
  mobile 'Pick | Explore' switcher
- ColorPicker: removes shadcn Input/Label, native input with dynamic
  contrast color matching the picked hue
- ColorInfo: removes shadcn Button, native copy buttons on hover,
  metadata chips with bg-primary/5 background
- ManipulationPanel: keeps Slider, replaces Button with glass action
  buttons, tighter spacing and muted labels
- ExportMenu: keeps Select, replaces Buttons with glass action buttons,
  code preview in dark terminal box (#06060e)
- ColorSwatch: rectangular full-width design for palette grids,
  hover reveals copy icon, hex label at bottom
- PaletteGrid: denser grid (4→5 cols), smaller swatch height

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 08:15:33 +01:00
parent 50dc009fdf
commit 0727ec7675
7 changed files with 496 additions and 652 deletions

View File

@@ -1,8 +1,6 @@
'use client';
import { HexColorPicker } from 'react-colorful';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils/cn';
import { hexToRgb } from '@/lib/color/utils/color';
@@ -13,45 +11,23 @@ interface ColorPickerProps {
}
export function ColorPicker({ color, onChange, className }: ColorPickerProps) {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// Allow partial input while typing
onChange(value);
};
// Determine text color based on background brightness
const getContrastColor = (hex: string) => {
const rgb = hexToRgb(hex);
if (!rgb) return 'inherit';
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
return brightness > 128 ? '#000000' : '#ffffff';
};
const textColor = getContrastColor(color);
const rgb = hexToRgb(color);
const brightness = rgb ? (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 : 0;
const textColor = brightness > 128 ? '#000000' : '#ffffff';
const borderColor = brightness > 128 ? 'rgba(0,0,0,0.12)' : 'rgba(255,255,255,0.2)';
return (
<div className={cn('flex flex-col items-center justify-center space-y-3', className)}>
<div className="w-full max-w-[200px] space-y-3">
<HexColorPicker color={color} onChange={onChange} className="!w-full" />
<div className="space-y-1.5">
<Label htmlFor="color-input" className="text-xs">
Hex Value
</Label>
<Input
id="color-input"
type="text"
value={color}
onChange={handleInputChange}
placeholder="#ff0099"
className="font-mono text-xs transition-colors duration-200"
style={{
backgroundColor: color,
color: textColor,
borderColor: textColor === '#000000' ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.2)'
}}
/>
</div>
</div>
<div className={cn('flex flex-col gap-3', className)}>
<HexColorPicker color={color} onChange={onChange} className="!w-full" />
<input
type="text"
value={color}
onChange={(e) => onChange(e.target.value)}
placeholder="#ff0099"
className="w-full font-mono text-xs rounded-lg px-3 py-2 outline-none transition-colors duration-200 border"
style={{ backgroundColor: color, color: textColor, borderColor }}
spellCheck={false}
/>
</div>
);
}