feat: implement color playground with interactive picker and info display
Add complete color manipulation interface:
**Color Components:**
- ColorPicker - Interactive hex color picker with text input
- Uses react-colorful for visual selection
- Manual input support for any color format
- Real-time updates
- ColorDisplay - Large color preview swatch
- Configurable sizes (sm, md, lg, xl)
- Optional border styling
- Accessible with ARIA labels
- ColorInfo - Comprehensive color information display
- Shows all formats: Hex, RGB, HSL, Lab, OkLab
- Copy to clipboard functionality for each format
- Displays brightness, luminance, light/dark type
- Shows named color matches
**API Integration:**
- React Query hooks for all Pastel API endpoints
- useColorInfo - Get color information
- useConvertFormat - Format conversion
- useLighten, useDarken, useSaturate, etc. - Manipulations
- useGenerateRandom, useGenerateDistinct, useGenerateGradient
- useNamedColors - Cached named colors list
- Automatic error handling and loading states
**Playground Page (/playground):**
- Two-column layout: picker + info
- Live color preview with large swatch
- Real-time API integration with loading states
- Error handling with user-friendly messages
- Quick action buttons (ready for implementation)
- Responsive grid layout
**Features:**
- Toast notifications for clipboard copy
- Loading spinners during API calls
- Error display with helpful messages
- Accessible keyboard navigation
- Smooth transitions and animations
Build successful with playground page rendering! Ready for color manipulation actions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 11:05:23 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { ColorInfo as ColorInfoType } from '@/lib/api/types';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Copy } from 'lucide-react';
|
|
|
|
|
import { toast } from 'sonner';
|
|
|
|
|
import { cn } from '@/lib/utils/cn';
|
|
|
|
|
|
|
|
|
|
interface ColorInfoProps {
|
|
|
|
|
info: ColorInfoType;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ColorInfo({ info, className }: ColorInfoProps) {
|
|
|
|
|
const copyToClipboard = (value: string, label: string) => {
|
|
|
|
|
navigator.clipboard.writeText(value);
|
|
|
|
|
toast.success(`Copied ${label} to clipboard`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatRgb = (rgb: { r: number; g: number; b: number; a?: number }) => {
|
|
|
|
|
if (rgb.a !== undefined && rgb.a < 1) {
|
|
|
|
|
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;
|
|
|
|
|
}
|
|
|
|
|
return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatHsl = (hsl: { h: number; s: number; l: number; a?: number }) => {
|
|
|
|
|
if (hsl.a !== undefined && hsl.a < 1) {
|
|
|
|
|
return `hsla(${Math.round(hsl.h)}°, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%, ${hsl.a})`;
|
|
|
|
|
}
|
|
|
|
|
return `hsl(${Math.round(hsl.h)}°, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%)`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatLab = (lab: { l: number; a: number; b: number }) => {
|
|
|
|
|
return `lab(${lab.l.toFixed(1)}, ${lab.a.toFixed(1)}, ${lab.b.toFixed(1)})`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formats = [
|
|
|
|
|
{ label: 'Hex', value: info.hex },
|
|
|
|
|
{ label: 'RGB', value: formatRgb(info.rgb) },
|
|
|
|
|
{ label: 'HSL', value: formatHsl(info.hsl) },
|
|
|
|
|
{ label: 'Lab', value: formatLab(info.lab) },
|
|
|
|
|
{ label: 'OkLab', value: formatLab(info.oklab) },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn('space-y-4', className)}>
|
|
|
|
|
<div className="grid grid-cols-1 gap-3">
|
|
|
|
|
{formats.map((format) => (
|
|
|
|
|
<div
|
|
|
|
|
key={format.label}
|
|
|
|
|
className="flex items-center justify-between p-3 bg-muted rounded-lg"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<div className="text-xs text-muted-foreground mb-1">{format.label}</div>
|
|
|
|
|
<div className="font-mono text-sm">{format.value}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
size="icon"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
onClick={() => copyToClipboard(format.value, format.label)}
|
|
|
|
|
aria-label={`Copy ${format.label} value`}
|
|
|
|
|
>
|
|
|
|
|
<Copy className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-3 pt-2 border-t">
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="text-xs text-muted-foreground">Brightness</div>
|
|
|
|
|
<div className="text-sm font-medium">{(info.brightness * 100).toFixed(1)}%</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="text-xs text-muted-foreground">Luminance</div>
|
|
|
|
|
<div className="text-sm font-medium">{(info.luminance * 100).toFixed(1)}%</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="text-xs text-muted-foreground">Type</div>
|
|
|
|
|
<div className="text-sm font-medium">{info.is_light ? 'Light' : 'Dark'}</div>
|
|
|
|
|
</div>
|
2025-11-07 13:53:23 +01:00
|
|
|
{info.name && typeof info.name === 'string' && (
|
feat: implement color playground with interactive picker and info display
Add complete color manipulation interface:
**Color Components:**
- ColorPicker - Interactive hex color picker with text input
- Uses react-colorful for visual selection
- Manual input support for any color format
- Real-time updates
- ColorDisplay - Large color preview swatch
- Configurable sizes (sm, md, lg, xl)
- Optional border styling
- Accessible with ARIA labels
- ColorInfo - Comprehensive color information display
- Shows all formats: Hex, RGB, HSL, Lab, OkLab
- Copy to clipboard functionality for each format
- Displays brightness, luminance, light/dark type
- Shows named color matches
**API Integration:**
- React Query hooks for all Pastel API endpoints
- useColorInfo - Get color information
- useConvertFormat - Format conversion
- useLighten, useDarken, useSaturate, etc. - Manipulations
- useGenerateRandom, useGenerateDistinct, useGenerateGradient
- useNamedColors - Cached named colors list
- Automatic error handling and loading states
**Playground Page (/playground):**
- Two-column layout: picker + info
- Live color preview with large swatch
- Real-time API integration with loading states
- Error handling with user-friendly messages
- Quick action buttons (ready for implementation)
- Responsive grid layout
**Features:**
- Toast notifications for clipboard copy
- Loading spinners during API calls
- Error display with helpful messages
- Accessible keyboard navigation
- Smooth transitions and animations
Build successful with playground page rendering! Ready for color manipulation actions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 11:05:23 +01:00
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="text-xs text-muted-foreground">Named</div>
|
|
|
|
|
<div className="text-sm font-medium">{info.name}</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|