{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timeline",
  "type": "registry:ui",
  "title": "Timeline",
  "description": "Smoothly animated timeline slider with resizable handles for interactive use.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/timeline.tsx",
      "content": "'use client';\nimport React, {\n  useState,\n  useCallback,\n  useEffect,\n  useRef,\n  createContext,\n  useContext,\n} from 'react';\nimport { motion, useMotionValue, animate, useInView } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\nconst TimelineContext = createContext<{\n  textRef: React.RefObject<HTMLDivElement | null>;\n  leftPos: number;\n  rightPos: number;\n  textHeight: number;\n  handleWidth: number;\n  handleLeftStart: (clientX: number) => void;\n  handleRightStart: (clientX: number) => void;\n} | null>(null);\n\nconst useTimelineContext = () => {\n  const context = useContext(TimelineContext);\n  if (!context)\n    throw new Error('Timeline components must be used within a Timeline');\n  return context;\n};\n\ninterface TimelineProps {\n  children: React.ReactNode;\n  rotation?: number;\n  initialLeft?: number;\n  minWidth?: number;\n  className?: string;\n  containerClassName?: string;\n  handleClassName?: string;\n  handleIndicatorClassName?: string;\n}\n\nconst Timeline: React.FC<TimelineProps> = ({\n  children,\n  rotation = -2.76,\n  initialLeft = 0,\n  minWidth = 56,\n  className,\n  containerClassName,\n  handleClassName,\n  handleIndicatorClassName,\n}) => {\n  const textRef = useRef<HTMLDivElement | null>(null);\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const rightMotion = useMotionValue(0);\n  const isInView = useInView(containerRef, { amount: 0.3 });\n  const [textWidth, setTextWidth] = useState(0);\n  const [textHeight, setTextHeight] = useState(0);\n  const [leftPos, setLeftPos] = useState(initialLeft);\n  const [rightPos, setRightPos] = useState(0);\n  const handleWidth = 28;\n\n  useEffect(() => {\n    const unsubscribe = rightMotion.onChange((v) => setRightPos(v));\n    return unsubscribe;\n  }, [rightMotion]);\n\n  useEffect(() => {\n    const measureText = () => {\n      if (!textRef.current) return;\n      const measuredWidth = textRef.current.offsetWidth;\n      const measuredHeight = textRef.current.offsetHeight;\n      setTextWidth(measuredWidth);\n      setTextHeight(measuredHeight);\n      const fullRight = measuredWidth + handleWidth * 2;\n      setRightPos(fullRight);\n      rightMotion.set(fullRight);\n    };\n    measureText();\n    window.addEventListener('resize', measureText);\n    return () => window.removeEventListener('resize', measureText);\n  }, [children, rightMotion, handleWidth]);\n\n  useEffect(() => {\n    if (!textWidth) return;\n    const fullRight = textWidth + handleWidth * 2;\n    if (isInView) {\n      animate(rightMotion, fullRight, {\n        duration: 0.6,\n        ease: [0.22, 1, 0.36, 1],\n      });\n    } else {\n      animate(rightMotion, leftPos + minWidth, {\n        duration: 0.6,\n        ease: 'easeInOut',\n      });\n    }\n  }, [isInView, textWidth, leftPos, minWidth, rightMotion, handleWidth]);\n\n  const handleLeftStart = useCallback(\n    (clientX: number) => {\n      rightMotion.stop();\n      const startLeft = leftPos;\n      const startX = clientX;\n      const handleMove = (x: number) => {\n        let newLeft = startLeft + (x - startX);\n        newLeft = Math.max(0, Math.min(newLeft, rightPos - minWidth));\n        setLeftPos(newLeft);\n      };\n      const mouseMove = (e: MouseEvent) => handleMove(e.clientX);\n      const touchMove = (e: TouchEvent) => handleMove(e.touches[0].clientX);\n      const end = () => {\n        document.removeEventListener('mousemove', mouseMove);\n        document.removeEventListener('mouseup', end);\n        document.removeEventListener('touchmove', touchMove);\n        document.removeEventListener('touchend', end);\n        document.body.style.cursor = '';\n        document.body.style.userSelect = '';\n      };\n      document.addEventListener('mousemove', mouseMove);\n      document.addEventListener('mouseup', end);\n      document.addEventListener('touchmove', touchMove);\n      document.addEventListener('touchend', end);\n      document.body.style.cursor = 'ew-resize';\n      document.body.style.userSelect = 'none';\n    },\n    [leftPos, rightPos, minWidth, rightMotion],\n  );\n\n  const handleRightStart = useCallback(\n    (clientX: number) => {\n      rightMotion.stop();\n      const startRight = rightPos;\n      const startX = clientX;\n      const maxRight = textWidth + handleWidth * 2;\n      const handleMove = (x: number) => {\n        let newRight = startRight + (x - startX);\n        newRight = Math.max(leftPos + minWidth, Math.min(newRight, maxRight));\n        setRightPos(newRight);\n        rightMotion.set(newRight);\n      };\n      const mouseMove = (e: MouseEvent) => handleMove(e.clientX);\n      const touchMove = (e: TouchEvent) => handleMove(e.touches[0].clientX);\n      const end = () => {\n        document.removeEventListener('mousemove', mouseMove);\n        document.removeEventListener('mouseup', end);\n        document.removeEventListener('touchmove', touchMove);\n        document.removeEventListener('touchend', end);\n        document.body.style.cursor = '';\n        document.body.style.userSelect = '';\n      };\n      document.addEventListener('mousemove', mouseMove);\n      document.addEventListener('mouseup', end);\n      document.addEventListener('touchmove', touchMove);\n      document.addEventListener('touchend', end);\n      document.body.style.cursor = 'ew-resize';\n      document.body.style.userSelect = 'none';\n    },\n    [leftPos, rightPos, minWidth, textWidth, rightMotion, handleWidth],\n  );\n\n  const width = Math.max(0, rightPos - leftPos);\n\n  return (\n    <TimelineContext.Provider\n      value={{\n        textRef,\n        leftPos,\n        rightPos,\n        textHeight,\n        handleWidth,\n        handleLeftStart,\n        handleRightStart,\n      }}\n    >\n      <div ref={containerRef} className={cn('inline-block', className)}>\n        <div\n          className='relative'\n          style={{\n            transform: `rotate(${rotation}deg)`,\n            width: `${textWidth + handleWidth * 2}px`,\n            height: `${textHeight}px`,\n          }}\n        >\n          <div\n            className={cn(\n              'absolute top-0 rounded-2xl border border-yellow-500 bg-background overflow-hidden',\n              containerClassName,\n            )}\n            style={{\n              left: `${leftPos}px`,\n              width: `${width}px`,\n              height: `${textHeight}px`,\n            }}\n          >\n            <motion.div\n              className={cn(\n                'absolute left-0 top-0 w-7 border border-yellow-500 rounded-full bg-background flex items-center justify-center cursor-ew-resize z-20 select-none',\n                handleClassName,\n              )}\n              onMouseDown={(e: React.MouseEvent<HTMLDivElement>) =>\n                handleLeftStart(e.clientX)\n              }\n              onTouchStart={(e: React.TouchEvent<HTMLDivElement>) =>\n                handleLeftStart(e.touches[0].clientX)\n              }\n              style={{ height: `${textHeight}px` }}\n              whileHover={{ scale: 1.05 }}\n              whileTap={{ scale: 0.95 }}\n            >\n              <div\n                className={cn(\n                  'w-2 h-8 rounded-full bg-yellow-500 pointer-events-none',\n                  handleIndicatorClassName,\n                )}\n              />\n            </motion.div>\n            <motion.div\n              className={cn(\n                'absolute right-0 top-0 w-7 border border-yellow-500 rounded-full bg-background flex items-center justify-center cursor-ew-resize z-20 select-none',\n                handleClassName,\n              )}\n              onMouseDown={(e: React.MouseEvent<HTMLDivElement>) =>\n                handleRightStart(e.clientX)\n              }\n              onTouchStart={(e: React.TouchEvent<HTMLDivElement>) =>\n                handleRightStart(e.touches[0].clientX)\n              }\n              style={{ height: `${textHeight}px` }}\n              whileHover={{ scale: 1.05 }}\n              whileTap={{ scale: 0.95 }}\n            >\n              <div\n                className={cn(\n                  'w-2 h-8 rounded-full bg-yellow-500 pointer-events-none',\n                  handleIndicatorClassName,\n                )}\n              />\n            </motion.div>\n            {children}\n          </div>\n        </div>\n      </div>\n    </TimelineContext.Provider>\n  );\n};\n\ninterface TimelineTextProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst TimelineText: React.FC<TimelineTextProps> = ({ children, className }) => {\n  const { textRef } = useTimelineContext();\n  return (\n    <div\n      ref={textRef}\n      className={cn(\n        'absolute left-7 flex items-center text-foreground font-bold text-4xl py-2 whitespace-nowrap pointer-events-none',\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport { Timeline, TimelineText };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
