Skip to content

Pagination - DsfrPagination

🌟 Introduction

Le composant DsfrPagination est un système de pagination conforme aux bonnes pratiques ergonomiques et accessible (ARIA). Il permet de naviguer facilement à travers plusieurs pages avec des fonctionnalités avancées comme la limitation de pages affichées et la gestion des événements.

🏅 La documentation sur le pagination sur le DSFR

La story sur le tag sur le storybook de VueDsfr

📐 Structure

Ce composant affiche des liens pour la première page, la précédente, les pages centrales, la suivante, et la dernière, avec des contrôles adaptatifs selon l'état de la pagination.

🛠️ Props

NomTypeDéfautDescription
pagesPage[]requisListe des pages, où chaque page est un objet contenant des informations comme href et label.
truncLimitnumber5Nombre maximum de pages affichées simultanément.
currentPagenumber0Index de la page actuellement sélectionnée (commence à 0).
firstPageTitlestring'Première page'Texte d'info-bulle pour le lien de la première page.
lastPageTitlestring'Dernière page'Texte d'info-bulle pour le lien de la dernière page.
nextPageTitlestring'Page suivante'Texte d'info-bulle pour le lien de la page suivante.
prevPageTitlestring'Page précédente'Texte d'info-bulle pour le lien de la page précédente.

📡 Événements

NomPayloadDescription
update:current-pagenumberÉmis lorsque l'utilisateur change de page.

Il faut donc utiliser v-model:current-page sur le composant (cf. l’exemple ci-dessous).

🧩 Slots

Ce composant n'utilise pas de slots, tout est configuré via les props et les données des pages. 🚀

📝 Exemple d'utilisation

vue
<script setup lang="ts">
import { ref } from 'vue'

import DsfrPagination, { type Page } from '../DsfrPagination.vue'

const currentPage = ref(1)

const pages = ref<Page[]>([
  { title: '1', href: '#1', label: '1' },
  { title: '2', href: '#2', label: '2' },
  { title: '3', href: '#3', label: '3' },
  { title: '4', href: '#4', label: '4' },
  { title: '5', href: '#5', label: '5' },
  { title: '6', href: '#6', label: '6' },
  { title: '7', href: '#7', label: '7' },
  { title: '8', href: '#8', label: '8' },
  { title: '9', href: '#9', label: '9' },
  { title: '10', href: '#10', label: '10' },
])
</script>

<template>
  <DsfrPagination
    v-model:current-page="currentPage"
    :pages="pages"
  />
</template>

⚙️ Code source du composant

vue
<script lang="ts" setup>
import { computed } from 'vue'

import type { DsfrPaginationProps, Page } from './DsfrPagination.types'

export type { DsfrPaginationProps, Page }

const props = withDefaults(defineProps<DsfrPaginationProps>(), {
  truncLimit: 5,
  currentPage: 0,
  firstPageTitle: 'Première page',
  lastPageTitle: 'Dernière page',
  nextPageTitle: 'Page suivante',
  prevPageTitle: 'Page précédente',
})

const emit = defineEmits<{ (e: 'update:current-page', payload: number): void }>()

const startIndex = computed(() => {
  return Math.min(props.pages.length - 1 - props.truncLimit, Math.max(props.currentPage - (props.truncLimit - props.truncLimit % 2) / 2, 0))
})
const endIndex = computed(() => {
  return Math.min(props.pages.length - 1, startIndex.value + props.truncLimit)
})
const displayedPages = computed(() => {
  return props.pages.length > props.truncLimit ? props.pages.slice(startIndex.value, endIndex.value + 1) : props.pages
})

const updatePage = (index: number) => emit('update:current-page', index)
const toPage = (index: number) => updatePage(index)
const tofirstPage = () => toPage(0)
const toPreviousPage = () => toPage(Math.max(0, props.currentPage - 1))
const toNextPage = () => toPage(Math.min(props.pages.length - 1, props.currentPage + 1))
const toLastPage = () => toPage(props.pages.length - 1)
const isCurrentPage = (page: Page) => props.pages.indexOf(page) === props.currentPage
</script>

<template>
  <nav
    role="navigation"
    class="fr-pagination"
    aria-label="Pagination"
  >
    <ul class="fr-pagination__list">
      <li>
        <a
          :href="pages[0]?.href"
          class="fr-pagination__link fr-pagination__link--first"
          :title="firstPageTitle"
          :disabled="currentPage === 0 ? true : undefined"
          :aria-disabled="currentPage === 0 ? true : undefined"
          @click.prevent="tofirstPage()"
        />
      </li>
      <li>
        <a
          :href="pages[Math.max(currentPage - 1, 0)]?.href"
          class="fr-pagination__link fr-pagination__link--prev fr-pagination__link--lg-label"
          :title="prevPageTitle"
          :disabled="currentPage === 0 ? true : undefined"
          :aria-disabled="currentPage === 0 ? true : undefined"
          @click.prevent="toPreviousPage()"
        >{{ prevPageTitle }}</a>
      </li>
      <li
        v-for="(page, idx) in displayedPages"
        :key="idx"
      >
        <a
          :href="page?.href"
          class="fr-pagination__link fr-unhidden-lg"
          :title="page.title"
          :aria-current="isCurrentPage(page) ? 'page' : undefined"
          @click.prevent="toPage(pages.indexOf(page))"
        >
          <span v-if="displayedPages.indexOf(page) === 0 && startIndex > 0 ">...</span>
          {{ page.label }}
          <span v-if="displayedPages.indexOf(page) === displayedPages.length - 1 && endIndex < pages.length - 1">...</span>
        </a>
      </li>
      <li>
        <a
          :href="pages[Math.min(currentPage + 1, pages.length - 1)]?.href"
          class="fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label"
          :title="nextPageTitle"
          :disabled="currentPage === pages.length - 1 ? true : undefined"
          :aria-disabled="currentPage === pages.length - 1 ? true : undefined"
          @click.prevent="toNextPage()"
        >{{ nextPageTitle }}</a>
      </li>
      <li>
        <a
          class="fr-pagination__link fr-pagination__link--last"
          :href="pages.at(-1)?.href"
          :title="lastPageTitle"
          :disabled="currentPage === pages.length - 1 ? true : undefined"
          :aria-disabled="currentPage === pages.length - 1 ? true : undefined"
          @click.prevent="toLastPage()"
        />
      </li>
    </ul>
  </nav>
</template>

<style scoped>
.fr-pagination__link:hover {
  background-image: linear-gradient(
  deg, rgba(224,224,224,0.5), rgba(224,224,224,0.5));
}
</style>
ts
export type Page = { href?: string, label: string, title: string }

export type DsfrPaginationProps = {
  pages: Page[]
  currentPage?: number
  firstPageTitle?: string
  lastPageTitle?: string
  nextPageTitle?: string
  prevPageTitle?: string
  truncLimit?: number
}