{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "color-input",
"type": "registry:ui",
"title": "ColorInput",
"description": "Focusable swatch button that opens the OS-native color picker and returns the chosen hex.",
"categories": [
"forms"
],
"registryDependencies": [
"https://whiskeyjack.net/r/utils.json"
],
"files": [
{
"path": "components/ui/color-input.tsx",
"type": "registry:ui",
"target": "components/ui/color-input.tsx",
"content": "import * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface ColorInputProps {\n /** Current color as a hex string (`#RRGGBB`). */\n value: string;\n /** Fired with the new hex when the user picks a color. */\n onChange: (hex: string) => void;\n /** Accessible label (required) -- callers pass a translated string. */\n \"aria-label\": string;\n /** Show the hex value as monospace text beside the swatch. Default false. */\n showHex?: boolean;\n /** Disable interaction (e.g. an auto-computed color). Default false. */\n disabled?: boolean;\n /** Swatch size: `md` (32px, default) or `sm` (28px). */\n size?: \"sm\" | \"md\";\n /**\n * Icon centered over the swatch -- e.g. a paint brush to mark this as a\n * free \"pick any color\" control rather than a fixed swatch. The icon stays\n * legible over the chosen color (its tint flips with the swatch's luminance).\n */\n icon?: React.ReactNode;\n /**\n * Render the swatch with a transparent (card) background instead of `value`,\n * for a \"no custom color chosen yet\" state. The picker still opens at `value`,\n * and once the user picks, drop `unset` so the swatch fills with their color.\n */\n unset?: boolean;\n /** Mark as the active selection -- draws the same neutral ring as a chosen swatch. */\n selected?: boolean;\n className?: string;\n}\n\n/** WCAG-ish readable icon tint over a solid hex background. */\nfunction readableOn(hex: string): string {\n const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());\n if (!m) return \"#ffffff\";\n const n = parseInt(m[1], 16);\n const lin = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((v) => {\n const s = v / 255;\n return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);\n });\n const L = 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];\n return L > 0.4 ? \"#1d1d1d\" : \"#ffffff\";\n}\n\n/**\n * A color swatch that opens the OS-native color picker (a hidden\n * `` triggered by the swatch button). Extracted from Icon\n * Stack's background editor so apps share one accessible color-picking control\n * (Icon Stack solid/gradient stops, Chip Away custom accent). Presentational\n * and i18n-agnostic -- pass a translated `aria-label`. The button is the single\n * focusable control (with a focus ring); the input is inert to AT.\n */\nexport const ColorInput = React.forwardRef(function ColorInput({\n value,\n onChange,\n \"aria-label\": ariaLabel,\n showHex = false,\n disabled = false,\n size = \"md\",\n icon,\n unset = false,\n selected = false,\n className,\n}, ref) {\n const inputRef = React.useRef(null);\n const swatch = size === \"sm\" ? \"w-7 h-7\" : \"w-8 h-8\";\n\n return (\n
\n );\n});\n\nColorInput.displayName = \"ColorInput\"\n"
}
],
"docs": "Presentational and i18n-agnostic, so pass a translated aria-label. Use it for a custom accent or a gradient stop, and pair it with applyAccentColor to retint the whole UI from the chosen hex. `unset` renders the \"nothing chosen yet\" state while still opening the picker at `value`.",
"meta": {
"group": "forms",
"related": [
"accent-foreground"
],
"exports": [
"ColorInput"
],
"siteSlug": "color-input"
}
}