Files
2025-12-24 23:53:24 +08:00

92 lines
2.3 KiB
TypeScript

/**
* MovieGrid - Grid layout for movie cards with infinite scroll
* Handles movie display and loading states
*/
import { MovieCard } from './MovieCard';
import { Icons } from '@/components/ui/Icon';
interface DoubanMovie {
id: string;
title: string;
cover: string;
rate: string;
url: string;
}
interface MovieGridProps {
movies: DoubanMovie[];
loading: boolean;
hasMore: boolean;
onMovieClick: (movie: DoubanMovie) => void;
prefetchRef: React.RefObject<HTMLDivElement | null>;
loadMoreRef: React.RefObject<HTMLDivElement | null>;
}
export function MovieGrid({
movies,
loading,
hasMore,
onMovieClick,
prefetchRef,
loadMoreRef
}: MovieGridProps) {
if (movies.length === 0 && !loading) {
return <MovieGridEmpty />;
}
return (
<>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6">
{movies.map((movie) => (
<MovieCard
key={movie.id}
movie={movie}
onMovieClick={onMovieClick}
/>
))}
</div>
{/* Prefetch Trigger - Earlier */}
{hasMore && !loading && <div ref={prefetchRef} className="h-1" />}
{/* Loading Indicator */}
{loading && <MovieGridLoading />}
{/* Intersection Observer Target */}
{hasMore && !loading && <div ref={loadMoreRef} className="h-20" />}
{/* No More Content */}
{!hasMore && movies.length > 0 && <MovieGridNoMore />}
</>
);
}
function MovieGridLoading() {
return (
<div className="flex justify-center py-12">
<div className="flex flex-col items-center gap-3">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-[var(--accent-color)] border-t-transparent"></div>
<p className="text-sm text-[var(--text-color-secondary)]">加载中...</p>
</div>
</div>
);
}
function MovieGridNoMore() {
return (
<div className="text-center py-12">
<p className="text-[var(--text-color-secondary)]">没有更多内容了</p>
</div>
);
}
function MovieGridEmpty() {
return (
<div className="text-center py-20">
<Icons.Film size={64} className="text-[var(--text-color-secondary)] mx-auto mb-4" />
<p className="text-[var(--text-color-secondary)]">暂无内容</p>
</div>
);
}