feat: Replace number inputs with new v-number-input compontent (#6767)

This commit is contained in:
Michael Genson
2025-12-22 18:45:52 -06:00
committed by GitHub
parent c64c2d25e7
commit 193b823688
12 changed files with 80 additions and 134 deletions

View File

@@ -144,11 +144,13 @@
variant="underlined" variant="underlined"
@update:model-value="setFieldValue(field, index, $event)" @update:model-value="setFieldValue(field, index, $event)"
/> />
<v-text-field <v-number-input
v-else-if="field.type === 'number'" v-else-if="field.type === 'number'"
:model-value="field.value" :model-value="field.value"
type="number"
variant="underlined" variant="underlined"
control-variant="stacked"
inset
:precision="null"
@update:model-value="setFieldValue(field, index, $event)" @update:model-value="setFieldValue(field, index, $event)"
/> />
<v-checkbox <v-checkbox

View File

@@ -22,12 +22,15 @@
cols="12" cols="12"
class="flex-grow-0 flex-shrink-0" class="flex-grow-0 flex-shrink-0"
> >
<v-text-field <v-number-input
v-model="model.quantity" v-model="model.quantity"
variant="solo" variant="solo"
:precision="null"
:min="0"
hide-details hide-details
control-variant="stacked"
inset
density="compact" density="compact"
type="number"
:placeholder="$t('recipe.quantity')" :placeholder="$t('recipe.quantity')"
@keypress="quantityFilter" @keypress="quantityFilter"
> >
@@ -38,7 +41,7 @@
{{ $globals.icons.arrowUpDown }} {{ $globals.icons.arrowUpDown }}
</v-icon> </v-icon>
</template> </template>
</v-text-field> </v-number-input>
</v-col> </v-col>
<v-col <v-col
v-if="!state.isRecipe" v-if="!state.isRecipe"

View File

@@ -10,14 +10,17 @@
v-for="(item, key, index) in modelValue" v-for="(item, key, index) in modelValue"
:key="index" :key="index"
> >
<v-text-field <v-number-input
density="compact"
:model-value="modelValue[key]" :model-value="modelValue[key]"
:label="labels[key].label" :label="labels[key].label"
:suffix="labels[key].suffix" :suffix="labels[key].suffix"
type="number" density="compact"
autocomplete="off" autocomplete="off"
variant="underlined" variant="underlined"
control-variant="stacked"
inset
:precision="null"
:min="0"
@update:model-value="updateValue(key, $event)" @update:model-value="updateValue(key, $event)"
/> />
</div> </div>

View File

@@ -11,27 +11,27 @@
<v-container class="ma-0 pa-0"> <v-container class="ma-0 pa-0">
<v-row> <v-row>
<v-col cols="3"> <v-col cols="3">
<v-text-field <v-number-input
:model-value="recipeServings" :model-value="recipe.recipeServings"
type="number"
:min="0" :min="0"
hide-spin-buttons :precision="null"
density="compact" density="compact"
:label="$t('recipe.servings')" :label="$t('recipe.servings')"
variant="underlined" variant="underlined"
@update:model-value="validateInput($event, 'recipeServings')" control-variant="hidden"
@update:model-value="recipe.recipeServings = $event"
/> />
</v-col> </v-col>
<v-col cols="3"> <v-col cols="3">
<v-text-field <v-number-input
:model-value="recipeYieldQuantity" :model-value="recipe.recipeYieldQuantity"
type="number"
:min="0" :min="0"
hide-spin-buttons :precision="null"
density="compact" density="compact"
:label="$t('recipe.yield')" :label="$t('recipe.yield')"
variant="underlined" variant="underlined"
@update:model-value="validateInput($event, 'recipeYieldQuantity')" control-variant="hidden"
@update:model-value="recipe.recipeYieldQuantity = $event"
/> />
</v-col> </v-col>
<v-col cols="6"> <v-col cols="6">
@@ -85,37 +85,4 @@ import type { NoUndefinedField } from "~/lib/api/types/non-generated";
import type { Recipe } from "~/lib/api/types/recipe"; import type { Recipe } from "~/lib/api/types/recipe";
const recipe = defineModel<NoUndefinedField<Recipe>>({ required: true }); const recipe = defineModel<NoUndefinedField<Recipe>>({ required: true });
const recipeServings = computed<number>({
get() {
return recipe.value.recipeServings;
},
set(val) {
validateInput(val.toString(), "recipeServings");
},
});
const recipeYieldQuantity = computed<number>({
get() {
return recipe.value.recipeYieldQuantity;
},
set(val) {
validateInput(val.toString(), "recipeYieldQuantity");
},
});
function validateInput(value: string | null, property: "recipeServings" | "recipeYieldQuantity") {
if (!value) {
recipe.value[property] = 0;
return;
}
const number = parseFloat(value.replace(/[^0-9.]/g, ""));
if (isNaN(number) || number <= 0) {
recipe.value[property] = 0;
return;
}
recipe.value[property] = number;
}
</script> </script>

View File

@@ -65,13 +65,13 @@
</v-card-title> </v-card-title>
<v-card-text class="mt-n5"> <v-card-text class="mt-n5">
<div class="mt-4 d-flex align-center"> <div class="mt-4 d-flex align-center">
<v-text-field <v-number-input
:model-value="yieldQuantity" :model-value="yieldQuantity"
type="number" :precision="null"
:min="0" :min="0"
variant="underlined" variant="underlined"
hide-spin-buttons control-variant="hidden"
@update:model-value="recalculateScale(parseFloat($event) || 0)" @update:model-value="recalculateScale($event || 0)"
/> />
<v-tooltip <v-tooltip
location="end" location="end"

View File

@@ -4,7 +4,19 @@
<v-card-text class="pb-3 pt-1"> <v-card-text class="pb-3 pt-1">
<div class="d-md-flex align-center mb-2" style="gap: 20px"> <div class="d-md-flex align-center mb-2" style="gap: 20px">
<div> <div>
<InputQuantity v-model="listItem.quantity" /> <v-number-input
v-model="listItem.quantity"
hide-details
:label="$t('form.quantity-label-abbreviated')"
:min="0"
:precision="null"
variant="plain"
control-variant="stacked"
inset
density="compact"
class="centered-number-input"
style="width: 100px;"
/>
</div> </div>
<InputLabelType <InputLabelType
v-model="listItem.unit" v-model="listItem.unit"
@@ -158,6 +170,15 @@ export default defineNuxtComponent({
}, },
}); });
watch(
() => props.modelValue.quantity,
() => {
if (!props.modelValue.quantity) {
listItem.value.quantity = 0;
}
},
);
watch( watch(
() => props.modelValue.food, () => props.modelValue.food,
(newFood) => { (newFood) => {
@@ -221,3 +242,10 @@ export default defineNuxtComponent({
}, },
}); });
</script> </script>
<style scoped>
.centered-number-input :deep(.v-field) {
display: flex;
align-items: center;
}
</style>

