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 VueDsfr🛠️ 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 } 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',
},
],
]
</script>
<template>
<DsfrTable
:title="title"
:headers="headers"
:rows="rows"
:no-caption="noCaption"
:pagination="pagination"
: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, watch } from 'vue'
import DsfrTableHeaders from './DsfrTableHeaders.vue'
import DsfrTableRow, { type DsfrTableRowProps } from './DsfrTableRow.vue'
import type { DsfrTableProps } from './DsfrTable.types'
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 optionSelected = ref(props.resultsDisplayed)
const pageCount = ref(
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
watch(
() => optionSelected.value,
(newVal) => {
pageCount.value =
props.rows.length > optionSelected.value ? Math.ceil(props.rows.length / newVal) : 1
},
)
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">
<span>Résultats par page : </span>
<select
v-model="optionSelected"
@change="emit('update:currentPage')"
>
<option
v-for="(option, idx) in paginationOptions"
:key="idx"
:value="option"
>
{{ option }}
</option>
</select>
</div>
<div class="flex ml-1">
<span class="self-center">Page {{ currentPage }} sur {{ pageCount }}</span>
</div>
<div class="flex ml-1">
<button
class="fr-icon-arrow-left-s-first-line"
@click="goFirstPage()"
/>
<button
class="fr-icon-arrow-left-s-line"
@click="goPreviousPage()"
/>
<button
class="fr-icon-arrow-right-s-line"
@click="goNextPage()"
/>
<button
class="fr-icon-arrow-right-s-last-line"
@click="goLastPage()"
/>
</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 ! 🎉