{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "color-picker",
  "type": "registry:ui",
  "description": "Interactive color picker component with HSL support and preset colors",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "input",
    "label",
    "popover"
  ],
  "files": [
    {
      "path": "registry/default/ui/color-picker.tsx",
      "content": "\"use client\"\n\nimport React, { useEffect, useState } from \"react\"\nimport { Check, ChevronDown } from \"lucide-react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\n\n// Helper functions for color conversion\nconst hslToHex = (h: number, s: number, l: number) => {\n  l /= 100\n  const a = (s * Math.min(l, 1 - l)) / 100\n  const f = (n: number) => {\n    const k = (n + h / 30) % 12\n    const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n    return Math.round(255 * color)\n      .toString(16)\n      .padStart(2, \"0\")\n  }\n  return `#${f(0)}${f(8)}${f(4)}`\n}\n\nconst hexToHsl = (hex: string): [number, number, number] => {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n  if (!result) return [0, 0, 0]\n\n  let r = parseInt(result[1], 16) / 255\n  let g = parseInt(result[2], 16) / 255\n  let b = parseInt(result[3], 16) / 255\n\n  const max = Math.max(r, g, b)\n  const min = Math.min(r, g, b)\n  let h = 0\n  let s = 0\n  let l = (max + min) / 2\n\n  if (max !== min) {\n    const d = max - min\n    s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n    switch (max) {\n      case r:\n        h = (g - b) / d + (g < b ? 6 : 0)\n        break\n      case g:\n        h = (b - r) / d + 2\n        break\n      case b:\n        h = (r - g) / d + 4\n        break\n    }\n    h /= 6\n  }\n\n  return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]\n}\n\nconst normalizeColor = (color: string): string => {\n  if (color.startsWith(\"#\")) {\n    return color.toUpperCase()\n  } else if (color.startsWith(\"hsl\")) {\n    const [h, s, l] = color.match(/\\d+(\\.\\d+)?/g)?.map(Number) || [0, 0, 0]\n    return `hsl(${Math.round(h)}, ${Math.round(s)}%, ${Math.round(l)}%)`\n  }\n  return color\n}\n\nconst trimColorString = (color: string, maxLength: number = 20): string => {\n  if (color.length <= maxLength) return color\n  return `${color.slice(0, maxLength - 3)}...`\n}\n\nexport function ColorPicker({\n  color,\n  onChange,\n}: {\n  color: string\n  onChange: (color: string) => void\n}) {\n  const [hsl, setHsl] = useState<[number, number, number]>([0, 0, 0])\n  const [colorInput, setColorInput] = useState(color)\n  const [isOpen, setIsOpen] = useState(false)\n\n  useEffect(() => {\n    handleColorChange(color)\n  }, [color])\n\n  const handleColorChange = (newColor: string) => {\n    const normalizedColor = normalizeColor(newColor)\n    setColorInput(normalizedColor)\n\n    let h, s, l\n    if (normalizedColor.startsWith(\"#\")) {\n      ;[h, s, l] = hexToHsl(normalizedColor)\n    } else {\n      ;[h, s, l] = normalizedColor.match(/\\d+(\\.\\d+)?/g)?.map(Number) || [\n        0, 0, 0,\n      ]\n    }\n\n    setHsl([h, s, l])\n    onChange(`hsl(${h.toFixed(1)}, ${s.toFixed(1)}%, ${l.toFixed(1)}%)`)\n  }\n\n  const handleHueChange = (hue: number) => {\n    const newHsl: [number, number, number] = [hue, hsl[1], hsl[2]]\n    setHsl(newHsl)\n    handleColorChange(`hsl(${newHsl[0]}, ${newHsl[1]}%, ${newHsl[2]}%)`)\n  }\n\n  const handleSaturationLightnessChange = (\n    event: React.MouseEvent<HTMLDivElement>\n  ) => {\n    const rect = event.currentTarget.getBoundingClientRect()\n    const x = event.clientX - rect.left\n    const y = event.clientY - rect.top\n    const s = Math.round((x / rect.width) * 100)\n    const l = Math.round(100 - (y / rect.height) * 100)\n    const newHsl: [number, number, number] = [hsl[0], s, l]\n    setHsl(newHsl)\n    handleColorChange(`hsl(${newHsl[0]}, ${newHsl[1]}%, ${newHsl[2]}%)`)\n  }\n\n  const handleColorInputChange = (\n    event: React.ChangeEvent<HTMLInputElement>\n  ) => {\n    const newColor = event.target.value\n    setColorInput(newColor)\n    if (\n      /^#[0-9A-Fa-f]{6}$/.test(newColor) ||\n      /^hsl$$\\d+,\\s*\\d+%,\\s*\\d+%$$$/.test(newColor)\n    ) {\n      handleColorChange(newColor)\n    }\n  }\n\n  const colorPresets = [\n    \"#FF3B30\",\n    \"#FF9500\",\n    \"#FFCC00\",\n    \"#4CD964\",\n    \"#5AC8FA\",\n    \"#007AFF\",\n    \"#5856D6\",\n    \"#FF2D55\",\n    \"#8E8E93\",\n    \"#EFEFF4\",\n    \"#E5E5EA\",\n    \"#D1D1D6\",\n  ]\n\n  return (\n    <Popover open={isOpen} onOpenChange={setIsOpen}>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          className=\"w-[200px] justify-start text-left font-normal\"\n        >\n          <div\n            className=\"mr-2 h-4 w-4 rounded-full shadow-sm\"\n            style={{ backgroundColor: colorInput }}\n          />\n          <span className=\"flex-grow\">{trimColorString(colorInput)}</span>\n          <ChevronDown className=\"h-4 w-4 opacity-50\" />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-[240px] p-3\">\n        <motion.div\n          initial={{ opacity: 0, scale: 0.95 }}\n          animate={{ opacity: 1, scale: 1 }}\n          exit={{ opacity: 0, scale: 0.95 }}\n          transition={{ duration: 0.2 }}\n          className=\"space-y-3\"\n        >\n          <motion.div\n            className=\"relative h-40 w-full cursor-crosshair overflow-hidden rounded-lg\"\n            style={{\n              background: `\n                linear-gradient(to top, rgba(0, 0, 0, 1), transparent),\n                linear-gradient(to right, rgba(255, 255, 255, 1), rgba(255, 0, 0, 0)),\n                hsl(${hsl[0]}, 100%, 50%)\n              `,\n            }}\n            onClick={handleSaturationLightnessChange}\n          >\n            <motion.div\n              className=\"absolute h-4 w-4 rounded-full border-2 border-white shadow-md\"\n              style={{\n                left: `${hsl[1]}%`,\n                top: `${100 - hsl[2]}%`,\n                backgroundColor: `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`,\n              }}\n              whileHover={{ scale: 1.2 }}\n              whileTap={{ scale: 0.9 }}\n            />\n          </motion.div>\n          <motion.input\n            type=\"range\"\n            min=\"0\"\n            max=\"360\"\n            value={hsl[0]}\n            onChange={(e) => handleHueChange(Number(e.target.value))}\n            className=\"h-3 w-full cursor-pointer appearance-none rounded-full\"\n            style={{\n              background: `linear-gradient(to right, \n                hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), \n                hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(360, 100%, 50%)\n              )`,\n            }}\n            whileHover={{ scale: 1.05 }}\n            whileTap={{ scale: 0.95 }}\n          />\n          <div className=\"flex items-center space-x-2\">\n            <Label htmlFor=\"color-input\" className=\"sr-only\">\n              Color\n            </Label>\n            <Input\n              id=\"color-input\"\n              type=\"text\"\n              value={colorInput}\n              onChange={handleColorInputChange}\n              className=\"h-8 flex-grow rounded-md border border-gray-300 bg-white px-2 text-sm\"\n              placeholder=\"#RRGGBB or hsl(h, s%, l%)\"\n            />\n            <motion.div\n              className=\"h-8 w-8 rounded-md shadow-sm\"\n              style={{ backgroundColor: colorInput }}\n              whileHover={{ scale: 1.1 }}\n              whileTap={{ scale: 0.9 }}\n            />\n          </div>\n          <div className=\"grid grid-cols-6 gap-2\">\n            <AnimatePresence>\n              {colorPresets.map((preset) => (\n                <motion.button\n                  key={preset}\n                  className=\"relative h-8 w-8 rounded-full\"\n                  style={{ backgroundColor: preset }}\n                  onClick={() => handleColorChange(preset)}\n                  whileHover={{ scale: 1.2, zIndex: 1 }}\n                  whileTap={{ scale: 0.9 }}\n                >\n                  {colorInput === preset && (\n                    <motion.div\n                      initial={{ scale: 0 }}\n                      animate={{ scale: 1 }}\n                      exit={{ scale: 0 }}\n                      transition={{ duration: 0.2 }}\n                    >\n                      <Check className=\"absolute inset-0 m-auto h-4 w-4 text-white\" />\n                    </motion.div>\n                  )}\n                </motion.button>\n              ))}\n            </AnimatePresence>\n          </div>\n        </motion.div>\n      </PopoverContent>\n    </Popover>\n  )\n}\n\nexport default ColorPicker\n",
      "type": "registry:ui"
    }
  ]
}