123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
// PaginationBar.tsx
|
||
import type { UseDataControlReturn } from 'hooks/useDataControl';
|
||
import { useMemo } from 'react';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||
|
||
type PaginationBarProps<T> = {
|
||
dataManager: UseDataControlReturn<T>;
|
||
};
|
||
|
||
export const PaginationBar = <T,>(props: PaginationBarProps<T>) => {
|
||
const { currentPage, pageCount, changePage, pageSize, setPageSize } = props.dataManager;
|
||
|
||
const pageSizeOptions = useMemo(() => [5, 10, 15, 20], []);
|
||
|
||
const handlePageSizeChange = (value: string) => {
|
||
const next = Number(value);
|
||
if (!Number.isFinite(next) || next <= 0) return;
|
||
setPageSize(next);
|
||
changePage(1);
|
||
};
|
||
|
||
const buttonClass =
|
||
'h-8 w-8 p-0 hover:bg-white text-muted-foreground disabled:opacity-100 disabled:pointer-events-none';
|
||
|
||
return (
|
||
<div className="bg-muted/50 flex w-full items-center justify-between border-t p-4">
|
||
{/* LEFT */}
|
||
<div className="flex items-center gap-2 min-[500px]:gap-3 text-sm text-muted-foreground whitespace-nowrap">
|
||
<span className="hidden min-[500px]:inline">
|
||
Showing page {currentPage} of {pageCount}
|
||
</span>
|
||
<span className="min-[500px]:hidden">
|
||
p {currentPage}/{pageCount}
|
||
</span>
|
||
|
||
<span className="bg-border h-5 w-px" aria-hidden="true" />
|
||
|
||
<Select value={String(pageSize)} onValueChange={handlePageSizeChange}>
|
||
<SelectTrigger
|
||
className="
|
||
h-8
|
||
w-[80px] min-[500px]:w-[110px]
|
||
justify-between
|
||
bg-transparent
|
||
text-muted-foreground
|
||
hover:bg-white
|
||
data-[state=open]:bg-muted
|
||
"
|
||
aria-label="Rows per page"
|
||
>
|
||
<SelectValue className="text-muted-foreground">
|
||
<span className="hidden min-[500px]:inline">{pageSize} / page</span>
|
||
<span className="min-[500px]:hidden">{pageSize}/p</span>
|
||
</SelectValue>
|
||
</SelectTrigger>
|
||
|
||
<SelectContent align="start">
|
||
{pageSizeOptions.map((n) => (
|
||
<SelectItem
|
||
key={n}
|
||
value={String(n)}
|
||
className="
|
||
text-muted-foreground
|
||
data-[state=checked]:bg-muted
|
||
data-[state=checked]:text-muted-foreground
|
||
"
|
||
>
|
||
<span className="hidden min-[500px]:inline">{n} / page</span>
|
||
<span className="min-[500px]:hidden">{n}/p</span>
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
{/* RIGHT */}
|
||
<div className="flex items-center gap-0.5 min-[500px]:gap-1">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className={buttonClass}
|
||
onClick={() => changePage(1)}
|
||
disabled={currentPage === 1}
|
||
aria-label="First"
|
||
>
|
||
«
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className={buttonClass}
|
||
onClick={() => changePage(currentPage - 1)}
|
||
disabled={currentPage === 1}
|
||
aria-label="Prev"
|
||
>
|
||
‹
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className={buttonClass}
|
||
onClick={() => changePage(currentPage + 1)}
|
||
disabled={currentPage === pageCount}
|
||
aria-label="Next"
|
||
>
|
||
›
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className={buttonClass}
|
||
onClick={() => changePage(pageCount)}
|
||
disabled={currentPage === pageCount}
|
||
aria-label="Last"
|
||
>
|
||
»
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|