mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	feat: Shopping list UI overhaul - collapsible labels (#4378)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
		| @@ -57,18 +57,22 @@ | |||||||
|       <!-- View By Label --> |       <!-- View By Label --> | ||||||
|       <div v-else> |       <div v-else> | ||||||
|         <div v-for="(value, key) in itemsByLabel" :key="key" class="mb-6"> |         <div v-for="(value, key) in itemsByLabel" :key="key" class="mb-6"> | ||||||
|           <div class="text-left"> |  | ||||||
|           <v-btn |           <v-btn | ||||||
|             :color="getLabelColor(value[0]) ? getLabelColor(value[0]) : '#959595'" |             :color="getLabelColor(value[0]) ? getLabelColor(value[0]) : '#959595'" | ||||||
|             :style="{ |             :style="{ | ||||||
|                 'color': getTextColor(getLabelColor(value[0])), |                 'color': getTextColor(getLabelColor(value[0])), | ||||||
|                 'letter-spacing': 'normal', |                 'letter-spacing': 'normal', | ||||||
|               }" |               }" | ||||||
|  |             @click="toggleShowLabel(key)" | ||||||
|           > |           > | ||||||
|  |             <v-icon> | ||||||
|  |               {{ labelOpenState[key] ? $globals.icons.chevronDown : $globals.icons.chevronRight }} | ||||||
|  |             </v-icon> | ||||||
|             {{ key }} |             {{ key }} | ||||||
|           </v-btn> |           </v-btn> | ||||||
|           </div> |  | ||||||
|         <v-divider/> |         <v-divider/> | ||||||
|  |         <v-expand-transition group> | ||||||
|  |           <div v-show="labelOpenState[key]"> | ||||||
|           <draggable :value="value" handle=".handle" delay="250" :delay-on-touch-only="true" @start="loadingCounter += 1" @end="loadingCounter -= 1" @input="updateIndexUncheckedByLabel(key, $event)"> |           <draggable :value="value" handle=".handle" delay="250" :delay-on-touch-only="true" @start="loadingCounter += 1" @end="loadingCounter -= 1" @input="updateIndexUncheckedByLabel(key, $event)"> | ||||||
|             <v-lazy v-for="(item, index) in value" :key="item.id" class="ml-2 my-2"> |             <v-lazy v-for="(item, index) in value" :key="item.id" class="ml-2 my-2"> | ||||||
|               <ShoppingListItem |               <ShoppingListItem | ||||||
| @@ -85,6 +89,8 @@ | |||||||
|             </v-lazy> |             </v-lazy> | ||||||
|             </draggable> |             </draggable> | ||||||
|           </div> |           </div> | ||||||
|  |         </v-expand-transition> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <!-- Reorder Labels --> |       <!-- Reorder Labels --> | ||||||
| @@ -301,7 +307,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import draggable from "vuedraggable"; | import draggable from "vuedraggable"; | ||||||
|  |  | ||||||
| import { defineComponent, useRoute, computed, ref, toRefs, onUnmounted, useContext, reactive } from "@nuxtjs/composition-api"; | import { defineComponent, useRoute, computed, ref, toRefs, onUnmounted, useContext, reactive, watch } from "@nuxtjs/composition-api"; | ||||||
| import { useIdle, useToggle } from "@vueuse/core"; | import { useIdle, useToggle } from "@vueuse/core"; | ||||||
| import { useCopyList } from "~/composables/use-copy"; | import { useCopyList } from "~/composables/use-copy"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| @@ -463,6 +469,39 @@ export default defineComponent({ | |||||||
|       }; |       }; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // ===================================== | ||||||
|  |     // Collapsables | ||||||
|  |     const labelOpenState = ref<{ [key: string]: boolean }>({}); | ||||||
|  |  | ||||||
|  |     const initializeLabelOpenStates = () => { | ||||||
|  |       if (!shoppingList.value?.listItems) return; | ||||||
|  |  | ||||||
|  |       const existingLabels = new Set(Object.keys(labelOpenState.value)); | ||||||
|  |       let hasChanges = false; | ||||||
|  |  | ||||||
|  |       for (const item of shoppingList.value.listItems) { | ||||||
|  |         const labelName = item.label?.name; | ||||||
|  |         if (labelName && !existingLabels.has(labelName) && !(labelName in labelOpenState.value)) { | ||||||
|  |           labelOpenState.value[labelName] = true; | ||||||
|  |           hasChanges = true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (hasChanges) { | ||||||
|  |         labelOpenState.value = { ...labelOpenState.value }; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const labelNames = computed(() => | ||||||
|  |       new Set(shoppingList.value?.listItems?.map(item => item.label?.name).filter(Boolean) ?? []) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     watch(labelNames, initializeLabelOpenStates, { immediate: true }); | ||||||
|  |  | ||||||
|  |     function toggleShowLabel(key: string) { | ||||||
|  |       labelOpenState.value[key] = !labelOpenState.value[key]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const [showChecked, toggleShowChecked] = useToggle(false); |     const [showChecked, toggleShowChecked] = useToggle(false); | ||||||
|  |  | ||||||
|     // ===================================== |     // ===================================== | ||||||
| @@ -1090,6 +1129,8 @@ export default defineComponent({ | |||||||
|       shoppingList, |       shoppingList, | ||||||
|       showChecked, |       showChecked, | ||||||
|       sortByLabels, |       sortByLabels, | ||||||
|  |       labelOpenState, | ||||||
|  |       toggleShowLabel, | ||||||
|       toggleShowChecked, |       toggleShowChecked, | ||||||
|       uncheckAll, |       uncheckAll, | ||||||
|       openUncheckAll, |       openUncheckAll, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user