CMenu
Плавающий контейнер контента, который позиционирует себя относительно элемента-активатора. Используется как основа для выпадающих меню, select'ов, тултипов и других overlay-компонентов.
Базовое использование
Показать код
<template>
<c-menu
width="auto"
open-on-click
close-on-click-outside
close-on-content-click
align="bottom"
:offset-y="4"
>
<template #activator="{ on, activator }">
<c-btn class="bg-indigo" v-bind="activator" v-on="on" style="gap:8px">
<c-icon name="fas:folder" source="fa" :size="14" />
File
<c-icon name="fas:chevron-down" source="fa" :size="10" />
</c-btn>
</template>
<c-card class="elevation-4" style="min-width:220px">
<c-card-body class="py-1 px-0">
<c-list>
<c-list-item class="px-4" style="gap:12px" @click="notify('New file')">
<c-icon name="fas:plus" source="fa" :size="13" style="width:14px;opacity:.55" />
<span style="flex:1">Новый файл</span>
<span class="kb">⌘N</span>
</c-list-item>
<c-list-item class="px-4" style="gap:12px" @click="notify('Save')">
<c-icon name="fas:save" source="fa" :size="13" style="width:14px;opacity:.55" />
<span style="flex:1">Сохранить</span>
<span class="kb">⌘S</span>
</c-list-item>
</c-list>
<div class="sep" />
<c-list>
<c-list-item class="px-4" style="gap:12px;color:#f44336" @click="notify('Delete')">
<c-icon name="fas:trash" source="fa" :size="13" style="width:14px" />
Удалить
</c-list-item>
</c-list>
</c-card-body>
</c-card>
</c-menu>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const msg = ref('')
function notify(text: string) {
msg.value = text
}
</script>
<style scoped>
.kb {
font-size: 11px;
color: var(--c-sys-color-on-surface-variant);
}
.sep {
height: 1px;
background: var(--c-sys-color-outline-variant);
margin: 4px 0;
}
</style>Hover
open-on-hover + close-on-leave для меню при наведении. open-delay / close-delay предотвращают случайные срабатывания.
Показать код
<template>
<div class="d-flex align-center gap-1 pa-2 radius-8 elevation-1">
<c-menu
v-for="item in nav"
:key="item.label"
width="auto"
open-on-hover
close-on-leave
align="bottom"
:offset-y="2"
:open-delay="80"
:close-delay="140"
>
<template #activator="{ on, activator }">
<c-btn variant="text" :class="item.color" v-bind="activator" v-on="on" style="gap:6px">
<c-icon :name="item.icon" source="fa" :size="13" />
{{ item.label }}
</c-btn>
</template>
<c-card class="elevation-4" style="min-width:180px">
<c-card-body class="py-1 px-0">
<c-list>
<c-list-item v-for="link in item.links" :key="link.label" class="px-4" style="gap:10px">
<c-icon :name="link.icon" source="fa" :size="12" style="width:14px;opacity:.5" />
{{ link.label }}
</c-list-item>
</c-list>
</c-card-body>
</c-card>
</c-menu>
</div>
</template>
<script setup lang="ts">
const nav = [
{
label: 'Продукты',
icon: 'fas:briefcase',
color: 'text-indigo',
links: [
{ icon: 'fas:list-ul', label: 'UI-компоненты' },
{ icon: 'fas:image', label: 'Иконки' },
],
},
{
label: 'Документы',
icon: 'fas:code',
color: 'text-teal',
links: [
{ icon: 'fas:home', label: 'Начало работы' },
{ icon: 'fas:file', label: 'Компоненты' },
],
},
]
</script>
<style scoped>
.text-indigo {
color: #3f51b5 !important;
}
.text-teal {
color: #009688 !important;
}
</style>Позиционирование
align управляет стороной и выравниванием. offsetX / offsetY добавляют отступ.
Если меню находится внутри элемента-активатора, передай activator="parent" — CMenu привяжет события к родительскому DOM-элементу и не будет рендерить слот activator.
Показать код
<!-- Снизу от активатора -->
<c-menu
width="auto"
align="bottom"
:offset-y="8"
open-on-click
close-on-click-outside
close-on-content-click
>
<template #activator="{ on, activator }">
<c-btn class="bg-indigo" v-bind="activator" v-on="on">Снизу</c-btn>
</template>
<c-card>...</c-card>
</c-menu>
<!-- Справа, с вертикальным центрированием -->
<c-menu width="auto" align="right-center" :offset-x="8" open-on-click close-on-click-outside>
<template #activator="{ on, activator }">
<c-btn class="bg-teal" v-bind="activator" v-on="on">Справа</c-btn>
</template>
<c-card>...</c-card>
</c-menu>Контекстное меню
position-x / position-y привязывают меню к фиксированным координатам вместо активатора.
Показать код
<template>
<div class="area" @contextmenu.prevent="onContextMenu">Правый клик в этой области</div>
<c-menu
v-model="open"
:position-x="x"
:position-y="y"
width="auto"
close-on-click-outside
close-on-content-click
>
<c-card class="elevation-4" style="min-width:200px">
<c-card-body class="py-1 px-0">
<c-list>
<c-list-item class="px-4" style="gap:12px" @click="toast('Открыто')">
<c-icon name="fas:eye" source="fa" :size="13" style="width:14px;opacity:.5" /> Открыть
</c-list-item>
<c-list-item class="px-4" style="gap:12px" @click="toast('Переименовано')">
<c-icon name="fas:pen" source="fa" :size="13" style="width:14px;opacity:.5" />
Переименовать
</c-list-item>
</c-list>
<div class="sep" />
<c-list>
<c-list-item class="px-4" style="gap:12px;color:#f44336" @click="toast('Удалено')">
<c-icon name="fas:trash" source="fa" :size="13" style="width:14px" /> В корзину
</c-list-item>
</c-list>
</c-card-body>
</c-card>
</c-menu>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
const x = ref(0)
const y = ref(0)
function onContextMenu(e: MouseEvent) {
open.value = false
setTimeout(() => {
x.value = e.pageX
y.value = e.pageY
open.value = true
}, 0)
}
function toast(msg: string) {
console.log(msg)
}
</script>
<style scoped>
.area {
padding: 48px;
text-align: center;
border: 2px dashed var(--c-sys-color-outline-variant);
border-radius: 8px;
}
.sep {
height: 1px;
background: var(--c-sys-color-outline-variant);
}
</style>Ширина
По умолчанию CMenu наследует ширину элемента-активатора. Передай width="auto" чтобы контент сам определял ширину, или укажи фиксированное значение.
<!-- Растягивается по ширине активатора (по умолчанию) -->
<c-menu align="bottom">...</c-menu>
<!-- Ширина определяется контентом -->
<c-menu align="bottom" width="auto">...</c-menu>
<!-- Фиксированная ширина -->
<c-menu align="bottom" :width="240">...</c-menu>Стратегии коллизий
strategy="reverse" переворачивает меню при нехватке места. strategy="bounce" удерживает внутри viewport.
<c-menu align="bottom" strategy="reverse" open-on-click>
...
</c-menu>v-model
<template>
<c-menu v-model="open">
<template #activator="{ on, activator }">
<button v-bind="activator" v-on="on">Переключить</button>
</template>
<div>Контент</div>
</c-menu>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>ARIA
CMenu не добавляет ARIA-атрибуты самостоятельно. Передавай role и aria-* напрямую:
<c-menu role="menu" aria-label="Действия" open-on-click>
...
</c-menu>API
Props
modelValuebooleanfalseopenOnClickbooleancloseOnClickbooleanopenOnHoverbooleancloseOnLeavebooleanopenOnFocusbooleancloseOnClickOutsidebooleancloseOnContentClickbooleanalignAlignValuebottom, top-center, right-center)offsetXnumber | stringoffsetYnumber | stringpositionXnumberpositionYnumberstrategy'reverse' | 'bounce'widthnumber | stringheightnumber | stringminWidthnumber | stringmaxWidthnumber | stringminHeightnumber | stringmaxHeightnumber | stringopenDelaynumber | stringcloseDelaynumber | stringtransitionstring'fade'ssrbooleanactivatorElement | ComponentPublicInstance | 'parent' | stringpresetstringCMenuPreset в реестре (зона root, состояния opened / closed). Внутри CSelect / CAutocomplete меню также берёт вложенный CMenuPreset из поля menu пресета комбобокса через контекстCSS-переменные
--c-menu-bg-colorvar(--c-sys-color-surface-container-high)--c-menu-text-colorvar(--c-sys-color-on-surface)--c-menu-border-radiusvar(--c-sys-shape-lg)--c-menu-elevationvar(--c-sys-elevation-3)--c-menu-max-height100%Slots
activator{ on, activator }defaultEvents
update:modelValuebooleanopencloseclickoutside-clickExpose
open() => voidclose() => voidtoggle() => void