Stop reserving layout space for the idle sync spinner
visibility:hidden still occupied the header's flex row, squeezing the title on narrow screens and clipping the unread counter. Render the spinner with v-if instead so it takes no space when not syncing.
This commit is contained in:
@@ -162,8 +162,8 @@ function handleToggleLayout() {
|
|||||||
<div class="app-nav__wrapper">
|
<div class="app-nav__wrapper">
|
||||||
<span class="app-nav__title">RSS Reader<span v-if="unreadCount" class="app-nav__unread"> ({{ unreadCount }})</span></span>
|
<span class="app-nav__title">RSS Reader<span v-if="unreadCount" class="app-nav__unread"> ({{ unreadCount }})</span></span>
|
||||||
<span
|
<span
|
||||||
|
v-if="isSyncing"
|
||||||
class="app-nav__sync-spinner"
|
class="app-nav__sync-spinner"
|
||||||
:class="{ 'app-nav__sync-spinner--active': isSyncing }"
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
<select
|
<select
|
||||||
@@ -268,11 +268,6 @@ function handleToggleLayout() {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-nav__sync-spinner--active {
|
|
||||||
visibility: visible;
|
|
||||||
animation: app-nav-spin 0.8s linear infinite;
|
animation: app-nav-spin 0.8s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +278,7 @@ function handleToggleLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.app-nav__sync-spinner--active {
|
.app-nav__sync-spinner {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,16 +311,14 @@ describe('AppNav', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('sync spinner', () => {
|
describe('sync spinner', () => {
|
||||||
it('always renders the spinner, inactive, when idle', async () => {
|
it('does not render the spinner, and reserves no space for it, when idle', async () => {
|
||||||
const wrapper = mountNav()
|
const wrapper = mountNav()
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
const spinner = wrapper.find('.app-nav__sync-spinner')
|
expect(wrapper.find('.app-nav__sync-spinner').exists()).toBe(false)
|
||||||
expect(spinner.exists()).toBe(true)
|
|
||||||
expect(spinner.classes()).not.toContain('app-nav__sync-spinner--active')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('activates while a sync triggered through the composable is in flight, and deactivates once it resolves', async () => {
|
it('renders while a sync triggered through the composable is in flight, and disappears once it resolves', async () => {
|
||||||
const wrapper = mountNav()
|
const wrapper = mountNav()
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
@@ -330,25 +328,35 @@ describe('AppNav', () => {
|
|||||||
const syncPromise = sync(true)
|
const syncPromise = sync(true)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(wrapper.find('.app-nav__sync-spinner').classes()).toContain('app-nav__sync-spinner--active')
|
expect(wrapper.find('.app-nav__sync-spinner').exists()).toBe(true)
|
||||||
|
|
||||||
axios.get.mockResolvedValueOnce({ data: { feeds: [] } })
|
axios.get.mockResolvedValueOnce({ data: { feeds: [] } })
|
||||||
resolvePost({ status: 200 })
|
resolvePost({ status: 200 })
|
||||||
await syncPromise
|
await syncPromise
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(wrapper.find('.app-nav__sync-spinner').classes()).not.toContain('app-nav__sync-spinner--active')
|
expect(wrapper.find('.app-nav__sync-spinner').exists()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders even on a page where the feed filter itself does not render', async () => {
|
it('renders while syncing even on a page where the feed filter itself does not render', async () => {
|
||||||
await router.push('/login')
|
await router.push('/login')
|
||||||
await router.isReady()
|
await router.isReady()
|
||||||
|
|
||||||
const wrapper = mountNav()
|
const wrapper = mountNav()
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
|
let resolvePost
|
||||||
|
axios.post.mockReturnValueOnce(new Promise(resolve => { resolvePost = resolve }))
|
||||||
|
const { sync } = useFeeds()
|
||||||
|
const syncPromise = sync(true)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
expect(wrapper.find('.app-nav__filter').exists()).toBe(false)
|
expect(wrapper.find('.app-nav__filter').exists()).toBe(false)
|
||||||
expect(wrapper.find('.app-nav__sync-spinner').exists()).toBe(true)
|
expect(wrapper.find('.app-nav__sync-spinner').exists()).toBe(true)
|
||||||
|
|
||||||
|
axios.get.mockResolvedValueOnce({ data: { feeds: [] } })
|
||||||
|
resolvePost({ status: 200 })
|
||||||
|
await syncPromise
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user