Skyroc Native UI

Pagination

分页器,支持页码折叠与简单模式

分页器(Pagination)把长列表切成若干页。总页数由 totalItems / itemsPerPage 推出,组件只持有「当前页」这一个状态;页码与上下页按钮都是 size="sm"Button

import { Pagination } from '@skyroc/native-ui';

基础用法

给出数据总条数与每页条数即可,默认显示当前页左右各 1 个兄弟页码。

PaginationBasic.tsx
Loading…
import { Pagination } from '@skyroc/native-ui';
import { View } from 'react-native';

/** 总页数由 totalItems / itemsPerPage 推出,默认显示当前页左右各 1 个兄弟页码 */
const PaginationBasic = () => {
  return (
    <View className="bg-background p-4">
      <Pagination
        itemsPerPage={10}
        totalItems={95}
      />
    </View>
  );
};

export { PaginationBasic };

空数据也保留一页,避免页码区在加载态里塌成空白。

何时使用

  • 数据量大且用户需要跳到指定页;移动端更常见的是「加载更多 / 下拉刷新」,两者按场景选。
  • 空间紧张时用 mode="simple"

简单模式

mode="simple" 只保留「当前页/总页数」文本,不再计算页码列表。

PaginationSimple.tsx
Loading…
import { Pagination } from '@skyroc/native-ui';
import { View } from 'react-native';

/** Mode="simple" 只保留「当前页/总页数」,适合空间紧张的场景 */
const PaginationSimple = () => {
  return (
    <View className="bg-background p-4">
      <Pagination
        itemsPerPage={10}
        mode="simple"
        totalItems={95}
      />
    </View>
  );
};

export { PaginationSimple };

固定首尾页

showEdges 始终显示首末页,中间用省略号折叠,布局是 首页 + 省略号 + 兄弟窗口 + 省略号 + 尾页

PaginationEdges.tsx
Loading…
import { Pagination, Text } from '@skyroc/native-ui';
import { View } from 'react-native';

/** ShowEdges 始终显示第一页与最后一页,中间用省略号折叠;折叠不足 2 页时不画省略号 */
const PaginationEdges = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Pagination
        showEdges
        defaultPage={1}
        itemsPerPage={10}
        siblingCount={0}
        totalItems={500}
      />
      <Pagination
        showEdges
        defaultPage={25}
        itemsPerPage={10}
        siblingCount={0}
        totalItems={500}
      />
      <Pagination
        showEdges
        defaultPage={50}
        itemsPerPage={10}
        siblingCount={0}
        totalItems={500}
      />
      <Text color="muted">总页数塞得下时(这里只有 5 页)一个省略号都不出现</Text>
      <Pagination
        showEdges
        defaultPage={3}
        itemsPerPage={10}
        siblingCount={0}
        totalItems={50}
      />
    </View>
  );
};

export { PaginationEdges };

两个阈值决定省略号什么时候出现:

  • 这套布局最多占 2 × siblingCount + 5 格,总页数没超过它就全铺开,一个省略号都不画。
  • 省略号自己也占一格,只折叠一两页等于白占位置(1 … 4 并不比 1 2 3 4 短),所以某一侧至少要藏起 2 页才画省略号。

兄弟页码数量

siblingCount 控制当前页左右各显示几个页码,0 表示只显示当前页。关掉 showEdges 时只渲染一个宽度为 2 × siblingCount + 1 的滑动窗口:页码数量恒定、不出现省略号,贴着首尾时窗口整体平移,可见页码数保持不变。

PaginationSiblingCount.tsx
Loading…
import Feather from '@expo/vector-icons/Feather';
import { Pagination, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';

const SIBLING_COUNTS = [0, 1, 2];

/** 紧凑箭头让 siblingCount 的差异在窄屏内完整可见 */
const Icon = withUniwind(Feather);

const PrevIcon = () => (
  <Icon
    colorClassName="accent-primary"
    name="chevron-left"
    size={18}
  />
);

const NextIcon = () => (
  <Icon
    colorClassName="accent-primary"
    name="chevron-right"
    size={18}
  />
);

/** SiblingCount 控制当前页左右各显示几个页码,0 表示只显示当前页 */
const PaginationSiblingCount = () => {
  return (
    <View className="gap-3 bg-background p-4">
      {SIBLING_COUNTS.map(siblingCount => (
        <View
          key={siblingCount}
          className="gap-1"
        >
          <Text color="muted">siblingCount={siblingCount}</Text>
          <Pagination
            showEdges
            classNames={{
              content: 'gap-0.5',
              ellipsis: 'min-w-4',
              item: 'min-w-6 px-1',
              navButton: 'min-w-7 px-1'
            }}
            defaultPage={20}
            itemsPerPage={10}
            next={<NextIcon />}
            prev={<PrevIcon />}
            siblingCount={siblingCount}
            totalItems={500}
          />
        </View>
      ))}
    </View>
  );
};

export { PaginationSiblingCount };

自定义上下页

prev / next 接受任意节点,传 string / number 会自动包一层 Text(默认是 'Prev' / 'Next')。

PaginationNav.tsx
Loading…
import Feather from '@expo/vector-icons/Feather';
import { Pagination } from '@skyroc/native-ui';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';

/** Feather 不认 className,用 withUniwind 把语义色映射到 color 上 */
const Icon = withUniwind(Feather);

const PrevIcon = () => (
  <Icon
    colorClassName="accent-primary"
    name="chevron-left"
    size={18}
  />
);

const NextIcon = () => (
  <Icon
    colorClassName="accent-primary"
    name="chevron-right"
    size={18}
  />
);

/** Prev / next 接受任意节点,传字符串会自动包裹 Text */
const PaginationNav = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Pagination
        itemsPerPage={10}
        next="下一页"
        prev="上一页"
        totalItems={95}
      />
      <Pagination
        itemsPerPage={10}
        next={<NextIcon />}
        prev={<PrevIcon />}
        totalItems={95}
      />
    </View>
  );
};

export { PaginationNav };

禁用

disabled 后所有页码与上下页按钮都不响应点击。

PaginationDisabled.tsx
Loading…
import { Pagination } from '@skyroc/native-ui';
import { View } from 'react-native';

const PaginationDisabled = () => {
  return (
    <View className="bg-background p-4">
      <Pagination
        disabled
        defaultPage={3}
        itemsPerPage={10}
        totalItems={95}
      />
    </View>
  );
};

export { PaginationDisabled };

受控

page + onPageChange 由外部持有页码;父级不更新 page 时组件也不会自己走。

