99 lines
3.8 KiB
JavaScript
99 lines
3.8 KiB
JavaScript
|
|
import { GlassSurface, Button, Input } from './ui';
|
|||
|
|
|
|||
|
|
export default function AdminCatalogPagination({
|
|||
|
|
pagination,
|
|||
|
|
hasMore,
|
|||
|
|
loadingMore,
|
|||
|
|
onLoadMore,
|
|||
|
|
onLimitChange
|
|||
|
|
}) {
|
|||
|
|
const { page, limit, total, pages } = pagination;
|
|||
|
|
|
|||
|
|
if (total === 0) return null;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<GlassSurface tint="mid" rim="subtle" className="mt-4 p-4">
|
|||
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|||
|
|
<div className="flex flex-wrap items-center gap-4">
|
|||
|
|
<span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|||
|
|
Showing {(page - 1) * limit + 1}–{Math.min(page * limit, total)} of {total.toLocaleString()}
|
|||
|
|
</span>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<label className="text-sm" style={{ color: 'var(--text-secondary)' }}>Per page:</label>
|
|||
|
|
<select
|
|||
|
|
value={limit}
|
|||
|
|
onChange={(e) => onLimitChange(parseInt(e.target.value))}
|
|||
|
|
disabled={loadingMore}
|
|||
|
|
className="glass-input rounded-xl px-3 py-1.5 text-sm"
|
|||
|
|
style={{
|
|||
|
|
border: '1px solid var(--border)',
|
|||
|
|
color: 'var(--text-primary)',
|
|||
|
|
backgroundColor: 'var(--bg-tertiary)',
|
|||
|
|
appearance: 'none',
|
|||
|
|
backgroundImage: "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\")",
|
|||
|
|
backgroundPosition: 'right 0.5rem center',
|
|||
|
|
backgroundRepeat: 'no-repeat',
|
|||
|
|
backgroundSize: '1.5em 1.5em',
|
|||
|
|
paddingRight: '2rem',
|
|||
|
|
minWidth: '80px'
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<option value="25">25</option>
|
|||
|
|
<option value="50">50</option>
|
|||
|
|
<option value="100">100</option>
|
|||
|
|
<option value="200">200</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<Button
|
|||
|
|
variant="secondary"
|
|||
|
|
size="sm"
|
|||
|
|
onClick={() => onLoadMore()}
|
|||
|
|
disabled={loadingMore || !hasMore}
|
|||
|
|
leadingIcon={loadingMore && (
|
|||
|
|
<span className="animate-spin inline-block h-4 w-4 border-2 border-current border-r-transparent rounded-full" />
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{hasMore ? 'Load More' : 'End of Results'}
|
|||
|
|
</Button>
|
|||
|
|
|
|||
|
|
{pages > 1 && (
|
|||
|
|
<>
|
|||
|
|
<Button
|
|||
|
|
variant="ghost"
|
|||
|
|
size="sm"
|
|||
|
|
onClick={() => onLimitChange(limit)} // placeholder - page nav handled by load more
|
|||
|
|
disabled={page === 1}
|
|||
|
|
leadingIcon={
|
|||
|
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|||
|
|
</svg>
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
Prev
|
|||
|
|
</Button>
|
|||
|
|
<span className="px-3 text-sm font-mono" style={{ color: 'var(--text-secondary)' }}>
|
|||
|
|
Page {page} of {pages}
|
|||
|
|
</span>
|
|||
|
|
<Button
|
|||
|
|
variant="ghost"
|
|||
|
|
size="sm"
|
|||
|
|
onClick={() => onLoadMore()}
|
|||
|
|
disabled={page === pages || loadingMore}
|
|||
|
|
trailingIcon={
|
|||
|
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|||
|
|
</svg>
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
Next
|
|||
|
|
</Button>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</GlassSurface>
|
|||
|
|
);
|
|||
|
|
}
|