{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"lib-virtual-rows","type":"registry:lib","title":"virtual-rows helpers","description":"The scrolling part of a virtual List: the rows arrive made (List draws them, and stays a component a server can","dependencies":["@tanstack/react-virtual","lucide-react"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-load-more.json","https://dashral.com/r/lib-locale.json"],"files":[{"path":"registry/dashral/lib/virtual-rows.tsx","type":"registry:file","target":"components/dashral/lib/virtual-rows.tsx","content":"\"use client\"\n\n// The scrolling part of a virtual List: the rows arrive made (List draws them, and stays a component a server can\n// render), and only the ones in view, plus a few around them, are put on the page. Each row's height is measured, so\n// rows of any height work, a narrow screen's stacked ones too.\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport { Loader2Icon } from \"lucide-react\"\n\nimport { useLoadMore } from \"./load-more\"\nimport { useLocale } from \"./locale\"\nimport { cn } from \"./utils\"\n\ntype VirtualRowsProps = Omit<React.ComponentProps<\"ul\">, \"children\"> & {\n  /** The rows, each an <li> with a key. */\n  rows: React.ReactElement[]\n  /** The box's height in pixels. */\n  height: number\n  /** A row's height before it's measured. */\n  estimateSize?: number\n  onLoadMore?: () => Promise<void> | void\n  hasMore?: boolean\n  /** The scrolling box's classes; className goes on the list inside it. */\n  boxClassName?: string\n}\n\nexport function VirtualRows({ rows, height, estimateSize = 57, onLoadMore, hasMore = true, boxClassName, className, style, ...props }: VirtualRowsProps) {\n  const { t } = useLocale()\n  const scrollRef = React.useRef<HTMLDivElement>(null)\n  const loader = onLoadMore !== undefined && hasMore\n\n  // TanStack Virtual hands back new values on every scroll, so the compiler leaves this component as it is.\n  // eslint-disable-next-line react-hooks/incompatible-library\n  const virtualizer = useVirtualizer({\n    count: rows.length + (loader ? 1 : 0),\n    getScrollElement: () => scrollRef.current,\n    estimateSize: () => estimateSize,\n    overscan: 8,\n  })\n  const items = virtualizer.getVirtualItems()\n  const top = items[0]?.start ?? 0\n  const bottom = virtualizer.getTotalSize() - (items.at(-1)?.end ?? 0)\n  const lastIndex = items.at(-1)?.index ?? 0\n  const { status, retry } = useLoadMore({ near: loader && lastIndex >= rows.length - 6, count: rows.length, hasMore, onLoadMore })\n\n  return (\n    // The box can take focus, so the arrow keys and Page Down scroll it.\n    <div\n      ref={scrollRef}\n      tabIndex={0}\n      style={{ height }}\n      className={cn(\"overflow-y-auto overscroll-contain outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset\", boxClassName)}\n    >\n      {/* The space above and below the drawn rows keeps the scrollbar true to the whole list. */}\n      <ul {...props} className={className} style={{ ...style, paddingTop: top, paddingBottom: bottom }}>\n        {items.map((item) =>\n          item.index < rows.length ? (\n            React.cloneElement(rows[item.index] as React.ReactElement<Record<string, unknown>>, {\n              ref: virtualizer.measureElement,\n              \"data-index\": item.index,\n            })\n          ) : (\n            <li key=\"more\" ref={virtualizer.measureElement} data-index={item.index} className=\"flex h-12 items-center justify-center gap-2 text-sm text-muted-foreground\">\n              {status === \"error\" ? (\n                <span role=\"alert\" className=\"inline-flex items-center gap-2\">\n                  {t(\"loadMoreFailed\")}\n                  <button\n                    type=\"button\"\n                    onClick={retry}\n                    className=\"inline-flex h-7 items-center rounded-md border bg-background px-2.5 text-xs font-medium text-foreground shadow-xs outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring/50\"\n                  >\n                    {t(\"tryAgain\")}\n                  </button>\n                </span>\n              ) : (\n                <>\n                  <Loader2Icon aria-hidden className=\"size-4 animate-spin motion-reduce:animate-[spin_1.5s_linear_infinite]\" />\n                  {t(\"loadingMore\")}\n                </>\n              )}\n            </li>\n          )\n        )}\n      </ul>\n    </div>\n  )\n}\n"}]}