{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "smooth-scroll",
  "title": "Smooth Scroll",
  "description": "Smooth wheel and trackpad scrolling for the page, off with reduced motion, with a scrollTo hook that works either way.",
  "dependencies": [
    "lenis@^1.3.26"
  ],
  "registryDependencies": [
    "@corsair-ui/use-media-query"
  ],
  "files": [
    {
      "path": "registry/default/ui/smooth-scroll.tsx",
      "content": "\"use client\";\n\nimport Lenis, { type LenisOptions } from \"lenis\";\nimport {\n  createContext,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type ReactNode,\n} from \"react\";\n\nimport { useMediaQuery } from \"@/registry/default/hooks/use-media-query\";\n\ntype ScrollTarget = number | string | HTMLElement;\n\ninterface SmoothScrollToOptions {\n  /** Added to the target position, in px: negative stops short, e.g. under a sticky header. */\n  offset?: number;\n  /** Jump there without animating. */\n  immediate?: boolean;\n}\n\ninterface SmoothScrollValue {\n  /** The Lenis instance, or `null` when scrolling is native (reduced motion, no provider). */\n  lenis: Lenis | null;\n  /** Scrolls the page to a position, a selector or an element, with or without Lenis. */\n  scrollTo: (target: ScrollTarget, options?: SmoothScrollToOptions) => void;\n}\n\nfunction prefersReducedMotion() {\n  return window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches === true;\n}\n\nconst EDGES: Record<string, \"top\" | \"bottom\"> = {\n  top: \"top\",\n  start: \"top\",\n  left: \"top\",\n  bottom: \"bottom\",\n  end: \"bottom\",\n  right: \"bottom\",\n};\n\n/** The same targets as Lenis, with the browser's own scrolling. */\nfunction nativeScrollTo(target: ScrollTarget, { offset = 0, immediate = false } = {}) {\n  const behavior: ScrollBehavior = immediate\n    ? \"instant\"\n    : prefersReducedMotion()\n      ? \"auto\"\n      : \"smooth\";\n  if (typeof target === \"number\") {\n    window.scrollTo({ top: target + offset, behavior });\n    return;\n  }\n  const edge = typeof target === \"string\" ? EDGES[target] : undefined;\n  if (edge) {\n    const top = edge === \"top\" ? 0 : document.documentElement.scrollHeight;\n    window.scrollTo({ top: top + offset, behavior });\n    return;\n  }\n  const element = typeof target === \"string\" ? document.querySelector(target) : target;\n  if (!element) return;\n  if (offset === 0) {\n    // Also scrolls any scrolling container the element sits in.\n    element.scrollIntoView({ behavior, block: \"start\" });\n    return;\n  }\n  const top = element.getBoundingClientRect().top + window.scrollY + offset;\n  window.scrollTo({ top, behavior });\n}\n\nconst SmoothScrollContext = createContext<SmoothScrollValue>({\n  lenis: null,\n  scrollTo: nativeScrollTo,\n});\n\n/** Holds the Lenis instance outside React, so the effect that makes it can hand it over. */\nfunction createLenisStore() {\n  let current: Lenis | null = null;\n  const listeners = new Set<() => void>();\n  return {\n    get: () => current,\n    set(next: Lenis | null) {\n      current = next;\n      for (const listener of listeners) listener();\n    },\n    subscribe(listener: () => void) {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n  };\n}\n\nconst getServerLenis = () => null;\n\ninterface SmoothScrollProps {\n  /**\n   * Lenis options, over the defaults `{ autoRaf: true, anchors: true }`.\n   * They are read when Lenis starts; change the provider's `key` to apply new ones.\n   */\n  options?: LenisOptions;\n  children?: ReactNode;\n}\n\n/**\n * Smooths wheel and trackpad scrolling of the page with Lenis, while the\n * page keeps scrolling natively underneath: position: sticky, scroll-driven\n * animations, IntersectionObserver, keyboard scrolling and find-in-page all\n * behave as usual, and in-page anchor links glide to their target. With\n * `prefers-reduced-motion` Lenis is not started at all.\n *\n * Nested scrolling areas (popovers, selects, dialogs with long content)\n * need `data-lenis-prevent` so they scroll themselves instead of the page.\n * `useSmoothScroll()` scrolls from code, with Lenis when it runs and the\n * browser otherwise.\n *\n * @example\n * <SmoothScroll options={{ lerp: 0.1 }}>\n *   <main>…</main>\n * </SmoothScroll>\n */\nfunction SmoothScroll({ options, children }: SmoothScrollProps) {\n  const reduced = useMediaQuery(\"(prefers-reduced-motion: reduce)\");\n  const [store] = useState(createLenisStore);\n  const lenis = useSyncExternalStore(store.subscribe, store.get, getServerLenis);\n\n  const optionsRef = useRef(options);\n  useEffect(() => {\n    optionsRef.current = options;\n  });\n\n  useEffect(() => {\n    if (reduced) return;\n    const instance = new Lenis({ autoRaf: true, anchors: true, ...optionsRef.current });\n    store.set(instance);\n    return () => {\n      store.set(null);\n      instance.destroy();\n    };\n  }, [reduced, store]);\n\n  const value = useMemo<SmoothScrollValue>(\n    () => ({\n      lenis,\n      scrollTo: (target, scrollOptions = {}) => {\n        if (lenis) lenis.scrollTo(target, scrollOptions);\n        else nativeScrollTo(target, scrollOptions);\n      },\n    }),\n    [lenis]\n  );\n\n  return <SmoothScrollContext.Provider value={value}>{children}</SmoothScrollContext.Provider>;\n}\n\n/**\n * The Lenis instance and a `scrollTo` that works with or without it.\n * Outside a `SmoothScroll` it scrolls natively.\n *\n * @example\n * const { scrollTo } = useSmoothScroll();\n * <Button onClick={() => scrollTo(\"#pricing\", { offset: -64 })}>See pricing</Button>\n */\nfunction useSmoothScroll() {\n  return useContext(SmoothScrollContext);\n}\n\nexport {\n  SmoothScroll,\n  useSmoothScroll,\n  type SmoothScrollProps,\n  type SmoothScrollToOptions,\n  type SmoothScrollValue,\n};\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    "html.lenis, html.lenis body": {
      "height": "auto"
    },
    ".lenis:not(.lenis-autoToggle).lenis-stopped": {
      "overflow": "clip"
    },
    ".lenis [data-lenis-prevent], .lenis [data-lenis-prevent-wheel], .lenis [data-lenis-prevent-touch], .lenis [data-lenis-prevent-vertical], .lenis [data-lenis-prevent-horizontal]": {
      "overscroll-behavior": "contain"
    },
    ".lenis.lenis-smooth iframe": {
      "pointer-events": "none"
    },
    ".lenis.lenis-autoToggle": {
      "transition-property": "overflow",
      "transition-duration": "1ms",
      "transition-behavior": "allow-discrete"
    }
  },
  "categories": [
    "motion"
  ],
  "type": "registry:ui"
}