"use client"; import { useId, useState, type CSSProperties } from "react"; export type Choice = { value: string; label: string; description?: string; disabled?: boolean; }; type ChoiceProps = { label: string; options: readonly Choice[]; value: string; onValueChange: (value: string) => void; name?: string; disabled?: boolean; className?: string; }; export function RadioGroup({ label, options, value, onValueChange, name, disabled, className = "", }: ChoiceProps) { const id = useId(); return (
{label} {options.map((option) => ( ))}
); } export function SegmentedControl({ label, options, value, onValueChange, name, disabled, className = "", }: ChoiceProps) { const id = useId(); return (
{label} {options.map((option) => ( ))}
); } export function NumberField({ label, value, onValueChange, min = -Infinity, max = Infinity, step = 1, disabled = false, className = "", }: { label: string; value: number; onValueChange: (value: number) => void; min?: number; max?: number; step?: number; disabled?: boolean; className?: string; }) { const id = useId(); const [draft, setDraft] = useState(null); const update = (n: number) => { if (Number.isFinite(n)) onValueChange(Math.min(max, Math.max(min, n))); }; const commit = () => { if (draft === null) return; if (draft.trim() !== "") update(Number(draft)); setDraft(null); }; const increment = Math.max(Number.EPSILON, Math.abs(step)); return (
{ setDraft(e.target.value); const n = e.target.valueAsNumber; if (Number.isFinite(n) && n >= min && n <= max) onValueChange(n); }} onBlur={commit} onKeyDown={(e) => { if (e.key === "Enter") commit(); }} />
); } export function Rating({ label, value, onValueChange, max = 5, disabled, className = "", }: { label: string; value: number; onValueChange: (value: number) => void; max?: number; disabled?: boolean; className?: string; }) { const id = useId(), count = Math.min(10, Math.max(1, Math.floor(max))); return (
{label} {Array.from({ length: count }, (_, i) => ( ))}
); } export function ColorPicker({ label, value, onValueChange, swatches = ["#ff4d1c", "#2f6bff", "#2e8b70", "#f4f0e8"], disabled, className = "", }: { label: string; value: string; onValueChange: (value: string) => void; swatches?: readonly string[]; disabled?: boolean; className?: string; }) { const valid = /^#[\da-f]{6}$/i.test(value); return (
{label}
{swatches .filter((s) => /^#[\da-f]{6}$/i.test(s)) .map((color) => (
); }