Core@paxeer/core/virtual-listimplemented
Virtual list
Windowed list for dense IDE, tape, and roster rows.
Install
$
pnpm exec designx add @paxeer/core/virtual-listSource code
components/designx/core/virtual-list.tsxtsx
"use client";
import { type ReactNode, useMemo, useState } from "react";
import { cn } from "../lib/cn";
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "./empty";
export interface VirtualListProps<T> {
items: T[];
itemHeight: number;
height: number;
overscan?: number;
getItemKey?: (item: T, index: number) => string | number;
renderItem: (item: T, index: number) => ReactNode;
label: string;
className?: string;
empty?: ReactNode;
}
export function VirtualList<T>({
items,
itemHeight,
height,
overscan = 4,
getItemKey,
renderItem,
label,
className,
empty,
}: VirtualListProps<T>) {
const [scrollTop, setScrollTop] = useState(0);
const { start, end, offsetY } = useMemo(() => {
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
const visibleCount = Math.ceil(height / itemHeight) + overscan * 2;
const endIndex = Math.min(items.length, startIndex + visibleCount);
return {
start: startIndex,
end: endIndex,
offsetY: startIndex * itemHeight,
};
}, [height, itemHeight, items.length, overscan, scrollTop]);
if (items.length === 0) {
return (
<Empty className={cn("p-6", className)} style={{ height }} role="list" aria-label={label}>
<EmptyHeader>
<EmptyTitle>No rows</EmptyTitle>
<EmptyDescription>{typeof empty === "string" ? empty : "Nothing to show yet."}</EmptyDescription>
</EmptyHeader>
</Empty>
);
}
return (
<div
role="list"
aria-label={label}
tabIndex={0}
className={cn("dx-focus-ring dx-scroll-region overflow-y-auto", className)}
style={{ height }}
onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}
>
<div style={{ height: items.length * itemHeight, position: "relative" }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{items.slice(start, end).map((item, offset) => {
const index = start + offset;
return (
<div
key={getItemKey ? getItemKey(item, index) : index}
role="listitem"
style={{ height: itemHeight }}
>
{renderItem(item, index)}
</div>
);
})}
</div>
</div>
</div>
);
}
Dependencies
Registry dependencies