feat: Refactor comment section to correctly handle API response structure
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 9s

fix: Update Gravatar URL size and improve avatar rendering logic

style: Adjust footer margin for better layout consistency

refactor: Remove old navbar component and integrate new layout structure

feat: Enhance user profile page with user header component

chore: Remove unused user profile component

fix: Update posts per page configuration for better pagination

feat: Extend device context to support system theme mode

refactor: Remove unused device hook

fix: Improve storage state hook for better error handling

i18n: Add new translations for blog home page

feat: Implement pagination component for better navigation

feat: Create theme toggle component for improved user experience

feat: Introduce responsive navbar or side layout with theme toggle

feat: Develop custom select component for better UI consistency

feat: Create user header component to display user information

chore: Add query key constants for better code maintainability
This commit is contained in:
2025-09-12 00:26:08 +08:00
parent b3e8a5ef77
commit d1d8aa529f
36 changed files with 1443 additions and 731 deletions

View File

@ -0,0 +1,144 @@
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import { useEffect, useState, useCallback } from "react"
export function PaginationController({
initialPage = 1,
totalPages = 10,
buttons = 7, // recommended odd number >=5
onPageChange,
...props
}: {
initialPage?: number
totalPages: number
buttons?: number
onPageChange?: (page: number) => void
} & React.HTMLAttributes<HTMLDivElement>) {
// normalize buttons
const btns = Math.max(5, buttons ?? 7);
const buttonsToShow = totalPages < btns ? totalPages : btns;
// rely on shadcn buttonVariants and PaginationLink's isActive prop for styling
const [currentPage, setCurrentPage] = useState(() => Math.min(Math.max(1, initialPage ?? 1), Math.max(1, totalPages)));
const [direction, setDirection] = useState(0) // 1 = forward (right->left), -1 = backward
// sync when initialPage or totalPages props change
useEffect(() => {
const p = Math.min(Math.max(1, initialPage ?? 1), Math.max(1, totalPages));
setCurrentPage(p);
}, [initialPage, totalPages]);
const handleSetPage = useCallback((p: number) => {
const next = Math.min(Math.max(1, Math.floor(p)), Math.max(1, totalPages));
setDirection(next > currentPage ? 1 : next < currentPage ? -1 : 0);
setCurrentPage(next);
if (typeof onPageChange === 'function') onPageChange(next);
}, [onPageChange, totalPages, currentPage]);
// helper to render page link
const renderPage = (pageNum: number) => (
<PaginationItem key={pageNum}>
<PaginationLink
isActive={pageNum === currentPage}
aria-current={pageNum === currentPage ? 'page' : undefined}
onClick={() => handleSetPage(pageNum)}
type="button"
>
{pageNum}
</PaginationLink>
</PaginationItem>
);
// if totalPages small, render all
if (totalPages <= buttonsToShow) {
return (
<Pagination>
<PaginationContent className="select-none">
<PaginationItem>
<PaginationPrevious
aria-disabled={currentPage === 1}
onClick={() => currentPage > 1 && handleSetPage(currentPage - 1)}
/>
</PaginationItem>
{Array.from({ length: totalPages }).map((_, i) => renderPage(i + 1))}
<PaginationItem>
<PaginationNext
aria-disabled={currentPage === totalPages}
onClick={() => currentPage < totalPages && handleSetPage(currentPage + 1)}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
// for larger totalPages, show: 1, 2 or ellipsis, center range, ellipsis or N-1, N
const centerCount = buttonsToShow - 4; // slots for center pages
let start = currentPage - Math.floor(centerCount / 2);
let end = start + centerCount - 1;
if (start < 3) {
start = 3;
end = start + centerCount - 1;
}
if (end > totalPages - 2) {
end = totalPages - 2;
start = end - (centerCount - 1);
}
const centerPages = [] as number[];
for (let i = start; i <= end; i++) centerPages.push(i);
return (
<div {...props}>
<Pagination >
<PaginationContent className="select-none">
<PaginationItem>
<PaginationPrevious aria-disabled={currentPage === 1} onClick={() => currentPage > 1 && handleSetPage(currentPage - 1)} />
</PaginationItem>
{renderPage(1)}
{/* second slot: either page 2 or ellipsis if center starts later */}
{start > 3 ? (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
) : renderPage(2)}
{/* center pages */}
{centerPages.map((p) => (
<PaginationItem key={p}>
<PaginationLink
isActive={p === currentPage}
aria-current={p === currentPage ? 'page' : undefined}
onClick={() => handleSetPage(p)}
type="button"
>
{p}
</PaginationLink>
</PaginationItem>
))}
{end < totalPages - 2 ? (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
) : renderPage(totalPages - 1)}
{renderPage(totalPages)}
<PaginationItem>
<PaginationNext aria-disabled={currentPage === totalPages} onClick={() => currentPage < totalPages && handleSetPage(currentPage + 1)} />
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
}