{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scrub-bar",
  "registryDependencies": [
    "progress"
  ],
  "files": [
    {
      "path": "components/ui/scrub-bar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useRef,\n  type ComponentProps,\n  type HTMLAttributes,\n} from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Progress } from \"@/components/ui/progress\"\n\nfunction formatTimestamp(value: number) {\n  if (!Number.isFinite(value) || value < 0) return \"0:00\"\n  const totalSeconds = Math.floor(value)\n  const minutes = Math.floor(totalSeconds / 60)\n  const seconds = totalSeconds % 60\n  return `${minutes}:${seconds.toString().padStart(2, \"0\")}`\n}\n\ninterface ScrubBarContextValue {\n  duration: number\n  value: number\n  progress: number\n  onScrub?: (time: number) => void\n  onScrubStart?: () => void\n  onScrubEnd?: () => void\n}\n\nconst ScrubBarContext = createContext<ScrubBarContextValue | null>(null)\n\nfunction useScrubBarContext() {\n  const context = useContext(ScrubBarContext)\n  if (!context) {\n    throw new Error(\"useScrubBarContext must be used within a ScrubBar.Root\")\n  }\n  return context\n}\n\ninterface ScrubBarContainerProps extends HTMLAttributes<HTMLDivElement> {\n  duration: number\n  value: number\n  onScrub?: (time: number) => void\n  onScrubStart?: () => void\n  onScrubEnd?: () => void\n}\n\nfunction ScrubBarContainer({\n  duration,\n  value,\n  onScrub,\n  onScrubStart,\n  onScrubEnd,\n  children,\n  className,\n  ...props\n}: ScrubBarContainerProps) {\n  const progress = duration > 0 ? (value / duration) * 100 : 0\n\n  const contextValue: ScrubBarContextValue = {\n    duration,\n    value,\n    progress,\n    onScrub,\n    onScrubStart,\n    onScrubEnd,\n  }\n\n  return (\n    <ScrubBarContext.Provider value={contextValue}>\n      <div\n        data-slot=\"scrub-bar-root\"\n        className={cn(\"flex w-full items-center\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </ScrubBarContext.Provider>\n  )\n}\nScrubBarContainer.displayName = \"ScrubBarContainer\"\n\ntype ScrubBarTrackProps = HTMLAttributes<HTMLDivElement>\n\nfunction ScrubBarTrack({ className, children, ...props }: ScrubBarTrackProps) {\n  const trackRef = useRef<HTMLDivElement | null>(null)\n  const { duration, onScrub, onScrubStart, onScrubEnd, value } =\n    useScrubBarContext()\n\n  const getTimeFromClientX = useCallback(\n    (clientX: number) => {\n      const track = trackRef.current\n      if (!track || !duration) return null\n      const rect = track.getBoundingClientRect()\n      const ratio = (clientX - rect.left) / rect.width\n      const clamped = Math.min(Math.max(ratio, 0), 1)\n      return duration * clamped\n    },\n    [duration]\n  )\n\n  const handlePointerDown = useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      if (!duration) return\n      event.preventDefault()\n      onScrubStart?.()\n      const time = getTimeFromClientX(event.clientX)\n      if (time != null) {\n        onScrub?.(time)\n      }\n\n      const handleMove = (moveEvent: PointerEvent) => {\n        const nextTime = getTimeFromClientX(moveEvent.clientX)\n        if (nextTime != null) {\n          onScrub?.(nextTime)\n        }\n      }\n\n      const handleUp = () => {\n        onScrubEnd?.()\n        window.removeEventListener(\"pointermove\", handleMove)\n        window.removeEventListener(\"pointerup\", handleUp)\n      }\n\n      window.addEventListener(\"pointermove\", handleMove)\n      window.addEventListener(\"pointerup\", handleUp, { once: true })\n    },\n    [duration, getTimeFromClientX, onScrub, onScrubEnd, onScrubStart]\n  )\n\n  const clampedValue = Math.min(Math.max(value, 0), duration || 0)\n\n  return (\n    <div\n      ref={trackRef}\n      data-slot=\"scrub-bar-track\"\n      className={cn(\n        \"bg-secondary relative h-2 w-full grow cursor-pointer touch-none rounded-full transition-none select-none\",\n        className\n      )}\n      onPointerDown={handlePointerDown}\n      role=\"slider\"\n      aria-valuemin={0}\n      aria-valuemax={duration || 0}\n      aria-valuenow={clampedValue}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\nScrubBarTrack.displayName = \"ScrubBarTrack\"\n\ntype ScrubBarProgressProps = Omit<ComponentProps<typeof Progress>, \"value\">\n\nfunction ScrubBarProgress({ className, ...props }: ScrubBarProgressProps) {\n  const { progress } = useScrubBarContext()\n\n  return (\n    <Progress\n      data-slot=\"scrub-bar-progress\"\n      value={progress}\n      className={cn(\"absolute h-full [&>div]:transition-none\", className)}\n      {...props}\n    />\n  )\n}\nScrubBarProgress.displayName = \"ScrubBarProgress\"\n\ntype ScrubBarThumbProps = HTMLAttributes<HTMLDivElement>\n\nfunction ScrubBarThumb({ className, children, ...props }: ScrubBarThumbProps) {\n  const { progress } = useScrubBarContext()\n  return (\n    <div\n      data-slot=\"scrub-bar-thumb\"\n      className={cn(\n        \"bg-primary absolute top-1/2 block h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full transition-colors disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      style={{ left: `${progress}%` }}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\nScrubBarThumb.displayName = \"ScrubBarThumb\"\n\ninterface ScrubBarTimeLabelProps extends HTMLAttributes<HTMLSpanElement> {\n  time: number\n  format?: (time: number) => string\n}\n\nfunction ScrubBarTimeLabel({\n  className,\n  time,\n  format = formatTimestamp,\n  ...props\n}: ScrubBarTimeLabelProps) {\n  return (\n    <span\n      data-slot=\"scrub-bar-time-label\"\n      {...props}\n      className={cn(\"tabular-nums\", className)}\n    >\n      {format(time)}\n    </span>\n  )\n}\nScrubBarTimeLabel.displayName = \"ScrubBarTimeLabel\"\n\nexport {\n  ScrubBarContainer,\n  ScrubBarTrack,\n  ScrubBarProgress,\n  ScrubBarThumb,\n  ScrubBarTimeLabel,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}