mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-05-11 12:33:32 -04:00
feat: Improve new shopping list UI (#7600)
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -11,7 +11,9 @@
|
||||
>
|
||||
<div class="d-flex flex-column ga-3">
|
||||
<v-card-actions class="pa-0">
|
||||
<div class="position-relative" style="flex: 1;">
|
||||
<InputLabelType
|
||||
ref="foodInputRef"
|
||||
v-model="listItem.food"
|
||||
v-model:item-id="listItem.foodId!"
|
||||
:items="foods"
|
||||
@@ -19,10 +21,18 @@
|
||||
:icon="$globals.icons.foods"
|
||||
:style="rail ? 'margin-inline: 3px;' : undefined"
|
||||
:search="rail"
|
||||
:menu-props="{ location: menuDirection }"
|
||||
create
|
||||
@create="createAssignFood"
|
||||
@focus="rail = false"
|
||||
/>
|
||||
<!-- Intercept clicks when collapsed so the drawer expands before the autocomplete opens -->
|
||||
<div
|
||||
v-if="rail"
|
||||
class="position-absolute"
|
||||
style="inset: 0; cursor: text;"
|
||||
@click="expandAndFocus"
|
||||
/>
|
||||
</div>
|
||||
<BaseButtonGroup
|
||||
v-if="!rail"
|
||||
:buttons="[
|
||||
@@ -84,6 +94,20 @@ defineEmits<{
|
||||
|
||||
const { createAssignFood } = useShoppingListItemEditor(listItem);
|
||||
|
||||
const { smAndDown } = useDisplay();
|
||||
const menuDirection = computed(() => smAndDown.value ? "top" : "bottom");
|
||||
|
||||
const foodInputRef = ref<{ focus: () => void } | null>(null);
|
||||
const rail = ref(true);
|
||||
|
||||
async function expandAndFocus() {
|
||||
rail.value = false;
|
||||
await nextTick();
|
||||
setTimeout(() => {
|
||||
foodInputRef.value?.focus();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => listItem.value.quantity,
|
||||
(newQty) => {
|
||||
@@ -100,6 +124,4 @@ watch(
|
||||
listItem.value.labelId = listItem.value.label?.id || null;
|
||||
},
|
||||
);
|
||||
|
||||
const rail = ref(true);
|
||||
</script>
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
>
|
||||
<v-row
|
||||
v-touch="{
|
||||
move: ({ originalEvent: { touches: [{ screenX }] } }) => {
|
||||
move: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
|
||||
swipeInfo.touchendX = screenX;
|
||||
swipeInfo.touchendY = screenY;
|
||||
},
|
||||
start: ({ originalEvent: { touches: [{ screenX }] } }) => {
|
||||
start: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
|
||||
swipeInfo.touchstartX = screenX;
|
||||
swipeInfo.touchstartY = screenY;
|
||||
},
|
||||
end: () => {
|
||||
if (swiping < SWIPE_THRESHOLD) {
|
||||
@@ -212,6 +214,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const SWIPE_THRESHOLD = 50;
|
||||
const SCROLL_THRESHOLD = 50;
|
||||
|
||||
const { isRtl } = useRtl();
|
||||
const i18n = useI18n();
|
||||
@@ -264,14 +267,22 @@ function save() {
|
||||
edit.value = false;
|
||||
}
|
||||
|
||||
const swipeInfo: Ref<{ touchstartX?: number; touchendX?: number }> = ref({ touchstartX: undefined, touchendX: undefined });
|
||||
const swipeInfo: Ref<{ touchstartX?: number; touchendX?: number; touchstartY?: number; touchendY?: number }> = ref({});
|
||||
const swiping = computed(() => {
|
||||
const { touchstartX, touchendX } = swipeInfo.value ?? {};
|
||||
const { touchstartX, touchendX, touchstartY, touchendY } = swipeInfo.value ?? {};
|
||||
if (touchstartX === undefined || touchendX === undefined) {
|
||||
return 0;
|
||||
}
|
||||
const delta = isRtl.value ? touchstartX - touchendX : touchendX - touchstartX;
|
||||
return Math.min(Math.max(0, delta), 100);
|
||||
const deltaX = isRtl.value ? touchstartX - touchendX : touchendX - touchstartX;
|
||||
|
||||
// If there's significant vertical movement, treat as a scroll gesture and ignore
|
||||
if (touchstartY !== undefined && touchendY !== undefined) {
|
||||
const deltaY = Math.abs(touchendY - touchstartY);
|
||||
if (deltaY > SCROLL_THRESHOLD) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.min(Math.max(0, deltaX), 100);
|
||||
});
|
||||
|
||||
const recipeList = computed<RecipeSummary[]>(() => {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
:items="units"
|
||||
:label="$t('recipe.unit')"
|
||||
:icon="$globals.icons.units"
|
||||
:menu-props="{ location: menuDirection }"
|
||||
style="flex: 3"
|
||||
create
|
||||
@create="createAssignUnit"
|
||||
@@ -35,6 +36,7 @@
|
||||
v-model:item-id="listItem.labelId!"
|
||||
:items="labels"
|
||||
:label="$t('shopping-list.label')"
|
||||
:menu-props="{ location: menuDirection }"
|
||||
style="flex: 1 0 200px"
|
||||
/>
|
||||
<BaseButton
|
||||
@@ -75,6 +77,9 @@ const emit = defineEmits<{ (e: "save"): void }>();
|
||||
|
||||
const { assignLabelToFood, createAssignUnit } = useShoppingListItemEditor(listItem);
|
||||
|
||||
const { smAndDown } = useDisplay();
|
||||
const menuDirection = computed(() => smAndDown.value ? "top" : "bottom");
|
||||
|
||||
function handleNoteKeyPress(event: KeyboardEvent) {
|
||||
// Save on Enter
|
||||
if (!event.shiftKey && event.key === "Enter") {
|
||||
|
||||
@@ -93,4 +93,8 @@ function emitCreate() {
|
||||
emit("create", searchInput.value);
|
||||
autocompleteRef.value?.blur();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
focus: () => autocompleteRef.value?.focus(),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Are you sure you want to uncheck all items?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Are you sure you want to delete all checked items?",
|
||||
"no-shopping-lists-found": "No Shopping Lists Found",
|
||||
"item-checked-off": "{item} was checked off"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "All Recipes",
|
||||
|
||||
@@ -377,6 +377,7 @@ const { store: allUnits } = useUnitStore();
|
||||
const { store: allFoods } = useFoodStore();
|
||||
|
||||
function itemCheckedToast(item: ShoppingListItemOut) {
|
||||
setTimeout(() => {
|
||||
alert.info(
|
||||
i18n.t("shopping-list.item-checked-off", { item: item.food?.name || item.note || i18n.t("recipe.ingredient") }),
|
||||
undefined,
|
||||
@@ -391,6 +392,7 @@ function itemCheckedToast(item: ShoppingListItemOut) {
|
||||
},
|
||||
},
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user