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

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

Comment personnaliser (Guide de personnalisation)

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>