{"name":"color-picker","type":"registry:ui","files":[{"path":"color-picker.tsx","type":"registry:ui","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Slider as SliderPrimitive } from \"@base-ui-components/react/slider\"\nimport {\n  clampChroma,\n  clampRgb,\n  converter,\n  formatHex,\n  formatHex8,\n  formatHsl,\n  formatRgb,\n  parse,\n  toGamut,\n} from \"culori\"\n\nimport { Input } from \"@/components/ui/input\"\nimport { Select } from \"@/components/ui/select\"\nimport { cn } from \"@/lib/utils\"\n\nconst SUPPORTED_MODES = [\"hex\", \"hsl\", \"rgb\", \"oklch\"] as const\nconst CHROMA_MAX = 0.4\nconst CANVAS_RESOLUTION = 96\nconst AREA_STEP = 0.01\nconst AREA_STEP_LARGE = 0.1\n\ntype ModeType = (typeof SUPPORTED_MODES)[number]\n\ntype ColorObject = {\n  l: number\n  c: number\n  h: number\n  alpha?: number\n}\n\ntype ColorPickerContextValue = ColorObject & {\n  alpha: number\n  mode: ModeType\n  setColor: (color: Partial<ColorObject>) => void\n  setMode: (mode: ModeType) => void\n}\n\nconst toOklch = converter(\"oklch\")\nconst toRgb = converter(\"rgb\")\nconst toHsl = converter(\"hsl\")\nconst toSrgb = toGamut(\"rgb\", \"oklch\")\nconst FALLBACK_COLOR: ColorObject = { l: 1, c: 0, h: 0, alpha: 1 }\n\nconst ColorPickerContext = React.createContext<\n  ColorPickerContextValue | undefined\n>(undefined)\n\nfunction useColorPicker() {\n  const context = React.useContext(ColorPickerContext)\n\n  if (!context) {\n    throw new Error(\"ColorPicker components must be used within ColorPicker\")\n  }\n\n  return context\n}\n\nfunction round(value: number, precision: number) {\n  return Number.parseFloat(value.toFixed(precision))\n}\n\nfunction clamp01(value: number) {\n  return Math.max(0, Math.min(1, value))\n}\n\nfunction normalizeHue(value: number) {\n  return ((value % 360) + 360) % 360\n}\n\nfunction parseColor(value: string): ColorObject {\n  const parsed = parse(value)\n  if (!parsed) return FALLBACK_COLOR\n\n  const oklch = toOklch(parsed)\n  if (!oklch) return FALLBACK_COLOR\n\n  const chroma = oklch.c ?? 0\n  return {\n    l: oklch.l ?? 0,\n    c: chroma,\n    h:\n      chroma === 0 || !Number.isFinite(oklch.h)\n        ? 0\n        : normalizeHue(oklch.h as number),\n    alpha: oklch.alpha ?? 1,\n  }\n}\n\nfunction formatOklch(color: ColorObject) {\n  const lightness = round(color.l, 4)\n  const chroma = round(color.c, 4)\n  const hue = chroma === 0 ? 0 : round(normalizeHue(color.h), 2)\n  const alpha = color.alpha ?? 1\n  const body = `${lightness} ${chroma} ${hue}`\n\n  return alpha === 1\n    ? `oklch(${body})`\n    : `oklch(${body} / ${round(alpha, 4)})`\n}\n\nfunction formatColor(\n  value: string,\n  format: \"hex\" | \"rgb\" | \"hsl\" | \"oklch\"\n) {\n  const parsed = parse(value)\n  if (!parsed) return null\n\n  if (format === \"oklch\") {\n    const oklch = toOklch(parsed)\n    if (!oklch) return null\n\n    return formatOklch({\n      l: oklch.l ?? 0,\n      c: oklch.c ?? 0,\n      h: Number.isFinite(oklch.h) ? (oklch.h as number) : 0,\n      alpha: oklch.alpha ?? 1,\n    })\n  }\n\n  const safe = toSrgb(parsed)\n  if (!safe) return null\n\n  if (format === \"hex\") {\n    const hex =\n      (safe.alpha ?? 1) === 1 ? formatHex(safe) : formatHex8(safe)\n    return hex?.toUpperCase() ?? null\n  }\n\n  if (format === \"hsl\") return formatHsl(safe) ?? null\n  return formatRgb(safe) ?? null\n}\n\nfunction getColorString(color: ColorObject, mode: ModeType) {\n  if (mode === \"oklch\") return formatOklch(color)\n\n  const rgb = toRgb({\n    mode: \"oklch\",\n    l: color.l,\n    c: color.c,\n    h: color.h,\n    alpha: color.alpha ?? 1,\n  })\n  if (!rgb) return \"\"\n\n  const clipped = clampRgb(rgb)\n  if (mode === \"hex\") {\n    const hex =\n      (clipped.alpha ?? 1) === 1\n        ? formatHex(clipped)\n        : formatHex8(clipped)\n    return hex?.toUpperCase() ?? \"\"\n  }\n\n  if (mode === \"hsl\") return formatHsl(clipped) ?? \"\"\n  return formatRgb(clipped) ?? \"\"\n}\n\nfunction oklchToRgb(lightness: number, chroma: number, hue: number) {\n  return toRgb({ mode: \"oklch\", l: lightness, c: chroma, h: hue })\n}\n\ntype HslColor = {\n  h: number\n  s: number\n  l: number\n}\n\nfunction oklchToHsl(color: ColorObject): HslColor {\n  const hsl = toHsl({\n    mode: \"oklch\",\n    l: color.l,\n    c: color.c,\n    h: color.h,\n    alpha: color.alpha ?? 1,\n  })\n\n  if (!hsl) return { h: color.h, s: 0, l: 100 }\n\n  return {\n    h:\n      color.c <= 1e-6 || !Number.isFinite(hsl.h)\n        ? color.h\n        : normalizeHue(hsl.h as number),\n    s: clamp01(hsl.s ?? 0) * 100,\n    l: clamp01(hsl.l ?? 0) * 100,\n  }\n}\n\nfunction hslToOklch(\n  hue: number,\n  saturation: number,\n  lightness: number,\n  alpha = 1\n): ColorObject {\n  const oklch = toOklch({\n    mode: \"hsl\",\n    h: normalizeHue(hue),\n    s: clamp01(saturation / 100),\n    l: clamp01(lightness / 100),\n    alpha,\n  })\n\n  if (!oklch) return { l: 0, c: 0, h: hue, alpha }\n\n  return {\n    l: oklch.l ?? 0,\n    c: oklch.c ?? 0,\n    h: Number.isFinite(oklch.h) ? (oklch.h as number) : hue,\n    alpha,\n  }\n}\n\nfunction clampToSrgb(color: ColorObject): ColorObject {\n  const result = clampChroma(\n    {\n      mode: \"oklch\",\n      l: color.l,\n      c: color.c,\n      h: color.h,\n      alpha: color.alpha ?? 1,\n    },\n    \"oklch\",\n    \"rgb\"\n  )\n\n  return {\n    l: result.l ?? color.l,\n    c: result.c ?? 0,\n    h: result.h ?? color.h,\n    alpha: result.alpha ?? color.alpha ?? 1,\n  }\n}\n\ntype ColorPickerProps = Omit<\n  React.ComponentProps<\"div\">,\n  \"defaultValue\" | \"onChange\"\n> & {\n  value?: string\n  defaultValue?: string\n  onValueChange?: (value: string, mode: ModeType) => void\n  defaultMode?: ModeType\n  mode?: ModeType\n  onModeChange?: (mode: ModeType) => void\n}\n\nfunction ColorPickerRoot({\n  value,\n  defaultValue = \"#FFFFFF\",\n  onValueChange,\n  defaultMode = \"hex\",\n  mode: providedMode,\n  onModeChange,\n  className,\n  ...props\n}: ColorPickerProps) {\n  const providedColor = React.useMemo(\n    () => (value ? parseColor(value) : undefined),\n    [value]\n  )\n  const [internalColor, setInternalColor] = React.useState(() =>\n    parseColor(defaultValue)\n  )\n  const [internalMode, setInternalMode] = React.useState(defaultMode)\n  const mode = providedMode ?? internalMode\n\n  const rawColor = React.useMemo<ColorObject>(\n    () => ({\n      l: providedColor?.l ?? internalColor.l,\n      c: providedColor?.c ?? internalColor.c,\n      h: providedColor?.h ?? internalColor.h,\n      alpha: providedColor?.alpha ?? internalColor.alpha ?? 1,\n    }),\n    [internalColor, providedColor]\n  )\n  const displayedColor = React.useMemo(\n    () => (mode === \"oklch\" ? rawColor : clampToSrgb(rawColor)),\n    [mode, rawColor]\n  )\n  const rawColorRef = React.useRef(rawColor)\n\n  React.useEffect(() => {\n    rawColorRef.current = rawColor\n  }, [rawColor])\n\n  const setColor = React.useCallback(\n    (nextColor: Partial<ColorObject>) => {\n      const next = { ...rawColorRef.current, ...nextColor }\n      rawColorRef.current = next\n\n      if (value === undefined) {\n        setInternalColor(next)\n      }\n\n      onValueChange?.(getColorString(next, mode), mode)\n    },\n    [mode, onValueChange, value]\n  )\n\n  const setMode = React.useCallback(\n    (nextMode: ModeType) => {\n      if (providedMode === undefined) {\n        setInternalMode(nextMode)\n      }\n      onModeChange?.(nextMode)\n    },\n    [onModeChange, providedMode]\n  )\n\n  const contextValue = React.useMemo<ColorPickerContextValue>(\n    () => ({\n      l: displayedColor.l,\n      c: displayedColor.c,\n      h: displayedColor.h,\n      alpha: displayedColor.alpha ?? 1,\n      mode,\n      setColor,\n      setMode,\n    }),\n    [displayedColor, mode, setColor, setMode]\n  )\n\n  return (\n    <ColorPickerContext.Provider value={contextValue}>\n      <div\n        data-slot=\"color-picker\"\n        className={cn(\n          \"flex w-full flex-col gap-[var(--hui-space-4)]\",\n          className\n        )}\n        {...props}\n      />\n    </ColorPickerContext.Provider>\n  )\n}\n\nColorPickerRoot.displayName = \"ColorPicker\"\n\ntype ColorPickerAreaProps = React.ComponentProps<\"div\">\n\nfunction ColorPickerArea(props: ColorPickerAreaProps) {\n  const { mode } = useColorPicker()\n  return mode === \"oklch\" ? (\n    <OklchColorArea {...props} />\n  ) : (\n    <HslColorArea {...props} />\n  )\n}\n\nColorPickerArea.displayName = \"ColorPicker.Area\"\n\nfunction getAreaPoint(\n  event: React.PointerEvent<HTMLDivElement>,\n  element: HTMLDivElement\n) {\n  const rect = element.getBoundingClientRect()\n  return {\n    x: clamp01((event.clientX - rect.left) / rect.width),\n    y: clamp01((event.clientY - rect.top) / rect.height),\n  }\n}\n\nfunction OklchColorArea({\n  className,\n  onPointerDown,\n  onPointerMove,\n  onPointerUp,\n  onPointerCancel,\n  onKeyDown,\n  \"aria-label\": ariaLabel = \"Color area, chroma and lightness\",\n  ...props\n}: ColorPickerAreaProps) {\n  const canvasRef = React.useRef<HTMLCanvasElement>(null)\n  const draggingRef = React.useRef(false)\n  const { l: lightness, c: chroma, h: hue, setColor } = useColorPicker()\n\n  React.useEffect(() => {\n    const canvas = canvasRef.current\n    if (!canvas) return\n\n    let cancelled = false\n    const frame = requestAnimationFrame(() => {\n      if (cancelled) return\n\n      const context = canvas.getContext(\"2d\")\n      if (!context) return\n\n      const image = context.createImageData(\n        CANVAS_RESOLUTION,\n        CANVAS_RESOLUTION\n      )\n\n      for (let y = 0; y < CANVAS_RESOLUTION; y += 1) {\n        const nextLightness = 1 - y / (CANVAS_RESOLUTION - 1)\n        for (let x = 0; x < CANVAS_RESOLUTION; x += 1) {\n          const nextChroma =\n            (x / (CANVAS_RESOLUTION - 1)) * CHROMA_MAX\n          const rgb = oklchToRgb(nextLightness, nextChroma, hue)\n          const index = (y * CANVAS_RESOLUTION + x) * 4\n\n          image.data[index] = Math.round(clamp01(rgb?.r ?? 0.5) * 255)\n          image.data[index + 1] = Math.round(\n            clamp01(rgb?.g ?? 0.5) * 255\n          )\n          image.data[index + 2] = Math.round(\n            clamp01(rgb?.b ?? 0.5) * 255\n          )\n          image.data[index + 3] = 255\n        }\n      }\n\n      context.putImageData(image, 0, 0)\n    })\n\n    return () => {\n      cancelled = true\n      cancelAnimationFrame(frame)\n    }\n  }, [hue])\n\n  const applyPosition = React.useCallback(\n    (x: number, y: number) => {\n      setColor({ c: clamp01(x) * CHROMA_MAX, l: 1 - clamp01(y) })\n    },\n    [setColor]\n  )\n\n  const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerDown?.(event)\n    if (event.defaultPrevented) return\n\n    event.preventDefault()\n    draggingRef.current = true\n    event.currentTarget.setPointerCapture(event.pointerId)\n    const point = getAreaPoint(event, event.currentTarget)\n    applyPosition(point.x, point.y)\n  }\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(event)\n    if (!draggingRef.current || event.defaultPrevented) return\n\n    const point = getAreaPoint(event, event.currentTarget)\n    applyPosition(point.x, point.y)\n  }\n\n  const stopDragging = (event: React.PointerEvent<HTMLDivElement>) => {\n    draggingRef.current = false\n    if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n      event.currentTarget.releasePointerCapture(event.pointerId)\n    }\n  }\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    onKeyDown?.(event)\n    if (event.defaultPrevented) return\n\n    let x = clamp01(chroma / CHROMA_MAX)\n    let y = clamp01(1 - lightness)\n    const step = event.shiftKey ? AREA_STEP_LARGE : AREA_STEP\n\n    switch (event.key) {\n      case \"ArrowLeft\":\n        x -= step\n        break\n      case \"ArrowRight\":\n        x += step\n        break\n      case \"ArrowUp\":\n        y -= step\n        break\n      case \"ArrowDown\":\n        y += step\n        break\n      case \"PageUp\":\n        y -= AREA_STEP_LARGE\n        break\n      case \"PageDown\":\n        y += AREA_STEP_LARGE\n        break\n      case \"Home\":\n        x = 0\n        break\n      case \"End\":\n        x = 1\n        break\n      default:\n        return\n    }\n\n    event.preventDefault()\n    applyPosition(x, y)\n  }\n\n  const x = clamp01(chroma / CHROMA_MAX)\n  const y = clamp01(1 - lightness)\n  const valueText = `chroma ${Math.round(x * 100)}%, lightness ${Math.round(\n    lightness * 100\n  )}%`\n\n  return (\n    <div\n      {...props}\n      role=\"slider\"\n      tabIndex={0}\n      aria-label={ariaLabel}\n      aria-valuemin={0}\n      aria-valuemax={100}\n      aria-valuenow={Math.round(x * 100)}\n      aria-valuetext={valueText}\n      data-slot=\"color-picker-area\"\n      className={cn(\n        \"relative aspect-square w-full touch-none overflow-hidden rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-primary)] select-none focus-visible:[outline:var(--hui-focus-ring)] focus-visible:outline-offset-[var(--hui-focus-ring-offset-inset)]\",\n        className\n      )}\n      onPointerDown={handlePointerDown}\n      onPointerMove={handlePointerMove}\n      onPointerUp={(event) => {\n        onPointerUp?.(event)\n        stopDragging(event)\n      }}\n      onPointerCancel={(event) => {\n        onPointerCancel?.(event)\n        stopDragging(event)\n      }}\n      onKeyDown={handleKeyDown}\n    >\n      <canvas\n        ref={canvasRef}\n        width={CANVAS_RESOLUTION}\n        height={CANVAS_RESOLUTION}\n        className=\"block size-full\"\n        data-slot=\"color-picker-area-canvas\"\n      />\n      <span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute top-0 left-0 block size-[var(--hui-space-4)] rounded-full border-[3px] border-[var(--hui-color-foreground-base-emphasis)] shadow-[var(--hui-shadow-soft)]\"\n        style={{\n          backgroundColor: formatOklch({\n            l: lightness,\n            c: chroma,\n            h: hue,\n          }),\n          transform: \"translate(-50%, -50%)\",\n          left: `${round(x * 100, 4)}%`,\n          top: `${round(y * 100, 4)}%`,\n        }}\n        data-slot=\"color-picker-area-thumb\"\n      />\n    </div>\n  )\n}\n\nfunction HslColorArea({\n  className,\n  style,\n  onPointerDown,\n  onPointerMove,\n  onPointerUp,\n  onPointerCancel,\n  onKeyDown,\n  \"aria-label\": ariaLabel = \"Color area, saturation and brightness\",\n  ...props\n}: ColorPickerAreaProps) {\n  const draggingRef = React.useRef(false)\n  const { l: lightness, c: chroma, h: hue, setColor } = useColorPicker()\n  const hsl = React.useMemo(\n    () => oklchToHsl({ l: lightness, c: chroma, h: hue }),\n    [chroma, hue, lightness]\n  )\n  const hueColor = `hsl(${round(hsl.h, 4)} 100% 50%)`\n\n  const applyPosition = React.useCallback(\n    (x: number, y: number) => {\n      const nextX = clamp01(x)\n      const saturation = nextX * 100\n      const topLightness = nextX < 0.01 ? 100 : 50 + 50 * (1 - nextX)\n      const next = hslToOklch(\n        hsl.h,\n        saturation,\n        topLightness * (1 - clamp01(y))\n      )\n      setColor(next)\n    },\n    [hsl.h, setColor]\n  )\n\n  const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerDown?.(event)\n    if (event.defaultPrevented) return\n\n    event.preventDefault()\n    draggingRef.current = true\n    event.currentTarget.setPointerCapture(event.pointerId)\n    const point = getAreaPoint(event, event.currentTarget)\n    applyPosition(point.x, point.y)\n  }\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(event)\n    if (!draggingRef.current || event.defaultPrevented) return\n\n    const point = getAreaPoint(event, event.currentTarget)\n    applyPosition(point.x, point.y)\n  }\n\n  const stopDragging = (event: React.PointerEvent<HTMLDivElement>) => {\n    draggingRef.current = false\n    if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n      event.currentTarget.releasePointerCapture(event.pointerId)\n    }\n  }\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    onKeyDown?.(event)\n    if (event.defaultPrevented) return\n\n    let x = clamp01(hsl.s / 100)\n    const topLightness = x < 0.01 ? 100 : 50 + 50 * (1 - x)\n    let y = clamp01(1 - hsl.l / topLightness)\n    const step = event.shiftKey ? AREA_STEP_LARGE : AREA_STEP\n\n    switch (event.key) {\n      case \"ArrowLeft\":\n        x -= step\n        break\n      case \"ArrowRight\":\n        x += step\n        break\n      case \"ArrowUp\":\n        y -= step\n        break\n      case \"ArrowDown\":\n        y += step\n        break\n      case \"PageUp\":\n        y -= AREA_STEP_LARGE\n        break\n      case \"PageDown\":\n        y += AREA_STEP_LARGE\n        break\n      case \"Home\":\n        x = 0\n        break\n      case \"End\":\n        x = 1\n        break\n      default:\n        return\n    }\n\n    event.preventDefault()\n    applyPosition(x, y)\n  }\n\n  const x = clamp01(hsl.s / 100)\n  const topLightness = x < 0.01 ? 100 : 50 + 50 * (1 - x)\n  const y = clamp01(1 - hsl.l / topLightness)\n  const valueText = `saturation ${Math.round(\n    hsl.s\n  )}%, brightness ${Math.round((hsl.l / topLightness) * 100)}%`\n\n  return (\n    <div\n      {...props}\n      role=\"slider\"\n      tabIndex={0}\n      aria-label={ariaLabel}\n      aria-valuemin={0}\n      aria-valuemax={100}\n      aria-valuenow={Math.round(hsl.s)}\n      aria-valuetext={valueText}\n      data-slot=\"color-picker-area\"\n      className={cn(\n        \"relative aspect-square w-full touch-none overflow-hidden rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-primary)] select-none focus-visible:[outline:var(--hui-focus-ring)] focus-visible:outline-offset-[var(--hui-focus-ring-offset-inset)]\",\n        className\n      )}\n      style={{\n        ...style,\n        backgroundColor: hueColor,\n        backgroundImage:\n          \"linear-gradient(0deg, rgb(0 0 0), transparent), linear-gradient(90deg, rgb(255 255 255), transparent)\",\n      }}\n      onPointerDown={handlePointerDown}\n      onPointerMove={handlePointerMove}\n      onPointerUp={(event) => {\n        onPointerUp?.(event)\n        stopDragging(event)\n      }}\n      onPointerCancel={(event) => {\n        onPointerCancel?.(event)\n        stopDragging(event)\n      }}\n      onKeyDown={handleKeyDown}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute block size-[var(--hui-space-4)] rounded-full border-[3px] border-[var(--hui-color-foreground-base-emphasis)] shadow-[var(--hui-shadow-soft)]\"\n        style={{\n          backgroundColor: `hsl(${round(hsl.h, 4)} ${round(hsl.s, 4)}% ${round(hsl.l, 4)}%)`,\n          left: `${round(x * 100, 4)}%`,\n          top: `${round(y * 100, 4)}%`,\n          transform: \"translate(-50%, -50%)\",\n        }}\n        data-slot=\"color-picker-area-thumb\"\n      />\n    </div>\n  )\n}\n\nconst sliderRootClassName =\n  \"relative flex w-full touch-none items-center select-none data-disabled:opacity-50\"\nconst sliderControlClassName = \"flex w-full items-center\"\nconst sliderTrackClassName =\n  \"relative h-[var(--hui-space-4)] w-full grow overflow-hidden rounded-[var(--hui-radius-3)] border-[0.5px] border-[var(--hui-color-border-base-primary)]\"\nconst sliderIndicatorClassName =\n  \"absolute h-full rounded-[var(--hui-radius-full)] bg-transparent\"\nconst sliderThumbClassName =\n  \"absolute block size-[var(--hui-space-4)] rounded-full border-[3px] border-[var(--hui-color-foreground-base-emphasis)] shadow-[var(--hui-shadow-soft)] outline-none focus-visible:[outline:var(--hui-focus-ring)] focus-visible:outline-offset-[var(--hui-focus-ring-offset-accent)]\"\n\ntype ColorPickerHueProps = Omit<\n  SliderPrimitive.Root.Props,\n  \"defaultValue\" | \"max\" | \"min\" | \"onValueChange\" | \"step\" | \"value\"\n>\n\nfunction ColorPickerHue({ className, ...props }: ColorPickerHueProps) {\n  const { l: lightness, c: chroma, h: hue, mode, setColor } =\n    useColorPicker()\n  const isOklch = mode === \"oklch\"\n  const hsl = React.useMemo(\n    () => (isOklch ? null : oklchToHsl({ l: lightness, c: chroma, h: hue })),\n    [chroma, hue, isOklch, lightness]\n  )\n  const value = isOklch ? hue : (hsl?.h ?? 0)\n\n  const handleValueChange = (nextValue: number | number[]) => {\n    const nextHue = Array.isArray(nextValue) ? (nextValue[0] ?? 0) : nextValue\n\n    if (isOklch) {\n      setColor({ h: nextHue })\n      return\n    }\n\n    if (!hsl) return\n    setColor(hslToOklch(nextHue, hsl.s, hsl.l))\n  }\n\n  return (\n    <SliderPrimitive.Root\n      {...props}\n      className={cn(sliderRootClassName, className)}\n      max={360}\n      step={isOklch ? 0.1 : 1}\n      value={value}\n      onValueChange={handleValueChange}\n      data-slot=\"color-picker-hue\"\n    >\n      <SliderPrimitive.Control\n        className={sliderControlClassName}\n        data-slot=\"color-picker-slider-control\"\n      >\n        <SliderPrimitive.Track\n          className={sliderTrackClassName}\n          style={{\n            background: isOklch\n              ? \"linear-gradient(90deg in oklch, oklch(0.7 0.18 0), oklch(0.7 0.18 60), oklch(0.7 0.18 120), oklch(0.7 0.18 180), oklch(0.7 0.18 240), oklch(0.7 0.18 300), oklch(0.7 0.18 360))\"\n              : \"linear-gradient(90deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)\",\n          }}\n          data-slot=\"color-picker-slider-track\"\n        >\n          <SliderPrimitive.Indicator\n            className={sliderIndicatorClassName}\n            data-slot=\"color-picker-slider-range\"\n          />\n          <SliderPrimitive.Thumb\n            className={sliderThumbClassName}\n            aria-label={isOklch ? \"OKLCH hue\" : \"HSL hue\"}\n            style={{\n              backgroundColor: `hsl(${round(value, 4)} 100% 50%)`,\n            }}\n            data-slot=\"color-picker-slider-thumb\"\n          />\n        </SliderPrimitive.Track>\n      </SliderPrimitive.Control>\n    </SliderPrimitive.Root>\n  )\n}\n\nColorPickerHue.displayName = \"ColorPicker.Hue\"\n\ntype ColorPickerAlphaProps = Omit<\n  SliderPrimitive.Root.Props,\n  \"defaultValue\" | \"max\" | \"min\" | \"onValueChange\" | \"step\" | \"value\"\n>\n\nfunction ColorPickerAlpha({ className, ...props }: ColorPickerAlphaProps) {\n  const { l: lightness, c: chroma, h: hue, alpha, setColor } =\n    useColorPicker()\n\n  return (\n    <SliderPrimitive.Root\n      {...props}\n      className={cn(sliderRootClassName, className)}\n      max={100}\n      step={1}\n      value={alpha * 100}\n      onValueChange={(nextValue) => {\n        const value = Array.isArray(nextValue)\n          ? (nextValue[0] ?? 100)\n          : nextValue\n        setColor({ alpha: value / 100 })\n      }}\n      data-slot=\"color-picker-alpha\"\n    >\n      <SliderPrimitive.Control\n        className={sliderControlClassName}\n        data-slot=\"color-picker-slider-control\"\n      >\n        <SliderPrimitive.Track\n          className={cn(\n            sliderTrackClassName,\n            \"bg-[var(--hui-color-background-base-primary)]\"\n          )}\n          style={{\n            backgroundImage:\n              \"linear-gradient(45deg, var(--hui-color-border-base-primary) 25%, transparent 25%), linear-gradient(-45deg, var(--hui-color-border-base-primary) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--hui-color-border-base-primary) 75%), linear-gradient(-45deg, transparent 75%, var(--hui-color-border-base-primary) 75%)\",\n            backgroundPosition: \"0 0, 0 6px, 6px -6px, -6px 0\",\n            backgroundSize: \"12px 12px\",\n          }}\n          data-slot=\"color-picker-slider-track\"\n        >\n          <span\n            aria-hidden=\"true\"\n            className=\"absolute inset-0 rounded-[var(--hui-radius-3)]\"\n            style={{\n              backgroundImage: `linear-gradient(90deg, transparent, ${formatOklch(\n                { l: lightness, c: chroma, h: hue }\n              )})`,\n            }}\n            data-slot=\"color-picker-alpha-gradient\"\n          />\n          <SliderPrimitive.Indicator\n            className={sliderIndicatorClassName}\n            data-slot=\"color-picker-slider-range\"\n          />\n          <SliderPrimitive.Thumb\n            className={sliderThumbClassName}\n            aria-label=\"Alpha\"\n            style={{\n              backgroundColor: formatOklch({\n                l: lightness,\n                c: chroma,\n                h: hue,\n                alpha,\n              }),\n            }}\n            data-slot=\"color-picker-slider-thumb\"\n          />\n        </SliderPrimitive.Track>\n      </SliderPrimitive.Control>\n    </SliderPrimitive.Root>\n  )\n}\n\nColorPickerAlpha.displayName = \"ColorPicker.Alpha\"\n\ntype ColorPickerInputProps = Omit<\n  React.ComponentProps<typeof Input>,\n  \"defaultValue\" | \"readOnly\" | \"value\"\n> & {\n  copyable?: boolean\n}\n\nfunction ColorPickerInput({\n  copyable = false,\n  className,\n  ...props\n}: ColorPickerInputProps) {\n  const { l: lightness, c: chroma, h: hue, alpha, mode } = useColorPicker()\n  const [copied, setCopied] = React.useState(false)\n  const copiedTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n    null\n  )\n  const value = React.useMemo(\n    () => getColorString({ l: lightness, c: chroma, h: hue, alpha }, mode),\n    [alpha, chroma, hue, lightness, mode]\n  )\n\n  React.useEffect(\n    () => () => {\n      if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current)\n    },\n    []\n  )\n\n  const copyValue = async () => {\n    try {\n      await navigator.clipboard.writeText(value)\n      setCopied(true)\n      if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current)\n      copiedTimerRef.current = setTimeout(() => setCopied(false), 1500)\n    } catch {\n      setCopied(false)\n    }\n  }\n\n  return (\n    <span className=\"relative flex min-w-0 flex-1\" data-slot=\"color-picker-input-wrapper\">\n      <Input\n        {...props}\n        value={value}\n        readOnly\n        className={cn(\n          copyable && \"[&_[data-slot=input]]:pr-[var(--hui-space-9)]\",\n          className\n        )}\n        data-slot=\"color-picker-input\"\n      />\n      {copyable && (\n        <button\n          type=\"button\"\n          aria-label={copied ? \"Color copied\" : \"Copy color\"}\n          title={copied ? \"Copied\" : \"Copy color\"}\n          onClick={copyValue}\n          className=\"absolute top-1/2 right-[var(--hui-space-2)] inline-flex size-[var(--hui-space-6)] -translate-y-1/2 cursor-pointer items-center justify-center rounded-[var(--hui-radius-1)] border-0 bg-transparent text-[var(--hui-color-foreground-base-secondary)] outline-none hover:bg-[var(--hui-color-background-base-primary-hover)] hover:text-[var(--hui-color-foreground-base-primary)] focus-visible:[outline:var(--hui-focus-ring)]\"\n          data-slot=\"color-picker-copy\"\n        >\n          {copied ? (\n            <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-3.5 fill-none stroke-current\">\n              <path d=\"m5 12 4 4L19 6\" strokeWidth=\"1.75\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n            </svg>\n          ) : (\n            <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-3.5 fill-none stroke-current\">\n              <rect x=\"8\" y=\"8\" width=\"11\" height=\"11\" rx=\"2\" strokeWidth=\"1.5\" />\n              <path d=\"M16 8V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h1\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n            </svg>\n          )}\n        </button>\n      )}\n    </span>\n  )\n}\n\nColorPickerInput.displayName = \"ColorPicker.Input\"\n\ntype ColorPickerModeProps = React.ComponentProps<typeof Select.Trigger> & {\n  options?: readonly ModeType[]\n}\n\nfunction ColorPickerMode({\n  className,\n  options = SUPPORTED_MODES,\n  ...props\n}: ColorPickerModeProps) {\n  const { mode, setMode } = useColorPicker()\n\n  return (\n    <Select\n      value={mode}\n      onValueChange={(nextMode) => {\n        if (nextMode && SUPPORTED_MODES.includes(nextMode as ModeType)) {\n          setMode(nextMode as ModeType)\n        }\n      }}\n    >\n      <Select.Trigger\n        {...props}\n        className={cn(\"min-w-[var(--hui-space-15)] shrink-0\", className)}\n        data-slot=\"color-picker-mode\"\n      >\n        <Select.Value>{mode.toUpperCase()}</Select.Value>\n      </Select.Trigger>\n      <Select.Content data-slot=\"color-picker-mode-content\">\n        {options.map((option) => (\n          <Select.Item key={option} value={option}>\n            {option.toUpperCase()}\n          </Select.Item>\n        ))}\n      </Select.Content>\n    </Select>\n  )\n}\n\nColorPickerMode.displayName = \"ColorPicker.Mode\"\n\nconst ColorPicker = Object.assign(ColorPickerRoot, {\n  Area: ColorPickerArea,\n  Hue: ColorPickerHue,\n  Alpha: ColorPickerAlpha,\n  Input: ColorPickerInput,\n  Mode: ColorPickerMode,\n})\n\nexport {\n  ColorPicker,\n  ColorPickerAlpha,\n  ColorPickerArea,\n  ColorPickerHue,\n  ColorPickerInput,\n  ColorPickerMode,\n  ColorPickerRoot,\n  SUPPORTED_MODES,\n  formatColor,\n  type ColorObject,\n  type ColorPickerAlphaProps,\n  type ColorPickerAreaProps,\n  type ColorPickerHueProps,\n  type ColorPickerInputProps,\n  type ColorPickerModeProps,\n  type ColorPickerProps,\n  type ModeType,\n}\n","target":"components/ui/color-picker.tsx"}],"dependencies":["@base-ui-components/react","culori"],"registryDependencies":["https://www.honestui.com/r/input.json","https://www.honestui.com/r/select.json"]}