diff --git a/src/screens/CategoryGridScreen.tsx b/src/screens/CategoryGridScreen.tsx index 62af8d3..a210471 100644 --- a/src/screens/CategoryGridScreen.tsx +++ b/src/screens/CategoryGridScreen.tsx @@ -124,9 +124,9 @@ export function CategoryGridScreen({ title, items, groups, isLoading = false, lo onClick={handlePosterClick} onScrollActivity={handleGridScroll} onNearBottom={onLoadMore} + isLoadingMore={isLoadingMore} /> )} - {isLoadingMore &&
{t('auto.loading')}
} {/* Right: detail panel */} @@ -245,7 +245,7 @@ function PanelIconBtn({ title, icon, onClick }: { title: string; icon: React.Rea -function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClick, onScrollActivity, onNearBottom }: { +function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClick, onScrollActivity, onNearBottom, isLoadingMore = false }: { items: Meta[]; selectedId: string | null; posterPrefs: PosterPrefs; @@ -253,6 +253,7 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic onClick: (m: Meta) => void; onScrollActivity: () => void; onNearBottom?: () => void; + isLoadingMore?: boolean; }) { const scrollRef = useRef(null); const rafRef = useRef(null); @@ -282,7 +283,9 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic ? Math.max(GRID_MIN_COLUMN_WIDTH, (availableWidth - GRID_GAP_X * (columns - 1)) / columns) : GRID_MIN_COLUMN_WIDTH; const rowStep = itemHeight + GRID_GAP_Y; - const rowCount = Math.ceil(items.length / columns); + const placeholderCount = isLoadingMore ? columns : 0; + const slotCount = items.length + placeholderCount; + const rowCount = Math.ceil(slotCount / columns); const totalHeight = GRID_PADDING_TOP + GRID_PADDING_BOTTOM + Math.max(0, rowCount * itemHeight + Math.max(0, rowCount - 1) * GRID_GAP_Y); const startRow = Math.max(0, Math.floor((viewport.scrollTop - GRID_PADDING_TOP) / rowStep) - GRID_OVERSCAN_ROWS); const endRow = Math.min(rowCount, Math.ceil((viewport.scrollTop + viewport.height - GRID_PADDING_TOP) / rowStep) + GRID_OVERSCAN_ROWS); @@ -294,11 +297,13 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic }, [viewport.scrollTop, viewport.height, totalHeight, rowStep, onNearBottom]); const visible: Array<{ item: Meta; row: number; col: number }> = []; + const placeholders: Array<{ row: number; col: number }> = []; for (let row = startRow; row < endRow; row++) { for (let col = 0; col < columns; col++) { - const item = items[row * columns + col]; - if (!item) continue; - visible.push({ item, row, col }); + const index = row * columns + col; + const item = items[index]; + if (item) visible.push({ item, row, col }); + else if (index < slotCount) placeholders.push({ row, col }); } } @@ -330,6 +335,13 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic ); })} + {placeholders.map(({ row, col }) => { + const left = GRID_PADDING_X + col * (columnWidth + GRID_GAP_X) + Math.max(0, (columnWidth - posterPrefs.width) / 2); + const top = GRID_PADDING_TOP + row * rowStep; + return ( +
+ ); + })}
); @@ -395,7 +407,6 @@ const S: Record = { display: 'flex', flexDirection: 'column', overflow: 'hidden', - position: 'relative', }, header: { display: 'flex', @@ -444,20 +455,6 @@ const S: Record = { contain: 'strict', willChange: 'scroll-position', }, - loadingMoreBar: { - position: 'absolute', - bottom: '1rem', - left: '50%', - transform: 'translateX(-50%)', - background: 'rgba(20,22,30,0.9)', - border: '1px solid rgba(255,255,255,0.1)', - borderRadius: '1.25rem', - padding: '0.4375rem 1.125rem', - color: 'rgba(255,255,255,0.7)', - fontSize: '0.8125rem', - fontWeight: 600, - pointerEvents: 'none', - }, right: { width: '18.75rem', flexShrink: 0, background: '#0C0D18',