{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sticky-scroll-reveal",
  "type": "registry:ui",
  "title": "Sticky Scroll Reveal",
  "description": "Split layout where the left column pins while the right scrolls through synced content steps.",
  "dependencies": [
    "@saasflare/ui",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/sticky-scroll-reveal.tsx",
      "type": "registry:ui",
      "content": "\"use client\"\n\n/**\n * @fileoverview Sticky scroll reveal — left text stays, right content scrolls.\n * @author Saasflare™\n * A split layout where the left column stays fixed while the right column\n * scrolls through content items. Great for feature walkthroughs.\n * @module packages/ui/components/ui/sticky-scroll-reveal\n * @package ui\n *\n * @component\n * @example\n * import { StickyScrollReveal } from '@saasflare/ui';\n * <StickyScrollReveal\n *   items={[\n *     { title: \"Step 1\", description: \"Connect your data\", content: <img src=\"/step1.png\" /> },\n *     { title: \"Step 2\", description: \"Configure your pipeline\", content: <img src=\"/step2.png\" /> },\n *   ]}\n * />\n */\n\nimport * as React from \"react\"\nimport { useRef, type ReactNode } from \"react\"\nimport { m, useScroll, useTransform, type MotionValue } from \"motion/react\"\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\nimport { useSaasflareMotion } from \"@saasflare/ui\"\n\n/** A single item in the sticky scroll reveal. */\nexport interface StickyScrollItem {\n  /** Section title. */\n  title: string\n  /** Section description text. */\n  description: string\n  /** Visual content (image, component, etc.) shown in the right column. */\n  content?: ReactNode\n}\n\n/** Props for the StickyScrollReveal component. */\nexport interface StickyScrollRevealProps\n  extends Omit<React.ComponentProps<\"div\">, keyof SaasflareComponentProps>,\n    SaasflareComponentProps {\n  /** Array of scroll items. */\n  items: StickyScrollItem[]\n}\n\n/**\n * Split layout with sticky left text and scrolling right content.\n *\n * - Left column text stays fixed during scroll\n * - Right column content transitions between items\n * - Each item fades in based on scroll position\n * - Falls back to stacked layout when reduced motion is preferred or\n *   when `animated={false}` is set (per-instance or via the provider)\n *\n * @component\n * @package ui\n */\nexport function StickyScrollReveal({\n  items,\n  className,\n  surface,\n  radius,\n  animated,\n  iconWeight,\n  ...props\n}: StickyScrollRevealProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n  const motion = useSaasflareMotion(sf.animated)\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  // Single shared scroll progress — avoids 2N independent scroll listeners.\n  const { scrollYProgress } = useScroll({\n    target: containerRef,\n    offset: [\"start start\", \"end end\"],\n  })\n\n  if (motion.disabled) {\n    return (\n      <div\n        {...props}\n        className={cn(\"space-y-16\", className)}\n        data-slot=\"sticky-scroll-reveal\"\n        data-surface={sf.surface}\n        data-radius={sf.radius}\n        data-animated={String(sf.animated)}\n      >\n        {items.map((item, i) => (\n          <div key={i} className=\"grid gap-8 md:grid-cols-2\">\n            <div>\n              <h3 className=\"text-2xl font-bold\">{item.title}</h3>\n              <p className=\"mt-2 text-muted-foreground\">{item.description}</p>\n            </div>\n            {item.content && (\n              <div className=\"rounded-xl border bg-card p-4\">{item.content}</div>\n            )}\n          </div>\n        ))}\n      </div>\n    )\n  }\n\n  return (\n    <div\n      {...props}\n      ref={containerRef}\n      className={cn(\"relative\", className)}\n      data-slot=\"sticky-scroll-reveal\"\n      data-surface={sf.surface}\n      data-radius={sf.radius}\n      data-animated={String(sf.animated)}\n    >\n      <div className=\"relative grid gap-8 md:grid-cols-2\">\n        {/* Left column — sticky text */}\n        <div className=\"relative md:h-fit\">\n          <div className=\"sticky top-32 space-y-24\">\n            {items.map((item, i) => (\n              <StickyItem\n                key={i}\n                item={item}\n                index={i}\n                total={items.length}\n                scrollYProgress={scrollYProgress}\n              />\n            ))}\n          </div>\n        </div>\n\n        {/* Right column — scrolling content */}\n        <div className=\"space-y-24\">\n          {items.map((item, i) => (\n            <div key={i} className=\"sticky top-32\">\n              <StickyContent\n                content={item.content}\n                index={i}\n                total={items.length}\n                scrollYProgress={scrollYProgress}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\n/** Internal: animated text item. */\nfunction StickyItem({\n  item,\n  index,\n  total,\n  scrollYProgress,\n}: {\n  item: StickyScrollItem\n  index: number\n  total: number\n  scrollYProgress: MotionValue<number>\n}) {\n  const sectionStart = index / total\n  const sectionEnd = (index + 1) / total\n  const opacity = useTransform(\n    scrollYProgress,\n    [sectionStart, sectionStart + 0.05, sectionEnd - 0.05, sectionEnd],\n    [0.3, 1, 1, 0.3],\n  )\n\n  return (\n    <m.div style={{ opacity }}>\n      <h3 className=\"text-2xl font-bold\">{item.title}</h3>\n      <p className=\"mt-2 text-muted-foreground\">{item.description}</p>\n    </m.div>\n  )\n}\n\n/** Internal: animated content panel. */\nfunction StickyContent({\n  content,\n  index,\n  total,\n  scrollYProgress,\n}: {\n  content?: ReactNode\n  index: number\n  total: number\n  scrollYProgress: MotionValue<number>\n}) {\n  const sectionStart = index / total\n  const sectionEnd = (index + 1) / total\n  const opacity = useTransform(\n    scrollYProgress,\n    [sectionStart, sectionStart + 0.05, sectionEnd - 0.05, sectionEnd],\n    [0, 1, 1, 0],\n  )\n\n  if (!content) return null\n\n  return (\n    <m.div\n      style={{ opacity }}\n      className=\"overflow-hidden rounded-xl border bg-card p-4\"\n    >\n      {content}\n    </m.div>\n  )\n}\n",
      "target": "components/ui/sticky-scroll-reveal.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/sticky-scroll-reveal.json"
  }
}
