{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"scroll-to","type":"registry:component","title":"Scroll Animation Scroll To","description":"Button that smooth-scrolls to a target (offset, selector or element) via the active SmoothScroll provider; reduced-motion jumps instantly.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","lenis","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/scroll-to.tsx","type":"registry:component","target":"@components/motion/scroll-to.tsx","content":"\"use client\";\n// beui.dev/components/motion/scroll-animation\n\nimport type { ButtonHTMLAttributes, ReactNode } from \"react\";\n\nimport {\n  type ScrollTarget,\n  type ScrollToOptions,\n  useSmoothScroll,\n} from \"@/components/motion/smooth-scroll\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface ScrollToProps\n  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"onClick\"> {\n  /** Where to scroll: px offset, selector string or element. */\n  to: ScrollTarget;\n  /** Extra px offset from the target (e.g. to clear a sticky header). */\n  offset?: number;\n  /** Override the ease duration in seconds. */\n  duration?: number;\n  children: ReactNode;\n  className?: string;\n}\n\n/**\n * Button that smooth-scrolls to a target via the active SmoothScroll provider\n * (or native scroll as a fallback). Respects reduced motion — jumps instantly.\n */\nexport function ScrollTo({\n  to,\n  offset,\n  duration,\n  children,\n  className,\n  ...rest\n}: ScrollToProps) {\n  const { scrollTo } = useSmoothScroll();\n  const options: ScrollToOptions = { offset, duration };\n  return (\n    <button\n      type=\"button\"\n      onClick={() => scrollTo(to, options)}\n      className={cn(className)}\n      {...rest}\n    >\n      {children}\n    </button>\n  );\n}\n"},{"path":"components/motion/smooth-scroll.tsx","type":"registry:component","target":"@components/motion/smooth-scroll.tsx","content":"\"use client\";\n\nimport type Lenis from \"lenis\";\nimport { ReactLenis, useLenis } from \"lenis/react\";\nimport { type MotionValue, useMotionValue, useReducedMotion } from \"motion/react\";\nimport {\n  createContext,\n  type ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n} from \"react\";\n\n// Lenis' own expo-out curve — the canonical smooth-scroll easing. Kept as a\n// named local fn (not a lib/ease token) because tokens are bezier control\n// points for the motion lib, while Lenis needs a (t) => number easing fn.\nconst EASE_SCROLL = (t: number) => Math.min(1, 1.001 - 2 ** (-10 * t));\n\nexport type ScrollTarget = number | string | HTMLElement;\n\nexport type ScrollToOptions = {\n  offset?: number;\n  immediate?: boolean;\n  duration?: number;\n};\n\nexport type SmoothScrollApi = {\n  /** Underlying Lenis instance, or null on the reduced-motion / native path. */\n  lenis: Lenis | null;\n  /** Current scroll offset in px. */\n  scrollY: MotionValue<number>;\n  /** Scroll position as 0..1 of the scrollable height. */\n  progress: MotionValue<number>;\n  /** Signed scroll velocity (px/frame); drives velocity-based effects. */\n  velocity: MotionValue<number>;\n  /** Programmatic smooth scroll. Respects reduced motion (jumps instantly). */\n  scrollTo: (target: ScrollTarget, options?: ScrollToOptions) => void;\n};\n\nconst SmoothScrollContext = createContext<SmoothScrollApi | null>(null);\n\nexport interface SmoothScrollProps {\n  children: ReactNode;\n  /** Drive the page (window) when true, or a contained scroll area when false. */\n  root?: boolean;\n  /** Smoothing factor; lower is smoother and heavier. */\n  lerp?: number;\n  /** Wheel / programmatic ease duration in seconds. */\n  duration?: number;\n  orientation?: \"vertical\" | \"horizontal\";\n  /** Wheel scroll speed multiplier. */\n  wheelMultiplier?: number;\n  /** Smooth touch scrolling. Off by default — native momentum is good on mobile. */\n  touch?: boolean;\n  className?: string;\n}\n\ntype ScrollSource = Window | HTMLElement;\n\nfunction readMetrics(target: ScrollSource) {\n  if (target instanceof Window) {\n    const max = Math.max(\n      0,\n      document.documentElement.scrollHeight - window.innerHeight,\n    );\n    return { y: window.scrollY, max };\n  }\n  return {\n    y: target.scrollTop,\n    max: Math.max(0, target.scrollHeight - target.clientHeight),\n  };\n}\n\nfunction resolveTop(\n  target: ScrollTarget,\n  source: ScrollSource,\n  offset = 0,\n): number {\n  if (typeof target === \"number\") return target + offset;\n  if (source instanceof Window) {\n    const el =\n      typeof target === \"string\" ? document.querySelector(target) : target;\n    if (!el) return window.scrollY;\n    return el.getBoundingClientRect().top + window.scrollY + offset;\n  }\n  const el =\n    typeof target === \"string\" ? source.querySelector(target) : target;\n  if (!(el instanceof HTMLElement)) return source.scrollTop;\n  return el.offsetTop + offset;\n}\n\n/** Pushes Lenis' live scroll state into the shared motion values. */\nfunction LenisBridge({\n  scrollY,\n  progress,\n  velocity,\n  lenisRef,\n}: {\n  scrollY: MotionValue<number>;\n  progress: MotionValue<number>;\n  velocity: MotionValue<number>;\n  lenisRef: { current: Lenis | null };\n}) {\n  const lenis = useLenis((instance) => {\n    scrollY.set(instance.scroll);\n    progress.set(instance.progress);\n    velocity.set(instance.velocity);\n  });\n  useEffect(() => {\n    lenisRef.current = lenis ?? null;\n    return () => {\n      lenisRef.current = null;\n    };\n  }, [lenis, lenisRef]);\n  return null;\n}\n\n/** Native scroll listener for the reduced-motion path and the no-provider fallback. */\nfunction useNativeScrollSync(\n  enabled: boolean,\n  getTarget: () => ScrollSource | null,\n  scrollY: MotionValue<number>,\n  progress: MotionValue<number>,\n  velocity: MotionValue<number>,\n) {\n  useEffect(() => {\n    if (!enabled) return;\n    const target = getTarget();\n    if (!target) return;\n    let lastY = readMetrics(target).y;\n    let lastT = performance.now();\n    const onScroll = () => {\n      const { y, max } = readMetrics(target);\n      const now = performance.now();\n      const dt = now - lastT || 16;\n      scrollY.set(y);\n      progress.set(max > 0 ? y / max : 0);\n      velocity.set(((y - lastY) / dt) * 16);\n      lastY = y;\n      lastT = now;\n    };\n    onScroll();\n    target.addEventListener(\"scroll\", onScroll, { passive: true });\n    return () => target.removeEventListener(\"scroll\", onScroll);\n  }, [enabled, getTarget, scrollY, progress, velocity]);\n}\n\nexport function SmoothScroll({\n  children,\n  root = true,\n  lerp = 0.1,\n  duration = 1.2,\n  orientation = \"vertical\",\n  wheelMultiplier = 1,\n  touch = false,\n  className,\n}: SmoothScrollProps) {\n  const reduce = useReducedMotion();\n  const scrollY = useMotionValue(0);\n  const progress = useMotionValue(0);\n  const velocity = useMotionValue(0);\n  const lenisRef = useRef<Lenis | null>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  const nativeSource = useCallback(\n    (): ScrollSource | null => (root ? window : containerRef.current),\n    [root],\n  );\n\n  const scrollTo = useCallback(\n    (target: ScrollTarget, options?: ScrollToOptions) => {\n      const lenis = lenisRef.current;\n      if (lenis && !reduce) {\n        lenis.scrollTo(target, {\n          offset: options?.offset,\n          duration: options?.duration,\n          immediate: options?.immediate,\n        });\n        return;\n      }\n      const source = nativeSource();\n      const behavior = reduce || options?.immediate ? \"auto\" : \"smooth\";\n      const top = resolveTop(target, source ?? window, options?.offset);\n      (source ?? window).scrollTo({ top, behavior });\n    },\n    [reduce, nativeSource],\n  );\n\n  // Reduced motion drives the native listener; the Lenis path leaves it\n  // disabled and lets LenisBridge feed the values instead.\n  useNativeScrollSync(!!reduce, nativeSource, scrollY, progress, velocity);\n\n  const api = useMemo<SmoothScrollApi>(\n    () => ({ lenis: lenisRef.current, scrollY, progress, velocity, scrollTo }),\n    [scrollY, progress, velocity, scrollTo],\n  );\n\n  if (reduce) {\n    return (\n      <SmoothScrollContext.Provider value={api}>\n        <div ref={containerRef} className={className}>\n          {children}\n        </div>\n      </SmoothScrollContext.Provider>\n    );\n  }\n\n  return (\n    <SmoothScrollContext.Provider value={api}>\n      <ReactLenis\n        root={root}\n        className={className}\n        options={{\n          lerp,\n          duration,\n          orientation,\n          wheelMultiplier,\n          smoothWheel: true,\n          syncTouch: touch,\n          easing: EASE_SCROLL,\n        }}\n      >\n        <LenisBridge\n          scrollY={scrollY}\n          progress={progress}\n          velocity={velocity}\n          lenisRef={lenisRef}\n        />\n        {children}\n      </ReactLenis>\n    </SmoothScrollContext.Provider>\n  );\n}\n\n/**\n * Read the page's smooth-scroll state. Inside <SmoothScroll> it returns the\n * shared motion values; outside it falls back to a native window scroll\n * listener so scroll-driven components still work without the provider.\n */\nexport function useSmoothScroll(): SmoothScrollApi {\n  const ctx = useContext(SmoothScrollContext);\n  const reduce = useReducedMotion();\n  const scrollY = useMotionValue(0);\n  const progress = useMotionValue(0);\n  const velocity = useMotionValue(0);\n\n  const windowSource = useCallback((): ScrollSource => window, []);\n  useNativeScrollSync(ctx === null, windowSource, scrollY, progress, velocity);\n\n  const scrollTo = useCallback((target: ScrollTarget, options?: ScrollToOptions) => {\n    window.scrollTo({\n      top: resolveTop(target, window, options?.offset),\n      behavior: reduce || options?.immediate ? \"auto\" : \"smooth\",\n    });\n  }, [reduce]);\n\n  const fallback = useMemo<SmoothScrollApi>(\n    () => ({ lenis: null, scrollY, progress, velocity, scrollTo }),\n    [scrollY, progress, velocity, scrollTo],\n  );\n\n  return ctx ?? fallback;\n}\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@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}\n"}]}