import Link from "next/link"; import type { ReactNode } from "react"; interface PaginationProps { /** 1-based. */ currentPage: number; totalPages: number; /** 각 페이지 번호로 이동할 href를 만들어 준다 — 라우팅은 소비처 책임. */ buildHref: (page: number) => string; /** 한 번에 보여줄 번호 개수. 기본 5. */ windowSize?: number; } const ITEM_CLASSES = "inline-flex size-9 items-center justify-center rounded-md text-body-sm transition-colors duration-200"; const ENABLED_CLASSES = "text-foreground-muted hover:bg-surface-muted hover:text-foreground"; const DISABLED_CLASSES = "text-foreground-subtle opacity-50"; const CURRENT_CLASSES = "bg-primary text-on-primary"; /** 현재 페이지를 중심으로 windowSize개 번호를 [1, totalPages] 범위 안에서 슬라이딩한다. */ function getPageWindow(currentPage: number, totalPages: number, windowSize: number): number[] { if (totalPages < 1) { return []; } const half = Math.floor(windowSize / 2); let start = Math.max(1, currentPage - half); const end = Math.min(totalPages, start + windowSize - 1); start = Math.max(1, end - windowSize + 1); const pages: number[] = []; for (let page = start; page <= end; page += 1) { pages.push(page); } return pages; } interface PaginationItemProps { disabled: boolean; href: string; label: string; children: ReactNode; } /** 처음·이전·다음·끝 이동 항목 — 비활성일 때는 링크 대신 aria-disabled span으로 렌더한다. */ function PaginationItem({ disabled, href, label, children }: PaginationItemProps) { if (disabled) { return ( {children} ); } return ( {children} ); } /** * 페이지네이션 — « ‹ [번호들] › ». 페이지 이동은 `next/link` href 기반이며 실제 라우팅(쿼리 * 구성 등)은 `buildHref`를 통해 호출부(feature)가 책임진다. 첫/마지막 페이지에서는 처음·이전· * 다음·끝 이동이 비활성 처리된다. */ export function Pagination({ currentPage, totalPages, buildHref, windowSize = 5, }: PaginationProps) { const pages = getPageWindow(currentPage, totalPages, windowSize); const isFirst = currentPage <= 1; const isLast = currentPage >= totalPages; return ( ); }