Skip to content
API 风格:

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 ​

Déclenchement par clic + événement command

Dernière commande : —

vue
<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>
vue
<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.

hover / contextmenu
vue
<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>
vue
<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 contextmenu empêche l'affichage du menu contextuel par défaut du navigateur.

Éléments désactivés et séparateurs ​

disabled / divided
vue
<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>
vue
<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éclenchent command.

Position et hauteur maximale ​

placement et maxHeight
vue
<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>
vue
<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.

Affichage contrôlé
vue
<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>
vue
<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>
Montage dans un conteneur désigné
vue
<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>
vue
<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>

Props ​

PropriétéDescriptionTypeValeur par défaut
triggerMode de déclenchement"hover" | "click" | "contextmenu"'click'
placementPosition 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—
disabledDésactiver le déclencheurboolean—
hideOnClickFermer automatiquement après un clic sur une entréebooleantrue
teleportTargetCible du Teleport (sélecteur ou élément)string | HTMLElement'body'
popperClassClasse personnalisée du calque flottantstring—
maxHeightHauteur maximale du menu (nombre en px)string | number—
themeVarsVariables de thème au niveau du composant{ [x: string]: string; }—
bgPatternMotif d'arrière-plan au niveau du composant, surchargeant la configuration de ConfigProviderstring—
sxPropriétés de mise en page rapide sx (appliquées à l'élément racine du déclencheur)LhSxProps—

Events ​

ÉvénementDescriptionParamètre
commandDéclenché lors d'un clic sur une entrée du menu(command: string | number | undefined)
clickDéclenché lors d'un clic sur une entrée (LhDropdownItem)(command: string | number | undefined)
visible-changeChangement d'affichage(value: boolean)
update:visibleChangement d'affichage (v-model)(value: boolean)

Slots ​

SlotDescription
defaultContenu du déclencheur
dropdownContenu du menu (liste de LhDropdownItem)

Methods ​

MéthodeDescription
showOuverture programmatique
hideFermeture programmatique
toggleBascule programmatique

Props ​

PropriétéDescriptionTypeValeur par défaut
commandIdentifiant de commande, remonté avec l'événement command lors du clicstring | number—
disabledDésactiver l'entréebooleanfalse
dividedAfficher un séparateur au-dessusbooleanfalse

Accessibilité ​

  • Le déclencheur porte aria-haspopup="menu" et aria-expanded ; le menu a le rôle role="menu" et les entrées role="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 command et ferment, Échap ferme et rend le focus
  • Les entrées désactivées utilisent aria-disabled plutô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
ts
/**
 * 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
ts
/**
 * 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))
ts
/** Mode de déclenchement */
export type LhDropdownTrigger = 'hover' | 'click' | 'contextmenu'

La beauté de la tradition, la fondation du code