Выпадающее меню
Раскрывающееся вниз командное меню. Триггером может быть любой элемент; пункты меню идентифицируются через command, а после нажатия значение выбрасывается событием command. Реализовано по паттерну меню WAI-ARIA: циклическая навигация стрелками, Home/End — в начало/конец, Enter/Space — активация, Esc — закрытие с возвратом фокуса.
Базовое использование
Последняя команда: —
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || 'Ещё действия' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">Создать</LhDropdownItem>
<LhDropdownItem command="edit">Редактировать</LhDropdownItem>
<LhDropdownItem command="delete">Удалить</LhDropdownItem>
</template>
</LhDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const lastCommand = ref('')
// Текст триггера поддерживается пользователем: событие command лишь выбрасывает значение команды,
// а то, как отображать подпись, решает внешняя сторона
const labelMap: Record<string, string> = { new: 'Создать', edit: 'Редактировать', delete: 'Удалить' }
const selectedLabel = ref('')
function onCommand(command: string | number | undefined) {
lastCommand.value = String(command)
selectedLabel.value = labelMap[String(command)] ?? String(command)
}
</script><script>
export default {
data() {
return {
lastCommand: '',
// Текст триггера поддерживается пользователем: событие command лишь выбрасывает значение команды,
// а то, как отображать подпись, решает внешняя сторона
labelMap: { new: 'Создать', edit: 'Редактировать', delete: 'Удалить' },
selectedLabel: '',
}
},
methods: {
onCommand(command) {
this.lastCommand = String(command)
this.selectedLabel = this.labelMap[String(command)] ?? String(command)
},
},
}
</script>
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || 'Ещё действия' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">Создать</LhDropdownItem>
<LhDropdownItem command="edit">Редактировать</LhDropdownItem>
<LhDropdownItem command="delete">Удалить</LhDropdownItem>
</template>
</LhDropdown>
</template>Способ запуска
По умолчанию click; также можно настроить раскрытие по наведению или по правой кнопке мыши.
<script setup lang="ts">
import { ref } from 'vue'
const hoverLabel = ref('')
function onHoverCommand(command: string | number | undefined) {
hoverLabel.value = String(command)
}
const contextLabel = ref('')
function onContextCommand(command: string | number | undefined) {
contextLabel.value = String(command)
}
</script>
<template>
<!-- Запуск по наведению -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || 'Наведение мыши' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">Пункт 1</LhDropdownItem>
<LhDropdownItem command="hover-2">Пункт 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- Запуск по правой кнопке мыши -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || 'Правая кнопка мыши' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">Обновить</LhDropdownItem>
<LhDropdownItem command="ctx-2">Копировать</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { hoverLabel: '', contextLabel: '' }
},
methods: {
onHoverCommand(command) {
this.hoverLabel = String(command)
},
onContextCommand(command) {
this.contextLabel = String(command)
},
},
}
</script>
<template>
<!-- Запуск по наведению -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || 'Наведение мыши' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">Пункт 1</LhDropdownItem>
<LhDropdownItem command="hover-2">Пункт 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- Запуск по правой кнопке мыши -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || 'Правая кнопка мыши' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">Обновить</LhDropdownItem>
<LhDropdownItem command="ctx-2">Копировать</LhDropdownItem>
</template>
</LhDropdown>
</template>Триггер
contextmenuблокирует стандартное контекстное меню браузера.
Блокировка и разделители
<script setup lang="ts">
import { ref } from 'vue'
const actionLabel = ref('')
function onActionCommand(command: string | number | undefined) {
actionLabel.value = String(command)
}
</script>
<template>
<!-- Меню действий с заблокированным пунктом и разделителем -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || 'Действие' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">Экспорт</LhDropdownItem>
<LhDropdownItem command="import">Импорт</LhDropdownItem>
<LhDropdownItem divided command="audit">Журнал аудита</LhDropdownItem>
<LhDropdownItem disabled command="purge">Очистить данные</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { actionLabel: '' }
},
methods: {
onActionCommand(command) {
this.actionLabel = String(command)
},
},
}
</script>
<template>
<!-- Меню действий с заблокированным пунктом и разделителем -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || 'Действие' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">Экспорт</LhDropdownItem>
<LhDropdownItem command="import">Импорт</LhDropdownItem>
<LhDropdownItem divided command="audit">Журнал аудита</LhDropdownItem>
<LhDropdownItem disabled command="purge">Очистить данные</LhDropdownItem>
</template>
</LhDropdown>
</template>Заблокированный пункт всё же может получать фокус (
aria-disabled="true"), и программа чтения с экрана его озвучивает; однако стрелки его пропускают, а нажатие и Enter/Space не вызываютcommand.
Позиция и максимальная высота
<script setup lang="ts">
import { ref } from 'vue'
const itemLabel = ref('')
function onItemCommand(command: string | number | undefined) {
itemLabel.value = `Пункт ${command}`
}
</script>
<template>
<!-- Выравнивание по нижнему правому краю с ограничением максимальной высоты -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || 'Нижний правый край' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">Пункт {{ i }}</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { itemLabel: '' }
},
methods: {
onItemCommand(command) {
this.itemLabel = `Пункт ${command}`
},
},
}
</script>
<template>
<!-- Выравнивание по нижнему правому краю с ограничением максимальной высоты -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || 'Нижний правый край' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">Пункт {{ i }}</LhDropdownItem>
</template>
</LhDropdown>
</template>Управляемое отображение и поведение при нажатии
v-model:visible управляет видимостью вручную; hide-on-click определяет, сворачивать ли меню после выбора; popper-class добавляет всплывающему слою пользовательский класс; teleport-target задаёт контейнер монтирования.
<script setup lang="ts">
import { ref } from 'vue'
const menuVisible = ref(false)
</script>
<template>
<!-- Управляемое отображение: после выбора не сворачивается -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? 'Свернуть' : 'Развернуть' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">Копировать (не сворачивать после выбора)</LhDropdownItem>
<LhDropdownItem command="paste">Вставить</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { menuVisible: false }
},
}
</script>
<template>
<!-- Управляемое отображение: после выбора не сворачивается -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? 'Свернуть' : 'Развернуть' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">Копировать (не сворачивать после выбора)</LhDropdownItem>
<LhDropdownItem command="paste">Вставить</LhDropdownItem>
</template>
</LhDropdown>
</template><script setup lang="ts">
</script>
<template>
<!-- Монтирование в заданный контейнер -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>Ещё ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">Пункт</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {}
</script>
<template>
<!-- Монтирование в заданный контейнер -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>Ещё ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">Пункт</LhDropdownItem>
</template>
</LhDropdown>
</template>Dropdown API
Props
| Свойство | Описание | Тип | По умолчанию |
|---|---|---|---|
| trigger | Способ запуска | "hover" | "click" | "contextmenu" | 'click' |
| placement | Позиция показа (12 вариантов, как у Tooltip) | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'bottom-start' |
| visible | Состояние видимости (v-model:visible) | boolean | — |
| disabled | Блокировать ли триггер | boolean | — |
| hideOnClick | Закрывать ли автоматически после нажатия пункта меню | boolean | true |
| teleportTarget | Цель Teleport (селектор или элемент) | string | HTMLElement | 'body' |
| popperClass | Пользовательский класс всплывающего слоя | string | — |
| maxHeight | Максимальная высота меню (число — px) | string | number | — |
| themeVars | Тематические переменные уровня компонента | { [x: string]: string; } | — |
| bgPattern | Фоновый узор уровня компонента, переопределяет конфигурацию ConfigProvider | string | — |
| sx | Свойство быстрой компоновки sx (применяется к корневому элементу триггера) | LhSxProps | — |
Events
| Событие | Описание | Параметр |
|---|---|---|
| command | Срабатывает при нажатии пункта меню | (command: string | number | undefined) |
| click | Срабатывает при нажатии пункта меню (LhDropdownItem) | (command: string | number | undefined) |
| visible-change | Изменение видимости | (value: boolean) |
| update:visible | Изменение видимости (v-model) | (value: boolean) |
Slots
| Слот | Описание |
|---|---|
| default | Содержимое триггера |
| dropdown | Содержимое меню (список LhDropdownItem) |
Methods
| Метод | Описание |
|---|---|
| show | Программное раскрытие |
| hide | Программное сворачивание |
| toggle | Программное переключение |
DropdownItem API
Props
| Свойство | Описание | Тип | По умолчанию |
|---|---|---|---|
| command | Идентификатор команды, выбрасывается с событием command при нажатии | string | number | — |
| disabled | Блокировка | boolean | false |
| divided | Показывать ли разделитель сверху | boolean | false |
Доступность
- Триггер несёт
aria-haspopup="menu"иaria-expanded; меню —role="menu", пункты —role="menuitem" - Клавиатура следует паттерну меню APG: ↑/↓ — циклически (с пропуском заблокированных), Home/End — в начало/конец, Enter/Space — вызов
commandи закрытие, Esc — закрытие с возвратом фокуса - Заблокированные пункты используют
aria-disabled, а не удаление из дерева фокуса; стрелки и Enter их пропускают - При раскрытии меню переполнение по главной оси автоматически разворачивает направление, по поперечной оси — вписывается обратно в область просмотра
Типы
Структура открытых TypeScript-типов этого компонента (автоматически извлечено из packages/components/dropdown/src/types.ts и исходников @lh-ui/share/core/*; раскройте каждый пункт, чтобы увидеть полное определение; описания свойств — в таблице Props выше):
LhDropdownProps
/**
* Props компонента Dropdown
*/
export interface LhDropdownProps {
/** Способ запуска */
trigger?: LhDropdownTrigger
/** Позиция показа */
placement?: LhDropdownPlacement
/** Управляемое вручную состояние отображения (v-model:visible) */
visible?: boolean
/** Блокировка */
disabled?: boolean
/** Закрывать ли автоматически после нажатия пункта меню */
hideOnClick?: boolean
/** Цель Teleport (селектор или элемент, по умолчанию body) */
teleportTarget?: string | HTMLElement
/** Пользовательский класс всплывающего слоя */
popperClass?: string
/** Максимальная высота меню */
maxHeight?: string | number
/** Тематические переменные уровня компонента, переопределяют конфигурацию ConfigProvider */
themeVars?: Record<string, string>
/** Фоновый узор уровня компонента, переопределяет конфигурацию ConfigProvider */
bgPattern?: string
/** Свойство быстрой компоновки sx (применяется к корневому элементу триггера) */
sx?: LhSxProps
}LhDropdownItemProps
/**
* Props компонента DropdownItem
*/
export interface LhDropdownItemProps {
/** Идентификатор команды, выбрасывается с событием command при нажатии */
command?: string | number
/** Блокировка (фокус сохраняется, чтобы оставаться в дереве доступности) */
disabled?: boolean
/** Показывать ли разделитель */
divided?: boolean
}LhDropdownTrigger (из @lh-ui/share/core/dropdown (logic.ts))
/** Способ запуска */
export type LhDropdownTrigger = 'hover' | 'click' | 'contextmenu'