Files
kit-ui/app/(app)/pastel/page.tsx

218 lines
8.0 KiB
TypeScript
Raw Normal View History

'use client';
import { useState, useEffect, Suspense } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { ColorPicker } from '@/components/pastel/ColorPicker';
import { ColorDisplay } from '@/components/pastel/ColorDisplay';
import { ColorInfo } from '@/components/pastel/ColorInfo';
import { ManipulationPanel } from '@/components/pastel/ManipulationPanel';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useColorInfo } from '@/lib/pastel/api/queries';
import { useColorHistory } from '@/lib/pastel/stores/historyStore';
import { Loader2, Share2, History, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
function PlaygroundContent() {
const searchParams = useSearchParams();
const router = useRouter();
const [color, setColor] = useState(() => {
// Initialize from URL if available
const urlColor = searchParams.get('color');
return urlColor ? `#${urlColor.replace('#', '')}` : '#ff0099';
});
const { data, isLoading, isError, error } = useColorInfo({
colors: [color],
});
const colorInfo = data?.colors[0];
// Color history
const { history, addColor, removeColor, clearHistory, getRecent } = useColorHistory();
const recentColors = getRecent(10);
// Update URL when color changes
useEffect(() => {
const hex = color.replace('#', '');
if (hex.length === 6 || hex.length === 3) {
router.push(`/pastel?color=${hex}`, { scroll: false });
}
}, [color, router]);
// Debounced history update
useEffect(() => {
const timer = setTimeout(() => {
const hex = color.replace('#', '');
// Only add valid hex colors to history
if (hex.length === 6 || hex.length === 3) {
addColor(color);
}
}, 1000); // Wait 1 second before adding to history
return () => clearTimeout(timer);
}, [color, addColor]);
// Share color via URL
const handleShare = () => {
const url = `${window.location.origin}/pastel?color=${color.replace('#', '')}`;
navigator.clipboard.writeText(url);
toast.success('Link copied to clipboard!');
};
// Copy color to clipboard
const handleCopyColor = () => {
navigator.clipboard.writeText(color);
toast.success('Color copied to clipboard!');
};
// Random color generation
const handleRandomColor = () => {
const randomHex = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
setColor(randomHex);
};
return (
<div className="min-h-screen py-12">
<div className="max-w-7xl mx-auto px-8 space-y-8">
<div>
<h1 className="text-4xl font-bold mb-2">Pastel</h1>
<p className="text-muted-foreground">
Interactive color manipulation and analysis tool
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Left Column: Color Picker and Display */}
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium">Color Picker</CardTitle>
</CardHeader>
<CardContent>
<ColorPicker color={color} onChange={setColor} />
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<CardTitle className="text-sm font-medium">Preview</CardTitle>
<Button onClick={handleShare} variant="outline" size="sm">
<Share2 className="h-4 w-4 mr-2" />
Share
</Button>
</CardHeader>
<CardContent>
<div className="flex justify-center">
<ColorDisplay color={color} size="xl" />
</div>
</CardContent>
</Card>
{recentColors.length > 0 && (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<div className="flex items-center gap-2">
<History className="h-5 w-5" />
<CardTitle className="text-sm font-medium">Recent Colors</CardTitle>
</div>
<Button
onClick={clearHistory}
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-foreground"
>
Clear
</Button>
</CardHeader>
<CardContent>
<div className="grid grid-cols-5 gap-2">
{recentColors.map((entry) => (
<div
key={entry.timestamp}
className="group relative aspect-square rounded-lg border-2 border-border hover:border-primary transition-all hover:scale-110 cursor-pointer"
style={{ backgroundColor: entry.color }}
onClick={() => setColor(entry.color)}
title={entry.color}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
setColor(entry.color);
}
}}
>
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-black/30 rounded-lg">
<button
onClick={(e) => {
e.stopPropagation();
removeColor(entry.color);
toast.success('Color removed from history');
}}
className="p-1 bg-destructive rounded-full hover:bg-destructive/80"
aria-label="Remove color"
>
<X className="h-3 w-3 text-destructive-foreground" />
</button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
{/* Right Column: Color Information */}
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium">Color Information</CardTitle>
</CardHeader>
<CardContent>
{isLoading && (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)}
{isError && (
<div className="p-4 bg-destructive/10 text-destructive rounded-lg">
<p className="font-medium">Error loading color information</p>
<p className="text-sm mt-1">{error?.message || 'Unknown error'}</p>
</div>
)}
{colorInfo && <ColorInfo info={colorInfo} />}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium">Color Manipulation</CardTitle>
</CardHeader>
<CardContent>
<ManipulationPanel color={color} onColorChange={setColor} />
</CardContent>
</Card>
</div>
</div>
</div>
</div>
);
}
export default function PlaygroundPage() {
return (
<Suspense fallback={
<div className="min-h-screen py-12">
<div className="max-w-7xl mx-auto px-8 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
</div>
}>
<PlaygroundContent />
</Suspense>
);
}