Menu déroulant
Menu de commandes s'ouvrant vers le bas. Le déclencheur peut être un élément quelconque, les entrées sont identifiées par command et un clic déclenche l'événement command. Le composant suit le modèle menu de WAI-ARIA : navigation cyclique avec les flèches, Home/End pour le premier/dernier élément, Enter/Space pour déclencher, Échap pour fermer et rendre le focus.
Utilisation de base
Dernière commande : —
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || "Plus d'actions" }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">Nouveau</LhDropdownItem>
<LhDropdownItem command="edit">Modifier</LhDropdownItem>
<LhDropdownItem command="delete">Supprimer</LhDropdownItem>
</template>
</LhDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const lastCommand = ref('')
// Le libellé du déclencheur est à la charge de l'utilisateur : l'événement command
// ne remonte que la valeur de la commande, l'affichage du libellé est laissé à l'extérieur
const labelMap: Record<string, string> = { new: 'Nouveau', edit: 'Modifier', delete: 'Supprimer' }
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: '',
// Le libellé du déclencheur est à la charge de l'utilisateur : l'événement command
// ne remonte que la valeur de la commande, l'affichage du libellé est laissé à l'extérieur
labelMap: { new: 'Nouveau', edit: 'Modifier', delete: 'Supprimer' },
selectedLabel: '',
}
},
methods: {
onCommand(command) {
this.lastCommand = String(command)
this.selectedLabel = this.labelMap[String(command)] ?? String(command)
},
},
}
</script>
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || "Plus d'actions" }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">Nouveau</LhDropdownItem>
<LhDropdownItem command="edit">Modifier</LhDropdownItem>
<LhDropdownItem command="delete">Supprimer</LhDropdownItem>
</template>
</LhDropdown>
</template>Mode de déclenchement
Le mode par défaut est click ; il est également possible de déclencher l'ouverture au survol ou au clic droit.
<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>
<!-- Déclenchement au survol -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || 'Déclenchement au survol' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">Option 1</LhDropdownItem>
<LhDropdownItem command="hover-2">Option 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- Déclenchement au clic droit -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || 'Déclenchement au clic droit' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">Actualiser</LhDropdownItem>
<LhDropdownItem command="ctx-2">Copier</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>
<!-- Déclenchement au survol -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || 'Déclenchement au survol' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">Option 1</LhDropdownItem>
<LhDropdownItem command="hover-2">Option 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- Déclenchement au clic droit -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || 'Déclenchement au clic droit' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">Actualiser</LhDropdownItem>
<LhDropdownItem command="ctx-2">Copier</LhDropdownItem>
</template>
</LhDropdown>
</template>Le déclenchement par
contextmenuempêche l'affichage du menu contextuel par défaut du navigateur.
Éléments désactivés et séparateurs
<script setup lang="ts">
import { ref } from 'vue'
const actionLabel = ref('')
function onActionCommand(command: string | number | undefined) {
actionLabel.value = String(command)
}
</script>
<template>
<!-- Menu d'actions avec entrée désactivée et séparateur -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || 'Actions' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">Exporter</LhDropdownItem>
<LhDropdownItem command="import">Importer</LhDropdownItem>
<LhDropdownItem divided command="audit">Journal d'audit</LhDropdownItem>
<LhDropdownItem disabled command="purge">Vider les données</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { actionLabel: '' }
},
methods: {
onActionCommand(command) {
this.actionLabel = String(command)
},
},
}
</script>
<template>
<!-- Menu d'actions avec entrée désactivée et séparateur -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || 'Actions' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">Exporter</LhDropdownItem>
<LhDropdownItem command="import">Importer</LhDropdownItem>
<LhDropdownItem divided command="audit">Journal d'audit</LhDropdownItem>
<LhDropdownItem disabled command="purge">Vider les données</LhDropdownItem>
</template>
</LhDropdown>
</template>Une entrée désactivée reste focusable (
aria-disabled="true"), afin que les lecteurs d'écran puissent l'annoncer ; mais la navigation au clavier la saute, et ni le clic ni Enter/Space ne déclenchentcommand.
Position et hauteur maximale
<script setup lang="ts">
import { ref } from 'vue'
const itemLabel = ref('')
function onItemCommand(command: string | number | undefined) {
itemLabel.value = `Élément ${command}`
}
</script>
<template>
<!-- Alignement en bas à droite et hauteur maximale limitée -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || 'Aligné en bas à droite' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">Élément {{ i }}</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { itemLabel: '' }
},
methods: {
onItemCommand(command) {
this.itemLabel = `Élément ${command}`
},
},
}
</script>
<template>
<!-- Alignement en bas à droite et hauteur maximale limitée -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || 'Aligné en bas à droite' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">Élément {{ i }}</LhDropdownItem>
</template>
</LhDropdown>
</template>Affichage contrôlé et comportement de clic
v-model:visible permet de contrôler manuellement l'affichage ; hide-on-click détermine si le menu se replie automatiquement après une sélection ; popper-class ajoute une classe personnalisée au calque flottant ; teleport-target désigne le conteneur de montage.
<script setup lang="ts">
import { ref } from 'vue'
const menuVisible = ref(false)
</script>
<template>
<!-- Affichage contrôlé : pas de repli après sélection -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? 'Replier' : 'Déplier' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">Copier (ne se replie pas après sélection)</LhDropdownItem>
<LhDropdownItem command="paste">Coller</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {
data() {
return { menuVisible: false }
},
}
</script>
<template>
<!-- Affichage contrôlé : pas de repli après sélection -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? 'Replier' : 'Déplier' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">Copier (ne se replie pas après sélection)</LhDropdownItem>
<LhDropdownItem command="paste">Coller</LhDropdownItem>
</template>
</LhDropdown>
</template><script setup lang="ts">
</script>
<template>
<!-- Montage dans un conteneur désigné -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>Plus ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">Option</LhDropdownItem>
</template>
</LhDropdown>
</template><script>
export default {}
</script>
<template>
<!-- Montage dans un conteneur désigné -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>Plus ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">Option</LhDropdownItem>
</template>
</LhDropdown>
</template>Dropdown API
Props
| Propriété | Description | Type | Valeur par défaut |
|---|---|---|---|
| trigger | Mode de déclenchement | "hover" | "click" | "contextmenu" | 'click' |
| placement | Position d'affichage (12 positions, comme Tooltip) | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'bottom-start' |
| visible | État d'affichage (v-model:visible) | boolean | — |
| disabled | Désactiver le déclencheur | boolean | — |
| hideOnClick | Fermer automatiquement après un clic sur une entrée | boolean | true |
| teleportTarget | Cible du Teleport (sélecteur ou élément) | string | HTMLElement | 'body' |
| popperClass | Classe personnalisée du calque flottant | string | — |
| maxHeight | Hauteur maximale du menu (nombre en px) | string | number | — |
| themeVars | Variables de thème au niveau du composant | { [x: string]: string; } | — |
| bgPattern | Motif d'arrière-plan au niveau du composant, surchargeant la configuration de ConfigProvider | string | — |
| sx | Propriétés de mise en page rapide sx (appliquées à l'élément racine du déclencheur) | LhSxProps | — |
Events
| Événement | Description | Paramètre |
|---|---|---|
| command | Déclenché lors d'un clic sur une entrée du menu | (command: string | number | undefined) |
| click | Déclenché lors d'un clic sur une entrée (LhDropdownItem) | (command: string | number | undefined) |
| visible-change | Changement d'affichage | (value: boolean) |
| update:visible | Changement d'affichage (v-model) | (value: boolean) |
Slots
| Slot | Description |
|---|---|
| default | Contenu du déclencheur |
| dropdown | Contenu du menu (liste de LhDropdownItem) |
Methods
| Méthode | Description |
|---|---|
| show | Ouverture programmatique |
| hide | Fermeture programmatique |
| toggle | Bascule programmatique |
DropdownItem API
Props
| Propriété | Description | Type | Valeur par défaut |
|---|---|---|---|
| command | Identifiant de commande, remonté avec l'événement command lors du clic | string | number | — |
| disabled | Désactiver l'entrée | boolean | false |
| divided | Afficher un séparateur au-dessus | boolean | false |
Accessibilité
- Le déclencheur porte
aria-haspopup="menu"etaria-expanded; le menu a le rôlerole="menu"et les entréesrole="menuitem" - La navigation clavier suit le modèle menu des APG : ↑/↓ en boucle (entrées désactivées sautées), Home/End pour atteindre le premier/dernier élément, Enter/Space déclenchent
commandet ferment, Échap ferme et rend le focus - Les entrées désactivées utilisent
aria-disabledplutôt que d'être retirées de l'arbre de focus ; les flèches et Enter les sautent - En cas de dépassement sur l'axe principal, le menu retourne automatiquement sa direction ; sur l'axe secondaire, il est ramené dans la fenêtre
Définitions de types
Les structures de types TypeScript exportées par ce composant sont présentées ci-dessous (extraction automatique à partir des sources packages/components/dropdown/src/types.ts et @lh-ui/share/core/* ; cliquez sur chaque entrée pour afficher la définition complète ; la description des propriétés se trouve dans le tableau Props ci-dessus) :
LhDropdownProps
/**
* Props du composant Dropdown
*/
export interface LhDropdownProps {
/** Mode de déclenchement */
trigger?: LhDropdownTrigger
/** Position d'affichage */
placement?: LhDropdownPlacement
/** État d'affichage contrôlé manuellement (v-model:visible) */
visible?: boolean
/** Désactiver */
disabled?: boolean
/** Fermer automatiquement après un clic sur une entrée du menu */
hideOnClick?: boolean
/** Cible du Teleport (sélecteur ou élément, body par défaut) */
teleportTarget?: string | HTMLElement
/** Classe personnalisée du calque flottant */
popperClass?: string
/** Hauteur maximale du menu */
maxHeight?: string | number
/** Variables de thème au niveau du composant, surchargeant la configuration de ConfigProvider */
themeVars?: Record<string, string>
/** Motif d'arrière-plan au niveau du composant, surchargeant la configuration de ConfigProvider */
bgPattern?: string
/** Propriétés de mise en page rapide sx (appliquées à l'élément racine du déclencheur) */
sx?: LhSxProps
}LhDropdownItemProps
/**
* Props du composant DropdownItem
*/
export interface LhDropdownItemProps {
/** Identifiant de commande, remonté avec l'événement command lors du clic */
command?: string | number
/** Désactiver (la focusabilité est conservée afin de rester dans l'arbre d'accessibilité) */
disabled?: boolean
/** Afficher un séparateur */
divided?: boolean
}LhDropdownTrigger (provient de @lh-ui/share/core/dropdown (logic.ts))
/** Mode de déclenchement */
export type LhDropdownTrigger = 'hover' | 'click' | 'contextmenu'