fix: refine Today mobile hierarchy
This commit is contained in:
@@ -51,8 +51,10 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
})).toEqual({
|
||||
media720: width <= 720,
|
||||
contentWidth: width >= 1440 ? 1080 : width === 721 ? 630 : width - 58,
|
||||
environmentHeight: width >= 721 ? 55 : width === 375 ? 90 : 93,
|
||||
environmentHeight: width >= 721 ? 55 : width === 375 ? 79 : 81,
|
||||
})
|
||||
await expect(page.locator('.today-heading')).toHaveCount(1)
|
||||
await expect(page.locator('.today-inline-add')).toHaveCount(1)
|
||||
await expect(taskRowLocator).toHaveCount(1)
|
||||
await expect(habitRowLocator).toHaveCount(1)
|
||||
await expect(page.locator('.today-environment')).toBeVisible()
|
||||
@@ -69,6 +71,8 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
const remaining = document.querySelector<HTMLElement>('.today-remaining')!
|
||||
const filter = document.querySelector<HTMLElement>('.today-inline-filter')!
|
||||
const firstSection = document.querySelector<HTMLElement>('.today-section-toggle')!
|
||||
const heading = document.querySelector<HTMLElement>('.today-heading')!
|
||||
const inlineAdd = document.querySelector<HTMLElement>('.today-inline-add')!
|
||||
const main = document.querySelector<HTMLElement>('main.today-main')!
|
||||
const content = rect('.today-context')
|
||||
const sectionTitle = firstSection.querySelector<HTMLElement>('.today-section-title')!
|
||||
@@ -84,8 +88,8 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
}))
|
||||
const taskRow = document.querySelector<HTMLElement>(`#today-tasks .task-row[data-task-id="${taskId}"]`)!
|
||||
const habitRow = document.querySelector<HTMLElement>(`.today-habit-row[data-habit-id="${habitId}"]`)!
|
||||
const fab = document.querySelector<HTMLElement>('.unified-fab')!
|
||||
const orderedElements = [environment, title, remaining, filter, firstSection]
|
||||
const fab = document.querySelector<HTMLElement>('.unified-fab')
|
||||
const orderedElements = [environment, heading, firstSection]
|
||||
const ordered = orderedElements.map((element) => {
|
||||
const box = element.getBoundingClientRect()
|
||||
return { left: box.left, right: box.right, top: box.top, bottom: box.bottom, width: box.width, height: box.height }
|
||||
@@ -136,6 +140,15 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
summaries,
|
||||
styles: {
|
||||
mainPaddingLeft: getComputedStyle(main).paddingLeft,
|
||||
contextHeight: content.height,
|
||||
headingDisplay: getComputedStyle(heading).display,
|
||||
headingAlignItems: getComputedStyle(heading).alignItems,
|
||||
filterTop: filter.getBoundingClientRect().top,
|
||||
filterRight: filter.getBoundingClientRect().right,
|
||||
inlineAddPosition: getComputedStyle(inlineAdd).position,
|
||||
inlineAddWidth: inlineAdd.getBoundingClientRect().width,
|
||||
inlineAddBottom: inlineAdd.getBoundingClientRect().bottom,
|
||||
viewportHeight: innerHeight,
|
||||
titleFontSize: getComputedStyle(title).fontSize,
|
||||
titleFontWeight: getComputedStyle(title).fontWeight,
|
||||
filterWidth: filter.getBoundingClientRect().width,
|
||||
@@ -154,8 +167,8 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
habitHeight: habitRow.getBoundingClientRect().height,
|
||||
habitBackground: getComputedStyle(habitRow).backgroundColor,
|
||||
habitBorderBottom: getComputedStyle(habitRow).borderBottomWidth,
|
||||
fabShadow: getComputedStyle(fab).boxShadow,
|
||||
fabIconWidth: fab.querySelector('svg')?.getBoundingClientRect().width,
|
||||
fabShadow: fab ? getComputedStyle(fab).boxShadow : '',
|
||||
fabIconWidth: fab?.querySelector('svg')?.getBoundingClientRect().width ?? 0,
|
||||
},
|
||||
}
|
||||
}, { taskId, habitId })
|
||||
@@ -188,8 +201,18 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
expect(metrics.styles.filterVisualHeight).toBeLessThanOrEqual(36)
|
||||
expect(metrics.styles.filterBackground).toBe('rgba(0, 0, 0, 0)')
|
||||
expect(metrics.styles.filterBorderRadius).toBe('0px')
|
||||
expect(metrics.styles.fabShadow).toContain('8px 18px')
|
||||
expect(metrics.styles.fabIconWidth).toBeCloseTo(30, 0)
|
||||
expect(metrics.styles.headingDisplay).toBe('grid')
|
||||
expect(metrics.styles.headingAlignItems).toBe('center')
|
||||
expect(metrics.styles.filterTop).toBeGreaterThanOrEqual(metrics.ordered[1].top)
|
||||
expect(metrics.styles.filterTop).toBeLessThanOrEqual(metrics.ordered[1].bottom - metrics.styles.filterHeight)
|
||||
expect(metrics.styles.filterRight).toBeCloseTo(metrics.content.right, 0)
|
||||
expect(metrics.styles.inlineAddPosition).toBe('relative')
|
||||
expect(metrics.styles.inlineAddWidth).toBeCloseTo(metrics.content.width, 0)
|
||||
expect(metrics.styles.inlineAddBottom).toBeGreaterThanOrEqual(metrics.ordered[metrics.ordered.length - 1].bottom)
|
||||
expect(metrics.styles.inlineAddBottom).toBeLessThanOrEqual(metrics.styles.viewportHeight + 1)
|
||||
if (width <= 720) expect(metrics.styles.contextHeight).toBeLessThanOrEqual(210)
|
||||
expect(metrics.styles.fabShadow).toBe('')
|
||||
expect(metrics.styles.fabIconWidth).toBe(0)
|
||||
if (width >= 721) {
|
||||
expect(metrics.uniqueRows).toHaveLength(1)
|
||||
expect(metrics.content.width).toBeCloseTo(width >= 1440 ? 1080 : 630, 0)
|
||||
@@ -214,7 +237,7 @@ test('Today plain-list layout matches the approved responsive geometry', async (
|
||||
expect(metrics.content.left).toBeCloseTo(29, 0)
|
||||
expect(metrics.styles.mainPaddingLeft).toBe('29px')
|
||||
expect(metrics.styles.titleFontSize).toBe('24px')
|
||||
expect(metrics.environment.height).toBeCloseTo(width === 375 ? 90 : 93, 0)
|
||||
expect(metrics.environment.height).toBeCloseTo(width === 375 ? 79 : 81, 0)
|
||||
}
|
||||
}
|
||||
if (width >= 721) {
|
||||
|
||||
@@ -1708,9 +1708,10 @@ onUnmounted(() => {
|
||||
<template v-else>
|
||||
<section v-if="activeView==='today'" class="today-context" aria-label="今日概览">
|
||||
<TodayEnvironmentStrip :environment="todayEnvironment" :loading="todayEnvironmentLoading" :failed="todayEnvironmentError" />
|
||||
<h1 class="today-page-title">今天</h1>
|
||||
<p class="today-remaining">还有 {{ todayTaskRemaining + todayHabitRemaining }} 项待完成</p>
|
||||
<CompletedFilterPill v-model="showCompleted" class="today-inline-filter" />
|
||||
<div class="today-heading">
|
||||
<div><h1 class="today-page-title">今天</h1><p class="today-remaining">还有 {{ todayTaskRemaining + todayHabitRemaining }} 项待完成</p></div>
|
||||
<CompletedFilterPill v-model="showCompleted" class="today-inline-filter" />
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="activeView==='tasks'" class="list-page-context">
|
||||
<div><h1 class="list-page-title" :title="activeName">{{ activeName }}</h1><p class="list-page-summary">{{ taskOpenTotal === null ? '待完成统计暂不可用' : `还有 ${taskOpenTotal} 项待完成` }}</p></div>
|
||||
@@ -1755,6 +1756,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<button v-if="activeView==='today' && showFloatingAdd" class="today-inline-add" type="button" aria-label="添加任务" @click="openTaskCompose"><Plus/><span>添加任务</span></button>
|
||||
</main>
|
||||
|
||||
<AppSheet v-if="selectedTask" :open="true" :modal="compactLayout" variant="detail" panel-class="detail" title-id="task-detail-title" :close-on-scrim="compactLayout" :busy="taskDetailBusy" @close="closeTaskDetail" @submit.prevent="saveSelectedTaskChanges">
|
||||
@@ -1789,7 +1791,7 @@ onUnmounted(() => {
|
||||
</AppSheet>
|
||||
|
||||
<nav class="bottom" :inert="memoBackgroundInert ? true : undefined" aria-label="主要导航"><button :class="{active:activeView==='today'}" :aria-current="activeView==='today' ? 'page' : undefined" @click="switchView('today')"><ListTodo/><span>今天</span></button><button :class="{active:activeView==='habits'}" :aria-current="activeView==='habits' ? 'page' : undefined" @click="switchView('habits')"><Repeat2/><span>习惯</span></button><button :class="{active:activeView==='countdowns'}" :aria-current="activeView==='countdowns' ? 'page' : undefined" @click="switchView('countdowns')"><CalendarHeart/><span>倒数日</span></button><button :class="{active:activeView==='settings'}" :aria-current="activeView==='settings' ? 'page' : undefined" @click="switchView('settings')"><Settings/><span>设置</span></button></nav>
|
||||
<FloatingAddButton v-if="['tasks','today','upcoming','habits','countdowns','memos'].includes(activeView)" :show="showFloatingAdd" :label="activeView==='habits' ? '添加习惯' : activeView==='countdowns' ? '添加倒数日' : activeView==='memos' ? '添加备忘录' : '添加任务'" @activate="activateFloatingAdd" />
|
||||
<FloatingAddButton v-if="activeView!=='today' && ['tasks','upcoming','habits','countdowns','memos'].includes(activeView)" :show="showFloatingAdd" :label="activeView==='habits' ? '添加习惯' : activeView==='countdowns' ? '添加倒数日' : activeView==='memos' ? '添加备忘录' : '添加任务'" @activate="activateFloatingAdd" />
|
||||
<AppSheet :open="taskComposeOpen" variant="create" panel-class="task-compose-sheet" title-id="task-compose-title" initial-focus=".task-compose-input" :style="taskComposeStyle" @close="closeTaskCompose" @submit.prevent="submitTaskCompose">
|
||||
<header class="app-sheet__header"><div><h2 id="task-compose-title">{{ taskComposeTitle }}</h2></div><button class="icon" type="button" aria-label="关闭添加任务" @click="closeTaskCompose"><X/></button></header>
|
||||
<div class="app-sheet__body">
|
||||
|
||||
@@ -49,6 +49,7 @@ describe('Today environment integration', () => {
|
||||
it('renders the approved plain-list composition without a board or progress tracks', () => {
|
||||
expect(app).toContain("<main :class=\"{'today-main':activeView==='today','list-main':activeView==='tasks'||activeView==='habits'}\"")
|
||||
expect(app).toContain('<section v-if="activeView===\'today\'" class="today-context" aria-label="今日概览">')
|
||||
expect(app).toContain('<div class="today-heading">')
|
||||
expect(app).toContain('<p class="today-remaining">还有 {{ todayTaskRemaining + todayHabitRemaining }} 项待完成</p>')
|
||||
expect(app).not.toContain('class="today-board"')
|
||||
expect(app).not.toContain('today-board-title')
|
||||
@@ -57,29 +58,34 @@ describe('Today environment integration', () => {
|
||||
expect(app).not.toContain('today-habit-track')
|
||||
expect(app).not.toContain('role="progressbar"')
|
||||
expect(app).toContain("'today-active': activeView==='today'")
|
||||
expect(css).toContain('.shell.today-active .unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}')
|
||||
expect(css).not.toContain('\n.unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}')
|
||||
expect(app).toContain('class="today-inline-add"')
|
||||
expect(app).toContain("activeView!=='today' && ['tasks','upcoming','habits','countdowns','memos'].includes(activeView)")
|
||||
expect(css).toContain('.today-inline-add{')
|
||||
expect(css).toContain('position:relative')
|
||||
expect(css).toContain('main.today-main{padding-left:max(44px,calc((100% - 1080px)/2));padding-right:max(44px,calc((100% - 1080px)/2))}')
|
||||
expect(css).toContain('.today-environment{grid-template-columns:repeat(3,minmax(0,1fr));')
|
||||
expect(css).toContain('@media(max-width:930px){main.today-main{padding-left:max(44px,calc((100% - 630px)/2));padding-right:max(44px,calc((100% - 630px)/2))}}')
|
||||
expect(css).toContain('@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important}')
|
||||
expect(css).toContain('@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important;padding-bottom:calc(78px + var(--safe-area-bottom))}')
|
||||
expect(css).toContain('.today-context{margin:0 0 14px;border-bottom:0}')
|
||||
expect(css).toContain('.today-remaining{margin:0 0 24px;color:var(--muted);font-size:13px}')
|
||||
expect(css).toContain('.today-heading{display:grid;')
|
||||
expect(css).toContain('.today-heading .today-remaining{margin:6px 0 0}')
|
||||
expect(css).not.toContain('.today-board{')
|
||||
expect(css).not.toContain('.today-board__progress{')
|
||||
expect(css).not.toContain('.today-track{')
|
||||
expect(css).not.toContain('.today-track-rail{')
|
||||
})
|
||||
|
||||
it('renders the approved plain-list DOM in prototype order without duplicating the Today title', () => {
|
||||
it('renders the approved plain-list DOM with the Today title and filter in one heading row', () => {
|
||||
const main = app.slice(app.indexOf('<main :class='), app.indexOf('</main>'))
|
||||
const environment = main.indexOf('<TodayEnvironmentStrip')
|
||||
const heading = main.indexOf('<div class="today-heading">')
|
||||
const title = main.indexOf('<h1 class="today-page-title">今天</h1>')
|
||||
const remaining = main.indexOf('<p class="today-remaining">')
|
||||
const filter = main.indexOf('<CompletedFilterPill v-model="showCompleted" class="today-inline-filter" />')
|
||||
const overdue = main.indexOf('id="today-overdue-heading"')
|
||||
expect(environment).toBeGreaterThan(-1)
|
||||
expect(title).toBeGreaterThan(environment)
|
||||
expect(heading).toBeGreaterThan(environment)
|
||||
expect(title).toBeGreaterThan(heading)
|
||||
expect(remaining).toBeGreaterThan(title)
|
||||
expect(filter).toBeGreaterThan(remaining)
|
||||
expect(overdue).toBeGreaterThan(filter)
|
||||
|
||||
+12
-3
@@ -152,7 +152,16 @@ main.list-main>.mvp-view>.habit-archive-section{border-top:1px solid #e8e0d5}
|
||||
@media(max-width:720px){main.list-main{padding-left:29px;padding-right:29px}main.list-main>.list-page-context,main.list-main>.list-search-reveal,main.list-main>.list-section-heading,main.list-main>.task-list,main.list-main>.list-page-meta,main.list-main>.pager,main.list-main>.mvp-view{width:100%}.list-page-title{font-size:24px}.list-search-reveal>.task-search-toggle{display:grid;position:relative;justify-self:end}.list-search-reveal:not(.mobile-search-open){min-height:0}.list-search-reveal:not(.mobile-search-open)>.list-search,.list-search-reveal:not(.mobile-search-open)>.list-search-clear{display:none}.list-search kbd{display:none}}
|
||||
|
||||
/* Approved Today plain-list prototype parity. */
|
||||
main.today-main .topbar{margin-bottom:18px}
|
||||
main.today-main .topbar{margin-bottom:12px}
|
||||
.today-heading{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:16px;margin-top:20px}
|
||||
.today-heading>div{min-width:0}
|
||||
.today-heading .today-page-title{margin:0}
|
||||
.today-heading .today-remaining{margin:6px 0 0}
|
||||
.today-heading .today-inline-filter{align-self:center;justify-self:end}
|
||||
.today-inline-add{position:relative;width:100%;height:44px;margin:18px auto 0;display:flex;align-items:center;justify-content:center;gap:7px;border:1px solid #dfc9b2;border-radius:11px;background:var(--surface-raised);color:var(--accent);box-shadow:var(--highlight-inner),0 5px 16px rgba(88,67,42,.1);font-size:13px;font-weight:700}
|
||||
.today-inline-add svg{width:17px;height:17px}
|
||||
.today-inline-add:active{transform:scale(.985)}
|
||||
.today-inline-add:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}
|
||||
.today-environment{grid-template-columns:repeat(3,minmax(0,1fr));align-items:center;gap:0;padding:0 0 18px;border-bottom:1px solid #e8e0d5;color:#8a8178}
|
||||
.today-environment__item{height:36px;padding:0 0 0 18px;justify-content:flex-start}
|
||||
.today-environment__item:first-child{padding-left:0}
|
||||
@@ -166,8 +175,8 @@ main.today-main .topbar{margin-bottom:18px}
|
||||
.today-main .task-tail{padding-left:12px;font-size:12px}.today-main .habit-row{gap:0}.today-main .habit-main{display:flex;align-items:center;min-width:0;padding:0}
|
||||
.shell.today-active .unified-fab{box-shadow:0 8px 18px rgba(174,65,29,.22)}.shell.today-active .unified-fab>svg{width:30px;height:30px;stroke-width:1.5}
|
||||
@media(max-width:930px){main.today-main{padding-left:max(44px,calc((100% - 630px)/2));padding-right:max(44px,calc((100% - 630px)/2))}}
|
||||
@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important}.today-environment{grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:21px auto;align-items:stretch;column-gap:0;row-gap:11px;padding-bottom:14px}.today-environment__calendar{grid-column:1/-1;grid-row:1;height:21px!important;padding:0;justify-content:space-between;align-items:start;border-left:0}.today-environment__weather,.today-environment__gold{grid-row:2;height:clamp(42px,calc(25vw - 51.75px),45.75px);padding-top:10px;border-top:1px solid #e8e0d5}.today-environment__weather{grid-column:1;padding-left:0;padding-right:10px;border-left:0}.today-environment__gold{grid-column:2;padding-left:12px}.today-environment__weather strong,.today-environment__gold strong,.today-environment__gold-primary{line-height:16px}.today-environment__item small{line-height:13px}.today-page-title{margin-top:23px;font-size:24px}.today-main .task-row,.today-main .habit-row{height:58px;min-height:58px;max-height:58px}.today-context .completed-filter-pill{width:87px;height:44px;min-height:44px}.today-context .completed-filter-pill__track{width:30px;height:18px}.today-context .completed-filter-pill__thumb{width:14px;height:14px}.today-context .completed-filter-pill[aria-checked="true"] .completed-filter-pill__thumb{transform:translateX(12px)}}
|
||||
@media(max-width:380px){.today-environment__weather,.today-environment__gold{height:43px;padding-top:7px;padding-bottom:7px}}
|
||||
@media(max-width:720.98px){main.today-main{padding-left:29px!important;padding-right:29px!important;padding-bottom:calc(78px + var(--safe-area-bottom))}.today-environment{grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:21px auto;align-items:stretch;column-gap:0;row-gap:7px;padding-bottom:10px}.today-environment__calendar{grid-column:1/-1;grid-row:1;height:21px!important;padding:0;justify-content:space-between;align-items:start;border-left:0}.today-environment__weather,.today-environment__gold{grid-row:2;height:clamp(38px,calc(25vw - 55.75px),42px);padding-top:7px;border-top:1px solid #e8e0d5}.today-environment__weather{grid-column:1;padding-left:0;padding-right:10px;border-left:0}.today-environment__gold{grid-column:2;padding-left:12px}.today-environment__weather strong,.today-environment__gold strong,.today-environment__gold-primary{line-height:16px}.today-environment__item small{line-height:13px}.today-heading{margin-top:16px}.today-page-title{font-size:24px}.today-heading .today-remaining{margin-top:4px}.today-main .task-row,.today-main .habit-row{height:58px;min-height:58px;max-height:58px}.today-context .completed-filter-pill{width:87px;height:44px;min-height:44px}.today-context .completed-filter-pill__track{width:30px;height:18px}.today-context .completed-filter-pill__thumb{width:14px;height:14px}.today-context .completed-filter-pill[aria-checked="true"] .completed-filter-pill__thumb{transform:translateX(12px)}.today-inline-add{margin-top:14px}}
|
||||
@media(max-width:380px){.today-environment__weather,.today-environment__gold{height:40px;padding-top:5px;padding-bottom:5px}}
|
||||
|
||||
/* Approved Settings 01: continuous paper ledger. */
|
||||
main:has(>.mvp-view .settings-sections){background:#fffdf8}
|
||||
|
||||
Reference in New Issue
Block a user