PaginationControlled.tsx
Loading…
import { Button, Pagination, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

/** Page + onPageChange 由外部持有页码;父级不更新 page 时组件也不会自己走 */
const PaginationControlled = () => {
  const [controlled, setControlled] = useState(3);

  return (
    <View className="gap-3 bg-background p-4">
      <Pagination
        itemsPerPage={10}
        page={controlled}
        totalItems={200}
        onPageChange={setControlled}
      />
      <Text color="muted">当前页:{controlled}</Text>
      <View className="flex-row gap-2">
        <Button
          color="primary"
          variant="outline"
          onPress={() => setControlled(1)}
        >
          回到首页
        </Button>
        <Button
          color="primary"
          variant="outline"
          onPress={() => setControlled(20)}
        >
          跳到末页
        </Button>
      </View>
    </View>
  );
};

export { PaginationControlled };

数据量变化

改筛选条件让数据变少时,外部持有的页码可能越界。组件渲染前会把显示值夹回 [1, pageCount],但不回写状态 —— 受控用法里组件写回去、调用方又传回来,两边会来回打架。

PaginationTotalChange.tsx
Loading…
import { Button, Pagination, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

/** 改筛选条件让总数变小、当前页越界时,组件只把显示值夹回合法区间,不会擅自回写外部状态 */
const PaginationTotalChange = () => {
  const [page, setPage] = useState(3);
  const [total, setTotal] = useState(200);

  return (
    <View className="gap-3 bg-background p-4">
      <Pagination
        itemsPerPage={10}
        page={page}
        totalItems={total}
        onPageChange={setPage}
      />
      <Text color="muted">
        totalItems={total},外部持有的 page={page}
      </Text>
      <View className="flex-row gap-2">
        <Button
          color="primary"
          variant="outline"
          onPress={() => setTotal(200)}
        >
          200 条
        </Button>
        <Button
          color="primary"
          variant="outline"
          onPress={() => setTotal(30)}
        >
          30 条
        </Button>
        <Button
          color="primary"
          variant="outline"
          onPress={() => setTotal(0)}
        >
          空数据
        </Button>
      </View>
    </View>
  );
};

export { PaginationTotalChange };

列表联动

分页器本身不管数据,页码变化后由你去切数据源。

PaginationWithList.tsx
Loading…
import { Divider, Pagination, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

/** 列表分页示例的数据源 */
const RECORDS = Array.from({ length: 23 }, (_, idx) => `第 ${idx + 1} 条数据`);

/** 列表分页示例每页条数 */
const PAGE_SIZE = 5;

const PaginationWithList = () => {
  const [listPage, setListPage] = useState(1);

  const listSlice = RECORDS.slice((listPage - 1) * PAGE_SIZE, listPage * PAGE_SIZE);

  return (
    <View className="gap-3 bg-background p-4">
      <View className="gap-2 rounded-xl bg-secondary p-4">
        {listSlice.map((record, index) => (
          <View key={record}>
            {index > 0 ? <Divider className="mb-2" /> : null}
            <Text>{record}</Text>
          </View>
        ))}
      </View>
      <Pagination
        itemsPerPage={PAGE_SIZE}
        page={listPage}
        totalItems={RECORDS.length}
        onPageChange={setListPage}
      />
    </View>
  );
};

export { PaginationWithList };

自定义样式

className 追加到根容器上,classNames 按 slot 细粒度覆盖:

slot作用位置
root根容器
content页码与按钮的横向容器
navButton上一页 / 下一页按钮
item单个页码按钮
itemText页码文字(当前页加粗)
ellipsis省略号占位
simplesimple 模式下「当前页/总页数」的容器
desc省略号与「当前页/总页数」的文字
PaginationStyles.tsx
Loading…
import { Pagination } from '@skyroc/native-ui';
import { View } from 'react-native';

/** ClassName 覆盖根容器,classNames 细粒度覆盖各 slot */
const PaginationStyles = () => {
  return (
    <View className="gap-3 bg-background p-4">
      <Pagination
        className="rounded-xl bg-secondary py-2"
        itemsPerPage={10}
        totalItems={95}
      />
      <Pagination
        showEdges
        classNames={{
          content: 'gap-2',
          desc: 'text-primary',
          ellipsis: 'rounded-full bg-primary-50',
          item: 'rounded-full bg-primary-50',
          itemText: 'text-primary',
          navButton: 'rounded-full bg-primary-50',
          root: 'rounded-xl border border-primary-200 py-2'
        }}
        defaultPage={5}
        itemsPerPage={10}
        siblingCount={0}
        totalItems={200}
      />
      <Pagination
        classNames={{ desc: 'text-base font-semibold text-primary', simple: 'rounded-lg bg-primary-50' }}
        defaultPage={3}
        itemsPerPage={10}
        mode="simple"
        totalItems={95}
      />
    </View>
  );
};

export { PaginationStyles };

页码格子用 min-w-8 而不是固定宽度:总页数上三位后固定宽度会把数字挤掉,让格子按内容横向生长即可。高度统一交给 Buttonsm 为 32),它在该尺寸下已经补了 hitSlop,不要再加内边距撑高。

无障碍

当前页码按钮带 accessibilityState={{ selected: true }} —— Button 自身只给 busy / disabled,「第几页是当前页」这层身份要在这里补,否则读屏念不出来。

API

Pagination

除下表外,Pagination 透传 View 的属性(styletestID 等,children 除外)。

属性说明类型默认值
totalItems数据总条数number0
itemsPerPage每页条数number10
page受控当前页码number-
defaultPage非受控初始页码number1
onPageChange页码变化回调(page: number) => void-
mode分页模式,simple 只显示「当前页/总页数」'multi' | 'simple''multi'
showEdges是否始终显示首尾页码,中间用省略号折叠booleanfalse
siblingCount当前页左右各显示几个兄弟页码number1
prev上一页按钮内容,string / number 会被包裹为 TextReactNode'Prev'
next下一页按钮内容,string / number 会被包裹为 TextReactNode'Next'
disabled禁用所有页码与上下页按钮booleanfalse
className根容器类名string-
classNames各 slot 的类名覆盖,见「自定义样式」一节SlotClassNames<PaginationSlots>-
ref根节点 View 的 ref,用于 measure / 滚动定位Ref<View>-

类型

import type { PaginationMode, PaginationProps, PaginationSlots } from '@skyroc/native-ui';

PaginationMode

分页模式,simple 只显示「当前页/总页数」。

'multi' | 'simple'

PaginationSlots

可通过 classNames 覆盖的 slot 名称。

'content' | 'desc' | 'ellipsis' | 'item' | 'itemText' | 'navButton' | 'root' | 'simple'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 PaginationSlots。

Partial<Record<Slots, string>>

包内还导出了 paginationVariants