feat: standardize table pagination styling

This commit is contained in:
Esdras Renan 2025-10-20 21:05:50 -03:00
parent 50f6796ffa
commit 2e7f575682
3 changed files with 215 additions and 129 deletions

View file

@ -13,13 +13,7 @@ import {
useReactTable,
SortingState,
} from "@tanstack/react-table"
import {
IconChevronLeft,
IconChevronRight,
IconFilter,
IconTrash,
IconUser,
} from "@tabler/icons-react"
import { IconFilter, IconTrash, IconUser } from "@tabler/icons-react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@ -49,6 +43,7 @@ import {
TableRow,
} from "@/components/ui/table"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { TablePagination } from "@/components/ui/table-pagination"
export type AdminClient = {
id: string
@ -386,29 +381,13 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
</Table>
</div>
<div className="flex flex-col items-center justify-between gap-3 text-sm text-neutral-600 md:flex-row">
<div>
Página {table.getState().pagination.pageIndex + 1} de {table.getPageCount() || 1}
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<IconChevronLeft className="size-4" />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<IconChevronRight className="size-4" />
</Button>
</div>
</div>
<TablePagination
table={table}
pageSizeOptions={[10, 20, 30, 40, 50]}
rowsPerPageLabel="Itens por página"
showSelectedRows
selectionLabel={(selected, total) => `${selected} de ${total} selecionados`}
/>
</div>
<Dialog

View file

@ -20,20 +20,16 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconChevronsLeft,
IconChevronsRight,
IconCircleCheckFilled,
IconDotsVertical,
IconGripVertical,
IconLayoutColumns,
IconLoader,
IconPlus,
IconTrendingUp,
} from "@tabler/icons-react"
import {
IconChevronDown,
IconCircleCheckFilled,
IconDotsVertical,
IconGripVertical,
IconLayoutColumns,
IconLoader,
IconPlus,
IconTrendingUp,
} from "@tabler/icons-react"
import {
ColumnDef,
ColumnFiltersState,
@ -99,12 +95,13 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"
import { TablePagination } from "@/components/ui/table-pagination"
export const schema = z.object({
id: z.number(),
@ -529,85 +526,18 @@ export function DataTable({
</TableBody>
</Table>
</DndContext>
</div>
<div className="flex items-center justify-between px-4">
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex w-full items-center gap-8 lg:w-fit">
<div className="hidden items-center gap-2 lg:flex">
<Label htmlFor="rows-per-page" className="text-sm font-medium">
Rows per page
</Label>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value))
}}
>
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
<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 w-fit items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</div>
<div className="ml-auto flex items-center gap-2 lg:ml-0">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<IconChevronsLeft />
</Button>
<Button
variant="outline"
className="size-8"
size="icon"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<IconChevronLeft />
</Button>
<Button
variant="outline"
className="size-8"
size="icon"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<IconChevronRight />
</Button>
<Button
variant="outline"
className="hidden size-8 lg:flex"
size="icon"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<IconChevronsRight />
</Button>
</div>
</div>
</div>
</TabsContent>
</div>
<TablePagination
table={table}
pageSizeOptions={[10, 20, 30, 40, 50]}
rowsPerPageLabel="Rows per page"
showSelectedRows
selectionLabel={(selected, total) =>
`${selected} of ${total} row${total === 1 ? "" : "s"} selected.`
}
className="pb-4"
/>
</TabsContent>
<TabsContent
value="past-performance"
className="flex flex-col px-4 lg:px-6"

View file

@ -0,0 +1,177 @@
import { useMemo } from "react"
import type { Table } from "@tanstack/react-table"
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { cn } from "@/lib/utils"
type TablePaginationProps<TData> = {
table: Table<TData>
pageSizeOptions?: number[]
rowsPerPageLabel?: string
showSelectedRows?: boolean
selectionLabel?: (selected: number, total: number) => React.ReactNode
className?: string
}
function buildPaginationRange(currentPage: number, pageCount: number) {
if (pageCount <= 7) {
return Array.from({ length: pageCount }, (_, index) => index + 1)
}
const range: Array<number | "ellipsis-left" | "ellipsis-right"> = [1]
const left = Math.max(2, currentPage - 1)
const right = Math.min(pageCount - 1, currentPage + 1)
if (left > 2) {
range.push("ellipsis-left")
}
for (let page = left; page <= right; page += 1) {
range.push(page)
}
if (right < pageCount - 1) {
range.push("ellipsis-right")
}
range.push(pageCount)
return range
}
export function TablePagination<TData>({
table,
pageSizeOptions = [10, 20, 30, 50],
rowsPerPageLabel = "Itens por página",
showSelectedRows = false,
selectionLabel,
className,
}: TablePaginationProps<TData>) {
const pageIndex = table.getState().pagination.pageIndex
const pageSize = table.getState().pagination.pageSize
const pageCount = Math.max(table.getPageCount(), 1)
const currentPage = Math.min(pageIndex + 1, pageCount)
const totalRows = table.getFilteredRowModel().rows.length
const currentRows = table.getRowModel().rows.length
const rangeStart = totalRows === 0 ? 0 : pageIndex * pageSize + 1
const rangeEnd = totalRows === 0 ? 0 : rangeStart + currentRows - 1
const selectedCount = table.getFilteredSelectedRowModel().rows.length
const paginationRange = useMemo(
() => buildPaginationRange(currentPage, pageCount),
[currentPage, pageCount]
)
const handlePageChange = (pageNumber: number) => {
table.setPageIndex(pageNumber - 1)
}
return (
<div
className={cn(
"flex flex-col gap-4 border-t border-slate-200 px-4 pt-4 text-sm text-neutral-600 md:flex-row md:items-center md:justify-between",
className
)}
>
{showSelectedRows ? (
<div className="text-xs text-neutral-500 md:text-sm">
{selectionLabel
? selectionLabel(selectedCount, totalRows)
: `${selectedCount} de ${totalRows} selecionados`}
</div>
) : (
<div className="sr-only">Paginação de tabela</div>
)}
<div className="flex flex-col-reverse items-center gap-3 md:flex-row md:gap-4 md:justify-end md:flex-1">
{pageSizeOptions.length > 0 ? (
<div className="flex items-center gap-2 text-xs uppercase tracking-wide text-neutral-500 md:text-sm">
<span>{rowsPerPageLabel}</span>
<Select
value={`${pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value))
table.setPageIndex(0)
}}
>
<SelectTrigger className="h-8 w-20">
<SelectValue placeholder={pageSize} />
</SelectTrigger>
<SelectContent align="end">
{pageSizeOptions.map((option) => (
<SelectItem key={option} value={`${option}`}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
) : null}
<div className="flex flex-col items-center gap-2 md:flex-row md:gap-3">
<span className="text-xs font-medium text-neutral-500 md:text-sm">
{totalRows === 0
? "Nenhum registro"
: `Mostrando ${rangeStart}-${rangeEnd} de ${totalRows}`}
</span>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
/>
</PaginationItem>
{paginationRange.map((item, index) => {
if (typeof item === "number") {
return (
<PaginationItem key={`page-${item}`}>
<PaginationLink
href="#"
isActive={item === currentPage}
onClick={(event) => {
event.preventDefault()
handlePageChange(item)
}}
>
{item}
</PaginationLink>
</PaginationItem>
)
}
return (
<PaginationItem key={`ellipsis-${item}-${index}`}>
<PaginationEllipsis />
</PaginationItem>
)
})}
<PaginationItem>
<PaginationNext
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
</div>
</div>
)
}