fix(series): sticky tabs + controls on the Series view
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m14s

The Series tab strip and the Browse search/sort (and Suggestions controls)
scrolled away on a long grid (operator-asked). Hoist the tabs + active-tab
controls into one sticky header pinned under the 64px TopNav. The controls
had to leave v-window — it clips sticky children — so they're driven by the
tab from the header instead of living inside each window-item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 20:30:27 -04:00
parent 5c3f8ebd70
commit 7fcef53d5b
+41 -26
View File
@@ -1,6 +1,11 @@
<template> <template>
<v-container fluid class="pt-2 pb-6"> <v-container fluid class="pt-2 pb-6">
<v-tabs v-model="tab" density="compact" class="mb-3"> <!-- Tabs + the active tab's controls live in one sticky block pinned under
the 64px TopNav (operator-asked 2026-06-12), so the axis switcher and
search/sort stay reachable on a long grid. The controls can't sit
inside v-window (it clips sticky children), so they're hoisted here. -->
<div class="fc-series__head">
<v-tabs v-model="tab" density="compact">
<v-tab value="browse">Browse</v-tab> <v-tab value="browse">Browse</v-tab>
<v-tab value="suggestions"> <v-tab value="suggestions">
Suggestions Suggestions
@@ -11,10 +16,7 @@
</v-tab> </v-tab>
</v-tabs> </v-tabs>
<v-window v-model="tab"> <div v-if="tab === 'browse'" class="fc-series-browse__controls">
<!-- ---- Browse ---- -->
<v-window-item value="browse">
<div class="fc-series-browse__controls">
<v-text-field <v-text-field
v-model="query" v-model="query"
density="compact" variant="outlined" hide-details clearable density="compact" variant="outlined" hide-details clearable
@@ -30,6 +32,29 @@
/> />
</div> </div>
<div v-else class="fc-sug__controls">
<v-switch
:model-value="sug.enabled" color="accent" density="compact"
hide-details label="Matching on"
@update:model-value="sug.setEnabled($event)"
/>
<v-text-field
:model-value="sug.threshold"
type="number" min="0" max="1" step="0.05"
density="compact" variant="outlined" hide-details
label="Threshold" style="max-width: 130px;"
@change="sug.setThreshold(Number($event.target.value))"
/>
<v-spacer />
<v-btn
variant="tonal" prepend-icon="mdi-radar" @click="sug.rescan()"
>Rescan</v-btn>
</div>
</div>
<v-window v-model="tab">
<!-- ---- Browse ---- -->
<v-window-item value="browse">
<v-alert v-if="store.error" type="error" variant="tonal" closable class="my-4"> <v-alert v-if="store.error" type="error" variant="tonal" closable class="my-4">
Failed to load: {{ store.error }} Failed to load: {{ store.error }}
</v-alert> </v-alert>
@@ -138,25 +163,6 @@
<!-- ---- Suggestions ---- --> <!-- ---- Suggestions ---- -->
<v-window-item value="suggestions"> <v-window-item value="suggestions">
<div class="fc-sug__controls">
<v-switch
:model-value="sug.enabled" color="accent" density="compact"
hide-details label="Matching on"
@update:model-value="sug.setEnabled($event)"
/>
<v-text-field
:model-value="sug.threshold"
type="number" min="0" max="1" step="0.05"
density="compact" variant="outlined" hide-details
label="Threshold" style="max-width: 130px;"
@change="sug.setThreshold(Number($event.target.value))"
/>
<v-spacer />
<v-btn
variant="tonal" prepend-icon="mdi-radar" @click="sug.rescan()"
>Rescan</v-btn>
</div>
<v-alert v-if="sug.error" type="error" variant="tonal" closable class="my-4"> <v-alert v-if="sug.error" type="error" variant="tonal" closable class="my-4">
{{ sug.error }} {{ sug.error }}
</v-alert> </v-alert>
@@ -287,8 +293,17 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
/* Sticky header (tabs + active-tab controls) pinned under the 64px TopNav, so
content scrolls cleanly beneath it. Surface bg matches SettingsView. */
.fc-series__head {
position: sticky;
top: 64px;
z-index: 4;
background: rgb(var(--v-theme-surface));
padding-bottom: 12px;
}
.fc-series-browse__controls { .fc-series-browse__controls {
display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; display: flex; flex-wrap: wrap; gap: 12px; padding-top: 12px;
} }
.fc-series-browse__empty { .fc-series-browse__empty {
padding: 48px; text-align: center; padding: 48px; text-align: center;
@@ -351,7 +366,7 @@ onMounted(() => {
/* Suggestions tab */ /* Suggestions tab */
.fc-sug__controls { .fc-sug__controls {
display: flex; align-items: center; gap: 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 16px; padding-top: 12px;
} }
.fc-sug__list { display: flex; flex-direction: column; gap: 8px; } .fc-sug__list { display: flex; flex-direction: column; gap: 8px; }
.fc-sug__row { .fc-sug__row {