2026-02-22 21:35:53 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState, useMemo } from 'react';
|
|
|
|
|
import { ColorSwatch } from '@/components/pastel/color/ColorSwatch';
|
2026-02-24 16:20:35 +01:00
|
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select';
|
2026-02-22 21:35:53 +01:00
|
|
|
import { useNamedColors } from '@/lib/pastel/api/queries';
|
|
|
|
|
import { Loader2 } from 'lucide-react';
|
2026-02-24 10:45:00 +01:00
|
|
|
import { parse_color } from '@valknarthing/pastel-wasm';
|
2026-02-22 21:35:53 +01:00
|
|
|
|
|
|
|
|
export default function NamedColorsPage() {
|
|
|
|
|
const [search, setSearch] = useState('');
|
|
|
|
|
const [sortBy, setSortBy] = useState<'name' | 'hue'>('name');
|
|
|
|
|
|
|
|
|
|
const { data, isLoading, isError } = useNamedColors();
|
|
|
|
|
|
|
|
|
|
const filteredColors = useMemo(() => {
|
|
|
|
|
if (!data) return [];
|
|
|
|
|
|
|
|
|
|
let colors = data.colors.filter(
|
|
|
|
|
(color) =>
|
|
|
|
|
color.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
|
|
|
color.hex.toLowerCase().includes(search.toLowerCase())
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (sortBy === 'name') {
|
|
|
|
|
colors.sort((a, b) => a.name.localeCompare(b.name));
|
2026-02-24 10:45:00 +01:00
|
|
|
} else if (sortBy === 'hue') {
|
|
|
|
|
colors.sort((a, b) => {
|
|
|
|
|
const infoA = parse_color(a.hex);
|
|
|
|
|
const infoB = parse_color(b.hex);
|
|
|
|
|
return infoA.hsl[0] - infoB.hsl[0];
|
|
|
|
|
});
|
2026-02-22 21:35:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return colors;
|
|
|
|
|
}, [data, search, sortBy]);
|
|
|
|
|
|
|
|
|
|
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">Named Colors</h1>
|
|
|
|
|
<p className="text-muted-foreground">
|
|
|
|
|
Explore 148 CSS/X11 named colors
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Search and Filters */}
|
|
|
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<Input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Search by name or hex..."
|
|
|
|
|
value={search}
|
|
|
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="w-full sm:w-48">
|
2026-02-24 16:20:35 +01:00
|
|
|
<Select value={sortBy} onValueChange={(value) => setSortBy(value as 'name' | 'hue')}>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="Sort by..." />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="name">Sort by Name</SelectItem>
|
|
|
|
|
<SelectItem value="hue">Sort by Hue</SelectItem>
|
|
|
|
|
</SelectContent>
|
2026-02-22 21:35:53 +01:00
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Colors Grid */}
|
|
|
|
|
<div className="p-6 border rounded-lg bg-card">
|
|
|
|
|
{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="text-center py-12 text-destructive">
|
|
|
|
|
Failed to load named colors
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{filteredColors.length > 0 && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mb-4 text-sm text-muted-foreground">
|
|
|
|
|
Showing {filteredColors.length} colors
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
|
|
|
|
|
{filteredColors.map((color) => (
|
|
|
|
|
<div key={color.name} className="flex flex-col items-center gap-2">
|
|
|
|
|
<ColorSwatch color={color.hex} showLabel={false} />
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<div className="text-sm font-medium">{color.name}</div>
|
|
|
|
|
<div className="text-xs font-mono text-muted-foreground">
|
|
|
|
|
{color.hex}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{filteredColors.length === 0 && !isLoading && !isError && (
|
|
|
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
|
|
|
No colors match your search
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|