mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-05-12 04:53:31 -04:00
feat: Improve new shopping list UI (#7600)
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -11,18 +11,28 @@
|
||||
>
|
||||
<div class="d-flex flex-column ga-3">
|
||||
<v-card-actions class="pa-0">
|
||||
<InputLabelType
|
||||
v-model="listItem.food"
|
||||
v-model:item-id="listItem.foodId!"
|
||||
:items="foods"
|
||||
:label="rail ? $t('shopping-list.add-item') : $t('shopping-list.food')"
|
||||
:icon="$globals.icons.foods"
|
||||
:style="rail ? 'margin-inline: 3px;' : undefined"
|
||||
:search="rail"
|
||||
create
|
||||
@create="createAssignFood"
|
||||
@focus="rail = false"
|
||||
/>
|
||||
<div class="position-relative" style="flex: 1;">
|
||||
<InputLabelType
|
||||
ref="foodInputRef"
|
||||
v-model="listItem.food"
|
||||
v-model:item-id="listItem.foodId!"
|
||||
:items="foods"
|
||||
:label="rail ? $t('shopping-list.add-item') : $t('shopping-list.food')"
|
||||
:icon="$globals.icons.foods"
|
||||
:style="rail ? 'margin-inline: 3px;' : undefined"
|
||||
:search="rail"
|
||||
:menu-props="{ location: menuDirection }"
|
||||
create
|
||||
@create="createAssignFood"
|
||||
/>
|
||||
<!-- 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>
|
||||
|
||||
Reference in New Issue
Block a user