Ensure consistent spacing and alignment between header, main content, and footer: - Update Navbar to use max-w-7xl and px-8 (matching Footer) - Standardize all page containers to use px-8 py-12 padding - Change home page from max-w-5xl to max-w-7xl for consistency - Update all 12 pages to use consistent padding (px-8 py-12 instead of p-8) This creates a unified visual alignment across the entire application, with all content sections (navbar, main, footer) using the same max-width (7xl) and horizontal padding (px-8). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
194 lines
6.7 KiB
TypeScript
194 lines
6.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Select } from '@/components/ui/select';
|
|
import { Input } from '@/components/ui/input';
|
|
import { PaletteGrid } from '@/components/color/PaletteGrid';
|
|
import { ExportMenu } from '@/components/tools/ExportMenu';
|
|
import { useLighten, useDarken, useSaturate, useDesaturate, useRotate } from '@/lib/api/queries';
|
|
import { Loader2, Upload, Download } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
|
|
type Operation = 'lighten' | 'darken' | 'saturate' | 'desaturate' | 'rotate';
|
|
|
|
export default function BatchPage() {
|
|
const [inputColors, setInputColors] = useState('');
|
|
const [operation, setOperation] = useState<Operation>('lighten');
|
|
const [amount, setAmount] = useState(0.2);
|
|
const [outputColors, setOutputColors] = useState<string[]>([]);
|
|
|
|
const lightenMutation = useLighten();
|
|
const darkenMutation = useDarken();
|
|
const saturateMutation = useSaturate();
|
|
const desaturateMutation = useDesaturate();
|
|
const rotateMutation = useRotate();
|
|
|
|
const parseColors = (text: string): string[] => {
|
|
// Parse colors from text (one per line, or comma-separated)
|
|
return text
|
|
.split(/[\n,]/)
|
|
.map((c) => c.trim())
|
|
.filter((c) => c.length > 0 && c.match(/^#?[0-9a-fA-F]{3,8}$/));
|
|
};
|
|
|
|
const handleProcess = async () => {
|
|
const colors = parseColors(inputColors);
|
|
|
|
if (colors.length === 0) {
|
|
toast.error('No valid colors found');
|
|
return;
|
|
}
|
|
|
|
if (colors.length > 100) {
|
|
toast.error('Maximum 100 colors allowed');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let result;
|
|
|
|
switch (operation) {
|
|
case 'lighten':
|
|
result = await lightenMutation.mutateAsync({ colors, amount });
|
|
break;
|
|
case 'darken':
|
|
result = await darkenMutation.mutateAsync({ colors, amount });
|
|
break;
|
|
case 'saturate':
|
|
result = await saturateMutation.mutateAsync({ colors, amount });
|
|
break;
|
|
case 'desaturate':
|
|
result = await desaturateMutation.mutateAsync({ colors, amount });
|
|
break;
|
|
case 'rotate':
|
|
result = await rotateMutation.mutateAsync({ colors, amount: amount * 360 });
|
|
break;
|
|
}
|
|
|
|
// Extract output colors from the result
|
|
const processed = result.colors.map((c) => c.output);
|
|
setOutputColors(processed);
|
|
toast.success(`Processed ${processed.length} colors`);
|
|
} catch (error) {
|
|
toast.error('Failed to process colors');
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
const isPending =
|
|
lightenMutation.isPending ||
|
|
darkenMutation.isPending ||
|
|
saturateMutation.isPending ||
|
|
desaturateMutation.isPending ||
|
|
rotateMutation.isPending;
|
|
|
|
return (
|
|
<div className="min-h-screen px-8 py-12">
|
|
<div className="max-w-7xl mx-auto space-y-8">
|
|
<div>
|
|
<h1 className="text-4xl font-bold mb-2">Batch Operations</h1>
|
|
<p className="text-muted-foreground">
|
|
Process multiple colors at once with manipulation operations
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
{/* Input */}
|
|
<div className="space-y-6">
|
|
<div className="p-6 border rounded-lg bg-card">
|
|
<h2 className="text-xl font-semibold mb-4">Input Colors</h2>
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
Enter colors (one per line or comma-separated). Supports hex format.
|
|
</p>
|
|
|
|
<textarea
|
|
value={inputColors}
|
|
onChange={(e) => setInputColors(e.target.value)}
|
|
placeholder="#ff0099, #00ff99, #9900ff #ff5533 #3355ff"
|
|
className="w-full h-48 p-3 border rounded-lg bg-background font-mono text-sm"
|
|
/>
|
|
|
|
<p className="text-xs text-muted-foreground mt-2">
|
|
{parseColors(inputColors).length} valid colors found
|
|
</p>
|
|
</div>
|
|
|
|
<div className="p-6 border rounded-lg bg-card">
|
|
<h2 className="text-xl font-semibold mb-4">Operation</h2>
|
|
<div className="space-y-4">
|
|
<Select
|
|
label="Operation"
|
|
value={operation}
|
|
onChange={(e) => setOperation(e.target.value as Operation)}
|
|
>
|
|
<option value="lighten">Lighten</option>
|
|
<option value="darken">Darken</option>
|
|
<option value="saturate">Saturate</option>
|
|
<option value="desaturate">Desaturate</option>
|
|
<option value="rotate">Rotate Hue</option>
|
|
</Select>
|
|
|
|
<div>
|
|
<label className="text-sm font-medium mb-2 block">
|
|
Amount: {operation === 'rotate' ? (amount * 360).toFixed(0) + '°' : (amount * 100).toFixed(0) + '%'}
|
|
</label>
|
|
<Input
|
|
type="range"
|
|
min="0"
|
|
max="1"
|
|
step="0.01"
|
|
value={amount}
|
|
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={handleProcess}
|
|
disabled={isPending || parseColors(inputColors).length === 0}
|
|
className="w-full"
|
|
>
|
|
{isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Upload className="mr-2 h-4 w-4" />
|
|
Process Colors
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Output */}
|
|
<div className="space-y-6">
|
|
{outputColors.length > 0 ? (
|
|
<>
|
|
<div className="p-6 border rounded-lg bg-card">
|
|
<h2 className="text-xl font-semibold mb-4">
|
|
Output Colors ({outputColors.length})
|
|
</h2>
|
|
<PaletteGrid colors={outputColors} />
|
|
</div>
|
|
|
|
<div className="p-6 border rounded-lg bg-card">
|
|
<ExportMenu colors={outputColors} />
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="p-12 border rounded-lg bg-card text-center text-muted-foreground">
|
|
<Download className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
|
<p>Enter colors and click Process to see results</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|