SnippetUI

Menu Popover

Un menu contextuel qui s'ouvre au clic (ou au focus) et affiche une liste d'actions, plutôt qu'au survol.

HTMLCSSJavaScript

Les menus contextuels, contrairement aux info-bulles qui apparaissent au survol, s’ouvrent au clic et sont utilisés pour présenter une liste d’actions (telles que modifier ou supprimer) à l’utilisateur. Il est conçu dans un souci d’accessibilité, comme la gestion de la mise au point et le traitement de fermeture sans clic.

Aperçu

Cas d’utilisation et fonctionnalités

  • Afficher les options : Idéal pour les interfaces utilisateur qui affichent diverses actions (modifier, supprimer, partager, etc.) en cliquant sur une icône de menu (…) ou un bouton.

  • Considérations sur l’accessibilité : utilise les attributs aria-expanded et aria-haspopup pour prendre en charge le masquage et la gestion du focus avec la touche Échap.

  • Animation fluide : Pour éviter un changement d’affichage non naturel, nous avons implémenté une transition qui combine le glissement dans la direction de l’axe Y et le fondu entrant.

Points clés du design et de l’UX (Design & UX)

  • Effacer la hiérarchie : les ombres (« box-shadow ») sont utilisées pour créer une sensation de flottement et séparer clairement le contenu du contenu d’arrière-plan.

  • Fonctionnement sécurisé : inclut un processus pour fermer le menu en appuyant sur une zone en dehors de la plage de clic (Cliquez à l’extérieur), ce qui rend son utilisation intuitive à la fois sur mobile et sur PC.

  • Mise en évidence des actions destructrices : nous utilisons du texte rouge (Danger) pour les actions irréversibles, telles que la suppression, afin de réduire les erreurs des utilisateurs.

Comment personnaliser (Guide de personnalisation)

  • Si vous souhaitez modifier la largeur du menu, veuillez ajuster la « largeur » de « .popover-menu ».

  • Si vous souhaitez que la direction d’ouverture soit en bas à droite ou en bas à gauche, vous pouvez facilement la personnaliser en modifiant les propriétés transform et left de .popover-menu.

Code d’implémentation (Implémentation)

Vous pouvez inclure un menu contextuel dans votre projet en utilisant le code ci-dessous.

<div class="popover-wrapper">

<button class="popover-trigger" aria-haspopup="true" aria-expanded="false" id="popover-btn">

  <span>action</span>

  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">

    <path d="m6 9 6 6 6-6"/>

  </svg>

</button>

<div class="popover-menu" role="menu" aria-labelledby="popover-btn">

  <div class="popover-content">

    <button class="popover-item" role="menuitem">

      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>

      Modifier

    </button>

    <button class="popover-item" role="menuitem">

      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M21 9H3"/><path d="M9 21V9"/></svg>

      dupliquer

    </button>

    <div class="popover-divider"></div>

    <button class="popover-item text-danger" role="menuitem">

      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>

      supprimer

    </button>

  </div>

</div>

</div>



<style>
/* Placer le CSS ici */

.popover-wrapper {

position: relative;

display: inline-block;

}



.popover-trigger {

display: flex;

align-items: center;

gap: 6px;

padding: 8px 16px;

font-size: 14px;

font-weight: 500;

color: #334155;

background-color: #ffffff;

border: 1px solid #cbd5e1;

border-radius: 8px;

cursor: pointer;

transition: all 0.2s ease;

box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);

}



.popover-trigger:hover {

background-color: #f1f5f9;

border-color: #94a3b8;

}



.popover-trigger:focus-visible {

outline: 2px solid #3b82f6;

outline-offset: 2px;

}



.popover-trigger svg {

transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);

}



.popover-trigger.is-open svg {

transform: rotate(180deg);

}



.popover-menu {

position: absolute;

top: calc(100% + 8px);

left: 50%;

transform: translateX(-50%) translateY(-10px);

width: 200px;

background: #ffffff;

border-radius: 12px;

box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);

border: 1px solid #e2e8f0;

opacity: 0;

visibility: hidden;

transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);

z-index: 50;

pointer-events: none;

}



.popover-wrapper.is-active .popover-menu {

opacity: 1;

visibility: visible;

transform: translateX(-50%) translateY(0);

pointer-events: auto;

}



.popover-content {

padding: 6px;

display: flex;

flex-direction: column;

gap: 2px;

}



.popover-item {

display: flex;

align-items: center;

gap: 10px;

width: 100%;

padding: 8px 12px;

font-size: 14px;

color: #475569;

background: transparent;

border: none;

border-radius: 6px;

cursor: pointer;

text-align: left;

transition: background-color 0.15s ease, color 0.15s ease;

}



.popover-item:hover,

.popover-item:focus-visible {

background-color: #f1f5f9;

color: #0f172a;

outline: none;

}



.popover-item.text-danger {

color: #ef4444;

}



.popover-item.text-danger:hover,

.popover-item.text-danger:focus-visible {

background-color: #fef2f2;

color: #dc2626;

}



.popover-divider {

height: 1px;

background-color: #e2e8f0;

margin: 4px 0;

}
</style>



<script>

// Placez JavaScript ici

document.addEventListener('DOMContentLoaded', () => {

const wrappers = document.querySelectorAll('.popover-wrapper');



wrappers.forEach(wrapper => {

  const trigger = wrapper.querySelector('.popover-trigger');

  const menu = wrapper.querySelector('.popover-menu');

  

  if (!trigger || !menu) return;



  const togglePopover = () => {

    const isActive = wrapper.classList.contains('is-active');

    if (isActive) {

      closePopover();

    } else {

      openPopover();

    }

  };



  const openPopover = () => {

    wrapper.classList.add('is-active');

    trigger.classList.add('is-open');

    trigger.setAttribute('aria-expanded', 'true');

  };



  const closePopover = () => {

    wrapper.classList.remove('is-active');

    trigger.classList.remove('is-open');

    trigger.setAttribute('aria-expanded', 'false');

  };



  trigger.addEventListener('click', togglePopover);



  //Fermer en dehors de la zone de clic

  document.addEventListener('click', (e) => {

    if (!wrapper.contains(e.target)) {

      closePopover();

    }

  });



  // Ferme avec la touche Echap

  document.addEventListener('keydown', (e) => {

    if (e.key === 'Escape' && wrapper.classList.contains('is-active')) {

      closePopover();

      trigger.focus();

    }

  });

});

});

</script>