nexus/ui/src/components/ThemeSeedInput.tsx

95 lines
2.7 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
interface ThemeSeedInputProps {
value: string;
onChange: (hex: string) => void;
className?: string;
}
function isValidHex(value: string): boolean {
return /^#[0-9a-fA-F]{6}$/.test(value);
}
export function ThemeSeedInput({ value, onChange, className }: ThemeSeedInputProps) {
const [hexText, setHexText] = useState(value);
const debounceRef = useRef<number | null>(null);
// Sync external value to internal text when it changes
useEffect(() => {
setHexText(value);
}, [value]);
const debouncedOnChange = useCallback(
(newHex: string) => {
if (debounceRef.current !== null) {
window.clearTimeout(debounceRef.current);
}
debounceRef.current = window.setTimeout(() => {
onChange(newHex);
debounceRef.current = null;
}, 150);
},
[onChange],
);
const handleColorChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newHex = e.target.value;
setHexText(newHex);
debouncedOnChange(newHex);
},
[debouncedOnChange],
);
const handleTextChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newVal = e.target.value;
setHexText(newVal);
if (isValidHex(newVal)) {
debouncedOnChange(newVal);
}
},
[debouncedOnChange],
);
const handleTextBlur = useCallback(() => {
// On blur, reset to valid value if text is invalid
if (!isValidHex(hexText)) {
setHexText(value);
}
}, [hexText, value]);
return (
<div className={cn("flex flex-col gap-2", className)}>
<Label htmlFor="seed-color" className="text-sm font-medium">
Seed color
</Label>
<div className="flex items-center gap-2">
<input
id="seed-color"
type="color"
value={isValidHex(hexText) ? hexText : "#000000"}
onChange={handleColorChange}
className="h-12 w-12 cursor-pointer rounded-md border border-input bg-transparent p-1 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
aria-label="Seed color picker"
/>
<Input
type="text"
value={hexText}
onChange={handleTextChange}
onBlur={handleTextBlur}
placeholder="#000000"
maxLength={7}
className="h-12 w-32 font-mono text-sm"
aria-label="Seed color hex value"
/>
</div>
<p className="text-xs text-muted-foreground">
We'll generate a full palette in OKLCH.
</p>
</div>
);
}