{
  "schemaVersion": 1,
  "name": "scroll-area",
  "useFor": "Use for a list or log inside a fixed-height window or panel.",
  "constraints": [
    "Scroll areas inside scroll areas",
    "Hiding the main content in one",
    "Scrolling the whole page in one"
  ],
  "documentation": "",
  "source": "Extension from opencommerce-agentcard-web src/app/globals.css (.key-white)",
  "exports": [
    "ScrollArea"
  ],
  "client": true,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/scroll-area",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/scroll-area.md",
  "examples": [
    {
      "path": "examples/scroll-area.tsx",
      "content": "import { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Window } from \"@/components/ui/window\";\n\nconst MERCHANTS = [\"Delta\", \"United\", \"Marriott\", \"Hilton\", \"Airbnb\", \"Uber\", \"Lyft\", \"Amazon\", \"Notion\", \"Figma\", \"Linear\", \"Vercel\", \"GitHub\", \"Slack\", \"Zoom\", \"OpenTable\"];\n\nexport function Site() {\n  return (\n    <Window name=\"merchants.list\" className=\"h-72 w-72\" bodyClassName=\"flex flex-col\">\n      <ScrollArea className=\"px-4 py-2\">\n        {MERCHANTS.map((merchant) => (\n          <p key={merchant} className=\"border-b border-line py-2.5 font-display text-body-sm text-fg last:border-b-0\">\n            {merchant}\n          </p>\n        ))}\n      </ScrollArea>\n    </Window>\n  );\n}\n",
      "exports": [
        "Site"
      ],
      "requires": [
        "scroll-area",
        "window"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/scroll-area/Site"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Scroll area",
  "description": "Scrollable container with overflow indicators and a scroll control.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "utils"
  ],
  "packages": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/ui/scroll-area.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst EDGE_FADE_PX = 28;\n\n/**\n * A scroll area that says when there's more: no scrollbar taking room on one side (so the padding stays even), the\n * content fading out at whichever edge has more past it, and while there's more below, a small white key at the bottom\n * that scrolls the rest into view. The key fades and rises in 200ms ease-out.\n */\nfunction ScrollArea({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const [edges, setEdges] = React.useState({ more: false, above: false, below: false });\n\n  React.useLayoutEffect(() => {\n    const area = ref.current;\n    if (!area) return;\n    const measure = () => {\n      const more = area.scrollHeight > area.clientHeight + 1;\n      const above = more && area.scrollTop > 1;\n      const below = more && area.scrollTop + area.clientHeight < area.scrollHeight - 1;\n      setEdges((previous) => (previous.more === more && previous.above === above && previous.below === below ? previous : { more, above, below }));\n    };\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(area);\n    for (const child of Array.from(area.children)) observer.observe(child);\n    area.addEventListener(\"scroll\", measure, { passive: true });\n    return () => {\n      observer.disconnect();\n      area.removeEventListener(\"scroll\", measure);\n    };\n  }, []);\n\n  const mask = `linear-gradient(to bottom, ${edges.above ? \"transparent\" : \"black\"}, black ${EDGE_FADE_PX}px, black calc(100% - ${EDGE_FADE_PX}px), ${edges.below ? \"transparent\" : \"black\"})`;\n\n  return (\n    <div data-slot=\"scroll-area\" className=\"relative flex min-h-0 flex-1 flex-col\">\n      <div\n        ref={ref}\n        data-slot=\"scroll-area-viewport\"\n        className={cn(\"min-h-0 flex-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\", className)}\n        style={edges.more ? { maskImage: mask, WebkitMaskImage: mask } : undefined}\n        {...props}\n      >\n        {children}\n      </div>\n      <button\n        type=\"button\"\n        tabIndex={-1}\n        aria-hidden\n        onClick={() => ref.current?.scrollBy({ top: ref.current.clientHeight * 0.8, behavior: \"smooth\" })}\n        className={cn(\n          \"absolute bottom-1 left-1/2 grid size-6 -translate-x-1/2 place-items-center rounded-sm border border-hairline bg-surface text-fg-brand shadow-tile transition-[opacity,translate,background-color] duration-200 ease-out hover:bg-subtle motion-reduce:transition-[opacity]\",\n          edges.below ? \"opacity-100\" : \"pointer-events-none translate-y-1 opacity-0\",\n        )}\n      >\n        <ChevronDown className=\"size-3.5\" strokeWidth={1.75} />\n      </button>\n    </div>\n  );\n}\n\nexport { ScrollArea };\n"
    }
  ]
}
