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
Nom | Type | Défaut | Description |
---|---|---|---|
pages | Page[] | requis | Liste des pages, où chaque page est un objet contenant des informations comme href et label . |
truncLimit | number | 5 | Nombre maximum de pages affichées simultanément. |
currentPage | number | 0 | Index de la page actuellement sélectionnée (commence à 0 ). |
firstPageTitle | string | 'Première page' | Texte d'info-bulle pour le lien de la première page. |
lastPageTitle | string | 'Dernière page' | Texte d'info-bulle pour le lien de la dernière page. |
nextPageTitle | string | 'Page suivante' | Texte d'info-bulle pour le lien de la page suivante. |
prevPageTitle | string | 'Page précédente' | Texte d'info-bulle pour le lien de la page précédente. |
📡 Événements
Nom | Payload | Description |
---|---|---|
update:current-page | number | É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
}