fix(ui): harden pagination and selection controls
This commit is contained in:
1 parent
32d872ed9c
commit
ef57f3f196
3 files changed
+30
-36
No files matched your search
@@ -1,22 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem } from "@/components/ui/pagination";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
interface TablePaginationProps {
|
||||
page: number; // 1-based
|
||||
@@ -49,19 +39,20 @@ export function TablePagination({
|
||||
pageSizeOptions = [10, 20, 50],
|
||||
}: TablePaginationProps) {
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
const from = total === 0 ? 0 : (page - 1) * pageSize + 1;
|
||||
const to = Math.min(page * pageSize, total);
|
||||
const safePage = Math.min(Math.max(1, page), totalPages);
|
||||
const from = total === 0 ? 0 : (safePage - 1) * pageSize + 1;
|
||||
const to = Math.min(safePage * pageSize, total);
|
||||
const onPageChangeRef = useRef(onPageChange);
|
||||
onPageChangeRef.current = onPageChange;
|
||||
|
||||
useEffect(() => {
|
||||
if (page !== safePage) onPageChangeRef.current(safePage);
|
||||
}, [page, safePage]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-t px-4 py-2 lg:px-6">
|
||||
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
||||
<Select
|
||||
value={String(pageSize)}
|
||||
onValueChange={(v) => {
|
||||
onPageSizeChange(Number(v));
|
||||
onPageChange(1);
|
||||
}}
|
||||
>
|
||||
<Select value={String(pageSize)} onValueChange={(v) => onPageSizeChange(Number(v))}>
|
||||
<SelectTrigger size="sm" className="h-7 w-16">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -90,14 +81,14 @@ export function TablePagination({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
disabled={page === 1}
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
disabled={safePage === 1}
|
||||
onClick={() => onPageChange(safePage - 1)}
|
||||
aria-label="上一页"
|
||||
>
|
||||
<ChevronLeftIcon className="size-4" />
|
||||
</Button>
|
||||
</PaginationItem>
|
||||
{pageWindows(page, totalPages).map((p, i) =>
|
||||
{pageWindows(safePage, totalPages).map((p, i) =>
|
||||
p === "..." ? (
|
||||
<PaginationItem key={`el-${i}`}>
|
||||
<PaginationEllipsis />
|
||||
@@ -105,10 +96,10 @@ export function TablePagination({
|
||||
) : (
|
||||
<PaginationItem key={p}>
|
||||
<Button
|
||||
variant={p === page ? "outline" : "ghost"}
|
||||
variant={p === safePage ? "outline" : "ghost"}
|
||||
size="icon-sm"
|
||||
onClick={() => onPageChange(p)}
|
||||
aria-current={p === page ? "page" : undefined}
|
||||
aria-current={p === safePage ? "page" : undefined}
|
||||
>
|
||||
{p}
|
||||
</Button>
|
||||
@@ -119,8 +110,8 @@ export function TablePagination({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
disabled={page === totalPages}
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={safePage === totalPages}
|
||||
onClick={() => onPageChange(safePage + 1)}
|
||||
aria-label="下一页"
|
||||
>
|
||||
<ChevronRightIcon className="size-4" />
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
||||
import { Checkbox as CheckboxPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CheckIcon } from "lucide-react"
|
||||
import { CheckIcon, MinusIcon } from "lucide-react"
|
||||
|
||||
function Checkbox({
|
||||
className,
|
||||
@@ -21,10 +21,10 @@ function Checkbox({
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
data-slot="checkbox-indicator"
|
||||
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
||||
className="group/checkbox-indicator grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
||||
>
|
||||
<CheckIcon
|
||||
/>
|
||||
<CheckIcon className="group-data-[state=indeterminate]/checkbox-indicator:hidden" />
|
||||
<MinusIcon className="hidden group-data-[state=indeterminate]/checkbox-indicator:block" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
)
|
||||
|
||||
@@ -93,14 +93,17 @@ function ComboboxContent({
|
||||
align = "start",
|
||||
alignOffset = 0,
|
||||
anchor,
|
||||
portalContainer,
|
||||
...props
|
||||
}: ComboboxPrimitive.Popup.Props &
|
||||
Pick<
|
||||
ComboboxPrimitive.Positioner.Props,
|
||||
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
||||
>) {
|
||||
> & {
|
||||
portalContainer?: ComboboxPrimitive.Portal.Props["container"]
|
||||
}) {
|
||||
return (
|
||||
<ComboboxPrimitive.Portal>
|
||||
<ComboboxPrimitive.Portal container={portalContainer}>
|
||||
<ComboboxPrimitive.Positioner
|
||||
side={side}
|
||||
sideOffset={sideOffset}
|
||||
|
||||
Reference in new issue
Block a user