mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-27 16:24:31 -04:00
146 lines
3.1 KiB
Vue
146 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"
|
|
@print="$emit('print')"
|
|
@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;
|
|
print: [];
|
|
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>
|