Skip to content

Navigation principale - DsfrNavigation

🌟 Introduction

Le système de navigation principal permet d'orienter les utilisateurs à travers l'application. Il constitue l'épine dorsale de la navigation d'un site, offrant une structure claire et accessible pour explorer les différentes sections et fonctionnalités.

Le composant DsfrNavigation est le système central de navigation au sein d'un site. Il permet d'orienter aisément l'usager à travers l'application avec une structure hiérarchique claire et des menus déroulants.

🏅 La documentation sur la navigation sur le DSFR

La story sur la navigation sur le storybook de VueDsfr

📐 Structure

La navigation principale est composée des éléments suivants :

  • un conteneur principal avec un identifiant unique (prop id)
  • un label d'accessibilité (prop label)
  • une liste de liens et sous-menus (prop navItems) organisée hiérarchiquement
  • des menus déroulants qui s'ouvrent/ferment au clic
  • une gestion des événements clavier pour l'accessibilité (touches Échap, flèches)

🛠️ Props

nomtypedéfautobligatoiredescription
idstring() => useRandomId(...)Identifiant unique pour la navigation
labelstring'Menu principal'Nom associé à la navigation pour l'accessibilité
navItemsarray() => []Tableau contenant les liens ou sous-menus de la navigation

📡 Événements

DsfrNavigation déclenche les événements suivants :

nomdonnée (payload)description
clickaucuneÉmis au clic qui déclenche l'ouverture ou la fermeture d'un menu
keydownaucuneÉmis en appuyant sur Échap qui déclenche la fermeture d'un menu ouvert

🧩 Slots

DsfrNavigation possède un slot par défaut pour le contenu personnalisé de la navigation.

nomdescription
defaultSlot par défaut pour le contenu personnalisé de la navigation

📝 Exemples

Exemple simple d'utilisation de DsfrNavigation :

vue
<script lang="ts" setup>
import DsfrNavigation from '../DsfrNavigation.vue'

const navItemsDemo = [
  {
    to: '/accueil',
    text: 'Accueil',
  },
  {
    to: '/tableau-de-bord',
    text: 'Tableau de bord',
  },
  {
    to: '/historique',
    text: 'Historique',
  },
]
</script>

<template>
  <DsfrNavigation
    label="Menu principal démo"
    :nav-items="navItemsDemo"
  />
</template>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import type {
  DsfrNavigationMegaMenuProps,
  DsfrNavigationMenuLinkProps,
  DsfrNavigationMenuLinks,
  DsfrNavigationMenuProps,
  DsfrNavigationProps,
} from './DsfrNavigation.types'

import { onMounted, onUnmounted, ref } from 'vue'

import { useRandomId } from '../../utils/random-utils'

import DsfrNavigationItem from './DsfrNavigationItem.vue'
import DsfrNavigationMegaMenu from './DsfrNavigationMegaMenu.vue'
import DsfrNavigationMenu from './DsfrNavigationMenu.vue'
import DsfrNavigationMenuLink from './DsfrNavigationMenuLink.vue'

export type { DsfrNavigationMenuLinks, DsfrNavigationProps }

const props = withDefaults(defineProps<DsfrNavigationProps>(), {
  id: () => useRandomId('nav'),
  label: 'Menu principal',
  navItems: () => [],
})

const expandedMenuId = ref<string | undefined>(undefined)

const toggle = (id: string | undefined) => {
  if (id === expandedMenuId.value) {
    expandedMenuId.value = undefined
    return
  }
  expandedMenuId.value = id
}

const handleElementClick = (el: HTMLElement) => {
  if (el === document.getElementById(props.id)) {
    return
  }

  if (!el?.parentNode) {
    toggle(expandedMenuId.value)
    return
  }

  handleElementClick(el.parentNode as HTMLElement)
}

const onDocumentClick = (e: MouseEvent) => {
  handleElementClick(e.target as HTMLElement)
}

const onKeyDown = (e: KeyboardEvent) => {
  if (e.key === 'Escape') {
    toggle(expandedMenuId.value)
  }
}

onMounted(() => {
  document.addEventListener('click', onDocumentClick)
  document.addEventListener('keydown', onKeyDown)
})
onUnmounted(() => {
  document.removeEventListener('click', onDocumentClick)
  document.removeEventListener('keydown', onKeyDown)
})
</script>

<template>
  <nav
    :id="id"
    class="fr-nav"
    role="navigation"
    :aria-label="label"
  >
    <ul class="fr-nav__list">
      <!-- @slot Slot par défaut pour le contenu de la liste. Sera dans `<ul class="fr-nav__list">` -->
      <slot />
      <DsfrNavigationItem
        v-for="(navItem, idx) of navItems"
        :id="navItem.id"
        :key="idx"
      >
        <DsfrNavigationMenuLink
          v-if="(navItem as DsfrNavigationMenuLinkProps).to && (navItem as DsfrNavigationMenuLinkProps).text"
          v-bind="navItem"
          :expanded-id="expandedMenuId"
          @toggle-id="toggle($event)"
        />
        <!-- @vue-ignore -->
        <DsfrNavigationMenu
          v-else-if="(navItem as DsfrNavigationMenuProps).title && (navItem as DsfrNavigationMenuProps).links"
          v-bind="(navItem as DsfrNavigationMenuProps)"
          :expanded-id="expandedMenuId"
          @toggle-id="toggle($event)"
        />
        <!-- @vue-ignore -->
        <DsfrNavigationMegaMenu
          v-else-if="(navItem as DsfrNavigationMegaMenuProps).title && (navItem as DsfrNavigationMegaMenuProps).menus"
          v-bind="(navItem as DsfrNavigationMegaMenuProps)"
          :expanded-id="expandedMenuId"
          @toggle-id="toggle($event)"
        />
      </DsfrNavigationItem>
    </ul>
  </nav>
</template>

<style>
.fr-nav__list {
  position: relative;
}
</style>
ts
import type { RouteLocationRaw } from 'vue-router'

export type DsfrNavigationMenuLinkProps = {
  id?: string
  to?: string | RouteLocationRaw
  text?: string
  icon?: string
  onClick?: ($event: MouseEvent) => void
}

export type DsfrNavigationMenuItemProps = {
  id?: string
  active?: boolean
}

export type DsfrNavigationMenuProps = {
  id?: string
  title: string
  links?: DsfrNavigationMenuLinkProps[]
  expandedId?: string
  active?: boolean
}

export type DsfrNavigationItemProps = {
  id?: string
  active?: boolean
}

export type DsfrNavigationMegaMenuCategoryProps = {
  title: string
  active?: boolean
  links: DsfrNavigationMenuLinkProps[]
}

export type DsfrNavigationMegaMenuProps = {
  id?: string
  title: string
  description?: string
  link?: { to: RouteLocationRaw, text: string }
  menus?: DsfrNavigationMegaMenuCategoryProps[]
  expandedId?: string
  active?: boolean
}

export type DsfrNavigationMenuLinks = (DsfrNavigationMenuLinkProps | DsfrNavigationMegaMenuProps | DsfrNavigationMenuProps)[]

export type DsfrNavigationProps = {
  id?: string
  label?: string
  navItems: (
    DsfrNavigationMenuLinkProps
    | DsfrNavigationMenuProps
    | DsfrNavigationMegaMenuProps
  )[]
}