{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "raws-color-picker",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "./components/ui/color-picker.tsx",
      "content": "'use client';\n\nimport { Button } from '@/components/website/ui/button';\nimport { Input } from '@/components/website/ui/input';\nimport { Label } from '@/components/website/ui/label';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/website/ui/popover';\nimport { cn } from '@/lib/utils';\nimport { Copy, Pipette } from 'lucide-react';\nimport { HexColorPicker } from 'react-colorful';\nimport { toast } from 'sonner';\n\ninterface ColorPickerProps {\n  color: string;\n  onChange: (color: string) => void;\n  label: string;\n  isEyeDroppper?: boolean;\n  className?: string;\n}\n\nexport function ColorPicker({\n  color,\n  onChange,\n  label,\n  isEyeDroppper = false,\n  className,\n}: ColorPickerProps) {\n  function copyToClipboard() {\n    navigator.clipboard.writeText(color);\n    toast('Copied!', {\n      description: `${color} copied to clipboard`,\n      duration: 2000,\n    });\n  }\n\n  async function useEyeDropper() {\n    if (!('EyeDropper' in window)) {\n      toast.error('Not supported', {\n        description: 'Eyedropper is not supported in your browser',\n        duration: 3000,\n      });\n      return;\n    }\n\n    try {\n      // @ts-expect-error - EyeDropper is not in the TypeScript DOM types yet\n      const eyeDropper = new window.EyeDropper();\n      const result = await eyeDropper.open();\n      onChange(result.sRGBHex);\n    } catch (e) {\n      console.error('Error using eyedropper', e);\n    }\n  }\n\n  return (\n    <div className={cn('flex items-center gap-2', className)}>\n      <Label className='w-24'>{label}</Label>\n      <Popover>\n        <PopoverTrigger asChild>\n          <Button\n            variant='outline'\n            className='h-8 w-12 border-2 p-0'\n            style={{ backgroundColor: color }}\n          >\n            <span className='sr-only'>Pick a color</span>\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent className='w-full p-3 bg-primary-foreground'>\n          <HexColorPicker color={color} onChange={onChange} className='w-full!' />\n          <div className='mt-2 flex w-full gap-2'>\n            <Input\n              value={color}\n              onChange={(e) => onChange(e.target.value)}\n              className='h-10 w-full'\n            />\n            <Button\n              variant='outline'\n              size='icon'\n              onClick={copyToClipboard}\n              className='h-10 w-12 shrink border-neutral-300 bg-neutral-100 text-neutral-900 hover:bg-neutral-200'\n              style={{ borderRadius: '6px' }}\n            >\n              <Copy className='h-4 w-4' />\n              <span className='sr-only'>Copy color</span>\n            </Button>\n          </div>\n\n          {isEyeDroppper && (\n            <div className='mt-2 flex gap-2'>\n              <Button\n                variant='outline'\n                size='icon'\n                onClick={useEyeDropper}\n                className='h-10 w-10 border-neutral-300 bg-neutral-100 text-neutral-900 hover:bg-neutral-200'\n                style={{ borderRadius: '6px' }}\n              >\n                <Pipette className='h-4 w-4' />\n                <span className='sr-only'>Pick color</span>\n              </Button>\n\n              <div\n                className='h-10 flex-1'\n                style={{ backgroundColor: color, borderRadius: '6px' }}\n              />\n            </div>\n          )}\n        </PopoverContent>\n      </Popover>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "./registry/components/color-picker/raws-color-picker.tsx",
      "content": "'use client';\n\nimport { Button } from '@/components/website/ui/button';\nimport { Copy, Pipette } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { toast } from 'sonner';\n\ninterface ColorPickerProps {\n  color: string;\n  onChange: (color: string) => void;\n  showEyedropper?: boolean;\n}\n\nexport function ColorPicker({ color, onChange, showEyedropper = true }: ColorPickerProps) {\n  const [hue, setHue] = useState(0);\n  const [position, setPosition] = useState({ x: 0, y: 0 });\n  const [internalColor, setInternalColor] = useState(color);\n  const [hexValue, setHexValue] = useState(color);\n  const [isDraggingHue, setIsDraggingHue] = useState(false);\n  const [isDraggingColor, setIsDraggingColor] = useState(false);\n\n  const colorPanelRef = useRef<HTMLDivElement>(null);\n  const hueSliderRef = useRef<HTMLDivElement>(null);\n\n  // Initialize picker state from the provided color\n  useEffect(() => {\n    if (color.startsWith('#')) {\n      setHexValue(color);\n      setInternalColor(color);\n      const { h, s, v } = hexToHsv(color);\n      setHue(h);\n\n      if (colorPanelRef.current) {\n        const width = colorPanelRef.current.clientWidth;\n        const height = colorPanelRef.current.clientHeight;\n        setPosition({\n          x: s * width,\n          y: (1 - v) * height,\n        });\n      }\n    }\n  }, [color]);\n\n  // Convert hex to HSV\n  function hexToHsv(hex: string): { h: number; s: number; v: number } {\n    hex = hex.replace(/^#/, '');\n\n    const r = Number.parseInt(hex.substring(0, 2), 16) / 255;\n    const g = Number.parseInt(hex.substring(2, 4), 16) / 255;\n    const b = Number.parseInt(hex.substring(4, 6), 16) / 255;\n\n    const max = Math.max(r, g, b);\n    const min = Math.min(r, g, b);\n    const delta = max - min;\n\n    let h = 0;\n    if (delta !== 0) {\n      if (max === r) {\n        h = ((g - b) / delta) % 6;\n      } else if (max === g) {\n        h = (b - r) / delta + 2;\n      } else {\n        h = (r - g) / delta + 4;\n      }\n\n      h = Math.round(h * 60);\n      if (h < 0) h += 360;\n    }\n\n    const s = max === 0 ? 0 : delta / max;\n    const v = max;\n\n    return { h, s, v };\n  }\n\n  // Convert HSV to hex\n  function hsvToHex(h: number, s: number, v: number): string {\n    const hi = Math.floor(h / 60) % 6;\n    const f = h / 60 - Math.floor(h / 60);\n    const p = v * (1 - s);\n    const q = v * (1 - f * s);\n    const t = v * (1 - (1 - f) * s);\n\n    let r = 0;\n    let g = 0;\n    let b = 0;\n\n    switch (hi) {\n      case 0:\n        r = v;\n        g = t;\n        b = p;\n        break;\n      case 1:\n        r = q;\n        g = v;\n        b = p;\n        break;\n      case 2:\n        r = p;\n        g = v;\n        b = t;\n        break;\n      case 3:\n        r = p;\n        g = q;\n        b = v;\n        break;\n      case 4:\n        r = t;\n        g = p;\n        b = v;\n        break;\n      case 5:\n        r = v;\n        g = p;\n        b = q;\n        break;\n    }\n\n    const toHex = (c: number) => {\n      const hex = Math.round(c * 255).toString(16);\n      return hex.length === 1 ? `0${hex}` : hex;\n    };\n\n    return `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n  }\n\n  // Update internal color based on position and hue\n  const updateInternalColor = useCallback((x: number, y: number, h: number) => {\n    if (!colorPanelRef.current) return;\n\n    const width = colorPanelRef.current.clientWidth;\n    const height = colorPanelRef.current.clientHeight;\n\n    // Clamp values\n    const clampedX = Math.max(0, Math.min(x, width));\n    const clampedY = Math.max(0, Math.min(y, height));\n\n    // Calculate saturation and value\n    const s = clampedX / width;\n    const v = 1 - clampedY / height;\n\n    // Update hex value\n    const newHex = hsvToHex(h, s, v);\n    setHexValue(newHex);\n    setInternalColor(newHex);\n\n    // Update position\n    setPosition({ x: clampedX, y: clampedY });\n  }, []);\n\n  // Commit the color change to the parent component\n  const commitColorChange = useCallback(() => {\n    onChange(internalColor);\n  }, [onChange, internalColor]);\n\n  // Handle color panel mouse/touch events\n  function handleColorPanelMouseDown(e: React.MouseEvent<HTMLDivElement>) {\n    setIsDraggingColor(true);\n    const rect = e.currentTarget.getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const y = e.clientY - rect.top;\n    updateInternalColor(x, y, hue);\n  }\n\n  // Handle hue slider mouse/touch events\n  function handleHueSliderMouseDown(e: React.MouseEvent<HTMLDivElement>) {\n    setIsDraggingHue(true);\n    const rect = e.currentTarget.getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const width = rect.width;\n    const h = (x / width) * 360;\n    setHue(h);\n    updateInternalColor(position.x, position.y, h);\n  }\n\n  // Handle mouse/touch move\n  useEffect(() => {\n    function handleMouseMove(e: MouseEvent) {\n      if (isDraggingColor && colorPanelRef.current) {\n        const rect = colorPanelRef.current.getBoundingClientRect();\n        const x = e.clientX - rect.left;\n        const y = e.clientY - rect.top;\n        updateInternalColor(x, y, hue);\n      } else if (isDraggingHue && hueSliderRef.current) {\n        const rect = hueSliderRef.current.getBoundingClientRect();\n        const x = e.clientX - rect.left;\n        const width = rect.width;\n        const h = Math.max(0, Math.min((x / width) * 360, 360));\n        setHue(h);\n        updateInternalColor(position.x, position.y, h);\n      }\n    }\n\n    function handleMouseUp() {\n      if (isDraggingColor || isDraggingHue) {\n        commitColorChange();\n      }\n      setIsDraggingColor(false);\n      setIsDraggingHue(false);\n    }\n\n    if (isDraggingColor || isDraggingHue) {\n      window.addEventListener('mousemove', handleMouseMove);\n      window.addEventListener('mouseup', handleMouseUp);\n    }\n\n    return () => {\n      window.removeEventListener('mousemove', handleMouseMove);\n      window.removeEventListener('mouseup', handleMouseUp);\n    };\n  }, [\n    isDraggingColor,\n    isDraggingHue,\n    hue,\n    position.x,\n    position.y,\n    commitColorChange,\n    updateInternalColor,\n  ]);\n\n  // Handle hex input change\n  function handleHexChange(e: React.ChangeEvent<HTMLInputElement>) {\n    const value = e.target.value;\n    setHexValue(value);\n\n    if (/^#[0-9A-F]{6}$/i.test(value)) {\n      setInternalColor(value);\n    }\n  }\n\n  // Handle hex input blur (commit change)\n  function handleHexBlur() {\n    if (/^#[0-9A-F]{6}$/i.test(hexValue)) {\n      commitColorChange();\n\n      // Update hue and position\n      const { h, s, v } = hexToHsv(hexValue);\n      setHue(h);\n\n      if (colorPanelRef.current) {\n        const width = colorPanelRef.current.clientWidth;\n        const height = colorPanelRef.current.clientHeight;\n        setPosition({\n          x: s * width,\n          y: (1 - v) * height,\n        });\n      }\n    } else {\n      // Reset to the last valid color\n      setHexValue(internalColor);\n    }\n  }\n\n  // Handle hex input key press (commit on Enter)\n  function handleHexKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {\n    if (e.key === 'Enter') {\n      handleHexBlur();\n    }\n  }\n\n  // Copy color to clipboard\n  function copyToClipboard() {\n    navigator.clipboard.writeText(hexValue);\n    toast('Copied!', {\n      description: `${hexValue} copied to clipboard`,\n      duration: 2000,\n    });\n  }\n\n  // Use eyedropper if available\n  async function useEyeDropper() {\n    if (!('EyeDropper' in window)) {\n      toast.error('Not supported', {\n        description: 'Eyedropper is not supported in your browser',\n        duration: 3000,\n      });\n      return;\n    }\n\n    try {\n      // @ts-expect-error - EyeDropper is not in the TypeScript DOM types yet\n      const eyeDropper = new window.EyeDropper();\n      const result = await eyeDropper.open();\n      setHexValue(result.sRGBHex);\n      setInternalColor(result.sRGBHex);\n      commitColorChange();\n\n      // Update hue and position\n      const { h, s, v } = hexToHsv(result.sRGBHex);\n      setHue(h);\n\n      if (colorPanelRef.current) {\n        const width = colorPanelRef.current.clientWidth;\n        const height = colorPanelRef.current.clientHeight;\n        setPosition({\n          x: s * width,\n          y: (1 - v) * height,\n        });\n      }\n    } catch (e) {\n      console.error('Error using eyedropper', e);\n    }\n  }\n\n  return (\n    <div className='w-full space-y-4 rounded-lg bg-primary-foreground p-4 text-primary'>\n      {/* Color panel */}\n      <div\n        ref={colorPanelRef}\n        className='relative h-48 w-full cursor-crosshair touch-none rounded-md'\n        style={{\n          backgroundColor: `hsl(${hue}, 100%, 50%)`,\n          backgroundImage: `\n          linear-gradient(to right, #fff, transparent),\n          linear-gradient(to bottom, transparent, #000)\n        `,\n        }}\n        onMouseDown={handleColorPanelMouseDown}\n      >\n        {/* Color selector */}\n        <div\n          className='-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute h-6 w-6 rounded-full border-2 border-white shadow-md'\n          style={{\n            left: position.x,\n            top: position.y,\n            backgroundColor: internalColor,\n          }}\n        />\n      </div>\n\n      {/* Hue slider */}\n      <div\n        ref={hueSliderRef}\n        className='relative h-6 w-full cursor-pointer touch-none rounded-md'\n        style={{\n          backgroundImage: `linear-gradient(to right, \n          #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)`,\n        }}\n        onMouseDown={handleHueSliderMouseDown}\n      >\n        {/* Hue selector */}\n        <div\n          className='-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute h-6 w-6 rounded-full border-2 border-white shadow-md'\n          style={{\n            left: `${(hue / 360) * 100}%`,\n            top: '50%',\n            backgroundColor: `hsl(${hue}, 100%, 50%)`,\n          }}\n        />\n      </div>\n\n      {/* Hex input and tools */}\n      <div className='space-y-2'>\n        <div className='flex items-center gap-2'>\n          <input\n            type='text'\n            value={hexValue}\n            onChange={handleHexChange}\n            onBlur={handleHexBlur}\n            onKeyDown={handleHexKeyDown}\n            className='flex-1 rounded-md border border-neutral-300 bg-white px-3 py-2 text-neutral-900'\n            pattern='^#[0-9A-F]{6}$'\n            maxLength={7}\n          />\n          <Button\n            variant='outline'\n            size='icon'\n            onClick={copyToClipboard}\n            className='h-10 w-10 border-neutral-300 bg-neutral-100 text-neutral-900 hover:bg-neutral-200'\n          >\n            <Copy className='h-4 w-4' />\n            <span className='sr-only'>Copy color</span>\n          </Button>\n        </div>\n\n        <div className='flex items-center gap-2'>\n          {showEyedropper && (\n            <Button\n              variant='outline'\n              size='icon'\n              onClick={useEyeDropper}\n              className='h-10 w-10 border-neutral-300 bg-neutral-100 text-neutral-900 hover:bg-neutral-200'\n            >\n              <Pipette className='h-4 w-4' />\n              <span className='sr-only'>Pick color</span>\n            </Button>\n          )}\n\n          <div\n            className='h-10 flex-1 rounded-md border border-neutral-300'\n            style={{ backgroundColor: internalColor }}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\nfunction RawsColorPicker() {\n  const [color, setColor] = useState('#002fff');\n  return (\n    <div className='flex gap-2 items-center w-96 mx-auto'>\n      <ColorPicker color={color} onChange={(color) => setColor(color)} />\n    </div>\n  );\n}\n\nexport default RawsColorPicker;\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}