Show a sync-in-progress spinner in the header next to the feed filter

activeSyncCount/isSyncing() in useFeeds.js already refcounted both boot
reload (fetchData) and manual sync() but wasn't reactive. Make it a ref
+ computed and expose it so AppNav can render a small spinner next to
the feed filter whenever a sync is in flight.

The spinner sits outside the filter's own v-if so it also shows during
a manual sync from pages other than /feeds, and its box is always
reserved (visibility toggle, not v-if) so the one-time --app-nav-height
measurement stays correct.
This commit is contained in:
2026-09-14 18:05:24 +02:00
parent 4d9e899519
commit 9c042411fb
4 changed files with 171 additions and 13 deletions
+34 -1
View File
@@ -5,7 +5,7 @@ import { useFeeds, logout as logoutSession } from '@/composables/useFeeds'
const router = useRouter()
const route = useRoute()
const { sync, viewMode, toggleViewMode, layout, toggleLayout, markAllRead, feedFilter, feedTitles, setFeedFilter, unreadCount, displayedFeedUnreadCounts, refreshUnreadDisplay, lastProgrammaticScroll } = useFeeds()
const { sync, viewMode, toggleViewMode, layout, toggleLayout, markAllRead, feedFilter, feedTitles, setFeedFilter, unreadCount, displayedFeedUnreadCounts, refreshUnreadDisplay, lastProgrammaticScroll, isSyncing } = useFeeds()
const headerRef = ref(null)
@@ -161,6 +161,11 @@ function handleToggleLayout() {
<header ref="headerRef" class="app-nav" :class="{ 'app-nav--hidden': hidden }">
<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__sync-spinner"
:class="{ 'app-nav__sync-spinner--active': isSyncing }"
aria-hidden="true"
></span>
<select
v-if="onFeedsPage && feedTitles.length"
class="app-nav__filter"
@@ -251,6 +256,34 @@ function handleToggleLayout() {
opacity: 0.6;
}
.app-nav__sync-spinner {
width: 16px;
height: 16px;
border: 2px solid var(--color-border);
border-top-color: var(--color-text);
border-radius: 50%;
opacity: 0.6;
flex-shrink: 0;
visibility: hidden;
}
.app-nav__sync-spinner--active {
visibility: visible;
animation: app-nav-spin 0.8s linear infinite;
}
@keyframes app-nav-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.app-nav__sync-spinner--active {
animation: none;
}
}
.app-nav__filter {
min-height: 44px;
max-width: clamp(120px, 40vw, 220px);