fix(ui): harden pagination and selection controls

This commit is contained in:
pphua committed 2026-08-24 19:31:09 +08:00
1 parent 32d872ed9c
commit ef57f3f196
3 files changed
+30 -36

No files matched your search

+21 -30
View File
@@ -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 -4
View File
@@ -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>
)
+5 -2
View File
@@ -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}