fix: compact mobile bottom navigation
This commit is contained in:
@@ -38,7 +38,7 @@ test('bottom navigation uses the paper baseline geometry', async ({ page }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
expect(metrics.documentOverflow).toBe(0)
|
expect(metrics.documentOverflow).toBe(0)
|
||||||
expect(metrics.nav).toEqual({ left: 0, right: metrics.viewportWidth, bottom: page.viewportSize()!.height, width: metrics.viewportWidth, height: 64 })
|
expect(metrics.nav).toEqual({ left: 0, right: metrics.viewportWidth, bottom: page.viewportSize()!.height, width: metrics.viewportWidth, height: 56 })
|
||||||
expect(Math.max(...metrics.buttonWidths) - Math.min(...metrics.buttonWidths)).toBeLessThanOrEqual(1)
|
expect(Math.max(...metrics.buttonWidths) - Math.min(...metrics.buttonWidths)).toBeLessThanOrEqual(1)
|
||||||
expect(metrics.buttonHeights.every(height => height >= 44)).toBeTruthy()
|
expect(metrics.buttonHeights.every(height => height >= 44)).toBeTruthy()
|
||||||
expect(metrics.background).toBe('rgb(255, 253, 248)')
|
expect(metrics.background).toBe('rgb(255, 253, 248)')
|
||||||
@@ -70,11 +70,11 @@ test('bottom navigation keeps its safe-area gap after dragging', async ({ page }
|
|||||||
const nav = page.locator('.bottom')
|
const nav = page.locator('.bottom')
|
||||||
await expect(nav).toBeVisible()
|
await expect(nav).toBeVisible()
|
||||||
await page.locator('html').evaluate(element => element.style.setProperty('--safe-area-bottom', '34px', 'important'))
|
await page.locator('html').evaluate(element => element.style.setProperty('--safe-area-bottom', '34px', 'important'))
|
||||||
await expect.poll(() => nav.evaluate(element => getComputedStyle(element).height)).toBe('98px')
|
await expect.poll(() => nav.evaluate(element => getComputedStyle(element).height)).toBe('90px')
|
||||||
|
|
||||||
const navBox = await nav.boundingBox()
|
const navBox = await nav.boundingBox()
|
||||||
expect(navBox).not.toBeNull()
|
expect(navBox).not.toBeNull()
|
||||||
expect(navBox!.height).toBe(98)
|
expect(navBox!.height).toBe(90)
|
||||||
|
|
||||||
const fab = page.locator('.unified-fab')
|
const fab = page.locator('.unified-fab')
|
||||||
const fabBox = await fab.boundingBox()
|
const fabBox = await fab.boundingBox()
|
||||||
|
|||||||
@@ -202,10 +202,10 @@ describe('MVP view utilities', () => {
|
|||||||
expect(clampFabPosition(120, 300, 390, 844)).toEqual({ x: 120, y: 300 })
|
expect(clampFabPosition(120, 300, 390, 844)).toEqual({ x: 120, y: 300 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('reserves the paper navigation, safe area, and gap below a dragged add button', () => {
|
it('reserves the compact paper navigation, safe area, and gap below a dragged add button', () => {
|
||||||
expect(fabBottomReserved(0)).toBe(76)
|
expect(fabBottomReserved(0)).toBe(68)
|
||||||
expect(fabBottomReserved(34)).toBe(110)
|
expect(fabBottomReserved(34)).toBe(102)
|
||||||
expect(clampFabPosition(500, 900, 390, 844, 56, 14, fabBottomReserved(34))).toEqual({ x: 320, y: 678 })
|
expect(clampFabPosition(500, 900, 390, 844, 56, 14, fabBottomReserved(34))).toEqual({ x: 320, y: 686 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('snaps the add button to the nearest horizontal edge while preserving its safe vertical position', () => {
|
it('snaps the add button to the nearest horizontal edge while preserving its safe vertical position', () => {
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export async function performTrashMutation(
|
|||||||
return { mutated: true, refreshed: await refresh() }
|
return { mutated: true, refreshed: await refresh() }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fabBottomReserved(safeAreaBottom = 0, navigationHeight = 64, gap = 12) {
|
export function fabBottomReserved(safeAreaBottom = 0, navigationHeight = 56, gap = 12) {
|
||||||
return navigationHeight + Math.max(0, safeAreaBottom) + gap
|
return navigationHeight + Math.max(0, safeAreaBottom) + gap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ input,select,textarea,.search{background:var(--surface-raised);border-color:var(
|
|||||||
.empty,.center{color:var(--muted)}.loader{background:var(--surface-raised)}
|
.empty,.center{color:var(--muted)}.loader{background:var(--surface-raised)}
|
||||||
.toast{background:#3b342c;color:#fff;border:1px solid #574d42;border-radius:var(--radius-control);box-shadow:var(--shadow-raised)}.error-toast{background:var(--danger);color:#fff;border:1px solid #9f2f22;border-radius:var(--radius-control);box-shadow:var(--shadow-raised)}
|
.toast{background:#3b342c;color:#fff;border:1px solid #574d42;border-radius:var(--radius-control);box-shadow:var(--shadow-raised)}.error-toast{background:var(--danger);color:#fff;border:1px solid #9f2f22;border-radius:var(--radius-control);box-shadow:var(--shadow-raised)}
|
||||||
:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}.task-check:focus-visible,.archived-lists-toggle:focus-visible,.archived-row-menu-trigger:focus-visible,.archived-row-actions button:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}
|
:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}.task-check:focus-visible,.archived-lists-toggle:focus-visible,.archived-row-menu-trigger:focus-visible,.archived-row-actions button:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}
|
||||||
@media(max-width:930px){.bottom{left:0;right:0;bottom:0;height:calc(64px + var(--safe-area-bottom));display:grid;grid-template-columns:repeat(4,minmax(0,1fr));background:var(--surface-raised);border:0;border-top:1px solid var(--border-cream);border-radius:0;padding:4px 10px var(--safe-area-bottom);box-shadow:none}.bottom button{position:relative;min-width:0;min-height:44px;border-radius:0;padding:2px 4px;line-height:1.1}.bottom button svg{width:19px;height:19px}.bottom button.active{background:transparent;color:var(--accent)}.bottom button.active:before{content:"";position:absolute;left:23%;right:23%;top:-5px;height:3px;border-radius:0 0 3px 3px;background:var(--accent)}.sidebar{border-radius:0 var(--radius-panel) var(--radius-panel) 0}.detail,.app-sheet,.task-compose-sheet,.habit-detail-sheet,.countdown-detail-sheet{border-radius:var(--sheet-radius) var(--sheet-radius) 0 0!important}.task-list,.habit-list,.countdown-timeline{display:grid;gap:0}.task-row,.habit-row,.countdown-row{min-height:62px;background:var(--surface-raised);border:0;border-radius:0;box-shadow:none}.task-row+.task-row,.habit-row+.habit-row,.countdown-row+.countdown-row{border-top:1px solid var(--border-cream)}.countdown-row:first-of-type{border-top:0}.unified-fab{bottom:calc(76px + var(--safe-area-bottom))}}
|
@media(max-width:930px){.bottom{left:0;right:0;bottom:0;height:calc(56px + var(--safe-area-bottom));display:grid;grid-template-columns:repeat(4,minmax(0,1fr));background:var(--surface-raised);border:0;border-top:1px solid var(--border-cream);border-radius:0;padding:4px 10px var(--safe-area-bottom);box-shadow:none}.bottom button{position:relative;min-width:0;min-height:44px;border-radius:0;padding:2px 4px;line-height:1.1}.bottom button svg{width:19px;height:19px}.bottom button.active{background:transparent;color:var(--accent)}.bottom button.active:before{content:"";position:absolute;left:23%;right:23%;top:-5px;height:3px;border-radius:0 0 3px 3px;background:var(--accent)}.sidebar{border-radius:0 var(--radius-panel) var(--radius-panel) 0}.detail,.app-sheet,.task-compose-sheet,.habit-detail-sheet,.countdown-detail-sheet{border-radius:var(--sheet-radius) var(--sheet-radius) 0 0!important}.task-list,.habit-list,.countdown-timeline{display:grid;gap:0}.task-row,.habit-row,.countdown-row{min-height:62px;background:var(--surface-raised);border:0;border-radius:0;box-shadow:none}.task-row+.task-row,.habit-row+.habit-row,.countdown-row+.countdown-row{border-top:1px solid var(--border-cream)}.countdown-row:first-of-type{border-top:0}.unified-fab{bottom:calc(68px + var(--safe-area-bottom))}}
|
||||||
@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}.completed-filter-pill,.completed-filter-pill__track,.completed-filter-pill__thumb{transition:none!important}.topbar-refresh.spinning svg{animation:none!important}.completed-filter-pill:active:not(:disabled){transform:none}.task-row.just-completed,.habit-row.just-completed,.task-row.just-completed .task-check-mark,.habit-row.just-completed .task-check-mark{animation:none}}
|
@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;animation-duration:.01ms!important;transition-duration:.01ms!important}.completed-filter-pill,.completed-filter-pill__track,.completed-filter-pill__thumb{transition:none!important}.topbar-refresh.spinning svg{animation:none!important}.completed-filter-pill:active:not(:disabled){transform:none}.task-row.just-completed,.habit-row.just-completed,.task-row.just-completed .task-check-mark,.habit-row.just-completed .task-check-mark{animation:none}}
|
||||||
/* Approved five-detail list/search polish. */
|
/* Approved five-detail list/search polish. */
|
||||||
@media(min-width:931px){.task-search-toggle{display:none}.search-reveal{min-width:320px}}
|
@media(min-width:931px){.task-search-toggle{display:none}.search-reveal{min-width:320px}}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ describe('solid cream material system', () => {
|
|||||||
it('keeps controls solid, focus visible, mobile targets safe, and motion reducible', () => {
|
it('keeps controls solid, focus visible, mobile targets safe, and motion reducible', () => {
|
||||||
expect(css).toContain('input,select,textarea,.search{background:var(--surface-raised)')
|
expect(css).toContain('input,select,textarea,.search{background:var(--surface-raised)')
|
||||||
expect(css).toContain(':focus-visible{outline:3px solid var(--focus-ring)')
|
expect(css).toContain(':focus-visible{outline:3px solid var(--focus-ring)')
|
||||||
expect(css).toContain('@media(max-width:930px){.bottom{left:0;right:0;bottom:0;height:calc(64px + var(--safe-area-bottom));')
|
expect(css).toContain('@media(max-width:930px){.bottom{left:0;right:0;bottom:0;height:calc(56px + var(--safe-area-bottom));')
|
||||||
expect(css).toContain('border:0;border-top:1px solid var(--border-cream);border-radius:0;padding:4px 10px var(--safe-area-bottom)')
|
expect(css).toContain('border:0;border-top:1px solid var(--border-cream);border-radius:0;padding:4px 10px var(--safe-area-bottom)')
|
||||||
expect(css).toMatch(/\.bottom button\{[^}]*min-height:44px/)
|
expect(css).toMatch(/\.bottom button\{[^}]*min-height:44px/)
|
||||||
expect(css).toMatch(/\.bottom button\.active\{[^}]*background:transparent/)
|
expect(css).toMatch(/\.bottom button\.active\{[^}]*background:transparent/)
|
||||||
@@ -1196,7 +1196,7 @@ describe('unified floating add interaction', () => {
|
|||||||
expect(css).toContain('.unified-fab.snapping')
|
expect(css).toContain('.unified-fab.snapping')
|
||||||
expect(countdownPanel).toContain('<AppSheet :open="open" variant="create"')
|
expect(countdownPanel).toContain('<AppSheet :open="open" variant="create"')
|
||||||
expect(countdownPanel).toContain('panel-class="countdown-modal"')
|
expect(countdownPanel).toContain('panel-class="countdown-modal"')
|
||||||
expect(css).toMatch(/@media\(max-width:930px\)\{[^}]*\.bottom\{[^}]*height:calc\(64px \+ var\(--safe-area-bottom\)\)[\s\S]*?\.unified-fab\{bottom:calc\(76px \+ var\(--safe-area-bottom\)\)\}/)
|
expect(css).toMatch(/@media\(max-width:930px\)\{[^}]*\.bottom\{[^}]*height:calc\(56px \+ var\(--safe-area-bottom\)\)[\s\S]*?\.unified-fab\{bottom:calc\(68px \+ var\(--safe-area-bottom\)\)\}/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the original orange circular plus button', () => {
|
it('renders the original orange circular plus button', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user