mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-30 17:53:31 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			144 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <div class="text-center">
 | |
|     <v-menu
 | |
|       offset-y
 | |
|       start
 | |
|       :eager="isMenuContentLoaded"
 | |
|       :bottom="!menuTop"
 | |
|       :nudge-bottom="!menuTop ? '5' : '0'"
 | |
|       :top="menuTop"
 | |
|       :nudge-top="menuTop ? '5' : '0'"
 | |
|       allow-overflow
 | |
|       close-delay="125"
 | |
|       :open-on-hover="$vuetify.display.mdAndUp"
 | |
|       content-class="d-print-none"
 | |
|       @update:model-value="onMenuToggle"
 | |
|     >
 | |
|       <template #activator="{ props: activatorProps }">
 | |
|         <v-btn
 | |
|           icon
 | |
|           :variant="fab ? 'flat' : undefined"
 | |
|           :rounded="fab ? 'circle' : undefined"
 | |
|           :size="fab ? 'small' : undefined"
 | |
|           :color="fab ? 'info' : 'secondary'"
 | |
|           :fab="fab"
 | |
|           v-bind="activatorProps"
 | |
|           @click.prevent
 | |
|           @mouseenter="onHover"
 | |
|         >
 | |
|           <v-icon
 | |
|             :size="!fab ? undefined : 'x-large'"
 | |
|             :color="fab ? 'white' : 'secondary'"
 | |
|           >
 | |
|             {{ icon }}
 | |
|           </v-icon>
 | |
|         </v-btn>
 | |
|       </template>
 | |
| 
 | |
|       <RecipeContextMenuContent
 | |
|         v-if="isMenuContentLoaded"
 | |
|         v-bind="contentProps"
 | |
|         @deleted="$emit('deleted', $event)"
 | |
|       />
 | |
|     </v-menu>
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script setup lang="ts">
 | |
| import type { Recipe } from "~/lib/api/types/recipe";
 | |
| 
 | |
| interface ContextMenuIncludes {
 | |
|   delete?: boolean;
 | |
|   edit?: boolean;
 | |
|   download?: boolean;
 | |
|   duplicate?: boolean;
 | |
|   mealplanner?: boolean;
 | |
|   shoppingList?: boolean;
 | |
|   print?: boolean;
 | |
|   printPreferences?: boolean;
 | |
|   share?: boolean;
 | |
|   recipeActions?: boolean;
 | |
| }
 | |
| 
 | |
| interface ContextMenuItem {
 | |
|   title: string;
 | |
|   icon: string;
 | |
|   color?: string;
 | |
|   event: string;
 | |
|   isPublic: boolean;
 | |
| }
 | |
| 
 | |
| interface Props {
 | |
|   useItems?: ContextMenuIncludes;
 | |
|   appendItems?: ContextMenuItem[];
 | |
|   leadingItems?: ContextMenuItem[];
 | |
|   menuTop?: boolean;
 | |
|   fab?: boolean;
 | |
|   color?: string;
 | |
|   slug: string;
 | |
|   menuIcon?: string | null;
 | |
|   name: string;
 | |
|   recipe?: Recipe;
 | |
|   recipeId: string;
 | |
|   recipeScale?: number;
 | |
| }
 | |
| 
 | |
| const props = withDefaults(defineProps<Props>(), {
 | |
|   useItems: () => ({
 | |
|     delete: true,
 | |
|     edit: true,
 | |
|     download: true,
 | |
|     duplicate: false,
 | |
|     mealplanner: true,
 | |
|     shoppingList: true,
 | |
|     print: true,
 | |
|     printPreferences: true,
 | |
|     share: true,
 | |
|     recipeActions: true,
 | |
|   }),
 | |
|   appendItems: () => [],
 | |
|   leadingItems: () => [],
 | |
|   menuTop: true,
 | |
|   fab: false,
 | |
|   color: "primary",
 | |
|   menuIcon: null,
 | |
|   recipe: undefined,
 | |
|   recipeScale: 1,
 | |
| });
 | |
| 
 | |
| defineEmits<{
 | |
|   [key: string]: any;
 | |
|   deleted: [slug: string];
 | |
| }>();
 | |
| 
 | |
| const { $globals } = useNuxtApp();
 | |
| 
 | |
| const isMenuContentLoaded = ref(false);
 | |
| 
 | |
| const icon = computed(() => {
 | |
|   return props.menuIcon || $globals.icons.dotsVertical;
 | |
| });
 | |
| 
 | |
| // Props to pass to the content component (excluding internal wrapper props)
 | |
| const contentProps = computed(() => {
 | |
|   const { ...rest } = props;
 | |
|   return rest;
 | |
| });
 | |
| 
 | |
| function onHover() {
 | |
|   if (!isMenuContentLoaded.value) {
 | |
|     isMenuContentLoaded.value = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function onMenuToggle(isOpen: boolean) {
 | |
|   if (isOpen && !isMenuContentLoaded.value) {
 | |
|     isMenuContentLoaded.value = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| const RecipeContextMenuContent = defineAsyncComponent(
 | |
|   () => import("./RecipeContextMenuContent.vue"),
 | |
| );
 | |
| </script>
 |