'use client'; import { useState } from 'react'; import { Slider } from '@/components/ui/slider'; import { Button } from '@/components/ui/button'; import { useLighten, useDarken, useSaturate, useDesaturate, useRotate, useComplement } from '@/lib/color/api/queries'; import { toast } from 'sonner'; import { Sun, Moon, Droplets, Droplet, RotateCcw, ArrowLeftRight } from 'lucide-react'; interface ManipulationPanelProps { color: string; onColorChange: (color: string) => void; } interface ManipulationRow { label: string; icon: React.ReactNode; value: number; setValue: (v: number) => void; format: (v: number) => string; min: number; max: number; step: number; onApply: () => Promise; } export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) { const [lightenAmount, setLightenAmount] = useState(0.2); const [darkenAmount, setDarkenAmount] = useState(0.2); const [saturateAmount, setSaturateAmount] = useState(0.2); const [desaturateAmount, setDesaturateAmount] = useState(0.2); const [rotateAmount, setRotateAmount] = useState(30); const lightenMutation = useLighten(); const darkenMutation = useDarken(); const saturateMutation = useSaturate(); const desaturateMutation = useDesaturate(); const rotateMutation = useRotate(); const complementMutation = useComplement(); const isLoading = lightenMutation.isPending || darkenMutation.isPending || saturateMutation.isPending || desaturateMutation.isPending || rotateMutation.isPending || complementMutation.isPending; const handleMutation = async ( mutationFn: (params: any) => Promise, params: any, successMsg: string, errorMsg: string ) => { try { const result = await mutationFn(params); if (result.colors[0]) { onColorChange(result.colors[0].output); toast.success(successMsg); } } catch { toast.error(errorMsg); } }; const rows: ManipulationRow[] = [ { label: 'Lighten', icon: , value: lightenAmount, setValue: setLightenAmount, format: (v) => `${(v * 100).toFixed(0)}%`, min: 0, max: 1, step: 0.05, onApply: () => handleMutation( lightenMutation.mutateAsync, { colors: [color], amount: lightenAmount }, `Lightened by ${(lightenAmount * 100).toFixed(0)}%`, 'Failed to lighten color' ), }, { label: 'Darken', icon: , value: darkenAmount, setValue: setDarkenAmount, format: (v) => `${(v * 100).toFixed(0)}%`, min: 0, max: 1, step: 0.05, onApply: () => handleMutation( darkenMutation.mutateAsync, { colors: [color], amount: darkenAmount }, `Darkened by ${(darkenAmount * 100).toFixed(0)}%`, 'Failed to darken color' ), }, { label: 'Saturate', icon: , value: saturateAmount, setValue: setSaturateAmount, format: (v) => `${(v * 100).toFixed(0)}%`, min: 0, max: 1, step: 0.05, onApply: () => handleMutation( saturateMutation.mutateAsync, { colors: [color], amount: saturateAmount }, `Saturated by ${(saturateAmount * 100).toFixed(0)}%`, 'Failed to saturate color' ), }, { label: 'Desaturate', icon: , value: desaturateAmount, setValue: setDesaturateAmount, format: (v) => `${(v * 100).toFixed(0)}%`, min: 0, max: 1, step: 0.05, onApply: () => handleMutation( desaturateMutation.mutateAsync, { colors: [color], amount: desaturateAmount }, `Desaturated by ${(desaturateAmount * 100).toFixed(0)}%`, 'Failed to desaturate color' ), }, { label: 'Rotate', icon: , value: rotateAmount, setValue: setRotateAmount, format: (v) => `${v}°`, min: -180, max: 180, step: 5, onApply: () => handleMutation( rotateMutation.mutateAsync, { colors: [color], amount: rotateAmount }, `Rotated hue by ${rotateAmount}°`, 'Failed to rotate hue' ), }, ]; const handleComplement = async () => { try { const result = await complementMutation.mutateAsync([color]); if (result.colors[0]) { onColorChange(result.colors[0].output); toast.success('Generated complementary color'); } } catch { toast.error('Failed to generate complement'); } }; return (
{rows.map((row) => (
{row.icon} {row.label}
{row.format(row.value)}
row.setValue(vals[0])} className="flex-1" />
))}
); }