玻璃分页

pagination · glass-apple · 灵感来源:Carbon Pagination / GOV.UK pagination

多页:首尾固定,中间收成省略号

当前第 4 页

少页:全部铺开,首尾按钮到端禁用

当前第 1 页

复用卡

Pagination · 玻璃分页 · 分页 / Glass

规格 · 术语提示词(给 AI 用)

做一个 Pagination(玻璃分页):
- nav aria-label + ul/li;当前页 aria-current=page;每个页码 aria-label=第 n 页
- 页码窗口:≤7 页全显示,更多时固定首尾、当前左右各 1,中间省略号不进 Tab 序列
- 首/末页时上/下一页禁用而不隐藏,按钮位置稳定
- 不做每页条数选择器——那是数据表的职责
用这些 CSS 变量:--color-bg --color-surface --color-text --color-text-muted --color-text-subtle --glass-border --glass-highlight
灵感参考:Carbon Pagination / GOV.UK pagination

参数(CSS 变量)

--color-bg --color-surface --color-text --color-text-muted --color-text-subtle --glass-border --glass-highlight

必缺状态

焦点 / 禁用

整页场景

安装(一条链接)

调研笔记

玻璃分页

  • 语义:nav aria-label="分页";当前页 aria-current="page";每个页码有 aria-label="第 n 页"
  • 页码窗口:总页数 ≤7 全显示;更多时固定首尾、当前左右各 1,中间用省略号(省略号不进 Tab 序列)
  • 首/末页时上/下一页禁用而不隐藏:按钮位置稳定,键盘用户不会因为少一个按钮而跳位
  • 不做每页条数选择器——那是数据表的职责,需要时由消费方补

实现

pagination/glass-apple
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Icon } from '~/components/Icon';

interface Props {
  /** 从 1 开始 */
  page: number;
  pageCount: number;
  onChange: (page: number) => void;
  className?: string;
}

/** 页码窗口:总页数 ≤7 全显示,否则首尾 + 当前左右各 1,中间用省略号 */
function pageItems(page: number, pageCount: number): (number | 'gap')[] {
  if (pageCount <= 7) return Array.from({ length: pageCount }, (_, i) => i + 1);
  const items: (number | 'gap')[] = [1];
  const start = Math.max(2, page - 1);
  const end = Math.min(pageCount - 1, page + 1);
  if (start > 2) items.push('gap');
  for (let p = start; p <= end; p += 1) items.push(p);
  if (end < pageCount - 1) items.push('gap');
  items.push(pageCount);
  return items;
}

export function GlassApplePagination({ page, pageCount, onChange, className = '' }: Props) {
  const go = (next: number) => {
    const target = Math.min(Math.max(1, next), pageCount);
    if (target !== page) onChange(target);
  };

  const base = `
    press-feedback grid place-items-center min-w-8 h-8 px-2 rounded-lg text-xs tabular-nums
    border transition-colors cursor-pointer
    disabled:opacity-40 disabled:cursor-not-allowed
  `;

  return (
    <nav aria-label="分页" className={className}>
      <ul className="flex items-center gap-1">
        <li>
          <button
            type="button"
            onClick={() => go(page - 1)}
            disabled={page <= 1}
            aria-label="上一页"
            className={`${base} border-[var(--glass-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)] hover:text-[var(--color-text)] hover:bg-[var(--glass-highlight)]`}
          >
            <Icon icon={ChevronLeft} size={13} />
          </button>
        </li>

        {pageItems(page, pageCount).map((item, i) =>
          item === 'gap' ? (
            <li key={`gap-${i}`} aria-hidden="true" className="px-1 text-xs text-[var(--color-text-subtle)]">
              …
            </li>
          ) : (
            <li key={item}>
              <button
                type="button"
                onClick={() => go(item)}
                aria-current={item === page ? 'page' : undefined}
                aria-label={`第 ${item} 页`}
                className={`
                  ${base}
                  ${item === page
                    ? 'border-transparent bg-[var(--color-text)] text-[var(--color-bg)]'
                    : 'border-[var(--glass-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)] hover:text-[var(--color-text)] hover:bg-[var(--glass-highlight)]'}
                `}
              >
                {item}
              </button>
            </li>
          ),
        )}

        <li>
          <button
            type="button"
            onClick={() => go(page + 1)}
            disabled={page >= pageCount}
            aria-label="下一页"
            className={`${base} border-[var(--glass-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)] hover:text-[var(--color-text)] hover:bg-[var(--glass-highlight)]`}
          >
            <Icon icon={ChevronRight} size={13} />
          </button>
        </li>
      </ul>
    </nav>
  );
}

元信息

标识
pagination/glass-apple
状态
done
最后提交
2026-11-01
风格轴
glass-apple
依赖 token
--color-bg --color-surface --color-text --color-text-muted --color-text-subtle --glass-border --glass-highlight
在工作台打开 源码与 notes 都在 src/components/pagination/glass-apple/