概要
shadcn/ui の Table コンポーネントにページネーション (Pagination) を実装する方法を解説します。
前提
以下のコードは React – shadcn/ui の Table コンポーネントについて解説 | pystyle で作成した動的なテーブルの実装の機能追加になります。 まず、上記のページでテーブルを実装し、そのコードに以下のコードを追加してください。
Pagination を実装する
1. Button コンポーネントを追加
ページ遷移ボタンの実装には、Button コンポーネント、Select コンポーネントを使用するため、インストールします。
npx shadcn@latest add button
npx shadcn@latest add select
2. ページネーションの実装
src/components/ui/pagenation.tsx
を作成し、以下の内容を記載します。
import { Table } from "@tanstack/react-table";
import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface DataTablePaginationProps<TData> {
table: Table<TData>;
className?: string;
}
export function DataTablePagination<TData>({
table,
className,
}: DataTablePaginationProps<TData>) {
return (
<div className={cn("flex justify-between items-center", className)}>
{/* ページ ${CurrentPage} / ${TotalPage} (${CurrentRow} 件中 ${TotalRow} 件) */}
<div className="font-medium text-sm">
{`ページ ${
table.getState().pagination.pageIndex + 1
} / ${table.getPageCount()} (${
table.getState().pagination.pageSize *
(table.getState().pagination.pageIndex + 1)
} 件 / ${table.getRowCount()} 件)`}
</div>
<div className="flex items-center space-x-6">
{/* 表示行数 */}
<div className="flex items-center space-x-2">
<p className="font-medium text-sm">表示件数</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="w-[70px] h-8">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* ページ遷移ボタン */}
<div className="flex border rounded-md [&_button]:rounded-none divide-x [&_button]:w-9">
{/* 最初へ */}
<Button
variant="ghost"
size="sm"
onClick={table.firstPage}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">最初へ</span>
<ChevronsLeft />
</Button>
{/* 前へ */}
<Button
variant="ghost"
size="sm"
onClick={table.previousPage}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">前へ</span>
<ChevronLeft />
</Button>
{/* 前後2ページ分のボタン */}
{Array.from({ length: table.getPageCount() }, (_, i) => i)
.filter(
(pageIndex) =>
pageIndex >= table.getState().pagination.pageIndex - 2 &&
pageIndex <= table.getState().pagination.pageIndex + 2
)
.map((pageIndex) => (
<Button
key={pageIndex}
variant="ghost"
size="sm"
className={cn(
"disabled:opacity-100",
table.getState().pagination.pageIndex === pageIndex &&
"bg-gray-300"
)}
onClick={() => table.setPageIndex(pageIndex)}
disabled={table.getState().pagination.pageIndex === pageIndex}
>
{pageIndex + 1}
</Button>
))}
{/* 次へ */}
<Button
variant="ghost"
size="sm"
onClick={table.nextPage}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">次へ</span>
<ChevronRight />
</Button>
{/* 最後へ */}
<Button
variant="ghost"
size="sm"
onClick={table.lastPage}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">最後へ</span>
<ChevronsRight />
</Button>
</div>
</div>
</div>
);
}
解説
ページ 5 / 100 (10 件中 1000 件)
のように、現在のページ数、総ページ数、現在の表示件数、総件数を表示します。
それぞれの値は、以下のように取得できます。
- 1 ページあたりの表示行数:
table.getState().pagination.pageSize
- 現在表示中のページ:
table.getState().pagination.pageIndex + 1
- 現在表示中のページの最終行:
table.getState().pagination.pageSize * (table.getState().pagination.pageIndex + 1)
- 総行数:
table.getRowCount()
<div className="font-medium text-sm">
{`ページ ${
table.getState().pagination.pageIndex + 1
} / ${table.getPageCount()} (${
table.getState().pagination.pageSize *
(table.getState().pagination.pageIndex + 1)
} 件 / ${table.getRowCount()} 件)`}
</div>
Select コンポーネントを使用して、1 ページあたりの表示行数を変更できるようにします。
<div className="flex items-center space-x-2">
<p className="font-medium text-sm">表示件数</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="w-[70px] h-8">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
最初のページに遷移するには table.firstPage()
を呼び出します。
先頭のページでは最初のページに遷移できないため、table.getCanPreviousPage()
が false
の場合は、ボタンを無効にします。
<Button
variant="ghost"
size="sm"
onClick={table.firstPage}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">最初へ</span>
<ChevronsLeft />
</Button>
前のページに遷移するには table.previousPage()
を呼び出します。
先頭のページでは前のページに遷移できないため、table.getCanPreviousPage()
が false
の場合は、ボタンを無効にします。
<Button
variant="ghost"
size="sm"
onClick={table.previousPage}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">前へ</span>
<ChevronLeft />
</Button>
現在のページの前後 2 ページへ遷移できるボタンを表示します。 現在のページのボタンはクリックできないように無効にし、背景色を変更して強調します。
{
Array.from({ length: table.getPageCount() }, (_, i) => i)
.filter(
(pageIndex) =>
pageIndex >= table.getState().pagination.pageIndex - 2 &&
pageIndex <= table.getState().pagination.pageIndex + 2
)
.map((pageIndex) => (
<Button
key={pageIndex}
variant="ghost"
size="sm"
className={cn(
"disabled:opacity-100",
table.getState().pagination.pageIndex === pageIndex && "bg-gray-300"
)}
onClick={() => table.setPageIndex(pageIndex)}
disabled={table.getState().pagination.pageIndex === pageIndex}
>
{pageIndex + 1}
</Button>
));
}
次のページに遷移するには table.nextPage()
を呼び出します。
最後のページでは次のページに遷移できないため、table.getCanNextPage()
が false
の場合は、ボタンを無効にします。
<Button
variant="ghost"
size="sm"
onClick={table.nextPage}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">次へ</span>
<ChevronRight />
</Button>
最後のページに遷移するには table.lastPage()
を呼び出します。
最後のページでは次のページに遷移できないため、table.getCanNextPage()
が false
の場合は、ボタンを無効にします。
<Button
variant="ghost"
size="sm"
onClick={table.lastPage}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">最後へ</span>
<ChevronsRight />
</Button>
3. ページネーションの追加
テーブルを実装した src/payment/payment-table.tsx
を以下のように変更します。
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel, // 追加
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { DataTablePagination } from "@/components/ui/pagenation"; // 追加
interface PaymentTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function PaymentTable<TData, TValue>({
columns,
data,
}: PaymentTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), // 追加
});
return (
<div>
<div className="border rounded-md">
<Table>
{/* Header */}
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
{/* Body */}
<TableBody>
{table.getRowModel().rows?.length ? (
// データが存在する場合
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
// データが存在しない場合
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
データなし
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* Pagination */}
<DataTablePagination table={table} className="mt-3" /> {/* 追加 */}
</div>
);
}
コメント