Tableau - DsfrTable
🌟 Introduction
Le composant DsfrTable
est un élément puissant et polyvalent pour afficher des données sous forme de tableaux dans vos applications Vue. Utilisant une combinaison de slots, de props, et d'événements personnalisés, ce composant offre une flexibilité remarquable. Plongeons dans les détails !
🏅 La documentation sur le tableau sur le DSFR
La story sur le tableau sur le storybook de VueDsfrWARNING
Pour des tableaux complexes, nous vous recommandons d’utiliser DsfrDataTable
🛠️ Props
Nom | Type | Défaut | Obligatoire | Description |
---|---|---|---|---|
title | string | ✅ | Les en-têtes de votre tableau. | |
headers | Array<string> | [] | Les en-têtes de votre tableau. | |
rows | Array<DsfrTableRowProps | string[] | DsfrTableCellProps[]> | [] | Les données de chaque rangée dans le tableau. | |
rowKey | string | Function | undefined | Une clé unique pour chaque rangée, utilisée pour optimiser la mise à jour du DOM. | |
currentPage | number | 1 | La page actuelle dans la pagination du tableau. | |
resultsDisplayed | number | 10 | Le nombre de résultats affichés par page dans la pagination. |
📡 Événements
Nom | Description |
---|---|
update:currentPage | Émis lors du changement de la page actuelle. |
🧩 Slots
header
: Ce slot permet de personnaliser les en-têtes du tableau. Par défaut, il utiliseDsfrTableHeaders
avec les propsheaders
.- Slot par défaut: Utilisé pour le corps du tableau. Par défaut, il affiche les rangées de données via
DsfrTableRow
.
📝 Exemples
Exemple Basique
vue
<script lang="ts" setup>
import DsfrTable from '../DsfrTable.vue'
</script>
<template>
<DsfrTable
title="Exemple de tableau simple"
:headers="['Nom', 'Age', 'Ville']"
:rows="[
{ rowData: ['Alice', '30', 'Paris'] },
{ rowData: ['Bob', '24', 'Lyon'] },
]"
/>
</template>
Exemple utilisant des composants dans les cellules
vue
<script lang="ts" setup>
import { getCurrentInstance, ref } from 'vue'
import DsfrTag from '../../DsfrTag/DsfrTag.vue'
import DsfrTable from '../DsfrTable.vue'
import type { DsfrTableCellProps, DsfrTableRowProps } from '../DsfrTable.types'
getCurrentInstance()?.appContext.app.component('DsfrTag', DsfrTag)
const title = 'Utilisateurs'
const headers = ['Nom', 'Prénom', 'Email', 'Statut']
const rows: (string | DsfrTableRowProps | DsfrTableCellProps | { component: string, [k: string]: unknown })[][] = [
[
'SÖZE',
'Keyser',
'keyser.soze@mastermind.com',
{
component: 'DsfrTag',
label: 'Info',
class: 'info',
},
],
[
'HUNT',
'Ethan',
'ethan.hunt@impossible.com',
{
component: 'DsfrTag',
label: 'Erreur',
class: 'error',
},
],
[
'HOLMES',
'Sherlock',
'sherlock.holmes@whodunit.com',
{
component: 'DsfrTag',
label: 'Succès',
class: 'success',
},
],
[
'JONES',
'Indiana',
'indiana.jones@marshall-college.com',
{
component: 'DsfrTag',
label: 'Info',
class: 'info',
},
],
[
'WAYNE',
'Bruce',
'bruce.wayne@batmail.com',
{
component: 'DsfrTag',
label: 'Erreur',
class: 'error',
},
],
]
const noCaption = true
const currentPage = ref(1)
const resultsDisplayed = 5
</script>
<template>
<DsfrTable
:title="title"
:headers="headers"
:rows="rows"
:no-caption="noCaption"
:current-page="currentPage"
:results-displayed="resultsDisplayed"
/>
</template>
<style scoped>
:deep(.info) {
color: var(--info-425-625);
background-color: var(--info-950-100);
}
:deep(.error) {
color: var(--error-425-625);
background-color: var(--error-950-100);
}
:deep(.success) {
color: var(--success-425-625);
background-color: var(--success-950-100);
}
</style>
⚙️ Code source du composant
vue
<script lang="ts" setup>
import { computed, ref } from 'vue'
import DsfrTableHeaders from './DsfrTableHeaders.vue'
import DsfrTableRow, { type DsfrTableRowProps } from './DsfrTableRow.vue'
import type { DsfrTableProps } from './DsfrTable.types'
import { useRandomId } from '@/utils/random-utils'
export type { DsfrTableProps }
const props = withDefaults(defineProps<DsfrTableProps>(), {
headers: () => [],
rows: () => [],
rowKey: undefined,
currentPage: 1,
resultsDisplayed: 10,
})
// Permet aux utilisateurs d'utiliser une fonction afin de charger des résultats au changement de page
const emit = defineEmits<{ (event: 'update:currentPage'): void }>()
const getRowData = (row: DsfrTableProps['rows']) => {
return Array.isArray(row) ? row : (row as unknown as DsfrTableRowProps).rowData
}
const currentPage = ref(props.currentPage)
const selectId = useRandomId('resultPerPage')
const optionSelected = ref(props.resultsDisplayed)
const pageCount = computed(() =>
props.rows.length > optionSelected.value
? Math.ceil(props.rows.length / optionSelected.value)
: 1,
)
const paginationOptions = [5, 10, 25, 50, 100]
const returnLowestLimit = () => currentPage.value * optionSelected.value - optionSelected.value
const returnHighestLimit = () => currentPage.value * optionSelected.value
const truncatedResults = computed(() => {
if (props.pagination) {
return props.rows.slice(returnLowestLimit(), returnHighestLimit())
}
return props.rows
})
const goFirstPage = () => {
currentPage.value = 1
emit('update:currentPage')
}
const goPreviousPage = () => {
if (currentPage.value > 1) {
currentPage.value -= 1
emit('update:currentPage')
}
}
const goNextPage = () => {
if (currentPage.value < pageCount.value) {
currentPage.value += 1
emit('update:currentPage')
}
}
const goLastPage = () => {
currentPage.value = pageCount.value
emit('update:currentPage')
}
</script>
<template>
<div
class="fr-table"
:class="{ 'fr-table--no-caption': noCaption }"
>
<table>
<caption class="caption">
{{ title }}
</caption>
<thead>
<!-- @slot Slot "header" pour les en-têtes du tableau. Sera dans `<thead>` -->
<slot name="header">
<DsfrTableHeaders
v-if="headers && headers.length"
:headers="headers"
/>
</slot>
</thead>
<tbody>
<!-- @slot Slot par défaut pour le corps du tableau. Sera dans `<tbody>` -->
<slot />
<template v-if="rows && rows.length">
<DsfrTableRow
v-for="(row, i) of truncatedResults"
:key="
rowKey && getRowData(row as string[][])
? typeof rowKey === 'string'
? getRowData(row as string[][])![headers.indexOf(rowKey)].toString()
: rowKey(getRowData(row as string[][]))
: i
"
:row-data="getRowData(row as string[][])"
:row-attrs="'rowAttrs' in row ? row.rowAttrs : {}"
/>
</template>
<tr v-if="pagination">
<td :colspan="headers.length">
<div class="flex justify-right">
<div class="self-center">
<label :for="selectId">Résultats par page : </label>
<select
:id="selectId"
v-model="optionSelected"
title="Résultats par page - le nombre résultats est mis à jour dès sélection d’une valeur"
@change="emit('update:currentPage')"
>
<option
v-for="(option, idx) in paginationOptions"
:key="idx"
:value="option"
>
{{ option }}
</option>
</select>
</div>
<div
class="flex ml-1"
aria-live="polite"
aria-atomic="true"
>
<p class="self-center fr-m-0">
Page {{ currentPage }} sur {{ pageCount }}
</p>
</div>
<div class="flex ml-1">
<button
class="fr-icon-arrow-left-s-first-line"
@click="goFirstPage()"
>
<span class="fr-sr-only">Première page du tableau</span>
</button>
<button
class="fr-icon-arrow-left-s-line"
@click="goPreviousPage()"
>
<span class="fr-sr-only">Page précédente du tableau</span>
</button>
<button
class="fr-icon-arrow-right-s-line"
@click="goNextPage()"
>
<span class="fr-sr-only">Page suivante du tableau</span>
</button>
<button
class="fr-icon-arrow-right-s-last-line"
@click="goLastPage()"
>
<span class="fr-sr-only">Dernière page du tableau</span>
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
.flex {
display: flex;
}
.justify-right {
justify-content: right;
}
.ml-1 {
margin-left: 1rem;
}
.self-center {
align-self: center;
}
</style>
ts
import type { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from 'vue'
import type VIcon from '../VIcon/VIcon.vue'
export type DsfrTableRowProps = {
rowData?: (string | Record<string, any>)[]
rowAttrs?: HTMLAttributes
}
export type DsfrTableHeaderProps = {
header?: string
headerAttrs?: ThHTMLAttributes & { onClick?: (e: MouseEvent) => void }
icon?: string | InstanceType<typeof VIcon>['$props']
}
export type DsfrTableHeadersProps = (string | (DsfrTableHeaderProps & { text?: string }))[]
export type DsfrTableCellProps = {
field: string | Record<string, unknown>
cellAttrs?: TdHTMLAttributes
component?: string
text?: string
title?: string
class?: string
onClick?: Promise<void>
}
export type DsfrTableProps = {
title: string
headers?: DsfrTableHeadersProps
rows?: (DsfrTableRowProps | (DsfrTableCellProps | { component: string, [k: string]: unknown } | string)[])[]
rowKey?: ((row: (string | Record<string, any>)[] | undefined) => string | number | symbol | undefined) | string
noCaption?: boolean
pagination?: boolean
currentPage?: number
resultsDisplayed?: number
}
C'est tout, amis développeurs ! Avec DsfrTable, donnez vie à vos données comme jamais auparavant ! 🎉