{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "compare-slider",
  "type": "registry:ui",
  "title": "Compare Slider",
  "description": "Two versions of the same frame, split by a divider you drag, or move with the arrow keys.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/loomui/compare-slider.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface CompareSliderProps extends Omit<\n  React.ComponentProps<\"div\">,\n  \"onChange\"\n> {\n  /** Sits in flow and sets the size of the frame. Shown before the divider. */\n  before: React.ReactNode\n  /** Laid over the top and clipped to after the divider. */\n  after: React.ReactNode\n  /** Divider position, 0 to 100. Leave it out to let the slider own it. */\n  position?: number\n  /** Starting position when the slider owns it. */\n  defaultPosition?: number\n  /** Called with the position the divider moved to. */\n  onPositionChange?: (position: number) => void\n  /** Split left to right, or top to bottom. */\n  orientation?: \"horizontal\" | \"vertical\"\n  /** Percent moved per arrow key. Shift moves five of these. */\n  step?: number\n  /** Name for the divider, which is a real slider. */\n  label?: string\n}\n\nconst clamp = (value: number) => Math.min(Math.max(value, 0), 100)\n\nexport function CompareSlider({\n  before,\n  after,\n  position,\n  defaultPosition = 50,\n  onPositionChange,\n  orientation = \"horizontal\",\n  step = 2,\n  label = \"Compare\",\n  className,\n  style,\n  ...props\n}: CompareSliderProps) {\n  const [own, setOwn] = React.useState(defaultPosition)\n  const [dragging, setDragging] = React.useState(false)\n  const frameRef = React.useRef<HTMLDivElement>(null)\n\n  const isControlled = position !== undefined\n  const value = clamp(isControlled ? position : own)\n  const vertical = orientation === \"vertical\"\n\n  const commit = (next: number) => {\n    const clamped = clamp(next)\n    if (!isControlled) {\n      setOwn(clamped)\n    }\n    onPositionChange?.(clamped)\n  }\n\n  const trackPointer = (event: React.PointerEvent<HTMLDivElement>) => {\n    const frame = frameRef.current\n    if (!frame) {\n      return\n    }\n    const rect = frame.getBoundingClientRect()\n    const ratio = vertical\n      ? (event.clientY - rect.top) / rect.height\n      : (event.clientX - rect.left) / rect.width\n    commit(ratio * 100)\n  }\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    const distance = event.shiftKey ? step * 5 : step\n    const back = vertical ? \"ArrowUp\" : \"ArrowLeft\"\n    const forward = vertical ? \"ArrowDown\" : \"ArrowRight\"\n\n    if (event.key === back) {\n      commit(value - distance)\n    } else if (event.key === forward) {\n      commit(value + distance)\n    } else if (event.key === \"Home\") {\n      commit(0)\n    } else if (event.key === \"End\") {\n      commit(100)\n    } else {\n      return\n    }\n\n    event.preventDefault()\n  }\n\n  return (\n    <div\n      ref={frameRef}\n      data-slot=\"compare-slider\"\n      data-dragging={dragging ? \"\" : undefined}\n      className={cn(\n        \"group/compare relative isolate overflow-hidden select-none\",\n        className\n      )}\n      onPointerDown={(event) => {\n        event.currentTarget.setPointerCapture(event.pointerId)\n        setDragging(true)\n        trackPointer(event)\n      }}\n      onPointerMove={(event) => {\n        if (dragging) {\n          trackPointer(event)\n        }\n      }}\n      onPointerUp={(event) => {\n        event.currentTarget.releasePointerCapture(event.pointerId)\n        setDragging(false)\n      }}\n      onPointerCancel={() => setDragging(false)}\n      style={{\n        // Block only the axis the divider travels on, so the page can still be\n        // scrolled with a finger that lands on the frame.\n        touchAction: vertical ? \"pan-x\" : \"pan-y\",\n        ...style,\n      }}\n      {...props}\n    >\n      <div className=\"h-full w-full\">{before}</div>\n\n      <div\n        className=\"absolute inset-0\"\n        style={{\n          clipPath: vertical\n            ? `inset(${value}% 0 0 0)`\n            : `inset(0 0 0 ${value}%)`,\n        }}\n      >\n        {after}\n      </div>\n\n      <div\n        // A real slider, so the comparison is reachable without a pointer. The\n        // value is the divider's position, which is the only thing to move.\n        role=\"slider\"\n        tabIndex={0}\n        aria-label={label}\n        aria-orientation={orientation}\n        aria-valuemin={0}\n        aria-valuemax={100}\n        aria-valuenow={Math.round(value)}\n        onKeyDown={handleKeyDown}\n        className={cn(\n          \"group/handle absolute z-10 flex items-center justify-center outline-none\",\n          vertical\n            ? \"inset-x-0 h-0 cursor-ns-resize\"\n            : \"inset-y-0 w-0 cursor-ew-resize\",\n          // Only glide for keys. During a drag the divider has to sit exactly\n          // under the finger or it feels like it is being towed.\n          !dragging &&\n            \"transition-[left,top] duration-200 ease-[var(--ease-out-quart)] motion-reduce:transition-none\"\n        )}\n        style={vertical ? { top: `${value}%` } : { left: `${value}%` }}\n      >\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"bg-background/90 absolute\",\n            vertical ? \"inset-x-0 h-0.5\" : \"inset-y-0 w-0.5\"\n          )}\n        />\n        <span\n          data-slot=\"compare-slider-knob\"\n          aria-hidden=\"true\"\n          className=\"bg-background text-foreground ring-ring/60 relative grid size-8 place-items-center rounded-full border shadow-sm group-focus-visible/handle:ring-2\"\n        >\n          <svg\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth={2}\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className={cn(\"size-4\", vertical && \"rotate-90\")}\n          >\n            <path d=\"m9 6-4 6 4 6\" />\n            <path d=\"m15 6 4 6-4 6\" />\n          </svg>\n        </span>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "cssVars": {
    "theme": {
      "ease-out-quart": "cubic-bezier(0.165, 0.84, 0.44, 1)"
    }
  }
}