feat: floating save button when editing recipe (#7320)

Co-authored-by: Docker User <user@example.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Bérubé
2026-07-18 11:22:33 -04:00
committed by GitHub
parent db4262dc67
commit ef7b27c00f

View File

@@ -23,6 +23,7 @@
<v-container v-show="!isCookMode" key="recipe-page" class="px-0" :class="{ 'pa-0': $vuetify.display.smAndDown }"> <v-container v-show="!isCookMode" key="recipe-page" class="px-0" :class="{ 'pa-0': $vuetify.display.smAndDown }">
<v-card flat class="d-print-none"> <v-card flat class="d-print-none">
<RecipePageHeader <RecipePageHeader
ref="recipeToolbar"
:recipe="recipe" :recipe="recipe"
:recipe-scale="scale" :recipe-scale="scale"
:landscape="landscape" :landscape="landscape"
@@ -112,6 +113,22 @@
/> />
<RecipePrintContainer :recipe="recipe" :scale="scale" /> <RecipePrintContainer :recipe="recipe" :scale="scale" />
</v-container> </v-container>
<!-- Floating save button when toolbar scrolls out of view -->
<v-fab
v-if="isEditMode && !toolbarVisible"
color="success"
location="bottom end"
size="large"
app
appear
class="d-print-none"
@click="saveRecipe"
>
<v-icon>{{ $globals.icons.save }}</v-icon>
<v-tooltip activator="parent" location="left">
{{ $t("general.save") }}
</v-tooltip>
</v-fab>
<!-- Cook mode displayes two columns with ingredients and instructions side by side, each being scrolled individually, allowing to view both at the same time --> <!-- Cook mode displayes two columns with ingredients and instructions side by side, each being scrolled individually, allowing to view both at the same time -->
<!-- The calc is to account for the navabar height (48px) --> <!-- The calc is to account for the navabar height (48px) -->
<v-sheet <v-sheet
@@ -191,6 +208,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ComponentPublicInstance } from "vue";
import { invoke, until } from "@vueuse/core"; import { invoke, until } from "@vueuse/core";
import type { RouteLocationNormalized } from "vue-router"; import type { RouteLocationNormalized } from "vue-router";
import RecipeIngredients from "../RecipeIngredients.vue"; import RecipeIngredients from "../RecipeIngredients.vue";
@@ -243,6 +261,26 @@ const notLinkedIngredients = computed(() => {
}); });
}); });
/** =============================================================
* Floating save button — track toolbar visibility
*/
const recipeToolbar = ref<ComponentPublicInstance | null>(null);
const toolbarVisible = ref(true);
let toolbarObserver: IntersectionObserver | undefined;
onMounted(async () => {
await nextTick();
const el = recipeToolbar.value?.$el as HTMLElement | undefined;
if (!el) return;
toolbarObserver = new IntersectionObserver(
([entry]) => { toolbarVisible.value = entry.isIntersecting; },
{ threshold: 0 },
);
toolbarObserver.observe(el);
});
onUnmounted(() => toolbarObserver?.disconnect());
/** ============================================================= /** =============================================================
* Recipe Snapshot on Mount * Recipe Snapshot on Mount
* this is used to determine if the recipe has been changed since the last save * this is used to determine if the recipe has been changed since the last save