All checks were successful
Release / release (push) Successful in 1m5s
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [x] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [x] CHANGELOG modifié Co-authored-by: kevin <kevin@yuno.malio.fr> Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr> Reviewed-on: #28 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
7.6 KiB
7.6 KiB
MalioDataTable — Design Spec
Composant de tableau de données presentational avec pagination, filtres par slots et lignes cliquables.
Ticket : MUI-22
Branche : feature/MUI-22-developper-le-composant-datatable
Architecture
Composant unique MalioDataTable dans app/components/malio/datatable/DataTable.vue. Pas de décomposition — la pagination est intégrée dans le composant.
Le composant est presentational : il ne fait aucun fetch. Le parent fournit les données (items) et le total (totalItems), et réagit aux events de pagination/filtre pour relancer ses propres requêtes API.
Props
| Prop | Type | Défaut | Description |
|---|---|---|---|
id |
string |
auto-généré | Identifiant HTML du wrapper |
columns |
Column[] |
requis | Définition des colonnes |
items |
Record<string, any>[] |
requis | Données à afficher |
totalItems |
number |
requis | Nombre total d'items (pour calculer le nb de pages) |
page |
number |
1 |
Page courante, 1-based (v-model) |
perPage |
number |
10 |
Nombre de lignes par page (v-model) |
perPageOptions |
number[] |
[10, 25, 50] |
Options du sélecteur de lignes |
rowClickable |
boolean |
true |
Rend les lignes cliquables (cursor pointer + hover) |
tableClass |
string |
'' |
Classes CSS additionnelles sur <table> (twMerge) |
emptyMessage |
string |
'Aucune donnée' |
Message affiché quand items est vide |
Type Column
type Column = {
key: string // Clé correspondant à item[key]
label: string // Texte affiché dans le <th> (fallback si pas de slot header)
}
Events
| Event | Payload | Description |
|---|---|---|
update:page |
number |
Changement de page (pagination ou Prev/Next) |
update:per-page |
number |
Changement du nombre de lignes par page |
row-click |
Record<string, any> |
Clic sur une ligne (l'item de la ligne) |
Slots
| Slot | Scope | Description |
|---|---|---|
#header-{key} |
{ column } |
Contenu du <th> — filtre (input, select…). Si absent, affiche column.label en texte |
#cell-{key} |
{ item, column } |
Contenu du <td>. Si absent, affiche item[column.key] en texte |
#empty |
— | Contenu affiché quand items est vide. Si absent, affiche emptyMessage |
Structure HTML
<div :id="id"> ← wrapper
<table>
<thead>
<tr>
<th v-for="col" scope="col"> ← une seule ligne d'en-tête
slot #header-{key} ← filtre (placeholder = nom colonne)
OU label texte ← si pas de slot
</th>
</tr>
</thead>
<tbody>
<tr v-for="item" ← cliquable si rowClickable
tabindex="0" ← (si rowClickable) navigation clavier
@click="emit row-click"
@keydown.enter/space="emit row-click">
<td v-for="col">
slot #cell-{key} ← contenu custom
OU item[col.key] ← texte brut
</td>
</tr>
<tr v-if="!items.length"> ← état vide
<td :colspan="columns.length">
slot #empty OU emptyMessage
</td>
</tr>
</tbody>
</table>
<div v-if="totalItems > 0"> ← barre de pagination (masquée si aucune donnée)
<MalioSelect /> ← sélecteur nb lignes (options mappées depuis perPageOptions)
<nav aria-label="Pagination"> ← numéros de page + Prev/Next
<MalioButton variant="tertiary" label="Prev" /> ← disabled si page 1
<button> pour chaque numéro de page ← éléments <button>
<span aria-hidden="true">…</span> ← ellipsis
<MalioButton variant="tertiary" label="Next" /> ← disabled si dernière page
</nav>
</div>
</div>
Logique de pagination (troncature)
Règles
- ≤ 5 pages : afficher toutes les pages, pas d'ellipsis
- > 5 pages : toujours afficher page 1 et dernière page, 1 voisin de chaque côté de la page active, ellipsis
…quand écart > 1 - Prev :
MalioButton variant="tertiary", toujours visible,disabledsur page 1 - Next :
MalioButton variant="tertiary", toujours visible,disabledsur dernière page - Changement de
perPage: émet automatiquementupdate:pageavec1(reset à la première page) totalItems = 0: la barre de pagination est masquée entièrement
Exemples
≤ 5 pages (toutes affichées) :
Page 1/3 : Prev(disabled) [1] 2 3 Next
Page 2/5 : Prev 1 [2] 3 4 5 Next
Page 5/5 : Prev 1 2 3 4 [5] Next(disabled)
> 5 pages (troncature 1 voisin) :
Page 1/20 : Prev(disabled) [1] 2 … 20 Next
Page 2/20 : Prev 1 [2] 3 … 20 Next
Page 3/20 : Prev 1 2 [3] 4 … 20 Next
Page 4/20 : Prev 1 … 3 [4] 5 … 20 Next
Page 7/20 : Prev 1 … 6 [7] 8 … 20 Next
Page 18/20 : Prev 1 … 17 [18] 19 20 Next
Page 19/20 : Prev 1 … 18 [19] 20 Next
Page 20/20 : Prev 1 … 19 [20] Next(disabled)
En-têtes — logique du <th>
Chaque <th> vérifie si le slot #header-{key} est fourni :
- Slot fourni → rend le slot (le consommateur y met un
MalioInputText,MalioSelect, etc. avec le placeholder qui sert de label de colonne) - Slot absent → rend
column.labelen texte (font-semibold text-m-primary)
Pas de label séparé au-dessus du filtre. Le placeholder de l'input/select fait office de nom de colonne.
Composants Malio utilisés en interne
MalioSelect— sélecteur du nombre de lignes par page. LesperPageOptionssont mappés au format{ label: string, value: number }[]attendu par MalioSelect (ex:{ label: '10', value: 10 })MalioButton variant="tertiary"— boutons Prev / Next
Exemple d'utilisation consommateur
<MalioDataTable
:columns="[
{ key: 'nom', label: 'Nom' },
{ key: 'ville', label: 'Ville' },
{ key: 'montant', label: 'Montant' },
]"
:items="data"
:total-items="total"
v-model:page="page"
v-model:per-page="perPage"
@row-click="router.push(`/contact/${$event.id}`)"
>
<!-- Filtre texte — placeholder sert de label -->
<template #header-nom>
<MalioInputText v-model="filtres.nom" placeholder="Nom" />
</template>
<!-- Filtre select — placeholder sert de label -->
<template #header-ville>
<MalioSelect v-model="filtres.ville" :options="villes"
empty-option-label="Ville" />
</template>
<!-- Pas de slot header pour "montant" → affiche "Montant" en texte -->
<!-- Cellule custom -->
<template #cell-montant="{ item }">
<strong>{{ item.montant }} €</strong>
</template>
</MalioDataTable>
Accessibilité
<table>élément natif (sémantique table implicite)<th scope="col">sur chaque en-tête- Pagination dans un
<nav aria-label="Pagination"> - Numéros de page : éléments
<button>, page courante avecaria-current="page" - Ellipsis
…:<span aria-hidden="true">(ignoré par les lecteurs d'écran) - Boutons Prev/Next avec
aria-labelexplicites ("Page précédente" / "Page suivante") - Lignes cliquables :
tabindex="0"+ gestionEnter/Spacepour navigation clavier (pas derole="link"— on garde la sémantique<tr>native)
Styles
- En-têtes :
bg-m-surface, label entext-m-primary font-semibold - Bordures :
border-m-border - Lignes hover :
hover:bg-m-bg(sirowClickable) - Ligne cursor :
cursor-pointer(sirowClickable) - Page active :
bg-m-btn-primary text-white rounded - Boutons Prev/Next :
MalioButton variant="tertiary" - Message vide :
text-m-muted text-center,<td>aveccolspansur toute la largeur