feat: enhance RecommendationLists component with responsive pagination and auto-advance functionality

- Added dynamic column measurement to adjust the number of visible plugins based on the grid layout.
- Implemented auto-advance feature for pagination every 5 seconds when there are more plugins than the visible count.
- Updated pagination controls to reflect the current page accurately.
- Refactored code to improve readability and maintainability.
This commit is contained in:
Junyan Qin
2026-03-08 17:35:30 +08:00
parent 40c7b0f731
commit fbd3d7ae3a
2 changed files with 3376 additions and 3367 deletions
Generated
+3331 -3358
View File
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useRef, useEffect, useCallback } from 'react';
import { ChevronLeft, ChevronRight, Star } from 'lucide-react'; import { ChevronLeft, ChevronRight, Star } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import PluginMarketCardComponent from './plugin-market-card/PluginMarketCardComponent'; import PluginMarketCardComponent from './plugin-market-card/PluginMarketCardComponent';
@@ -18,7 +18,7 @@ export interface RecommendationList {
plugins: PluginV4[]; plugins: PluginV4[];
} }
const PAGE_SIZE = 4; // plugins per page in a recommendation row // Match the main plugin grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4
function pluginToVO( function pluginToVO(
plugin: PluginV4, plugin: PluginV4,
@@ -54,11 +54,44 @@ function RecommendationListRow({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [perPage, setPerPage] = useState(4);
const gridRef = useRef<HTMLDivElement>(null);
const plugins = list.plugins || []; const plugins = list.plugins || [];
const totalPages = Math.ceil(plugins.length / PAGE_SIZE);
const start = page * PAGE_SIZE; // Measure how many columns the CSS grid actually renders
const visiblePlugins = plugins.slice(start, start + PAGE_SIZE); const measureCols = useCallback(() => {
if (!gridRef.current) return;
const style = window.getComputedStyle(gridRef.current);
const cols = style.gridTemplateColumns.split(' ').length;
setPerPage(cols);
}, []);
useEffect(() => {
measureCols();
const observer = new ResizeObserver(measureCols);
if (gridRef.current) observer.observe(gridRef.current);
return () => observer.disconnect();
}, [measureCols]);
// Auto-advance every 5 seconds
useEffect(() => {
if (plugins.length <= perPage) return;
const timer = setInterval(() => {
setPage((p) => {
const tp = Math.max(1, Math.ceil(plugins.length / perPage));
return p >= tp - 1 ? 0 : p + 1;
});
}, 5000);
return () => clearInterval(timer);
}, [plugins.length, perPage]);
const totalPages = Math.max(1, Math.ceil(plugins.length / perPage));
const safePage = Math.min(page, totalPages - 1);
if (safePage !== page) setPage(safePage);
const start = safePage * perPage;
const visiblePlugins = plugins.slice(start, start + perPage);
if (plugins.length === 0) return null; if (plugins.length === 0) return null;
@@ -77,19 +110,19 @@ function RecommendationListRow({
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => setPage((p) => Math.max(0, p - 1))} onClick={() => setPage((p) => Math.max(0, p - 1))}
disabled={page === 0} disabled={safePage === 0}
className="h-7 w-7 p-0" className="h-7 w-7 p-0"
> >
<ChevronLeft className="w-4 h-4" /> <ChevronLeft className="w-4 h-4" />
</Button> </Button>
<span className="text-xs text-muted-foreground px-1"> <span className="text-xs text-muted-foreground px-1">
{page + 1} / {totalPages} {safePage + 1} / {totalPages}
</span> </span>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))} onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
disabled={page >= totalPages - 1} disabled={safePage >= totalPages - 1}
className="h-7 w-7 p-0" className="h-7 w-7 p-0"
> >
<ChevronRight className="w-4 h-4" /> <ChevronRight className="w-4 h-4" />
@@ -97,7 +130,10 @@ function RecommendationListRow({
</div> </div>
)} )}
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6"> <div
ref={gridRef}
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6"
>
{visiblePlugins.map((plugin) => ( {visiblePlugins.map((plugin) => (
<PluginMarketCardComponent <PluginMarketCardComponent
key={plugin.author + ' / ' + plugin.name} key={plugin.author + ' / ' + plugin.name}