91 lines
2.5 KiB
TypeScript
91 lines
2.5 KiB
TypeScript
|
|
import { Button } from "./Button";
|
|||
|
|
import { Select } from "./Select";
|
|||
|
|
import {
|
|||
|
|
clampPage,
|
|||
|
|
DEFAULT_PAGE_SIZES,
|
|||
|
|
pageCount,
|
|||
|
|
pageRangeLabel,
|
|||
|
|
} from "../../lib/pagination";
|
|||
|
|
import { useI18n } from "../../i18n/I18nContext";
|
|||
|
|
|
|||
|
|
type Props = {
|
|||
|
|
/** 總筆數 */
|
|||
|
|
total: number;
|
|||
|
|
/** 目前頁(從 1 起) */
|
|||
|
|
page: number;
|
|||
|
|
pageSize: number;
|
|||
|
|
onPageChange: (page: number) => void;
|
|||
|
|
/** 有傳才顯示每頁筆數 */
|
|||
|
|
onPageSizeChange?: (size: number) => void;
|
|||
|
|
pageSizeOptions?: readonly number[];
|
|||
|
|
/** 僅一頁時是否仍顯示(預設否) */
|
|||
|
|
showWhenSingle?: boolean;
|
|||
|
|
className?: string;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 統一分頁列:上一頁/下一頁、目前頁、可選每頁筆數。
|
|||
|
|
* 只有一頁且無調整 pageSize 時隱藏,避免噪音。
|
|||
|
|
*/
|
|||
|
|
export function Pager({
|
|||
|
|
total,
|
|||
|
|
page,
|
|||
|
|
pageSize,
|
|||
|
|
onPageChange,
|
|||
|
|
onPageSizeChange,
|
|||
|
|
pageSizeOptions = DEFAULT_PAGE_SIZES,
|
|||
|
|
showWhenSingle = false,
|
|||
|
|
className = "",
|
|||
|
|
}: Props) {
|
|||
|
|
const { t } = useI18n();
|
|||
|
|
const totalPages = pageCount(total, pageSize);
|
|||
|
|
const safePage = clampPage(page, totalPages);
|
|||
|
|
const single = totalPages <= 1;
|
|||
|
|
const hide = total <= 0 || (single && !showWhenSingle && !onPageSizeChange);
|
|||
|
|
if (hide) return null;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className={`hb-pager ${className}`.trim()} role="navigation" aria-label={t("pager.nav")}>
|
|||
|
|
<span className="hb-pager__meta text-muted">{pageRangeLabel(safePage, pageSize, total)}</span>
|
|||
|
|
<div className="hb-pager__controls">
|
|||
|
|
{onPageSizeChange ? (
|
|||
|
|
<Select
|
|||
|
|
label=""
|
|||
|
|
aria-label={t("pager.pageSize")}
|
|||
|
|
value={String(pageSize)}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
onPageSizeChange(Number(e.target.value) || pageSize);
|
|||
|
|
onPageChange(1);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{pageSizeOptions.map((n) => (
|
|||
|
|
<option key={n} value={n}>
|
|||
|
|
{t("pager.perPage", { n })}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</Select>
|
|||
|
|
) : null}
|
|||
|
|
<Button
|
|||
|
|
type="button"
|
|||
|
|
variant="ghost"
|
|||
|
|
disabled={safePage <= 1}
|
|||
|
|
onClick={() => onPageChange(safePage - 1)}
|
|||
|
|
>
|
|||
|
|
{t("pager.prev")}
|
|||
|
|
</Button>
|
|||
|
|
<span className="hb-pager__page" aria-current="page">
|
|||
|
|
{safePage} / {totalPages}
|
|||
|
|
</span>
|
|||
|
|
<Button
|
|||
|
|
type="button"
|
|||
|
|
variant="ghost"
|
|||
|
|
disabled={safePage >= totalPages}
|
|||
|
|
onClick={() => onPageChange(safePage + 1)}
|
|||
|
|
>
|
|||
|
|
{t("pager.next")}
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|