Menú desplegable
Menú de comandos que se despliega hacia abajo. El disparador puede ser cualquier elemento; los elementos del menú se identifican mediante command y, al hacer clic, se emiten a través del evento command. Está implementado según el patrón menu de WAI-ARIA: las flechas recorren el menú en bucle, Home/End van al primer y último elemento, Enter/Space lo activan, y Esc cierra y devuelve el foco.
Uso básico
Último comando: —
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || 'Más acciones' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">Nuevo</LhDropdownItem>
<LhDropdownItem command="edit">Editar</LhDropdownItem>
<LhDropdownItem command="delete">Eliminar</LhDropdownItem>
</template>
</LhDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const lastCommand = ref('')
// El texto del disparador lo mantiene quien lo usa: el evento command solo emite el valor del comando;
// cómo se muestra la etiqueta queda decidiéndolo el exterior
const labelMap: Record<string, string> = { new: 'Nuevo', edit: 'Editar', delete: 'Eliminar' }
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: '',
// El texto del disparador lo mantiene quien lo usa: el evento command solo emite el valor del comando;
// cómo se muestra la etiqueta queda decidiéndolo el exterior
labelMap: { new: 'Nuevo', edit: 'Editar', delete: 'Eliminar' },
selectedLabel: '',
}
},
methods: {
onCommand(command) {
this.lastCommand = String(command)
this.selectedLabel = this.labelMap[String(command)] ?? String(command)
},
},
}
</script>
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || 'Más acciones' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">Nuevo</LhDropdownItem>
<LhDropdownItem command="edit">Editar</LhDropdownItem>
<LhDropdownItem command="delete">Eliminar</LhDropdownItem>
</template>
</LhDropdown>
</template>Forma de disparo
Por defecto es click, pero puede cambiarse a despliegue al pasar el puntero o con el botón derecho.
<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>
<!-- Disparo al pasar el puntero -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || 'Disparo al pasar el puntero' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">Opción 1</LhDropdownItem>
<LhDropdownItem command="hover-2">Opción 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- Disparo con el botón derecho -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || 'Disparo con el botón derecho' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">Actualizar</LhDropdownItem>
<LhDropdownItem command="ctx-2">Copiar</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>
<!-- Disparo al pasar el puntero -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || 'Disparo al pasar el puntero' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">Opción 1</LhDropdownItem>
<LhDropdownItem command="hover-2">Opción 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- Disparo con el botón derecho -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || 'Disparo con el botón derecho' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">Actualizar</LhDropdownItem>
<LhDropdownItem command="ctx-2">Copiar</LhDropdownItem>
</template>
</LhDropdown>
</template>El disparo
contextmenubloquea el menú contextual predeterminado del navegador.
Elementos desactivados y línea divisoria
<script setup lang="ts">
import { ref } from 'vue'
const actionLabel = ref('')
function onActionCommand(command: string | number | undefined) {
actionLabel.value = String(command)
}
</script>
<template>
<!-- Menú de acciones con elemento desactivado y línea divisoria -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || 'Acción' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">Exportar</LhDropdownItem>
<LhDropdownItem command="import">Importar</LhDropdownItem>
<LhDropdownItem divided command="audit">Registro de auditoría</LhDropdownItem>
<LhDropdownItem disabled command="purge">Vaciar datos</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { actionLabel: '' }
},
methods: {
onActionCommand(command) {
this.actionLabel = String(command)
},
},
}
</script>
<template>
<!-- Menú de acciones con elemento desactivado y línea divisoria -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || 'Acción' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">Exportar</LhDropdownItem>
<LhDropdownItem command="import">Importar</LhDropdownItem>
<LhDropdownItem divided command="audit">Registro de auditoría</LhDropdownItem>
<LhDropdownItem disabled command="purge">Vaciar datos</LhDropdownItem>
</template>
</LhDropdown>
</template>El elemento desactivado sigue siendo enfocable (
aria-disabled="true"), de modo que los lectores de pantalla pueden leerlo; pero las flechas lo saltan, y ni el clic ni Enter/Space disparancommand.
Posición y altura máxima
<script setup lang="ts">
import { ref } from 'vue'
const itemLabel = ref('')
function onItemCommand(command: string | number | undefined) {
itemLabel.value = `Elemento ${command}`
}
</script>
<template>
<!-- Alineado a la derecha abajo y con altura máxima limitada -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || 'Alineado a la derecha abajo' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">Elemento {{ i }}</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { itemLabel: '' }
},
methods: {
onItemCommand(command) {
this.itemLabel = `Elemento ${command}`
},
},
}
</script>
<template>
<!-- Alineado a la derecha abajo y con altura máxima limitada -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || 'Alineado a la derecha abajo' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">Elemento {{ i }}</LhDropdownItem>
</template>
</LhDropdown>
</template>Visibilidad controlada y comportamiento tras el clic
v-model:visible controla manualmente la visibilidad; hide-on-click controla si el menú se repliega automáticamente tras seleccionar un elemento; popper-class añade una clase personalizada a la capa emergente; teleport-target especifica el contenedor de montaje.
<script setup lang="ts">
import { ref } from 'vue'
const menuVisible = ref(false)
</script>
<template>
<!-- Visibilidad controlada: no se repliega al seleccionar -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? 'Replegar' : 'Desplegar' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">Copiar (no se repliega al seleccionar)</LhDropdownItem>
<LhDropdownItem command="paste">Pegar</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { menuVisible: false }
},
}
</script>
<template>
<!-- Visibilidad controlada: no se repliega al seleccionar -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? 'Replegar' : 'Desplegar' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">Copiar (no se repliega al seleccionar)</LhDropdownItem>
<LhDropdownItem command="paste">Pegar</LhDropdownItem>
</template>
</LhDropdown>
</template><script setup lang="ts">
</script>
<template>
<!-- Montar en un contenedor especificado -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>Más ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">Opción</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {}
</script>
<template>
<!-- Montar en un contenedor especificado -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>Más ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">Opción</LhDropdownItem>
</template>
</LhDropdown>
</template>Dropdown API
Props
| Prop | Descripción | Tipo | Predeterminado |
|---|---|---|---|
| trigger | Forma de disparo | "hover" | "click" | "contextmenu" | 'click' |
| placement | Posición de despliegue (12 opciones, igual que Tooltip) | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'bottom-start' |
| visible | Estado de visibilidad (v-model:visible) | boolean | — |
| disabled | Si se desactiva el disparador | boolean | — |
| hideOnClick | Si se cierra automáticamente tras hacer clic en un elemento del menú | boolean | true |
| teleportTarget | Objetivo del Teleport (selector o elemento) | string | HTMLElement | 'body' |
| popperClass | Nombre de clase personalizado de la capa emergente | string | — |
| maxHeight | Altura máxima del menú (un número son px) | string | number | — |
| themeVars | Variables de tema a nivel de componente | { [x: string]: string; } | — |
| bgPattern | Patrón de fondo a nivel de componente; sobrescribe la configuración del ConfigProvider | string | — |
| sx | Props de disposición rápida (aplicadas al elemento raíz del disparador) | LhSxProps | — |
Events
| Evento | Descripción | Parámetros |
|---|---|---|
| command | Se dispara al hacer clic en un elemento del menú | (command: string | number | undefined) |
| click | Se dispara al hacer clic en un elemento del menú (LhDropdownItem) | (command: string | number | undefined) |
| visible-change | Cambio de visibilidad | (value: boolean) |
| update:visible | Cambio de visibilidad (v-model) | (value: boolean) |
Slots
| Slot | Descripción |
|---|---|
| default | Contenido del disparador |
| dropdown | Contenido del menú (lista de LhDropdownItem) |
Methods
| Método | Descripción |
|---|---|
| show | Despliegue programático |
| hide | Repliegue programático |
| toggle | Alternancia programática |
DropdownItem API
Props
| Prop | Descripción | Tipo | Predeterminado |
|---|---|---|---|
| command | Identificador del comando; al hacer clic se emite con el evento command | string | number | — |
| disabled | Si se desactiva | boolean | false |
| divided | Si se muestra una línea divisoria encima | boolean | false |
Accesibilidad
- El disparador lleva
aria-haspopup="menu"yaria-expanded; el menú esrole="menu"y sus elementos,role="menuitem" - El teclado sigue el patrón menu de APG: ↑/↓ en bucle (saltando los elementos desactivados), Home/End saltan al primer y último elemento, Enter/Space disparan
commandy cierran, Esc cierra y devuelve el foco - Los elementos desactivados usan
aria-disableden lugar de eliminarse del árbol de foco; tanto las flechas como Enter los saltan - Cuando el menú se desborda en el eje principal, la dirección se invierte automáticamente; si lo hace en el eje secundario, el menú se vuelve a limitar al viewport
Definiciones de tipos
Las estructuras de tipos TypeScript exportadas por este componente (extraídas automáticamente de packages/components/dropdown/src/types.ts y de las fuentes @lh-ui/share/core/*; abre cada entrada para ver la definición completa; la descripción de los atributos está en la tabla de Props de arriba):
LhDropdownProps
export interface LhDropdownProps {
/** Forma de disparo */
trigger?: LhDropdownTrigger
/** Posición de despliegue */
placement?: LhDropdownPlacement
/** Estado de visibilidad controlado manualmente (v-model:visible) */
visible?: boolean
/** Si se desactiva */
disabled?: boolean
/** Si se cierra automáticamente tras hacer clic en un elemento del menú */
hideOnClick?: boolean
/** Objetivo del Teleport (selector o elemento; por defecto, body) */
teleportTarget?: string | HTMLElement
/** Nombre de clase personalizado de la capa emergente */
popperClass?: string
/** Altura máxima del menú */
maxHeight?: string | number
/** Variables de tema a nivel de componente; sobrescribe la configuración del ConfigProvider */
themeVars?: Record<string, string>
/** Patrón de fondo a nivel de componente; sobrescribe la configuración del ConfigProvider */
bgPattern?: string
/** Props de disposición rápida (aplicadas al elemento raíz del disparador) */
sx?: LhSxProps
}LhDropdownItemProps
export interface LhDropdownItemProps {
/** Identificador del comando; al hacer clic se emite con el evento command */
command?: string | number
/** Si se desactiva (conserva la capacidad de recibir el foco para entrar en el árbol de accesibilidad) */
disabled?: boolean
/** Si se muestra una línea divisoria */
divided?: boolean
}LhDropdownTrigger (de @lh-ui/share/core/dropdown (logic.ts))
/** Forma de disparo */
export type LhDropdownTrigger = 'hover' | 'click' | 'contextmenu'