feat: improve BaseDialog on mobile and use it globally (#7076)

Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
Arsène Reymond
2026-03-31 14:34:44 +02:00
committed by GitHub
parent f6305b785e
commit f36c892bb7
12 changed files with 179 additions and 248 deletions

View File

@@ -1,91 +1,60 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<v-dialog <BaseButton @click="dialog = true">
v-model="dialog"
width="800"
>
<template #activator="{ props: activatorProps }">
<BaseButton
v-bind="activatorProps"
@click="inputText = inputTextProp"
>
{{ $t("new-recipe.bulk-add") }} {{ $t("new-recipe.bulk-add") }}
</BaseButton> </BaseButton>
</template> <BaseDialog
v-model="dialog"
<v-card> width="800"
<v-app-bar :title="$t('new-recipe.bulk-add')"
density="compact" :icon="$globals.icons.createAlt"
dark :submit-text="$t('general.add')"
color="primary" :disable-submit-on-enter="true"
class="mb-2 position-relative left-0 top-0 w-100" can-submit
@submit="save"
> >
<v-icon
size="large"
start
>
{{ $globals.icons.createAlt }}
</v-icon>
<v-toolbar-title class="headline">
{{ $t("new-recipe.bulk-add") }}
</v-toolbar-title>
<v-spacer />
</v-app-bar>
<v-card-text> <v-card-text>
<v-textarea <v-textarea
v-model="inputText" v-model="inputText"
variant="outlined" variant="outlined"
rows="12" rows="12"
hide-details hide-details
autofocus
:placeholder="$t('new-recipe.paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list')" :placeholder="$t('new-recipe.paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list')"
/> />
<v-divider /> <v-divider />
<v-list lines="two">
<template <template
v-for="(util) in utilities" v-for="(util) in utilities"
:key="util.id" :key="util.id"
> >
<v-list-item <v-list-item
density="compact" class="px-0"
class="py-1"
> >
<v-list-item-title> <template #prepend>
<v-list-item-subtitle class="wrap-word"> <v-avatar>
{{ util.description }} <v-btn
</v-list-item-subtitle> icon
</v-list-item-title> variant="tonal"
<BaseButton base-color="info"
size="small" :title="$t('general.run')"
color="info"
@click="util.action" @click="util.action"
> >
<template #icon> <v-icon>
{{ $globals.icons.robot }} {{ $globals.icons.play }}
</v-icon>
</v-btn>
</v-avatar>
</template> </template>
{{ $t("general.run") }} <v-list-item-title class="text-pre-wrap">
</BaseButton> {{ util.description }}
</v-list-item-title>
</v-list-item> </v-list-item>
<v-divider class="mx-2" />
</template> </template>
</v-list>
</v-card-text> </v-card-text>
</BaseDialog>
<v-divider />
<v-card-actions>
<BaseButton
cancel
@click="dialog = false"
/>
<v-spacer />
<BaseButton
save
color="success"
@click="save"
/>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>

View File

@@ -1,37 +1,18 @@
<template> <template>
<div> <div>
<v-dialog <BaseDialog
v-model="dialog" v-model="dialog"
width="500" width="500"
:title="properties.title"
:icon="properties.icon"
can-submit
:submit-disabled="!name"
@submit="select"
> >
<v-card> <v-form>
<v-app-bar
density="compact"
dark
color="primary mb-2 position-relative left-0 top-0 w-100 pl-3"
>
<v-icon
size="large"
start
class="mt-1"
>
{{ itemType === Organizer.Tool ? $globals.icons.potSteam
: itemType === Organizer.Category ? $globals.icons.categories
: $globals.icons.tags }}
</v-icon>
<v-toolbar-title class="headline">
{{ properties.title }}
</v-toolbar-title>
<v-spacer />
</v-app-bar>
<v-card-title />
<v-form @submit.prevent="select">
<v-card-text> <v-card-text>
<v-text-field <v-text-field
v-model="name" v-model="name"
density="compact"
:label="properties.label" :label="properties.label"
:rules="[rules.required]" :rules="[rules.required]"
autofocus autofocus
@@ -42,21 +23,8 @@
:label="$t('tool.on-hand')" :label="$t('tool.on-hand')"
/> />
</v-card-text> </v-card-text>
<v-card-actions>
<BaseButton
cancel
@click="dialog = false"
/>
<v-spacer />
<BaseButton
type="submit"
create
:disabled="!name"
/>
</v-card-actions>
</v-form> </v-form>
</v-card> </BaseDialog>
</v-dialog>
</div> </div>
</template> </template>
@@ -65,6 +33,8 @@ import { useUserApi } from "~/composables/api";
import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store"; import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store";
import { type RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated"; import { type RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated";
const { $globals } = useNuxtApp();
const CREATED_ITEM_EVENT = "created-item"; const CREATED_ITEM_EVENT = "created-item";
interface Props { interface Props {
@@ -115,18 +85,21 @@ const properties = computed(() => {
return { return {
title: i18n.t("tag.create-a-tag"), title: i18n.t("tag.create-a-tag"),
label: i18n.t("tag.tag-name"), label: i18n.t("tag.tag-name"),
icon: $globals.icons.tags,
api: userApi.tags, api: userApi.tags,
}; };
case Organizer.Tool: case Organizer.Tool:
return { return {
title: i18n.t("tool.create-a-tool"), title: i18n.t("tool.create-a-tool"),
label: i18n.t("tool.tool-name"), label: i18n.t("tool.tool-name"),
icon: $globals.icons.potSteam,
api: userApi.tools, api: userApi.tools,
}; };
default: default:
return { return {
title: i18n.t("category.create-a-category"), title: i18n.t("category.create-a-category"),
label: i18n.t("category.category-name"), label: i18n.t("category.category-name"),
icon: $globals.icons.categories,
api: userApi.categories, api: userApi.categories,
}; };
} }
@@ -139,12 +112,9 @@ const rules = {
async function select() { async function select() {
if (store) { if (store) {
// @ts-expect-error the same state is used for different organizer types, which have different requirements // @ts-expect-error the same state is used for different organizer types, which have different requirements
await store.actions.createOne({ name: name.value, onHand: onHand.value }); const newItem = await store.actions.createOne({ name: name.value, onHand: onHand.value });
}
const newItem = store.store.value.find(item => item.name === name.value);
emit(CREATED_ITEM_EVENT, newItem); emit(CREATED_ITEM_EVENT, newItem);
}
dialog.value = false; dialog.value = false;
} }
</script> </script>

View File

@@ -26,6 +26,7 @@
v-if="updateTarget" v-if="updateTarget"
v-model="dialogs.update" v-model="dialogs.update"
:title="$t('general.update')" :title="$t('general.update')"
:icon="$globals.icons.edit"
can-confirm can-confirm
@confirm="updateOne()" @confirm="updateOne()"
> >

View File

@@ -1,36 +1,21 @@
<template> <template>
<section @keyup.ctrl.z="undoMerge"> <section @keyup.ctrl.z="undoMerge">
<!-- Ingredient Link Editor --> <!-- Ingredient Link Editor -->
<v-dialog <BaseDialog
v-if="dialog"
v-model="dialog" v-model="dialog"
width="600" :title="$t('recipe.ingredient-linker')"
:icon="$globals.icons.link"
width="100%"
max-width="600px"
max-height="40%"
> >
<v-card :ripple="false">
<v-sheet
color="primary"
class="mt-n1 mb-3 pa-3 d-flex align-center"
style="border-radius: 6px; width: 100%;"
>
<v-icon
size="large"
start
>
{{ $globals.icons.link }}
</v-icon>
<v-toolbar-title class="headline">
{{ $t("recipe.ingredient-linker") }}
</v-toolbar-title>
<v-spacer />
</v-sheet>
<v-card-text class="pt-4"> <v-card-text class="pt-4">
<p> <p>
{{ activeText }} {{ activeText }}
</p> </p>
<v-divider class="mb-4" /> <v-divider class="my-4" />
<template v-if="Object.keys(groupedUnusedIngredients).length > 0"> <template v-if="Object.keys(groupedUnusedIngredients).length > 0">
<h4 class="py-3 ml-1"> <h4 class="ml-1">
{{ $t("recipe.unlinked") }} {{ $t("recipe.unlinked") }}
</h4> </h4>
<template v-for="(ingredients, title) in groupedUnusedIngredients" :key="title"> <template v-for="(ingredients, title) in groupedUnusedIngredients" :key="title">
@@ -76,7 +61,7 @@
<v-divider /> <v-divider />
<v-card-actions> <template #card-actions>
<BaseButton <BaseButton
cancel cancel
@click="dialog = false" @click="dialog = false"
@@ -109,9 +94,8 @@
{{ $t("recipe.nextStep") }} {{ $t("recipe.nextStep") }}
</BaseButton> </BaseButton>
</div> </div>
</v-card-actions> </template>
</v-card> </BaseDialog>
</v-dialog>
<div class="d-flex justify-space-between justify-start"> <div class="d-flex justify-space-between justify-start">
<h2 <h2
@@ -851,6 +835,10 @@ function openImageUpload(index: number) {
font-size: 1.5rem; font-size: 1.5rem;
} }
.v-card-text {
font-size: 1rem;
}
.recipe-step-title { .recipe-step-title {
/* Multiline display */ /* Multiline display */
white-space: normal; white-space: normal;

View File

@@ -24,7 +24,7 @@
</v-btn> </v-btn>
<BaseDialog <BaseDialog
v-model="showTimeline" v-model="showTimeline"
:title="timelineAttrs.title" :title="$t('recipe.timeline')"
:icon="$globals.icons.timelineText" :icon="$globals.icons.timelineText"
width="70%" width="70%"
> >
@@ -53,8 +53,6 @@ const props = withDefaults(defineProps<Props>(), {
recipeName: "", recipeName: "",
}); });
const i18n = useI18n();
const { smAndDown } = useDisplay();
const showTimeline = ref(false); const showTimeline = ref(false);
function toggleTimeline() { function toggleTimeline() {
@@ -62,13 +60,7 @@ function toggleTimeline() {
} }
const timelineAttrs = computed(() => { const timelineAttrs = computed(() => {
let title = i18n.t("recipe.timeline");
if (smAndDown.value) {
title += ` ${props.recipeName}`;
}
return { return {
title,
queryFilter: `recipe.slug="${props.slug}"`, queryFilter: `recipe.slug="${props.slug}"`,
}; };
}); });

View File

@@ -14,7 +14,13 @@
@click:outside="emit('cancel')" @click:outside="emit('cancel')"
@keydown.esc="emit('cancel')" @keydown.esc="emit('cancel')"
> >
<v-card height="100%"> <v-card height="100%" :loading="loading">
<template #loader="{ isActive }">
<v-progress-linear
:active="isActive"
indeterminate
/>
</template>
<v-toolbar <v-toolbar
dark dark
density="comfortable" density="comfortable"
@@ -28,17 +34,12 @@
{{ title }} {{ title }}
</v-toolbar-title> </v-toolbar-title>
</v-toolbar> </v-toolbar>
<v-progress-linear
v-if="loading"
class="mt-1"
indeterminate
color="primary"
/>
<div> <div>
<slot v-bind="{ submitEvent }" /> <slot v-bind="{ submitEvent }" />
</div> </div>
<v-spacer />
<v-divider class="mx-2" /> <v-divider class="mx-2" />
<v-card-actions> <v-card-actions>

View File

@@ -107,6 +107,7 @@ import {
mdiOpenInNew, mdiOpenInNew,
mdiOrderAlphabeticalAscending, mdiOrderAlphabeticalAscending,
mdiPageLayoutBody, mdiPageLayoutBody,
mdiPlay,
mdiPlus, mdiPlus,
mdiPlusCircle, mdiPlusCircle,
mdiPotSteamOutline, mdiPotSteamOutline,
@@ -252,6 +253,7 @@ export const icons = {
openInNew: mdiOpenInNew, openInNew: mdiOpenInNew,
orderAlphabeticalAscending: mdiOrderAlphabeticalAscending, orderAlphabeticalAscending: mdiOrderAlphabeticalAscending,
pageLayoutBody: mdiPageLayoutBody, pageLayoutBody: mdiPageLayoutBody,
play: mdiPlay,
printer: mdiPrinter, printer: mdiPrinter,
printerSettings: mdiPrinterPosCog, printerSettings: mdiPrinterPosCog,
refreshCircle: mdiRefreshCircle, refreshCircle: mdiRefreshCircle,

View File

@@ -20,6 +20,7 @@
<BaseDialog <BaseDialog
v-model="state.confirmDialog" v-model="state.confirmDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error" color="error"
can-confirm can-confirm
@confirm="deleteGroup(state.deleteTarget)" @confirm="deleteGroup(state.deleteTarget)"

View File

@@ -38,6 +38,7 @@
<BaseDialog <BaseDialog
v-model="confirmDialog" v-model="confirmDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error" color="error"
can-confirm can-confirm
@confirm="deleteHousehold(deleteTarget)" @confirm="deleteHousehold(deleteTarget)"

View File

@@ -4,6 +4,7 @@
<BaseDialog <BaseDialog
v-model="state.deleteDialog" v-model="state.deleteDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error" color="error"
can-confirm can-confirm
@confirm="deleteUser(state.deleteTargetId)" @confirm="deleteUser(state.deleteTargetId)"

View File

@@ -6,6 +6,7 @@
<BaseDialog <BaseDialog
v-model="state.checkAllDialog" v-model="state.checkAllDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.checkboxOutline"
can-confirm can-confirm
@confirm="checkAll" @confirm="checkAll"
> >
@@ -17,6 +18,7 @@
<BaseDialog <BaseDialog
v-model="state.uncheckAllDialog" v-model="state.uncheckAllDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.checkboxBlankOutline"
can-confirm can-confirm
@confirm="uncheckAll" @confirm="uncheckAll"
> >
@@ -28,6 +30,7 @@
<BaseDialog <BaseDialog
v-model="state.deleteCheckedDialog" v-model="state.deleteCheckedDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
can-confirm can-confirm
@confirm="deleteChecked" @confirm="deleteChecked"
> >

View File

@@ -6,6 +6,7 @@
<BaseDialog <BaseDialog
v-model="state.createDialog" v-model="state.createDialog"
:title="$t('shopping-list.create-shopping-list')" :title="$t('shopping-list.create-shopping-list')"
:icon="$globals.icons.formatListCheck"
can-submit can-submit
@submit="createOne" @submit="createOne"
> >
@@ -43,6 +44,7 @@
<BaseDialog <BaseDialog
v-model="state.deleteDialog" v-model="state.deleteDialog"
:title="$t('general.confirm')" :title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error" color="error"
can-confirm can-confirm
@confirm="deleteOne" @confirm="deleteOne"