Updated context menu demos

pull/4775/head
Cagatay Civici 2023-11-07 10:55:48 +03:00
parent ee7783f1af
commit d9751d36f2
7 changed files with 787 additions and 404 deletions

View File

@ -1,9 +1,9 @@
<template> <template>
<DocSectionText v-bind="$attrs"> <DocSectionText v-bind="$attrs">
<p>ContextMenu requires a collection of menuitems as its <i>model</i> and the <i>show</i> method needs to be called explicity using the <i>contextmenu</i> event of the target to display the menu.</p> <p>ContextMenu requires a collection of menuitems as its <i>model</i> and the <i>show</i> method needs to be called explicity using an event of the target like <i>contextmenu</i> to display the menu.</p>
</DocSectionText> </DocSectionText>
<div class="card flex md:justify-content-center"> <div class="card flex md:justify-content-center">
<img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature3.jpg" class="w-full md:w-auto" @contextmenu="onImageRightClick" aria-haspopup="true" /> <img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature2.jpg" class="w-full md:w-30rem border-round shadow-4" @contextmenu="onImageRightClick" aria-haspopup="true" />
<ContextMenu ref="menu" :model="items" /> <ContextMenu ref="menu" :model="items" />
</div> </div>
<DocSectionCode :code="code" /> <DocSectionCode :code="code" />
@ -14,18 +14,18 @@ export default {
data() { data() {
return { return {
items: [ items: [
{ label: 'View', icon: 'pi pi-fw pi-search' }, { label: 'Copy', icon: 'pi pi-copy' },
{ label: 'Delete', icon: 'pi pi-fw pi-trash' } { label: 'Rename', icon: 'pi pi-file-edit' }
], ],
code: { code: {
basic: ` basic: `
<img alt="Logo" src="/images/nature/nature3.jpg" class="w-full md:w-auto" @contextmenu="onImageRightClick" aria-haspopup="true" /> <img alt="Logo" src="/images/nature/nature2.jpg" class="w-full md:w-30rem border-round shadow-4" @contextmenu="onImageRightClick" aria-haspopup="true" />
<ContextMenu ref="menu" :model="items" /> <ContextMenu ref="menu" :model="items" />
`, `,
options: ` options: `
<template> <template>
<div class="card flex md:justify-content-center"> <div class="card flex md:justify-content-center">
<img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature3.jpg" @contextmenu="onImageRightClick" class="w-full md:w-auto" aria-haspopup="true" /> <img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature2.jpg" @contextmenu="onImageRightClick" class="w-full md:w-30rem border-round shadow-4" aria-haspopup="true" />
<ContextMenu ref="menu" :model="items" /> <ContextMenu ref="menu" :model="items" />
</div> </div>
</template> </template>
@ -35,8 +35,8 @@ export default {
data() { data() {
return { return {
items: [ items: [
{ label: 'View', icon: 'pi pi-fw pi-search' }, { label: 'Copy', icon: 'pi pi-copy' },
{ label: 'Delete', icon: 'pi pi-fw pi-trash' } { label: 'Rename', icon: 'pi pi-file-edit' }
] ]
}; };
}, },
@ -51,7 +51,7 @@ export default {
composition: ` composition: `
<template> <template>
<div class="card"> <div class="card">
<img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature3.jpg" @contextmenu="onImageRightClick" class="w-full md:w-auto" aria-haspopup="true" /> <img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature2.jpg" @contextmenu="onImageRightClick" class="w-full md:w-30rem border-round shadow-4" aria-haspopup="true" />
<ContextMenu ref="menu" :model="items" /> <ContextMenu ref="menu" :model="items" />
</div> </div>
</template> </template>
@ -61,8 +61,8 @@ import { ref } from 'vue';
const menu = ref(); const menu = ref();
const items = ref([ const items = ref([
{ label: 'View', icon: 'pi pi-fw pi-search' }, { label: 'Copy', icon: 'pi pi-copy' },
{ label: 'Delete', icon: 'pi pi-fw pi-trash' } { label: 'Rename', icon: 'pi pi-file-edit' }
]); ]);
const onImageRightClick = (event) => { const onImageRightClick = (event) => {

View File

@ -0,0 +1,269 @@
<template>
<DocSectionText v-bind="$attrs">
<p>ContextMenu requires a collection of menuitems as its <i>model</i> and the <i>show</i> method needs to be called explicity using an event of the target like <i>contextmenu</i> to display the menu.</p>
</DocSectionText>
<div class="card flex md:justify-content-center">
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="user in users"
:key="user.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200 flex align-items-center justify-content-between', { 'border-primary': selectedUser?.id === user.id }]"
@contextmenu="onRightClick($event, user)"
>
<div class="flex align-items-center gap-2">
<img :alt="user.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${user.image}`" style="width: 32px" />
<span class="font-bold">{{ user.name }}</span>
</div>
<Tag :value="user.role" :severity="getBadge(user)" />
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedUser = null" />
</div>
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
selectedUser: null,
users: [
{ id: 0, name: 'Amy Elsner', image: 'amyelsner.png', role: 'Admin' },
{ id: 1, name: 'Anna Fali', image: 'annafali.png', role: 'Member' },
{ id: 2, name: 'Asiya Javayant', image: 'asiyajavayant.png', role: 'Member' },
{ id: 3, name: 'Bernardo Dominic', image: 'bernardodominic.png', role: 'Guest' },
{ id: 4, name: 'Elwin Sharvill', image: 'elwinsharvill.png', role: 'Member' }
],
items: [
{
label: 'Roles',
icon: 'pi pi-users',
items: [
{
label: 'Admin',
command: () => {
this.selectedUser.role = 'Admin';
}
},
{
label: 'Member',
command: () => {
this.selectedUser.role = 'Member';
}
},
{
label: 'Guest',
command: () => {
this.selectedUser.role = 'Guest';
}
}
]
},
{
label: 'Invite',
icon: 'pi pi-user-plus',
command: () => {
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'Invitation sent!', life: 3000 });
}
}
],
code: {
basic: `
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="user in users"
:key="user.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200 flex align-items-center justify-content-between', { 'border-primary': selectedUser?.id === user.id }]"
@contextmenu="onRightClick($event, user)"
>
<div class="flex align-items-center gap-2">
<img :alt="user.name" :src="\`/images/avatar/\${user.image}\`" style="width: 32px" />
<span class="font-bold">{{ user.name }}</span>
</div>
<Tag :value="user.role" :severity="getBadge(user)" />
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedUser = null" />
<Toast />
`,
options: `
<template>
<div class="card flex md:justify-content-center">
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="user in users"
:key="user.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200 flex align-items-center justify-content-between', { 'border-primary': selectedUser?.id === user.id }]"
@contextmenu="onRightClick($event, user)"
>
<div class="flex align-items-center gap-2">
<img :alt="user.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${user.image}\`" style="width: 32px" />
<span class="font-bold">{{ user.name }}</span>
</div>
<Tag :value="user.role" :severity="getBadge(user)" />
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedUser = null" />
<Toast />
</div>
</template>
<script>
export default {
data() {
return {
selectedUser: null,
users: [
{ id: 0, name: 'Amy Elsner', image: 'amyelsner.png', role: 'Admin' },
{ id: 1, name: 'Anna Fali', image: 'annafali.png', role: 'Member' },
{ id: 2, name: 'Asiya Javayant', image: 'asiyajavayant.png', role: 'Member' },
{ id: 3, name: 'Bernardo Dominic', image: 'bernardodominic.png', role: 'Guest' },
{ id: 4, name: 'Elwin Sharvill', image: 'elwinsharvill.png', role: 'Member' }
],
items: [
{
label: 'Roles',
icon: 'pi pi-users',
items: [
{
label: 'Admin',
command: () => {
this.selectedUser.role = 'Admin';
}
},
{
label: 'Member',
command: () => {
this.selectedUser.role = 'Member';
}
},
{
label: 'Guest',
command: () => {
this.selectedUser.role = 'Guest';
}
}
]
},
{
label: 'Invite',
icon: 'pi pi-user-plus',
command: () => {
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'Invitation sent!', life: 3000 });
}
}
]
};
},
methods: {
onRightClick(event, user) {
this.selectedUser = user;
this.$refs.menu.show(event);
},
getBadge(user) {
if (user.role === 'Member') return 'info';
else if (user.role === 'Guest') return 'warning';
else return null;
}
}
};
<\/script>
`,
composition: `
<template>
<div class="card flex md:justify-content-center">
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="user in users"
:key="user.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200 flex align-items-center justify-content-between', { 'border-primary': selectedUser?.id === user.id }]"
@contextmenu="onRightClick($event, user)"
>
<div class="flex align-items-center gap-2">
<img :alt="user.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${user.image}\`" style="width: 32px" />
<span class="font-bold">{{ user.name }}</span>
</div>
<Tag :value="user.role" :severity="getBadge(user)" />
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedUser = null" />
<Toast />
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useToast } from "primevue/usetoast";
const toast = useToast();
const selectedUser = ref();
const menu = ref();
const users = ref([
{ id: 0, name: 'Amy Elsner', image: 'amyelsner.png', role: 'Admin' },
{ id: 1, name: 'Anna Fali', image: 'annafali.png', role: 'Member' },
{ id: 2, name: 'Asiya Javayant', image: 'asiyajavayant.png', role: 'Member' },
{ id: 3, name: 'Bernardo Dominic', image: 'bernardodominic.png', role: 'Guest' },
{ id: 4, name: 'Elwin Sharvill', image: 'elwinsharvill.png', role: 'Member' }
]);
const items = ref([
{
label: 'Roles',
icon: 'pi pi-users',
items: [
{
label: 'Admin',
command: () => {
selectedUser.value.role = 'Admin';
}
},
{
label: 'Member',
command: () => {
selectedUser.value.role = 'Member';
}
},
{
label: 'Guest',
command: () => {
selectedUser.value.role = 'Guest';
}
}
]
},
{
label: 'Invite',
icon: 'pi pi-user-plus',
command: () => {
toast.add({ severity: 'success', summary: 'Success', detail: 'Invitation sent!', life: 3000 });
}
}
]);
const onRightClick = (event, user) => {
selectedUser.value = user;
menu.value.show(event);
};
const getBadge = (user) => {
if (user.role === 'Member') return 'info';
else if (user.role === 'Guest') return 'warning';
else return null;
}
<\/script>
`
}
};
},
methods: {
onRightClick(event, user) {
this.selectedUser = user;
this.$refs.menu.show(event);
},
getBadge(user) {
if (user.role === 'Member') return 'info';
else if (user.role === 'Guest') return 'warning';
else return null;
}
}
};
</script>

View File

@ -0,0 +1,5 @@
<template>
<DocSectionText v-bind="$attrs">
<p>DataTable has built-in support for ContextMenu, see the <NuxtLink to="/datatable/#contextmenu">ContextMenu</NuxtLink> demo for an example.</p>
</DocSectionText>
</template>

View File

@ -15,133 +15,33 @@ export default {
return { return {
items: [ items: [
{ {
label: 'File', label: 'Translate',
icon: 'pi pi-fw pi-file', icon: 'pi pi-language'
items: [
{
label: 'New',
icon: 'pi pi-fw pi-plus',
items: [
{
label: 'Bookmark',
icon: 'pi pi-fw pi-bookmark'
}, },
{ {
label: 'Video', label: 'Speech',
icon: 'pi pi-fw pi-video' icon: 'pi pi-volume-up',
items: [
{
label: 'Start',
icon: 'pi pi-caret-right'
},
{
label: 'Stop',
icon: 'pi pi-pause'
} }
] ]
}, },
{
label: 'Delete',
icon: 'pi pi-fw pi-trash'
},
{ {
separator: true separator: true
}, },
{
label: 'Export',
icon: 'pi pi-fw pi-external-link'
}
]
},
{
label: 'Edit',
icon: 'pi pi-fw pi-pencil',
items: [
{
label: 'Left',
icon: 'pi pi-fw pi-align-left'
},
{
label: 'Right',
icon: 'pi pi-fw pi-align-right'
},
{
label: 'Center',
icon: 'pi pi-fw pi-align-center'
},
{
label: 'Justify',
icon: 'pi pi-fw pi-align-justify'
}
]
},
{
label: 'Users',
icon: 'pi pi-fw pi-user',
items: [
{
label: 'New',
icon: 'pi pi-fw pi-user-plus'
},
{
label: 'Delete',
icon: 'pi pi-fw pi-user-minus'
},
{
label: 'Search',
icon: 'pi pi-fw pi-users',
items: [
{
label: 'Filter',
icon: 'pi pi-fw pi-filter',
items: [
{ {
label: 'Print', label: 'Print',
icon: 'pi pi-fw pi-print' icon: 'pi pi-print'
}
]
},
{
icon: 'pi pi-fw pi-bars',
label: 'List'
}
]
}
]
},
{
label: 'Events',
icon: 'pi pi-fw pi-calendar',
items: [
{
label: 'Edit',
icon: 'pi pi-fw pi-pencil',
items: [
{
label: 'Save',
icon: 'pi pi-fw pi-calendar-plus'
},
{
label: 'Delete',
icon: 'pi pi-fw pi-calendar-minus'
}
]
},
{
label: 'Archive',
icon: 'pi pi-fw pi-calendar-times',
items: [
{
label: 'Remove',
icon: 'pi pi-fw pi-calendar-minus'
}
]
}
]
},
{
separator: true
},
{
label: 'Quit',
icon: 'pi pi-fw pi-power-off'
} }
], ],
code: { code: {
basic: ` basic: `
<p class="mb-0">Right-Click anywhere on this page to view the global ContextMenu.</p>
<ContextMenu global :model="items" /> <ContextMenu global :model="items" />
`, `,
options: ` options: `
@ -158,128 +58,29 @@ export default {
return { return {
items: [ items: [
{ {
label: 'File', label: 'Translate',
icon: 'pi pi-fw pi-file', icon: 'pi pi-language'
items: [
{
label: 'New',
icon: 'pi pi-fw pi-plus',
items: [
{
label: 'Bookmark',
icon: 'pi pi-fw pi-bookmark'
}, },
{ {
label: 'Video', label: 'Speech',
icon: 'pi pi-fw pi-video' icon: 'pi pi-volume-up',
items: [
{
label: 'Start',
icon: 'pi pi-caret-right'
},
{
label: 'Stop',
icon: 'pi pi-pause'
} }
] ]
}, },
{
label: 'Delete',
icon: 'pi pi-fw pi-trash'
},
{ {
separator: true separator: true
}, },
{
label: 'Export',
icon: 'pi pi-fw pi-external-link'
}
]
},
{
label: 'Edit',
icon: 'pi pi-fw pi-pencil',
items: [
{
label: 'Left',
icon: 'pi pi-fw pi-align-left'
},
{
label: 'Right',
icon: 'pi pi-fw pi-align-right'
},
{
label: 'Center',
icon: 'pi pi-fw pi-align-center'
},
{
label: 'Justify',
icon: 'pi pi-fw pi-align-justify'
}
]
},
{
label: 'Users',
icon: 'pi pi-fw pi-user',
items: [
{
label: 'New',
icon: 'pi pi-fw pi-user-plus'
},
{
label: 'Delete',
icon: 'pi pi-fw pi-user-minus'
},
{
label: 'Search',
icon: 'pi pi-fw pi-users',
items: [
{
label: 'Filter',
icon: 'pi pi-fw pi-filter',
items: [
{ {
label: 'Print', label: 'Print',
icon: 'pi pi-fw pi-print' icon: 'pi pi-print'
}
]
},
{
icon: 'pi pi-fw pi-bars',
label: 'List'
}
]
}
]
},
{
label: 'Events',
icon: 'pi pi-fw pi-calendar',
items: [
{
label: 'Edit',
icon: 'pi pi-fw pi-pencil',
items: [
{
label: 'Save',
icon: 'pi pi-fw pi-calendar-plus'
},
{
label: 'Delete',
icon: 'pi pi-fw pi-calendar-minus'
}
]
},
{
label: 'Archive',
icon: 'pi pi-fw pi-calendar-times',
items: [
{
label: 'Remove',
icon: 'pi pi-fw pi-calendar-minus'
}
]
}
]
},
{
separator: true
},
{
label: 'Quit',
icon: 'pi pi-fw pi-power-off'
} }
] ]
}; };
@ -300,128 +101,29 @@ import { ref } from "vue";
const items = ref([ const items = ref([
{ {
label: 'File', label: 'Translate',
icon: 'pi pi-fw pi-file', icon: 'pi pi-language'
items: [
{
label: 'New',
icon: 'pi pi-fw pi-plus',
items: [
{
label: 'Bookmark',
icon: 'pi pi-fw pi-bookmark'
}, },
{ {
label: 'Video', label: 'Speech',
icon: 'pi pi-fw pi-video' icon: 'pi pi-volume-up',
items: [
{
label: 'Start',
icon: 'pi pi-caret-right'
},
{
label: 'Stop',
icon: 'pi pi-pause'
} }
] ]
}, },
{
label: 'Delete',
icon: 'pi pi-fw pi-trash'
},
{ {
separator: true separator: true
}, },
{
label: 'Export',
icon: 'pi pi-fw pi-external-link'
}
]
},
{
label: 'Edit',
icon: 'pi pi-fw pi-pencil',
items: [
{
label: 'Left',
icon: 'pi pi-fw pi-align-left'
},
{
label: 'Right',
icon: 'pi pi-fw pi-align-right'
},
{
label: 'Center',
icon: 'pi pi-fw pi-align-center'
},
{
label: 'Justify',
icon: 'pi pi-fw pi-align-justify'
}
]
},
{
label: 'Users',
icon: 'pi pi-fw pi-user',
items: [
{
label: 'New',
icon: 'pi pi-fw pi-user-plus'
},
{
label: 'Delete',
icon: 'pi pi-fw pi-user-minus'
},
{
label: 'Search',
icon: 'pi pi-fw pi-users',
items: [
{
label: 'Filter',
icon: 'pi pi-fw pi-filter',
items: [
{ {
label: 'Print', label: 'Print',
icon: 'pi pi-fw pi-print' icon: 'pi pi-print'
}
]
},
{
icon: 'pi pi-fw pi-bars',
label: 'List'
}
]
}
]
},
{
label: 'Events',
icon: 'pi pi-fw pi-calendar',
items: [
{
label: 'Edit',
icon: 'pi pi-fw pi-pencil',
items: [
{
label: 'Save',
icon: 'pi pi-fw pi-calendar-plus'
},
{
label: 'Delete',
icon: 'pi pi-fw pi-calendar-minus'
}
]
},
{
label: 'Archive',
icon: 'pi pi-fw pi-calendar-times',
items: [
{
label: 'Remove',
icon: 'pi pi-fw pi-calendar-minus'
}
]
}
]
},
{
separator: true
},
{
label: 'Quit',
icon: 'pi pi-fw pi-power-off'
} }
]); ]);
<\/script> <\/script>

View File

@ -1,28 +1,36 @@
<template> <template>
<DocSectionText v-bind="$attrs"> <DocSectionText v-bind="$attrs">
<p> <p>Items with navigation are defined with templating to be able to use a router link component, an external link or programmatic navigation.</p>
Since v3.33.0 the vue-router dependency of menu components is deprecated and templating should be used to define router links instead. This approach provides flexibility to be able to use any kind of router link component such as
<i>NuxtLink</i> or <i>router-link</i>. Here is an example with vue-router.
</p>
</DocSectionText> </DocSectionText>
<div class="card flex md:justify-content-center"> <div class="card flex justify-content-center">
<img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature3.jpg" class="w-full md:w-auto" @contextmenu="onImageRightClick" aria-haspopup="true" /> <span class="inline-flex align-items-center justify-content-center border-2 border-primary border-round w-4rem h-4rem" @contextmenu="onRightClick" aria-haspopup="true">
<svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M25.87 18.05L23.16 17.45L25.27 20.46V29.78L32.49 23.76V13.53L29.18 14.73L25.87 18.04V18.05ZM25.27 35.49L29.18 31.58V27.67L25.27 30.98V35.49ZM20.16 17.14H20.03H20.17H20.16ZM30.1 5.19L34.89 4.81L33.08 12.33L24.1 15.67L30.08 5.2L30.1 5.19ZM5.72 14.74L2.41 13.54V23.77L9.63 29.79V20.47L11.74 17.46L9.03 18.06L5.72 14.75V14.74ZM9.63 30.98L5.72 27.67V31.58L9.63 35.49V30.98ZM4.8 5.2L10.78 15.67L1.81 12.33L0 4.81L4.79 5.19L4.8 5.2ZM24.37 21.05V34.59L22.56 37.29L20.46 39.4H14.44L12.34 37.29L10.53 34.59V21.05L12.42 18.23L17.45 26.8L22.48 18.23L24.37 21.05ZM22.85 0L22.57 0.69L17.45 13.08L12.33 0.69L12.05 0H22.85Z"
fill="var(--primary-color)"
/>
<path
d="M30.69 4.21L24.37 4.81L22.57 0.69L22.86 0H26.48L30.69 4.21ZM23.75 5.67L22.66 3.08L18.05 14.24V17.14H19.7H20.03H20.16H20.2L24.1 15.7L30.11 5.19L23.75 5.67ZM4.21002 4.21L10.53 4.81L12.33 0.69L12.05 0H8.43002L4.22002 4.21H4.21002ZM21.9 17.4L20.6 18.2H14.3L13 17.4L12.4 18.2L12.42 18.23L17.45 26.8L22.48 18.23L22.5 18.2L21.9 17.4ZM4.79002 5.19L10.8 15.7L14.7 17.14H14.74H15.2H16.85V14.24L12.24 3.09L11.15 5.68L4.79002 5.2V5.19Z"
fill="var(--text-color)"
/>
</svg>
</span>
<ContextMenu ref="routemenu" :model="items"> <ContextMenu ref="routemenu" :model="items">
<template #item="{ label, item, props }"> <template #item="{ item, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom> <router-link v-if="item.route" v-slot="{ href, navigate }" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="routerProps.navigate"> <a v-ripple :href="href" v-bind="props.action" @click="navigate">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</router-link> </router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action"> <a v-else v-ripple :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</template> </template>
</ContextMenu> </ContextMenu>
</div> </div>
<DocSectionCode :code="code" /> <DocSectionCode :code="code" hideStackBlitz hideCodeSandbox />
</template> </template>
<script> <script>
@ -30,48 +38,73 @@ export default {
data() { data() {
return { return {
items: [ items: [
{ label: 'View', icon: 'pi pi-fw pi-search' },
{ label: 'Delete', icon: 'pi pi-fw pi-trash' },
{ separator: true },
{ {
label: 'Upload', label: 'Router Link',
icon: 'pi pi-upload', icon: 'pi pi-palette',
route: '/fileupload' route: '/unstyled'
},
{
label: 'Programmatic',
icon: 'pi pi-link',
command: () => {
this.$router.push('/installation');
}
},
{
label: 'External',
icon: 'pi pi-home',
url: 'https://vuejs.org/'
} }
], ],
code: { code: {
basic: ` basic: `
<img alt="Logo" src="/images/nature/nature3.jpg" class="w-full md:w-auto" @contextmenu="onImageRightClick" aria-haspopup="true" /> <span class="inline-flex align-items-center justify-content-center border-2 border-primary border-round w-4rem h-4rem" @contextmenu="onRightClick" aria-haspopup="true">
<svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="..." fill="var(--primary-color)" />
<path d="..." fill="var(--text-color)" />
</svg>
</span>
<ContextMenu ref="routemenu" :model="items"> <ContextMenu ref="routemenu" :model="items">
<template #item="{ label, item, props }"> <template #item="{ item, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom> <router-link v-if="item.route" v-slot="{ href, navigate }" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="routerProps.navigate"> <a v-ripple :href="href" v-bind="props.action" @click="navigate">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</router-link> </router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action"> <a v-else v-ripple :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</template> </template>
</ContextMenu> </ContextMenu>
`, `,
options: ` options: `
<template> <template>
<div class="card flex md:justify-content-center"> <div class="card flex justify-content-center">
<img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature3.jpg" @contextmenu="onImageRightClick" class="w-full md:w-auto" aria-haspopup="true" /> <span class="inline-flex align-items-center justify-content-center border-2 border-primary border-round w-4rem h-4rem" @contextmenu="onRightClick" aria-haspopup="true">
<svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M25.87 18.05L23.16 17.45L25.27 20.46V29.78L32.49 23.76V13.53L29.18 14.73L25.87 18.04V18.05ZM25.27 35.49L29.18 31.58V27.67L25.27 30.98V35.49ZM20.16 17.14H20.03H20.17H20.16ZM30.1 5.19L34.89 4.81L33.08 12.33L24.1 15.67L30.08 5.2L30.1 5.19ZM5.72 14.74L2.41 13.54V23.77L9.63 29.79V20.47L11.74 17.46L9.03 18.06L5.72 14.75V14.74ZM9.63 30.98L5.72 27.67V31.58L9.63 35.49V30.98ZM4.8 5.2L10.78 15.67L1.81 12.33L0 4.81L4.79 5.19L4.8 5.2ZM24.37 21.05V34.59L22.56 37.29L20.46 39.4H14.44L12.34 37.29L10.53 34.59V21.05L12.42 18.23L17.45 26.8L22.48 18.23L24.37 21.05ZM22.85 0L22.57 0.69L17.45 13.08L12.33 0.69L12.05 0H22.85Z"
fill="var(--primary-color)"
/>
<path
d="M30.69 4.21L24.37 4.81L22.57 0.69L22.86 0H26.48L30.69 4.21ZM23.75 5.67L22.66 3.08L18.05 14.24V17.14H19.7H20.03H20.16H20.2L24.1 15.7L30.11 5.19L23.75 5.67ZM4.21002 4.21L10.53 4.81L12.33 0.69L12.05 0H8.43002L4.22002 4.21H4.21002ZM21.9 17.4L20.6 18.2H14.3L13 17.4L12.4 18.2L12.42 18.23L17.45 26.8L22.48 18.23L22.5 18.2L21.9 17.4ZM4.79002 5.19L10.8 15.7L14.7 17.14H14.74H15.2H16.85V14.24L12.24 3.09L11.15 5.68L4.79002 5.2V5.19Z"
fill="var(--text-color)"
/>
</svg>
</span>
<ContextMenu ref="routemenu" :model="items"> <ContextMenu ref="routemenu" :model="items">
<template #item="{ label, item, props }"> <template #item="{ item, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom> <router-link v-if="item.route" v-slot="{ href, navigate }" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="routerProps.navigate"> <a v-ripple :href="href" v-bind="props.action" @click="navigate">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</router-link> </router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action"> <a v-else v-ripple :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</template> </template>
</ContextMenu> </ContextMenu>
@ -83,18 +116,28 @@ export default {
data() { data() {
return { return {
items: [ items: [
{ label: 'View', icon: 'pi pi-fw pi-search' },
{ label: 'Delete', icon: 'pi pi-fw pi-trash' },
{ {
label: 'Upload', label: 'Router Link',
icon: 'pi pi-upload', icon: 'pi pi-palette',
route: '/fileupload' route: '/unstyled'
},
{
label: 'Programmatic',
icon: 'pi pi-link',
command: () => {
this.$router.push('/installation');
}
},
{
label: 'External',
icon: 'pi pi-home',
url: 'https://vuejs.org/'
} }
] ]
}; };
}, },
methods: { methods: {
onImageRightClick(event) { onRightClick(event) {
this.$refs.routemenu.show(event); this.$refs.routemenu.show(event);
} }
} }
@ -103,19 +146,30 @@ export default {
`, `,
composition: ` composition: `
<template> <template>
<div class="card"> <div class="card flex justify-content-center">
<img alt="Logo" src="https://primefaces.org/cdn/primevue/images/nature/nature3.jpg" @contextmenu="onImageRightClick" class="w-full md:w-auto" aria-haspopup="true" /> <span class="inline-flex align-items-center justify-content-center border-2 border-primary border-round w-4rem h-4rem" @contextmenu="onRightClick" aria-haspopup="true">
<svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M25.87 18.05L23.16 17.45L25.27 20.46V29.78L32.49 23.76V13.53L29.18 14.73L25.87 18.04V18.05ZM25.27 35.49L29.18 31.58V27.67L25.27 30.98V35.49ZM20.16 17.14H20.03H20.17H20.16ZM30.1 5.19L34.89 4.81L33.08 12.33L24.1 15.67L30.08 5.2L30.1 5.19ZM5.72 14.74L2.41 13.54V23.77L9.63 29.79V20.47L11.74 17.46L9.03 18.06L5.72 14.75V14.74ZM9.63 30.98L5.72 27.67V31.58L9.63 35.49V30.98ZM4.8 5.2L10.78 15.67L1.81 12.33L0 4.81L4.79 5.19L4.8 5.2ZM24.37 21.05V34.59L22.56 37.29L20.46 39.4H14.44L12.34 37.29L10.53 34.59V21.05L12.42 18.23L17.45 26.8L22.48 18.23L24.37 21.05ZM22.85 0L22.57 0.69L17.45 13.08L12.33 0.69L12.05 0H22.85Z"
fill="var(--primary-color)"
/>
<path
d="M30.69 4.21L24.37 4.81L22.57 0.69L22.86 0H26.48L30.69 4.21ZM23.75 5.67L22.66 3.08L18.05 14.24V17.14H19.7H20.03H20.16H20.2L24.1 15.7L30.11 5.19L23.75 5.67ZM4.21002 4.21L10.53 4.81L12.33 0.69L12.05 0H8.43002L4.22002 4.21H4.21002ZM21.9 17.4L20.6 18.2H14.3L13 17.4L12.4 18.2L12.42 18.23L17.45 26.8L22.48 18.23L22.5 18.2L21.9 17.4ZM4.79002 5.19L10.8 15.7L14.7 17.14H14.74H15.2H16.85V14.24L12.24 3.09L11.15 5.68L4.79002 5.2V5.19Z"
fill="var(--text-color)"
/>
</svg>
</span>
<ContextMenu ref="routemenu" :model="items"> <ContextMenu ref="routemenu" :model="items">
<template #item="{ label, item, props }"> <template #item="{ item, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom> <router-link v-if="item.route" v-slot="{ href, navigate }" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="routerProps.navigate"> <a v-ripple :href="href" v-bind="props.action" @click="navigate">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</router-link> </router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action"> <a v-else v-ripple :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.icon" /> <span :class="item.icon" />
<span v-bind="props.label">{{ label }}</span> <span class="ml-2">{{ item.label }}</span>
</a> </a>
</template> </template>
</ContextMenu> </ContextMenu>
@ -124,29 +178,41 @@ export default {
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from "vue";
const router = useRouter();
const routemenu = ref(); const routemenu = ref();
const items = ref([ const items = ref([
{ label: 'View', icon: 'pi pi-fw pi-search' },
{ label: 'Delete', icon: 'pi pi-fw pi-trash' },
{ {
label: 'Upload', label: 'Router Link',
icon: 'pi pi-upload', icon: 'pi pi-palette',
route: '/fileupload' route: '/unstyled'
},
{
label: 'Programmatic',
icon: 'pi pi-link',
command: () => {
router.push('/installation');
}
},
{
label: 'External',
icon: 'pi pi-home',
url: 'https://vuejs.org/'
} }
]); ]);
const onImageRightClick = (event) => { const onRightClick = (event) => {
routemenu.value.show(event); routemenu.value.show(event);
}; };
<\/script> <\/script>
` `
} }
}; };
}, },
methods: { methods: {
onImageRightClick(event) { onRightClick(event) {
this.$refs.routemenu.show(event); this.$refs.routemenu.show(event);
} }
} }

View File

@ -0,0 +1,323 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
ContextMenu offers item customization with the <i>item</i> template that receives the menuitem instance from the model as a parameter. When item templating is used, bind the <i>action</i> prop from the slot props to handle accessibility
and pass through attributes.
</p>
</DocSectionText>
<div class="card flex md:justify-content-center">
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="product in products"
:key="product.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200', { 'border-primary': selectedId === product.id }]"
@contextmenu="onRightClick($event, product.id)"
>
<div class="flex flex-wrap p-2 align-items-center gap-3">
<img class="w-4rem shadow-2 flex-shrink-0 border-round" :src="'https://primefaces.org/cdn/primevue/images/product/' + product.image" :alt="product.name" />
<div class="flex-1 flex flex-column gap-1">
<span class="font-bold">{{ product.name }}</span>
<div class="flex align-items-center gap-2">
<i class="pi pi-tag text-sm"></i>
<span>{{ product.category }}</span>
</div>
</div>
<span class="font-bold text-900 ml-5">${{ product.price }}</span>
</div>
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedId = null">
<template #item="{ item, props }">
<a v-ripple class="flex align-items-center" v-bind="props.action">
<span :class="item.icon" />
<span class="ml-2">{{ item.label }}</span>
<Badge v-if="item.badge" class="ml-auto" :value="item.badge" />
<span v-if="item.shortcut" class="ml-auto border-1 surface-border border-round surface-100 text-xs p-1">{{ item.shortcut }}</span>
<i v-if="item.items" class="pi pi-angle-right ml-auto text-primary"></i>
</a>
</template>
</ContextMenu>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedId: null,
items: [
{
label: 'Favorite',
icon: 'pi pi-star',
shortcut: '⌘+D'
},
{
label: 'Add',
icon: 'pi pi-shopping-cart',
shortcut: '⌘+A'
},
{
separator: true
},
{
label: 'Share',
icon: 'pi pi-share-alt',
items: [
{
label: 'Whatsapp',
icon: 'pi pi-whatsapp',
badge: 2
},
{
label: 'Instagram',
icon: 'pi pi-instagram',
badge: 3
}
]
}
],
code: {
basic: `
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="product in products"
:key="product.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200', { 'border-primary': selectedId === product.id }]"
@contextmenu="onRightClick($event, product.id)"
>
<div class="flex flex-wrap p-2 align-items-center gap-3">
<img class="w-4rem shadow-2 flex-shrink-0 border-round" :src="'/images/product/' + product.image" :alt="product.name" />
<div class="flex-1 flex flex-column gap-1">
<span class="font-bold">{{ product.name }}</span>
<div class="flex align-items-center gap-2">
<i class="pi pi-tag text-sm"></i>
<span>{{ product.category }}</span>
</div>
</div>
<span class="font-bold text-900 ml-5">\${{ product.price }}</span>
</div>
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedId = null">
<template #item="{ item, props }">
<a v-ripple class="flex align-items-center" v-bind="props.action">
<span :class="item.icon" />
<span class="ml-2">{{ item.label }}</span>
<Badge v-if="item.badge" class="ml-auto" :value="item.badge" />
<span v-if="item.shortcut" class="ml-auto border-1 surface-border border-round surface-100 text-xs p-1">{{ item.shortcut }}</span>
<i v-if="item.items" class="pi pi-angle-right ml-auto text-primary"></i>
</a>
</template>
</ContextMenu>
`,
options: `
<template>
<div class="card flex md:justify-content-center">
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem">
<li
v-for="product in products"
:key="product.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200', { 'border-primary': selectedId === product.id }]"
@contextmenu="onRightClick($event, product.id)"
>
<div class="flex flex-wrap p-2 align-items-center gap-3">
<img class="w-4rem shadow-2 flex-shrink-0 border-round" :src="'https://primefaces.org/cdn/primevue/images/product/' + product.image" :alt="product.name" />
<div class="flex-1 flex flex-column gap-1">
<span class="font-bold">{{ product.name }}</span>
<div class="flex align-items-center gap-2">
<i class="pi pi-tag text-sm"></i>
<span>{{ product.category }}</span>
</div>
</div>
<span class="font-bold text-900 ml-5">\${{ product.price }}</span>
</div>
</li>
</ul>
<ContextMenu ref="menu" :model="items" @hide="selectedId = null">
<template #item="{ item, props }">
<a v-ripple class="flex align-items-center" v-bind="props.action">
<span :class="item.icon" />
<span class="ml-2">{{ item.label }}</span>
<Badge v-if="item.badge" class="ml-auto" :value="item.badge" />
<span v-if="item.shortcut" class="ml-auto border-1 surface-border border-round surface-100 text-xs p-1">{{ item.shortcut }}</span>
<i v-if="item.items" class="pi pi-angle-right ml-auto text-primary"></i>
</a>
</template>
</ContextMenu>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedId: null,
items: [
{
label: 'Favorite',
icon: 'pi pi-star',
shortcut: '⌘+D'
},
{
label: 'Add',
icon: 'pi pi-shopping-cart',
shortcut: '⌘+A'
},
{
separator: true
},
{
label: 'Share',
icon: 'pi pi-share-alt',
items: [
{
label: 'Whatsapp',
icon: 'pi pi-whatsapp',
badge: 2
},
{
label: 'Instagram',
icon: 'pi pi-instagram',
badge: 3
}
]
}
]
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
},
methods: {
onRightClick(event, id) {
this.selectedId = id;
this.$refs.menu.show(event);
}
}
};
<\/script>
`,
composition: `
<template>
<div class="card flex md:justify-content-center">
<ul class="m-0 p-0 list-none border-1 surface-border border-round p-3 flex flex-column gap-2 w-full md:w-30rem" @hide="selectedId = null">
<li
v-for="product in products"
:key="product.id"
:class="['p-2 hover:surface-hover border-round border-1 border-transparent transition-all transition-duration-200', { 'border-primary': selectedId === product.id }]"
@contextmenu="onRightClick($event, product.id)"
>
<div class="flex flex-wrap p-2 align-items-center gap-3">
<img class="w-4rem shadow-2 flex-shrink-0 border-round" :src="'https://primefaces.org/cdn/primevue/images/product/' + product.image" :alt="product.name" />
<div class="flex-1 flex flex-column gap-1">
<span class="font-bold">{{ product.name }}</span>
<div class="flex align-items-center gap-2">
<i class="pi pi-tag text-sm"></i>
<span>{{ product.category }}</span>
</div>
</div>
<span class="font-bold text-900 ml-5">\${{ product.price }}</span>
</div>
</li>
</ul>
<ContextMenu ref="menu" :model="items">
<template #item="{ item, props }">
<a v-ripple class="flex align-items-center" v-bind="props.action">
<span :class="item.icon" />
<span class="ml-2">{{ item.label }}</span>
<Badge v-if="item.badge" class="ml-auto" :value="item.badge" />
<span v-if="item.shortcut" class="ml-auto border-1 surface-border border-round surface-100 text-xs p-1">{{ item.shortcut }}</span>
<i v-if="item.items" class="pi pi-angle-right ml-auto text-primary"></i>
</a>
</template>
</ContextMenu>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService'
const menu = ref();
const items = ref([
{
label: 'Favorite',
icon: 'pi pi-star',
shortcut: '⌘+D'
},
{
label: 'Add',
icon: 'pi pi-shopping-cart',
shortcut: '⌘+A'
},
{
separator: true
},
{
label: 'Share',
icon: 'pi pi-share-alt',
items: [
{
label: 'Whatsapp',
icon: 'pi pi-whatsapp',
badge: 2
},
{
label: 'Instagram',
icon: 'pi pi-instagram',
badge: 3
}
]
}
]);
const products = ref(null);
const selectedId = ref(null);
onMounted(() => {
ProductService.getProductsSmall().then((data) => (products.value = data));
});
const onRightClick = (event, id) => {
selectedId.value = id;
menu.value.show(event);
};
<\/script>
`
},
data: `
/* ProductService */
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...`
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRightClick(event, id) {
this.selectedId = id;
this.$refs.menu.show(event);
}
}
};
</script>

View File

@ -2,7 +2,7 @@
<DocComponent <DocComponent
title="Vue ContextMenu Component" title="Vue ContextMenu Component"
header="ContextMenu" header="ContextMenu"
description="ContextMenu displays an overlay menu on right click of its target." description="ContextMenu displays an overlay menu to display actions related to an element."
:componentDocs="docs" :componentDocs="docs"
:apiDocs="['ContextMenu', 'MenuItem']" :apiDocs="['ContextMenu', 'MenuItem']"
:ptTabComponent="ptComponent" :ptTabComponent="ptComponent"
@ -13,9 +13,12 @@
<script> <script>
import AccessibilityDoc from '@/doc/contextmenu/AccessibilityDoc.vue'; import AccessibilityDoc from '@/doc/contextmenu/AccessibilityDoc.vue';
import BasicDoc from '@/doc/contextmenu/BasicDoc.vue'; import BasicDoc from '@/doc/contextmenu/BasicDoc.vue';
import CommandDoc from '@/doc/contextmenu/CommandDoc.vue';
import DataTableDoc from '@/doc/contextmenu/DataTableDoc.vue';
import DocumentDoc from '@/doc/contextmenu/DocumentDoc.vue'; import DocumentDoc from '@/doc/contextmenu/DocumentDoc.vue';
import ImportDoc from '@/doc/contextmenu/ImportDoc.vue'; import ImportDoc from '@/doc/contextmenu/ImportDoc.vue';
import RouterDoc from '@/doc/contextmenu/RouterDoc.vue'; import RouterDoc from '@/doc/contextmenu/RouterDoc.vue';
import TemplateDoc from '@/doc/contextmenu/TemplateDoc.vue';
import PTComponent from '@/doc/contextmenu/pt/index.vue'; import PTComponent from '@/doc/contextmenu/pt/index.vue';
import ThemingDoc from '@/doc/contextmenu/theming/index.vue'; import ThemingDoc from '@/doc/contextmenu/theming/index.vue';
@ -38,11 +41,26 @@ export default {
label: 'Document', label: 'Document',
component: DocumentDoc component: DocumentDoc
}, },
{
id: 'template',
label: 'Template',
component: TemplateDoc
},
{
id: 'command',
label: 'Command',
component: CommandDoc
},
{ {
id: 'router', id: 'router',
label: 'Router', label: 'Router',
component: RouterDoc component: RouterDoc
}, },
{
id: 'datatable',
label: 'DataTable',
component: DataTableDoc
},
{ {
id: 'accessibility', id: 'accessibility',
label: 'Accessibility', label: 'Accessibility',