Pagination 分页

切换页码、调整每页条数并浏览省略的页码范围。

'use client'

import { useState } from 'react'
import { Pagination } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(8)

  return <Pagination value={page} onValueChange={setPage} total={246} />
}
tsx

使用

import { Pagination } from '@hina-ui/react'
ts

用 value / onValueChange 绑定当前页,从 1 开始。total 是总条数,pageSize 是每页条数,页数由二者计算。点击页码或翻页按钮会更新当前页。

默认只显示页码、前后翻页按钮和交互省略号。信息区、条数选择器与跳页输入框按需开启。

当前页:1 / 10

'use client'

import { useState } from 'react'
import { Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(1)

  return (
    <Stack gap="sm">
      <Pagination value={page} onValueChange={setPage} total={95} pageSize={10} />
      <Text size="sm" tone="muted">
        当前页:{page} / 10
      </Text>
    </Stack>
  )
}
tsx

示例

页码范围

siblingCount 控制当前页两侧显示的相邻页码数。showEdges 默认开启,保留第一页、最后一页的页码,中间断开的范围显示省略号。

showFirstLast 独立控制跳到第一页、最后一页的箭头按钮,默认关闭。

sibling-count="1"

sibling-count="0" + show-first-last

show-edges="false"

'use client'

import { useState } from 'react'
import { Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(10)

  return (
    <Stack gap="lg">
      <Stack gap="xs">
        <Text size="sm" tone="muted">
          sibling-count=&quot;1&quot;
        </Text>
        <Pagination value={page} onValueChange={setPage} total={200} />
      </Stack>
      <Stack gap="xs">
        <Text size="sm" tone="muted">
          sibling-count=&quot;0&quot; + show-first-last
        </Text>
        <Pagination
          value={page}
          onValueChange={setPage}
          total={200}
          siblingCount={0}
          showFirstLast
        />
      </Stack>
      <Stack gap="xs">
        <Text size="sm" tone="muted">
          show-edges=&quot;false&quot;
        </Text>
        <Pagination value={page} onValueChange={setPage} total={200} showEdges={false} />
      </Stack>
    </Stack>
  )
}
tsx

省略号

鼠标点击省略号向对应方向跳过 2 × siblingCount + 1 页;悬停展开该处省略的页码列表。列表只包含未显示在分页栏中的页码,选择后关闭。触摸点击直接展开列表。

两侧省略号共用一个浮层,切换方向时更新位置和页码。连续点击按组跳页时,展开的范围同步更新。浮层在可用空间不足时翻转方向,长列表可滚动;页数较多时仅渲染可见区域附近的选项。

退场期间,浮层继续跟随仍存在的触发器;触发器移除后,浮层在最后的位置完成退场。

当前页: 10

'use client'

import { useState } from 'react'
import { Button, Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(10)
  const [large, setLarge] = useState(false)

  return (
    <Stack gap="sm">
      <Pagination value={page} onValueChange={setPage} total={large ? 100000 : 250} />
      <Text size="sm" tone="muted">
        当前页: {page}
      </Text>
      <Button
        variant="soft"
        tone="neutral"
        className="self-start"
        onClick={() => setLarge(value => !value)}
      >
        {large ? '25 页' : '10,000 页'}
      </Button>
    </Stack>
  )
}
tsx

可选控件

showInfo 显示条目范围、总条数和当前页数。itemCount 可指定当前页实际展示的条数;未设置时按 pageSize 计算,并限制到总条数以内。

设置 pageSizeOptions 显示条数选择器,使用 pageSize / onPageSizeChange 绑定。选择新条数时回到第 1 页,onChange 一次传出新的 { page, pageSize },可以直接在这个回调中更新数据。

showJump 显示跳页输入框。Enter 或失焦提交,越界值限制到有效范围;空值和无效输入恢复当前页,Escape 取消编辑。

change: —

'use client'

import { useState } from 'react'
import { Pagination, Stack, Text, type PaginationChange } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(8)
  const [pageSize, setPageSize] = useState(10)
  const [lastChange, setLastChange] = useState<PaginationChange>()

  return (
    <Stack gap="sm">
      <Pagination
        value={page}
        onValueChange={setPage}
        pageSize={pageSize}
        onPageSizeChange={setPageSize}
        total={246}
        pageSizeOptions={[10, 20, 50]}
        showInfo
        showJump
        onChange={setLastChange}
      />
      <Text size="sm" tone="muted">
        change: {lastChange ? JSON.stringify(lastChange, null, 2) : '—'}
      </Text>
    </Stack>
  )
}
tsx

总条数与每页条数

从外部修改 total 或 pageSize 时,当前页在有效范围内则保持;越界时调整到最后一页,并调用 onValueChange。直接修改有效的受控值不会反向调用 onChange。

当前页:18

'use client'

