import { useEffect, useId, useRef, useState } from "react"; import { Eyedropper, Palette } from "@phosphor-icons/react"; import { cn } from "../../lib/cn"; import { fieldClassName, fieldNote } from "./shared"; export type UhuruColorPickerProps = { className?: string; defaultValue?: string; disabled?: boolean; error?: string; hint?: string; id?: string; label?: string; name?: string; onValueChange?: (value: string) => void; palette?: string[]; value?: string; }; const fallbackColor = "#29796B"; function normalizeColor(value?: string) { const raw = value?.trim() ?? ""; const withHash = raw.startsWith("#") ? raw : `#${raw}`; const shortHex = /^#([0-9a-f]{3})$/i.exec(withHash); if (shortHex) { return `#${shortHex[1].split("").map((character) => character.repeat(2)).join("").toUpperCase()}`; } return /^#[0-9a-f]{6}$/i.test(withHash) ? withHash.toUpperCase() : null; } function colorInputValue(value?: string) { return normalizeColor(value) ?? fallbackColor; } export function UhuruColorPicker({ className, defaultValue = fallbackColor, disabled = false, error, hint, id, label, name, onValueChange, palette, value, }: UhuruColorPickerProps) { const generatedId = useId(); const fieldId = id ?? generatedId; const hintId = hint ? `${fieldId}-hint` : undefined; const errorId = error ? `${fieldId}-error` : undefined; const isControlled = value !== undefined; const resolvedValue = colorInputValue(isControlled ? value : defaultValue); const [draft, setDraft] = useState(resolvedValue); const colorInputRef = useRef(null); const normalizedDraft = normalizeColor(draft); const hasInvalidDraft = draft.length > 0 && !normalizedDraft; useEffect(() => { setDraft(resolvedValue); }, [resolvedValue]); const commit = (nextValue: string) => { const normalized = normalizeColor(nextValue); setDraft(nextValue); if (normalized) { setDraft(normalized); onValueChange?.(normalized); } }; const selectColor = (nextValue: string) => { const normalized = colorInputValue(nextValue); setDraft(normalized); onValueChange?.(normalized); }; return (
{label ? {label} : null}
selectColor(event.target.value)} ref={colorInputRef} tabIndex={-1} type="color" value={colorInputValue(normalizedDraft ?? resolvedValue)} />
{palette?.length ? (
{palette.map((color) => { const normalized = normalizeColor(color); if (!normalized) return null; return (
) : null} {fieldNote(error, hasInvalidDraft ? "Enter a valid 3 or 6 digit hex color." : hint, hintId, errorId)}
); } export const ColorPicker = UhuruColorPicker;