View File

@@ -1,61 +0,0 @@
<template>
<div
class="d-flex align-center"
style="max-width: 60px"
>
<v-text-field
v-model.number="quantity"
hide-details
:label="$t('form.quantity-label-abbreviated')"
:min="min"
:max="max"
type="number"
variant="plain"
density="compact"
style="width: 60px;"
/>
</div>
</template>
<script lang="ts">
export default defineNuxtComponent({
name: "VInputNumber",
props: {
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 9999,
},
rules: {
type: Array,
default: () => [],
},
step: {
type: Number,
default: 1,
},
modelValue: {
type: Number,
default: 0,
},
},
emits: ["update:modelValue"],
setup(props, context) {
const quantity = computed({
get: () => {
return Number(props.modelValue);
},
set: (val) => {
context.emit("update:modelValue", val);
},
});
return {
quantity,
};
},
});
</script>

View File

@@ -34,7 +34,7 @@
"vue-advanced-cropper": "^2.8.9", "vue-advanced-cropper": "^2.8.9",
"vue-draggable-plus": "^0.6.0", "vue-draggable-plus": "^0.6.0",
"vuetify": "^3.9.7", "vuetify": "^3.9.7",
"vuetify-nuxt-module": "^0.18.8" "vuetify-nuxt-module": "^0.18.9"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "^1.2.0", "@nuxt/eslint": "^1.2.0",

View File

@@ -134,18 +134,22 @@
<v-card> <v-card>
<v-card-text> <v-card-text>
<div> <div>
<v-text-field <v-number-input
v-model="settings.maxMissingFoods" v-model="settings.maxMissingFoods"
type="number" :precision="null"
:min="0"
control-variant="stacked"
inset
hide-details hide-details
hide-spin-buttons
:label="$t('recipe-finder.max-missing-ingredients')" :label="$t('recipe-finder.max-missing-ingredients')"
/> />
<v-text-field <v-number-input
v-model="settings.maxMissingTools" v-model="settings.maxMissingTools"
type="number" :precision="null"
:min="0"
control-variant="stacked"
inset
hide-details hide-details
hide-spin-buttons
:label="$t('recipe-finder.max-missing-tools')" :label="$t('recipe-finder.max-missing-tools')"
class="mt-4" class="mt-4"
/> />

View File

@@ -30,9 +30,11 @@
/> />
<v-card-text> <v-card-text>
<v-text-field <v-number-input
v-model="numberOfDays" v-model="numberOfDays"
type="number" :min="1"
control-variant="stacked"
inset
:label="$t('meal-plan.numberOfDays-label')" :label="$t('meal-plan.numberOfDays-label')"
:hint="$t('meal-plan.numberOfDays-hint')" :hint="$t('meal-plan.numberOfDays-hint')"
persistent-hint persistent-hint

