导航
Pagination - 分页
带页码的分页,中间省略。
用法
import { Pagination } from '@misoto22/folio'说明
带页码的分页。
当前页由一个在数字之间移动的实心药丸标出,而不是靠一个背景在这边关掉、在那边打开。两个背景交叉淡入淡出读起来像两样东西在变;一个形状在移动读起来才是那一样东西真的动了。
一个 <nav> 包一个列表,当前页是 <button aria-current> 而不是加了样式的 <span>——按地标跳转的读者需要找到这个控件,而正停在当前页的读者需要被告知他已经在这儿了。
只有一页或更少时什么都不渲染。为一页做的分页器是摆设。
读者听到的每一个字符串都是一个 prop。那两个箭头不带文字,所以 previousLabel 和 nextLabel 就是这两个控件名字的全部;而 pageLabel 是个函数,因为“第 3 页”是一个各部分会在不同语言之间挪位置的短语。
结构
| 部件 | 说明 |
|---|---|
| 导航区必填 | 一个由 label 命名的 <nav>,默认叫「Pagination」。这里没有别的东西是地标,所以读者能直接跳到分页器、而不是滚过去,靠的就是它。 |
| 步进按钮必填 | 上一页和下一页,--control-h-sm 高的胶囊圆角,名字由 previousLabel 和 nextLabel 给——在调用处另有说法之前,是「Previous page」和「Next page」。各自到了区间的尽头就被 disabled,这会把它移出 Tab 顺序,而不是留下一个按了什么都不做的控件。 |
| 页码列表必填 | 一个装着数字的 <ol>,每个数字是一个由 pageLabel 命名的 <button>——默认是「Page N」——你所在的那一个带着 aria-current。 |
| 滑动胶囊 | 一块 aria-hidden 的填充,尺寸是从选中那个按钮量出来的,靠 transform 移动,而不是两块底色交叉淡入淡出。第一次测量落定之前它不存在,而在 prefers-reduced-motion 下它保持不动。 |
| 省略号 | 序列跳过不止一页的地方,放一个 aria-hidden 的 <li>。只跳过一页时改成把那一页印出来——「1 … 3」比「1 2 3」还长,说的却更少。 |
实践建议
推荐
- page 要和发请求写在同一次状态更新里:它是完全受控的,所以一个只加载下一页、却没有设 page 的处理函数,会让分页器一直标着读者刚离开的那一页。
- siblings 要往上调,别往下调——总页数不到 2 × siblings + 5 时每一页反正都会印出来,所以这个属性在短列表上什么都不做,在长列表上又是你手里唯一的那根杆。
- 别的东西必须和分页器口径一致时,把 paginationRange import 过来:它是导出的、纯的——服务端渲染的那句摘要和这个组件最后描述的是同一个窗口而不是两个,靠的就是它。
- 让外面那一行能塌下去:页数不超过一页时这个组件返回 null,所以一个按固定高度搭的页脚,会在列表变短的那天露出一条空带。
避免
- 别把它放进一个紧凑区域,再管它叫触摸目标:那些胶囊是 --control-h-sm,默认密度下 36px、data-density="compact" 下 30px,彼此只隔 4px——远低于 WCAG 2.5.5 对指针目标要求的 44px。
- 别指望 pageLabel 会改变印出来的东西。它只为读屏软件命名这个控件,别的什么都不做;按钮显示的仍然是交给它的那个西文数字——所以数字写法不同的语言,还得在调用处自己把它们格式化一遍。
示例
default
二十页,完全受控。要在触发取数的那同一次状态更新里改 page:一个加载下一页却不设 page 的处理器,会让分页器一直标着读者刚刚离开的那一页。当前页是一个带 aria-current 的按钮,而不是一个加了样式的 span,因为一个靠控件跳转的读者必须能找到它——而且那颗填充的药丸是在数字之间移动的,不是一个底色熄掉、另一个底色亮起,前者读起来才像是那个真正变化了的东西。总页数不超过一页时它什么都不渲染,所以一个按固定高度搭的页脚,会在列表变短的那天露出一条空带子。
the edges
同样的二十四页,从两端和从中间各看一次,而分页器通常正是在这几处出错。第一页时「上一页」禁用,最后一页时「下一页」禁用,这会把它们各自从 tab 顺序里拿走,而不是留下一个按了没反应的控件。省略号只出现在序列跳过不止一页的地方——只跳过一页时会把那一页印出来,因为「1 … 3」比「1 2 3」还长,却告诉读者更少。三个分页器就是三个 nav 地标,所以这里每一个都命了名。
At the first page
In the middle, elided on both sides
At the last page
a wider window
siblings 是当前页两侧各排几个带编号的页。要往上调,别往下调:在总页数低于 2 × siblings + 5 时,每一页反正都会印出来,所以这个属性在短列表上什么都不做,而在长列表上它是你唯一的杠杆。这里两个分页器共用同一个 page,所以点任何一个,两个都动。更宽的窗口只是同样小尺寸的目标更多了,不是更好了——那些药丸是 --control-h-sm,宽松下 36px、紧凑密度下 30px,远低于 WCAG 2.5.5 对指针目标要求的 44px。
One sibling, the default
Two siblings
组成部分
在调用处组合,而不是通过 props 配置——所以这个组件没有预料到的布局,依然表达得出来。
paginationRange
算出可见的页码列表:首页和末页总是在,当前页周围开一个窗口,序列有跳跃的地方放一个省略号。
返回的是数字和一个字面量省略号,而不是渲染好的节点,这样不用 DOM 就能测——窗口两端的差一错误正是这个组件的全部难点。
这个组件没有自己的属性。
键盘操作
| 按键 | 作用 |
|---|---|
| Tab | 能走到每一个控件,包括当前页。 |
| EnterSpace | 跳到那一页。 |
无障碍
- 当前页是带 aria-current 的按钮,不是加了样式的 span——按控件跳转的读者需要能找到它。
- 只有一页时什么都不渲染。为一页做的分页器是摆设。
- 读者听到的每一个字符串都是属性:两个尖角各有自己的名字,每一个页码走 pageLabel——它是一个函数而不是一个模板,因为「Page 3」是一个各部分会随语言挪位置的短语。