- LazyMount: mount a grid card only when it scrolls within 600px (skeleton-space placeholder otherwise), so a long feed no longer mounts every card + its hooks/DOM up front. Falls back to immediate render without IntersectionObserver. - GearSwapItemCard: clicking the card now opens /listing/:pubkey/:identifier (was opening a DM). 'Contact Seller' still opens the DM. Card set to h-full so the lazy wrapper keeps the equal-height grid. Gate: 101 tests, 0 errors/warnings, boot smoke green.
60 lines
1.5 KiB
TypeScript
60 lines
1.5 KiB
TypeScript
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
|
|
|
/**
|
|
* Defer mounting `children` until the wrapper scrolls near the viewport.
|
|
*
|
|
* Long grids (e.g. the marketplace) otherwise mount every card — and every
|
|
* hook/query/DOM node each one pulls in — up front. With this, off-screen cards
|
|
* stay as a cheap placeholder until they're ~600px away, then mount once and
|
|
* stay mounted. Falls back to immediate render when IntersectionObserver is
|
|
* unavailable.
|
|
*/
|
|
export function LazyMount({
|
|
children,
|
|
minHeight = 300,
|
|
rootMargin = '600px',
|
|
className,
|
|
}: {
|
|
children: ReactNode;
|
|
/** Reserve height so the page doesn't jump when the card mounts. */
|
|
minHeight?: number;
|
|
rootMargin?: string;
|
|
className?: string;
|
|
}) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (visible) return;
|
|
const el = ref.current;
|
|
if (!el) return;
|
|
|
|
if (typeof IntersectionObserver === 'undefined') {
|
|
setVisible(true);
|
|
return;
|
|
}
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
if (entries.some((e) => e.isIntersecting)) {
|
|
setVisible(true);
|
|
observer.disconnect();
|
|
}
|
|
},
|
|
{ rootMargin },
|
|
);
|
|
observer.observe(el);
|
|
return () => observer.disconnect();
|
|
}, [visible, rootMargin]);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={className}
|
|
style={visible ? undefined : { minHeight }}
|
|
aria-busy={!visible || undefined}
|
|
>
|
|
{visible ? children : null}
|
|
</div>
|
|
);
|
|
}
|