Pagination
分页器,支持页码折叠与简单模式
分页器(Pagination)把长列表切成若干页。总页数由 totalItems / itemsPerPage 推出,组件只持有「当前页」这一个状态;页码与上下页按钮都是 size="sm" 的 Button。
import { Pagination } from '@skyroc/native-ui';基础用法
给出数据总条数与每页条数即可,默认显示当前页左右各 1 个兄弟页码。
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" 只保留「当前页/总页数」文本,不再计算页码列表。
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 始终显示首末页,中间用省略号折叠,布局是 首页 + 省略号 + 兄弟窗口 + 省略号 + 尾页。
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 的滑动窗口:页码数量恒定、不出现省略号,贴着首尾时窗口整体平移,可见页码数保持不变。
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')。
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 后所有页码与上下页按钮都不响应点击。
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 时组件也不会自己走。
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],但不回写状态 —— 受控用法里组件写回去、调用方又传回来,两边会来回打架。
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 };列表联动
分页器本身不管数据,页码变化后由你去切数据源。
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 | 省略号占位 |
simple | simple 模式下「当前页/总页数」的容器 |
desc | 省略号与「当前页/总页数」的文字 |
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 而不是固定宽度:总页数上三位后固定宽度会把数字挤掉,让格子按内容横向生长即可。高度统一交给 Button(sm 为 32),它在该尺寸下已经补了 hitSlop,不要再加内边距撑高。
无障碍
当前页码按钮带 accessibilityState={{ selected: true }} —— Button 自身只给 busy / disabled,「第几页是当前页」这层身份要在这里补,否则读屏念不出来。
API
Pagination
除下表外,Pagination 透传 View 的属性(style、testID 等,children 除外)。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| totalItems | 数据总条数 | number | 0 |
| itemsPerPage | 每页条数 | number | 10 |
| page | 受控当前页码 | number | - |
| defaultPage | 非受控初始页码 | number | 1 |
| onPageChange | 页码变化回调 | (page: number) => void | - |
| mode | 分页模式,simple 只显示「当前页/总页数」 | 'multi' | 'simple' | 'multi' |
| showEdges | 是否始终显示首尾页码,中间用省略号折叠 | boolean | false |
| siblingCount | 当前页左右各显示几个兄弟页码 | number | 1 |
| prev | 上一页按钮内容,string / number 会被包裹为 Text | ReactNode | 'Prev' |
| next | 下一页按钮内容,string / number 会被包裹为 Text | ReactNode | 'Next' |
| disabled | 禁用所有页码与上下页按钮 | boolean | false |
| className | 根容器类名 | string | - |
| classNames | 各 slot 的类名覆盖,见「自定义样式」一节 | SlotClassNames<PaginationSlots> | - |
| ref | 根节点 View 的 ref,用于 measure / 滚动定位 | Ref<View> | - |
类型
import type { PaginationMode, PaginationProps, PaginationSlots } from '@skyroc/native-ui';PaginationMode
分页模式,simple 只显示「当前页/总页数」。
PaginationSlots
可通过 classNames 覆盖的 slot 名称。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 PaginationSlots。
包内还导出了 paginationVariants。