Skip to content

Navigation principale - DsfrNavigation

🌟 Introduction

Le composant DsfrNavigation, est le système central de navigation au sein d’un site. Elle permet d’orienter aisément l’usager à travers l'application voire même jusqu'aux confins de la galaxie !

🛠️Props

NomTypeDéfautObligatoireDescription
idstring() => useRandomId(...)Identifiant unique pour la nav. Si non spécifié, un ID aléatoire est généré.
labelstringMenu principalNom associé à la navigation. Utile pour l'accessibilité.
navItemsarray() => []Tableau contenant les liens ou sous menus accessibles depuis la navigation.

📡Événements

NomDescription
clickÉvénement émis au clic qui déclence l'ouverture ou la fermeture d'un menu.
keydownÉvénement émis en appuyant sur la touche "Echap" qui déclence lla fermeture d'un menu ouvert.

🧩 Slots

NomDescription
defaultSlot par défaut pour le contenu de la navigation, il se trouve dans la balise <ul class="fr-nav__list">.

📝 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 { 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'
import type {
  DsfrNavigationMegaMenuProps,
  DsfrNavigationMenuLinkProps,
  DsfrNavigationMenuLinks,
  DsfrNavigationMenuProps,
  DsfrNavigationProps,
} from './DsfrNavigation.types'

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
  )[]
}

Avec DsfrNavigation, toute destination est à portée de clic, Bon voyage !