Menu latéral 
Le menu latéral est un système de navigation secondaire présentant une liste verticale de liens placée à côté du contenu.
Le composant DsfrSideMenu fournit une navigation latérale avec support du collapse/expand, gestion des éléments de menu imbriqués, et intégration avec le routeur Vue.
🌟 Introduction 
Le menu latéral est un système de navigation secondaire présentant une liste verticale de liens placée à côté du contenu.
🏅 La documentation sur le menu latéral sur le DSFR
La story sur le menu latéral sur le storybook de VueDsfr📐 Structure 
Le menu latéral crée :
- Un élément <nav>avec la classefr-sidemenu
- Un bouton de toggle pour réduire/expandre le menu
- Un conteneur collapsible avec les éléments de menu
- Support des liens externes et internes avec le routeur Vue
- Gestion automatique des états actifs et expandés
🛠️ Props 
| nom | type | défaut | obligatoire | description | 
|---|---|---|---|---|
| buttonLabel | string | 'Dans cette rubrique' | Label associé au bouton en état responsive dont le rôle est de déplier le side menu. | |
| id | string | () => useRandomId(...) | (optionnel) Valeur de l’attribut id du side menu. Par défaut, un id pseudo-aléatoire sera donné. | |
| sideMenuListId | string | () => useRandomId(...) | Identifiant de la liste de menu | |
| collapseValue | string | '-492px' | Valeur de collapse CSS | |
| menuItems | DsfrSideMenuListItemProps[] | undefined | Tableau d’objets contenant les props attendus par DsfrSideMenuList. | |
| headingTitle | string | '' | Titre de la rubrique (c’est le titre du menu latéral). | |
| titleTag | TitleTag | 'h3' | Balise HTML pour le titre | |
| focusOnExpanding | boolean | true | Focus automatique lors de l'expansion | 
📡 Événements 
DsfrSideMenu déclenche l'événement suivant :
| nom | donnée (payload) | description | 
|---|---|---|
| toggleExpand | string | Émis lors du toggle d'expansion d'un élément | 
🧩 Slots 
| nom | description | 
|---|---|
| default | Contenu du menu latéral (remplace la liste par défaut) | 
📝 Exemples 
Exemple d'utilisation basique du menu latéral 
vue
<script setup lang="ts">
import { ref } from 'vue'
const menuItems = ref([
  { text: 'Accueil', to: '/' },
  { text: 'À propos', to: '/about' },
  {
    text: 'Services',
    menuItems: [
      { text: 'Service 1', to: '/service1' },
      { text: 'Service 2', to: '/service2' },
    ],
  },
])
const onToggleExpand = (id: string) => {
  console.log('Toggle expand:', id)
}
</script>
<template>
  <DsfrSideMenu
    heading-title="Navigation"
    :menu-items="menuItems"
    @toggle-expand="onToggleExpand"
  />
</template>Exemple plus complet 
vue
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrSideMenu from '../DsfrSideMenu.vue'
const headingTitle = 'Titre de la rubrique'
const menuItems = ref([
  {
    id: '11',
    to: '/rubrique-1',
    text: 'Premier titre de niveau 1',
  },
  {
    id: '12',
    text: 'Deuxième titre de niveau 1',
    active: true,
    menuItems: [
      {
        id: '21',
        to: '/rubrique-2/sous-rubrique-1',
        text: 'Premier titre de niveau 2',
      },
      {
        id: '22',
        text: 'Deuxième titre de niveau 2',
        active: true,
        menuItems: [
          {
            id: '31',
            to: '/rubrique-2/sous-rubrique-2/sous-sous-rubrique-1',
            text: 'Premier titre de niveau 3',
          },
          {
            id: '32',
            to: '/rubrique-2/sous-rubrique-2/sous-sous-rubrique-2',
            text: 'Deuxième titre de niveau 3',
            active: true,
          },
        ],
      },
    ],
  },
])
</script>
<template>
  <div class="fr-container fr-my-2w">
    <h2>SideMenu</h2>
    <DsfrSideMenu
      :heading-title="headingTitle"
      :menu-items="menuItems"
    />
  </div>
</template>⚙️ Code source du composant 
vue
<script lang="ts" setup>
import type { DsfrSideMenuProps } from './DsfrSideMenu.types'
import { ref, watch } from 'vue'
import { useCollapsable } from '../../composables'
import { useRandomId } from '../../utils/random-utils'
import DsfrSideMenuList from './DsfrSideMenuList.vue'
export type { DsfrSideMenuProps }
withDefaults(defineProps<DsfrSideMenuProps>(), {
  buttonLabel: 'Dans cette rubrique',
  id: () => useRandomId('sidemenu'),
  sideMenuListId: () => useRandomId('sidemenu', 'list'),
  collapseValue: '-492px',
  // @ts-expect-error this is really undefined
  menuItems: () => undefined,
  headingTitle: '',
  titleTag: 'h3',
  focusOnExpanding: true,
})
defineEmits<{ (e: 'toggleExpand', payload: string): void }>()
const {
  collapse,
  collapsing,
  cssExpanded,
  doExpand,
  onTransitionEnd,
} = useCollapsable()
const expanded = ref(false)
/*
 * @see https://github.com/GouvernementFR/dsfr/blob/main/src/core/script/collapse/collapse.js
 */
watch(expanded, (newValue, oldValue) => {
  if (newValue !== oldValue) {
    doExpand(newValue)
  }
})
</script>
<template>
  <nav
    class="fr-sidemenu"
    :aria-labelledby="id"
  >
    <div class="fr-sidemenu__inner">
      <button
        class="fr-sidemenu__btn"
        :aria-controls="id"
        :aria-expanded="expanded"
        @click.prevent.stop="expanded = !expanded"
      >
        {{ buttonLabel }}
      </button>
      <div
        :id="id"
        ref="collapse"
        class="fr-collapse"
        :class="{
          'fr-collapse--expanded': cssExpanded, // Need to use a separate data to add/remove the class after a requestAnimationFrame (RAF)
          'fr-collapsing': collapsing,
        }"
        @transitionend="onTransitionEnd(expanded, focusOnExpanding)"
      >
        <component
          :is="titleTag"
          class="fr-sidemenu__title"
        >
          {{ headingTitle }}
        </component>
        <!-- @slot Slot par défaut du contenu du menu latéral -->
        <slot>
          <DsfrSideMenuList
            :id="sideMenuListId"
            :menu-items="menuItems"
            @toggle-expand="$emit('toggleExpand', $event)"
          />
        </slot>
      </div>
    </div>
  </nav>
</template>ts
import type { RouteLocationRaw } from 'vue-router'
export type DsfrSideMenuListItemProps = { active?: boolean }
export type DsfrSideMenuProps = {
  buttonLabel?: string
  id?: string
  sideMenuListId?: string
  collapseValue?: string
  menuItems?: DsfrSideMenuListItemProps[]
  headingTitle?: string
  titleTag?: string
  focusOnExpanding?: boolean
}
export type DsfrSideMenuButtonProps = {
  active?: boolean
  expanded?: boolean
  controlId: string
}
export type DsfrSideMenuListProps = {
  id: string
  collapsable?: boolean
  expanded?: boolean
  menuItems?: (
    DsfrSideMenuListItemProps & Partial<DsfrSideMenuListProps & { to?: RouteLocationRaw, text?: string }>
    & { menuItems?: (DsfrSideMenuListItemProps & Partial<DsfrSideMenuListProps & { to?: RouteLocationRaw, text?: string }>)[] }
  )[]
  focusOnExpanding?: boolean
}