Files
kit-ui/components/color/ColorInfo.tsx

92 lines
3.4 KiB
TypeScript

'use client';
import { ColorInfo as ColorInfoType } from '@/lib/color/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 formatOkLab = (oklab: { l: number; a: number; b: number }) => {
return `oklab(${(oklab.l * 100).toFixed(1)}% ${oklab.a.toFixed(3)} ${oklab.b.toFixed(3)})`;
};
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: formatOkLab(info.oklab) },
];
return (
<div className={cn('space-y-3', className)}>
<div className="grid grid-cols-1 gap-1.5">
{formats.map((format) => (
<div
key={format.label}
className="flex items-center justify-between px-3 py-2 bg-muted/50 rounded-md group"
>
<div className="flex items-baseline gap-2 min-w-0 flex-1">
<span className="text-[10px] uppercase tracking-wider text-muted-foreground w-10 shrink-0">{format.label}</span>
<span className="font-mono text-xs truncate">{format.value}</span>
</div>
<Button
size="icon-xs"
variant="ghost"
onClick={() => copyToClipboard(format.value, format.label)}
aria-label={`Copy ${format.label} value`}
className="opacity-0 group-hover:opacity-100 transition-opacity"
>
<Copy className="h-3 w-3" />
</Button>
</div>
))}
</div>
<div className="grid grid-cols-3 gap-3 pt-2 border-t text-xs">
<div>
<div className="text-muted-foreground mb-0.5">Brightness</div>
<div className="font-medium">{(info.brightness * 100).toFixed(1)}%</div>
</div>
<div>
<div className="text-muted-foreground mb-0.5">Luminance</div>
<div className="font-medium">{(info.luminance * 100).toFixed(1)}%</div>
</div>
<div>
<div className="text-muted-foreground mb-0.5">{info.name && typeof info.name === 'string' ? 'Name' : 'Type'}</div>
<div className="font-medium">{info.name && typeof info.name === 'string' ? info.name : (info.is_light ? 'Light' : 'Dark')}</div>
</div>
</div>
</div>
);
}