feat: redesign task pagination controls
This commit is contained in:
@@ -39,17 +39,49 @@ test('task pagination stays below the list and returns to the list start after n
|
||||
const pager = page.locator('.pager')
|
||||
await expect(taskList.locator('.task-row')).toHaveCount(50)
|
||||
await expect(pager).toBeVisible()
|
||||
await expect(pager).toContainText('1 / 2')
|
||||
await expect(pager.locator('.pager-status')).toHaveText('1/ 2')
|
||||
|
||||
const firstPageGeometry = await page.evaluate(() => {
|
||||
const list = document.querySelector<HTMLElement>('.task-list')!.getBoundingClientRect()
|
||||
const pagerBox = document.querySelector<HTMLElement>('.pager')!.getBoundingClientRect()
|
||||
return { listBottom: list.bottom, pagerTop: pagerBox.top }
|
||||
const pagerElement = document.querySelector<HTMLElement>('.pager')!
|
||||
const pagerBox = pagerElement.getBoundingClientRect()
|
||||
const previous = pagerElement.querySelector<HTMLButtonElement>('.pager-button--previous')!
|
||||
const next = pagerElement.querySelector<HTMLButtonElement>('.pager-button--next')!
|
||||
const status = pagerElement.querySelector<HTMLElement>('.pager-status')!
|
||||
const previousBox = previous.getBoundingClientRect()
|
||||
const nextBox = next.getBoundingClientRect()
|
||||
const statusBox = status.getBoundingClientRect()
|
||||
const pagerCenter = pagerBox.left + pagerBox.width / 2
|
||||
return {
|
||||
listBottom: list.bottom,
|
||||
pagerTop: pagerBox.top,
|
||||
previousWidth: previousBox.width,
|
||||
nextWidth: nextBox.width,
|
||||
previousHeight: previousBox.height,
|
||||
nextHeight: nextBox.height,
|
||||
statusCenterOffset: Math.abs(statusBox.left + statusBox.width / 2 - pagerCenter),
|
||||
rootScrollWidth: document.documentElement.scrollWidth,
|
||||
viewportWidth: innerWidth,
|
||||
}
|
||||
})
|
||||
expect(firstPageGeometry.pagerTop).toBeGreaterThanOrEqual(firstPageGeometry.listBottom)
|
||||
expect(Math.abs(firstPageGeometry.previousWidth - firstPageGeometry.nextWidth)).toBeLessThanOrEqual(1)
|
||||
expect(firstPageGeometry.previousHeight).toBeGreaterThanOrEqual(44)
|
||||
expect(firstPageGeometry.nextHeight).toBeGreaterThanOrEqual(44)
|
||||
expect(firstPageGeometry.statusCenterOffset).toBeLessThanOrEqual(1)
|
||||
expect(firstPageGeometry.rootScrollWidth).toBeLessThanOrEqual(firstPageGeometry.viewportWidth)
|
||||
|
||||
await pager.getByRole('button', { name: '下一页' }).click()
|
||||
await expect(pager).toContainText('2 / 2')
|
||||
const nextButton = pager.getByRole('button', { name: '下一页' })
|
||||
await page.locator('body').click({ position: { x: 1, y: 1 } })
|
||||
for (let index = 0; index < 300; index += 1) {
|
||||
await page.keyboard.press('Tab')
|
||||
if (await nextButton.evaluate(element => element === document.activeElement)) break
|
||||
}
|
||||
await expect(nextButton).toBeFocused()
|
||||
const focusOutline = await nextButton.evaluate(element => getComputedStyle(element).outlineStyle)
|
||||
expect(focusOutline).not.toBe('none')
|
||||
await nextButton.click()
|
||||
await expect(pager.locator('.pager-status')).toHaveText('2/ 2')
|
||||
await expect(taskList.locator('.task-row')).toHaveCount(1)
|
||||
|
||||
const secondPageGeometry = await page.evaluate(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import {
|
||||
ArchiveRestore, Bold, CalendarDays, CalendarHeart, Check, ChevronDown, ChevronRight, Code, Folder,
|
||||
ArchiveRestore, Bold, CalendarDays, CalendarHeart, Check, ChevronDown, ChevronLeft, ChevronRight, Code, Folder,
|
||||
Ellipsis, GripVertical, Heading2, Inbox, Italic, Link, List, ListChecks, ListOrdered, ListTodo, Menu, Pencil, Plus, Quote,
|
||||
Settings, Trash2, X, Repeat2, StickyNote,
|
||||
} from 'lucide-vue-next'
|
||||
@@ -1746,7 +1746,7 @@ onUnmounted(() => {
|
||||
<div v-else-if="!visibleTasks.length&&!loading" class="empty"><ListTodo/><b>{{ hiddenCompletedTaskCount > 0 ? '已完成任务已隐藏' : '这里还很安静' }}</b><span>{{hiddenCompletedTaskCount > 0 ? '开启“显示已完成”即可查看。':'写下第一件想完成的小事吧'}}</span></div>
|
||||
</section>
|
||||
<div v-if="activeView==='tasks' && totalPages > 1" class="list-page-meta" aria-live="polite"><span>第 {{ page }} / {{ totalPages }} 页 · 共 {{ totalTasks }} 项</span></div>
|
||||
<div v-if="totalPages > 1 && (activeView!=='today' || !todaySectionCollapse.tasks)" class="pager"><button class="secondary" :disabled="page<=1 || loading" @click="previousPage">上一页</button><span aria-live="polite">{{page}} / {{totalPages}}</span><button class="secondary" :disabled="page>=totalPages || loading" @click="nextPage">下一页</button></div>
|
||||
<nav v-if="totalPages > 1 && (activeView!=='today' || !todaySectionCollapse.tasks)" class="pager" aria-label="任务分页"><button class="pager-button pager-button--previous" :disabled="page<=1 || loading" aria-label="上一页" @click="previousPage"><ChevronLeft aria-hidden="true"/><span>上一页</span></button><span class="pager-status" aria-live="polite"><strong>{{page}}</strong><span>/ {{totalPages}}</span></span><button class="pager-button pager-button--next" :disabled="page>=totalPages || loading" aria-label="下一页" @click="nextPage"><span>下一页</span><ChevronRight aria-hidden="true"/></button></nav>
|
||||
<div v-if="activeView==='today'" class="today-habits-section today-section-anchor">
|
||||
<button id="today-habits-heading" class="today-section-toggle" type="button" :aria-expanded="!todaySectionCollapse.habits" aria-controls="today-habits" @click="toggleTodaySection('habits')"><span class="today-section-title">习惯</span><span class="today-section-summary">{{todayHabitTotal}}</span><span class="today-section-chevron" aria-hidden="true">{{ todaySectionCollapse.habits ? '›' : '⌄' }}</span></button>
|
||||
<div v-show="!todaySectionCollapse.habits" id="today-habits" role="region" aria-labelledby="today-habits-heading">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -88,7 +88,16 @@ describe('approved five-detail polish', () => {
|
||||
expect(app).toContain("totalPages > 1 && (activeView!=='today' || !todaySectionCollapse.tasks)")
|
||||
expect(app).toContain("taskListElement.value?.scrollIntoView({ block: 'start' })")
|
||||
expect(app).toContain('class="list-page-meta" aria-live="polite"')
|
||||
expect(app).toContain('<span aria-live="polite">{{page}} / {{totalPages}}</span>')
|
||||
expect(app).toContain('<nav v-if="totalPages > 1 && (activeView!==\'today\' || !todaySectionCollapse.tasks)" class="pager" aria-label="任务分页">')
|
||||
expect(app).toContain('class="pager-button pager-button--previous"')
|
||||
expect(app).toContain('<ChevronLeft aria-hidden="true"/><span>上一页</span>')
|
||||
expect(app).toContain('<span class="pager-status" aria-live="polite"><strong>{{page}}</strong><span>/ {{totalPages}}</span></span>')
|
||||
expect(app).toContain('class="pager-button pager-button--next"')
|
||||
expect(app).toContain('<span>下一页</span><ChevronRight aria-hidden="true"/>')
|
||||
expect(css).toContain('.pager{min-height:52px;padding:4px;display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:4px;margin:0 0 14px;border:1px solid #e8e0d5;border-radius:15px;background:#f7f1e7;box-shadow:inset 0 1px 0 rgba(255,255,255,.88),0 4px 12px rgba(92,67,40,.05)}')
|
||||
expect(css).toContain('.pager-button{min-width:0;min-height:44px;padding:0 14px;display:flex;align-items:center;justify-content:center;gap:7px;border:1px solid transparent;border-radius:11px;background:#fffdf8;color:#514a40;font-size:13px;font-weight:650;box-shadow:inset 0 1px 0 #fff,0 1px 3px rgba(92,67,40,.08);transition:transform .14s ease,border-color .14s ease,background-color .14s ease,color .14s ease,box-shadow .14s ease}')
|
||||
expect(css).toContain('.pager-status{min-width:0;width:62px;display:flex;align-items:baseline;justify-content:center;gap:4px;color:var(--muted);font-size:12px;font-variant-numeric:tabular-nums}')
|
||||
expect(css).toContain('@media(max-width:390px){.pager-button{padding:0 10px}.pager{grid-template-columns:minmax(0,1fr) 56px minmax(0,1fr)}.pager-status{width:56px}}')
|
||||
expect(app).toContain("if (activeView.value === 'upcoming') { openParams.set('due_from', isoAtLocalDayOffset(0)); openParams.set('due_to', isoAtLocalDayOffset(8)) }")
|
||||
expect(app).toContain("new Date(task.due_at) >= startOfLocalDay(0) && new Date(task.due_at) < startOfLocalDay(8)")
|
||||
expect(app).not.toContain('class="list-search-clear"')
|
||||
|
||||
Reference in New Issue
Block a user