"use client"; import { LayoutGroup, motion, useReducedMotion } from "motion/react"; import { createContext, useContext, useId, useState, type ComponentPropsWithRef } from "react"; import { SPRING_LAYOUT, SPRING_PRESS } from "@/lib/ease"; import { cn } from "@/lib/utils"; type ColorSelectorContextValue = { value: string; select: (value: string) => void; name: string; disabled: boolean; required: boolean; form?: string; }; const ColorSelectorContext = createContext(null); export interface ColorSelectorProps extends Omit, "onChange" | "defaultValue"> { value?: string; defaultValue?: string; onValueChange?: (value: string) => void; /** The radio group name used in form submission. Generated when omitted. */ name?: string; required?: boolean; } /** A single color choice. Give it a ColorSelectorLabel or an aria-label. */ export function ColorSelector({ value, defaultValue = "", onValueChange, name, disabled = false, required = false, form, className, children, ...props }: ColorSelectorProps) { const id = useId(); const [internal, setInternal] = useState(defaultValue); const current = value ?? internal; return ( { if (next === current) return; if (value === undefined) setInternal(next); onValueChange?.(next); }, name: name ?? id, disabled, required, form, }}>
{children}
); } export type ColorSelectorLabelProps = ComponentPropsWithRef<"legend">; export function ColorSelectorLabel({ className, ...props }: ColorSelectorLabelProps) { return ; } export type ColorSelectorListProps = ComponentPropsWithRef<"div">; export function ColorSelectorList({ className, ...props }: ColorSelectorListProps) { return
; } export interface ColorSelectorItemProps extends Omit, "type" | "value" | "defaultValue" | "checked" | "defaultChecked" | "name" | "children" | "size"> { value: string; /** Any CSS color, including a custom property. */ color: string; /** Accessible color name; selection never relies on color alone. */ label: string; /** Applied to the visible swatch surface. */ className?: string; } export function ColorSelectorItem({ value, color, label, disabled = false, className, style, onChange, ...props }: ColorSelectorItemProps) { const context = useContext(ColorSelectorContext); const reduce = useReducedMotion(); if (!context) throw new Error("ColorSelectorItem must be used within ColorSelector"); const selected = context.value === value; const unavailable = disabled || context.disabled; return ( { onChange?.(event); if (!event.defaultPrevented) context.select(value); }} className="peer sr-only" /> ); }