Infinite Masonry
beui/infinite-masonryFreeBlock
Responsive virtualized masonry that measures variable-height cards and loads more data as the user nears the end.
Install it
npx shadcn@latest add https://beui.dev/r/infinite-masonry.jsonSource
1"use client";2// beui.dev/components/blocks/infinite-masonry34import { useVirtualizer } from "@tanstack/react-virtual";5import { AlertCircle, Inbox } from "lucide-react";6import { motion, useReducedMotion } from "motion/react";7import {8 useEffect,9 useRef,10 useState,11 type MutableRefObject,12 type ReactNode,13} from "react";14import { EASE_OUT, SPRING_PANEL } from "@/lib/ease";15import { cn } from "@/lib/utils";1617export type InfiniteMasonryKey = string | number | bigint;1819export interface InfiniteMasonryProps<T> {20 items: readonly T[];21 getItemKey: (item: T, index: number) => InfiniteMasonryKey;22 renderItem: (item: T, index: number) => ReactNode;23 onLoadMore: () => void | Promise<void>;24 hasMore: boolean;25 loading?: boolean;26 error?: ReactNode;27 onRetry?: () => void;28 estimateSize?: (item: T, index: number) => number;29 renderLoadingItem?: (index: number) => ReactNode;30 emptyState?: ReactNode;31 endState?: ReactNode;32 minColumnWidth?: number;33 maxColumns?: number;34 gap?: number;35 overscan?: number;36 prefetch?: number;37 animateItems?: boolean;38 ariaLabel?: string;39 className?: string;40 contentClassName?: string;41 itemClassName?: string;42}4344type MasonryMetrics = {45 columns: number;46 width: number;47};4849function useMasonryMetrics({50 elementRef,51 gap,52 maxColumns,53 minColumnWidth,54}: {55 elementRef: React.RefObject<HTMLDivElement | null>;56 gap: number;57 maxColumns: number;58 minColumnWidth: number;59}) {60 const [metrics, setMetrics] = useState<MasonryMetrics>({61 columns: 1,62 width: 0,63 });6465 useEffect(() => {66 const element = elementRef.current;67 if (!element) return;6869 const update = (width: number) => {70 const columns = Math.min(71 maxColumns,72 Math.max(1, Math.floor((width + gap) / (minColumnWidth + gap))),73 );7475 setMetrics((current) =>76 current.columns === columns && current.width === width77 ? current78 : { columns, width },79 );80 };8182 update(element.getBoundingClientRect().width);8384 const observer = new ResizeObserver(([entry]) => {85 update(entry.contentRect.width);86 });87 observer.observe(element);8889 return () => observer.disconnect();90 }, [elementRef, gap, maxColumns, minColumnWidth]);9192 return metrics;93}9495function DefaultLoadingItem({ index }: { index: number }) {96 return (97 <div98 aria-hidden="true"99 className="animate-pulse rounded-2xl border border-border bg-card p-3"100// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| File Upload Attachment Uploadattachment-upload | beui | Blocks | Free | 4 deps · 5 files | |
| Availability Scheduleravailability-scheduler | beui | Blocks | Free | 4 deps · 15 files | |
| Bloom Menubloom-menu | beui | Blocks | Free | 4 deps · 3 files | |
| Command Palettecommand-palette | beui | Blocks | Free | 4 deps · 3 files | |
| Dynamic Islanddynamic-island | beui | Blocks | Free | 3 deps · 3 files | |
| Expandable Action Barexpandable-action-bar | beui | Blocks | Free | 3 deps · 2 files | |
| Expandable Tabsexpandable-tabs | beui | Blocks | Free | 3 deps · 3 files | |
| Feedback Widgetfeedback-widget | beui | Blocks | Free | 4 deps · 9 files |