import { useState } from 'react'
import { Button, Inline, Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(18)
  const [total, setTotal] = useState(200)
  const [pageSize, setPageSize] = useState(10)

  return (
    <Stack gap="sm">
      <Inline gap="sm">
        <Button
          variant="soft"
          tone="neutral"
          onClick={() => setTotal(value => (value === 200 ? 45 : 200))}
        >
          total: {total}
        </Button>
        <Button
          variant="soft"
          tone="neutral"
          onClick={() => setPageSize(value => (value === 10 ? 25 : 10))}
        >
          page-size: {pageSize}
        </Button>
      </Inline>
      <Pagination value={page} onValueChange={setPage} total={total} pageSize={pageSize} />
      <Text size="sm" tone="muted">
        当前页:{page}
      </Text>
    </Stack>
  )
}
tsx

布局与组合

align 控制各部分的对齐:start、center、end 或 between。children 可组合 PaginationInfo、PaginationContent、PaginationSize、PaginationJump,自由调整顺序与分组;这些子组件共享根组件的状态、尺寸、禁用和语言配置。

PaginationInfo 的 children 提供分页状态,可以改写信息文本。示例用 Inline 分组右侧控件。

'use client'

import { useState } from 'react'
import {
  Inline,
  Pagination,
  PaginationContent,
  PaginationInfo,
  PaginationJump,
  PaginationSize,
} from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(4)
  const [pageSize, setPageSize] = useState(10)

  return (
    <Pagination
      value={page}
      onValueChange={setPage}
      pageSize={pageSize}
      onPageSizeChange={setPageSize}
      total={180}
      pageSizeOptions={[10, 20, 50]}
      align="between"
    >
      <PaginationInfo>
        {({ page: current, pageCount }) => `${current} / ${pageCount}`}
      </PaginationInfo>
      <Inline gap="sm">
        <PaginationContent />
        <PaginationSize />
        <PaginationJump />
      </Inline>
    </Pagination>
  )
}
tsx

加载

pending 禁用分页操作并关闭省略号列表。提供 renderList 时,列表保留挂载并由 LoadingOverlay 遮罩,列表内容暂停交互。

加载期间总条数暂时变为 0 不会重写绑定的页码;加载结束后再校正越界页码。示例用定时器切换加载状态,组件本身不发起请求。

1

2

3

4

5

'use client'

import { useEffect, useRef, useState } from 'react'
import { Card, Pagination, Stack, Text, type PaginationChange } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(1)
  const [displayedPage, setDisplayedPage] = useState(1)
  const [pending, setPending] = useState(false)
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined)

  function change(value: PaginationChange) {
    setPending(true)
    clearTimeout(timer.current)
    timer.current = setTimeout(() => {
      setDisplayedPage(value.page)
      setPending(false)
    }, 1000)
  }

  useEffect(() => () => clearTimeout(timer.current), [])

  return (
    <Pagination
      value={page}
      onValueChange={setPage}
      total={96}
      pageSize={5}
      pending={pending}
      showInfo
      onChange={change}
      renderList={() => (
        <Stack gap="xs">
          {[1, 2, 3, 4, 5].map(index => (
            <Card key={index}>
              <Text size="sm">{(displayedPage - 1) * 5 + index}</Text>
            </Card>
          ))}
        </Stack>
      )}
    />
  )
}
tsx

单页隐藏

hideSinglePage 在总页数不超过 1 时隐藏导航栏,renderList 仍然保留。

列表插槽始终保留。

'use client'

import { useState } from 'react'
import { Button, Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [total, setTotal] = useState(5)

  return (
    <Stack gap="sm">
      <Button
        variant="soft"
        tone="neutral"
        className="self-start"
        onClick={() => setTotal(value => (value === 5 ? 50 : 5))}
      >
        total: {total}
      </Button>
      <Pagination
        total={total}
        hideSinglePage
        renderList={() => (
          <Text size="sm" tone="muted">
            列表插槽始终保留。
          </Text>
        )}
      />
    </Stack>
  )
}
tsx

尺寸

size 为 sm、md 或 lg,沿用 Button 的高度。页码按钮保持等宽等高,随密度设置调整,不会被内容撑宽。超长内容截断显示。

sm

md

lg

'use client'

import { useState } from 'react'
import { Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(3)

  return (
    <Stack gap="lg">
      {(['sm', 'md', 'lg'] as const).map(size => (
        <Stack key={size} gap="xs">
          <Text size="sm" tone="muted">
            {size}
          </Text>
          <Pagination value={page} onValueChange={setPage} total={50} size={size} />
        </Stack>
      ))}
    </Stack>
  )
}
tsx

内容截断

页码内容实际截断时,悬停或键盘聚焦按钮会通过 Tooltip 显示完整文本,支持自定义的 renderPage 内容。需要外层有 TooltipProvider,AppShell 已包含。

'use client'

import { useState } from 'react'
import { Pagination } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(100000)

  return <Pagination value={page} onValueChange={setPage} total={2000000} size="sm" />
}
tsx

状态

disabled 禁用全部控件;第一页的向前按钮、最后一页的向后按钮自动禁用。total={0} 时保留第 1 页,所有翻页方向按钮禁用。

禁用

total="0"

import { Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack gap="lg">
      <Stack gap="xs">
        <Text size="sm" tone="muted">
          禁用
        </Text>
        <Pagination value={3} total={50} disabled />
      </Stack>
      <Stack gap="xs">
        <Text size="sm" tone="muted">
          total=&quot;0&quot;
        </Text>
        <Pagination total={0} showFirstLast />
      </Stack>
    </Stack>
  )
}
tsx

方向

dir 接受 ltr 或 rtl,优先于 ConfigProvider 的全局方向配置;都未设置时,继承外层元素的方向。RTL 下按钮排列和箭头一起翻转,下一页仍然增加页码。

LTR

RTL

'use client'

import { useState } from 'react'
import { Pagination, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [page, setPage] = useState(3)

  return (
    <Stack gap="lg">
      {(['ltr', 'rtl'] as const).map(dir => (
        <Stack key={dir} gap="xs">
          <Text size="sm" tone="muted">
            {dir.toUpperCase()}
          </Text>
          <Pagination value={page} onValueChange={setPage} total={50} dir={dir} />
        </Stack>
      ))}
    </Stack>
  )
}
tsx

页码内容

renderPage 提供 { page, selected },替换页码按钮内的内容,保留交互和当前页语义。renderEllipsis 提供 { side, expanded },替换省略号按钮内容。

示例使用 Text 设置当前页文字的字重。

'use client'

import { useState } from 'react'
import { Pagination, Text } from '@hina-ui/react'

const numerals = ['I', 'II', 'III', 'IV', 'V']

export default function Demo() {
  const [page, setPage] = useState(2)

  return (
    <Pagination
      value={page}
      onValueChange={setPage}
      total={50}
      renderPage={({ page: number, selected }) => (
        <Text as="span" weight={selected ? 'semibold' : 'normal'} className="text-inherit">
          {numerals[number - 1]}
        </Text>
      )}
    />
  )
}
tsx

行为

  • 当前页限制在 1 到总页数之间;页码总数由 Math.ceil(total / pageSize) 计算,最少为 1。
  • 用户操作实际改变页码或条数时,调用一次 onChange;重复选择当前值不会调用。自动校正越界页码也会调用 onChange。
  • 条数选择器始终包含当前值,去除重复、非整数和小于 1 的选项。
  • 容器宽度不足时换行;可通过 siblingCount 和 showEdges 减少可见页码。

无障碍

  • 导航区域为 nav,默认名称随界面语言变化,label 可覆盖。当前页使用 aria-current="page"。
  • Tab 在可用控件间移动,Enter 或空格激活;禁用控件跳过。按钮不会触发表单提交。
  • 省略号获得键盘焦点时展开列表。Enter 或空格按组跳页,↓ / ↑ 分别进入列表的第一项 / 最后一项。
  • 列表内 ↑ / ↓ 移动,Home / End 跳到首尾,PageUp / PageDown 按可见范围移动;Enter 或空格选择。
  • Escape 关闭列表并恢复焦点。选择后若原省略号消失,焦点移到当前页按钮;Tab 可离开列表。

API

Props

Prop
类型
默认值
说明
value
number
1
当前页,可受控
total
number
必填
总条数
pageSize
number
10
每页条数,可受控
itemCount
number
—
当前页实际展示条数
siblingCount
number
1
当前页两侧的相邻页码数
showEdges
boolean
true
保留首尾页码及省略号
showFirstLast
boolean
false
显示跳到首尾页的箭头按钮
showInfo
boolean
false
显示分页信息
showJump
boolean
false
显示跳页输入框
pageSizeOptions
number[]
—
每页条数选项;非空时显示选择器
hideSinglePage
boolean
false
总页数不超过 1 时隐藏导航
pending
boolean
false
加载状态
align
'start' | 'center' | 'end' | 'between'
'start'
各部分对齐方式
size
'sm' | 'md' | 'lg'
'md'
控件尺寸
disabled
boolean
false
禁用全部控件
dir
'ltr' | 'rtl'
—
排列方向,未设置时继承
label
string
界面语言
导航区域的无障碍名称
className
string
—
附加到根节点的类名

其余属性透传到外层 div,导航区域位于其中。

回调

回调
参数
说明
onValueChange
page: number
用户切换页码或当前页超出范围
onPageSizeChange
pageSize: number
用户调整每页条数
onChange
PaginationChange
一次有效的页码或条数变更

内容属性

属性
参数
说明
children
PaginationState
组合导航控件,替换默认排列
renderList
PaginationState
导航上方的列表内容
renderPage
{ page: number; selected: boolean }
页码按钮的内容
renderEllipsis
{ side: 'prev' | 'next'; expanded: boolean }
省略号按钮的内容

组合组件与类型

PaginationContent 提供页码与翻页按钮,支持相同的 renderPage、renderEllipsis。PaginationInfo 的 children 可以是函数 (state: PaginationState) => ReactNode。PaginationSize 和 PaginationJump 分别提供条数选择器与跳页输入框。四个组件均接受 className,需放在 Pagination 的 children 内。

interface PaginationChange {
  page: number
  pageSize: number
}

interface PaginationState extends PaginationChange {
  total: number
  pageCount: number
  from: number
  to: number
}
ts

组件及以上类型均从 @hina-ui/react 导出。