120 lines
2.9 KiB
TypeScript
120 lines
2.9 KiB
TypeScript
import { Badge } from "@/components/ui/badge";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export interface PaletteRole {
|
|
name: string;
|
|
dark: { oklch: string; hex: string; wcagAA: boolean };
|
|
light: { oklch: string; hex: string; wcagAA: boolean };
|
|
}
|
|
|
|
interface ThemePaletteGridProps {
|
|
palette: PaletteRole[];
|
|
variant?: "dark" | "light";
|
|
className?: string;
|
|
}
|
|
|
|
const ROLE_LABELS: Record<string, string> = {
|
|
background: "Background",
|
|
surface: "Surface",
|
|
overlay: "Overlay",
|
|
text: "Text",
|
|
"accent-1": "Accent-1",
|
|
"accent-2": "Accent-2",
|
|
"accent-3": "Accent-3",
|
|
};
|
|
|
|
function Swatch({
|
|
role,
|
|
variant,
|
|
}: {
|
|
role: PaletteRole;
|
|
variant: "dark" | "light";
|
|
}) {
|
|
const entry = variant === "dark" ? role.dark : role.light;
|
|
const label = ROLE_LABELS[role.name] ?? role.name;
|
|
|
|
return (
|
|
<div className="flex flex-col items-center gap-1" style={{ minWidth: 56 }}>
|
|
<div
|
|
className="rounded-md border border-border"
|
|
style={{
|
|
width: 40,
|
|
height: 40,
|
|
backgroundColor: entry.hex,
|
|
minWidth: 40,
|
|
minHeight: 40,
|
|
}}
|
|
title={`${label}: ${entry.hex}`}
|
|
aria-label={`${label} swatch ${entry.hex}`}
|
|
/>
|
|
<span className="font-mono text-xs text-muted-foreground">
|
|
{entry.hex}
|
|
</span>
|
|
{entry.wcagAA ? (
|
|
<Badge
|
|
variant="default"
|
|
className="bg-[#40a02b] dark:bg-[#a6e3a1] text-white dark:text-black border-0 text-[10px] px-1.5 py-0"
|
|
>
|
|
AA
|
|
</Badge>
|
|
) : (
|
|
<Badge variant="destructive" className="text-[10px] px-1.5 py-0">
|
|
Fails AA
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PaletteRow({
|
|
palette,
|
|
variant,
|
|
}: {
|
|
palette: PaletteRole[];
|
|
variant: "dark" | "light";
|
|
}) {
|
|
return (
|
|
<div className="flex flex-col gap-2">
|
|
<span className="text-xs font-medium text-muted-foreground capitalize">
|
|
{variant}
|
|
</span>
|
|
<div className="flex flex-row flex-wrap gap-2">
|
|
{palette.map((role) => (
|
|
<Swatch key={role.name} role={role} variant={variant} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function ThemePaletteGrid({
|
|
palette,
|
|
variant = "dark",
|
|
className,
|
|
}: ThemePaletteGridProps) {
|
|
if (palette.length === 0) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border p-8 text-center",
|
|
className,
|
|
)}
|
|
>
|
|
<h3 className="text-base font-semibold text-foreground">
|
|
No palette yet
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground max-w-xs">
|
|
Pick a seed color to generate a full OKLCH palette with dark and light
|
|
variants.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={cn("flex flex-col gap-4", className)}>
|
|
<PaletteRow palette={palette} variant="dark" />
|
|
<PaletteRow palette={palette} variant="light" />
|
|
</div>
|
|
);
|
|
}
|