View File

@@ -27,7 +27,6 @@ import type HelpIcon from "@/components/global/HelpIcon.vue";
import type ImageCropper from "@/components/global/ImageCropper.vue"; import type ImageCropper from "@/components/global/ImageCropper.vue";
import type InputColor from "@/components/global/InputColor.vue"; import type InputColor from "@/components/global/InputColor.vue";
import type InputLabelType from "@/components/global/InputLabelType.vue"; import type InputLabelType from "@/components/global/InputLabelType.vue";
import type InputQuantity from "@/components/global/InputQuantity.vue";
import type LanguageDialog from "@/components/global/LanguageDialog.vue"; import type LanguageDialog from "@/components/global/LanguageDialog.vue";
import type MarkdownEditor from "@/components/global/MarkdownEditor.vue"; import type MarkdownEditor from "@/components/global/MarkdownEditor.vue";
import type RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue"; import type RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue";
@@ -69,7 +68,6 @@ declare module "vue" {
ImageCropper: typeof ImageCropper; ImageCropper: typeof ImageCropper;
InputColor: typeof InputColor; InputColor: typeof InputColor;
InputLabelType: typeof InputLabelType; InputLabelType: typeof InputLabelType;
InputQuantity: typeof InputQuantity;
LanguageDialog: typeof LanguageDialog; LanguageDialog: typeof LanguageDialog;
MarkdownEditor: typeof MarkdownEditor; MarkdownEditor: typeof MarkdownEditor;
RecipeJsonEditor: typeof RecipeJsonEditor; RecipeJsonEditor: typeof RecipeJsonEditor;

View File

@@ -11082,7 +11082,7 @@ vite-plugin-vue-tracer@^1.0.1:
pathe "^2.0.3" pathe "^2.0.3"
source-map-js "^1.2.1" source-map-js "^1.2.1"
vite-plugin-vuetify@^2.1.0: vite-plugin-vuetify@^2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/vite-plugin-vuetify/-/vite-plugin-vuetify-2.1.2.tgz#8e28dcb5b20f4635d350010547654cf2b4dad373" resolved "https://registry.yarnpkg.com/vite-plugin-vuetify/-/vite-plugin-vuetify-2.1.2.tgz#8e28dcb5b20f4635d350010547654cf2b4dad373"
integrity sha512-I/wd6QS+DO6lHmuGoi1UTyvvBTQ2KDzQZ9oowJQEJ6OcjWfJnscYXx2ptm6S7fJSASuZT8jGRBL3LV4oS3LpaA== integrity sha512-I/wd6QS+DO6lHmuGoi1UTyvvBTQ2KDzQZ9oowJQEJ6OcjWfJnscYXx2ptm6S7fJSASuZT8jGRBL3LV4oS3LpaA==
@@ -11224,10 +11224,10 @@ vue@^3.4, vue@^3.5.13, vue@^3.5.22:
"@vue/server-renderer" "3.5.22" "@vue/server-renderer" "3.5.22"
"@vue/shared" "3.5.22" "@vue/shared" "3.5.22"
vuetify-nuxt-module@^0.18.8: vuetify-nuxt-module@^0.18.9:
version "0.18.8" version "0.18.9"
resolved "https://registry.yarnpkg.com/vuetify-nuxt-module/-/vuetify-nuxt-module-0.18.8.tgz#04b7b04606cbf22cb9b8317309db6166a8058654" resolved "https://registry.yarnpkg.com/vuetify-nuxt-module/-/vuetify-nuxt-module-0.18.9.tgz#154b8f8e689da4fac3bcb2372ef0e745b8e0b536"
integrity sha512-57J0MgmRTyEX4ZIIXZUyY+UodDi+hoJ9/UnUZQoBbpTc/9WAZyRonaJedIcHLnTBKyIxxWdvtWHkJdosngX3NQ== integrity sha512-jr+Hujsw5U465LKJD1/SQgqJIXuJNbXM0HOp9vPmtPFPlGFwZ4kb1YfUPNmuCEDaSIY6rkb7+W+FEJvt9PQELQ==
dependencies: dependencies:
"@nuxt/kit" "^3.12.4" "@nuxt/kit" "^3.12.4"
defu "^6.1.4" defu "^6.1.4"
@@ -11238,7 +11238,7 @@ vuetify-nuxt-module@^0.18.8:
ufo "^1.5.4" ufo "^1.5.4"
unconfig "^0.5.5" unconfig "^0.5.5"
upath "^2.0.1" upath "^2.0.1"
vite-plugin-vuetify "^2.1.0" vite-plugin-vuetify "^2.1.2"
vuetify "^3.7.0" vuetify "^3.7.0"
vuetify@^3.7.0: vuetify@^3.7.0: