feat : wip ajout du composant Datatable
This commit is contained in:
195
app/story/datatable/datatable.story.vue
Normal file
195
app/story/datatable/datatable.story.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<Story title="Data/DataTable">
|
||||
<Variant title="Avec filtres et pagination">
|
||||
<div class="p-4">
|
||||
<MalioDataTable
|
||||
:columns="columns"
|
||||
:items="paginatedItems"
|
||||
:total-items="filteredItems.length"
|
||||
v-model:page="page"
|
||||
v-model:per-page="perPage"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<template #header-nom>
|
||||
<input
|
||||
v-model="filtreNom"
|
||||
type="text"
|
||||
placeholder="Nom"
|
||||
class="w-full border-0 border-b border-black bg-transparent px-0 py-1 text-sm outline-none"
|
||||
>
|
||||
</template>
|
||||
<template #header-ville>
|
||||
<select
|
||||
:value="filtreVille ?? ''"
|
||||
class="w-full appearance-none border-0 border-b border-black bg-transparent px-0 py-1 text-sm outline-none"
|
||||
@change="filtreVille = ($event.target as HTMLSelectElement).value || null"
|
||||
>
|
||||
<option value="">Ville</option>
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</select>
|
||||
</template>
|
||||
<template #cell-montant="{ item }">
|
||||
<strong>{{ item.montant }} €</strong>
|
||||
</template>
|
||||
</MalioDataTable>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Sans filtres">
|
||||
<div class="p-4">
|
||||
<MalioDataTable
|
||||
:columns="columnsSimple"
|
||||
:items="simpleItems"
|
||||
:total-items="simpleItems.length"
|
||||
v-model:page="pageSimple"
|
||||
v-model:per-page="perPageSimple"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="État vide">
|
||||
<div class="p-4">
|
||||
<MalioDataTable
|
||||
:columns="columns"
|
||||
:items="[]"
|
||||
:total-items="0"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Lignes non cliquables">
|
||||
<div class="p-4">
|
||||
<MalioDataTable
|
||||
:columns="columnsSimple"
|
||||
:items="simpleItems.slice(0, 3)"
|
||||
:total-items="3"
|
||||
:row-clickable="false"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Sans filtre ni pagination">
|
||||
<div class="p-4">
|
||||
<MalioDataTable
|
||||
:columns="columnsSimple"
|
||||
:items="simpleItems.slice(0, 5)"
|
||||
:total-items="0"
|
||||
:row-clickable="false"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<docs lang="md">
|
||||
# MalioDataTable
|
||||
|
||||
Tableau de données presentational avec pagination, filtres par slots et lignes cliquables.
|
||||
|
||||
## Props détaillées
|
||||
|
||||
| Prop | Type | Défaut | Description |
|
||||
|------|------|--------|-------------|
|
||||
| `id` | `string` | auto-généré | Identifiant HTML |
|
||||
| `columns` | `{ key: string, label: string }[]` | **requis** | Définition des colonnes |
|
||||
| `items` | `Record<string, any>[]` | **requis** | Données à afficher |
|
||||
| `totalItems` | `number` | **requis** | Total pour la pagination |
|
||||
| `page` | `number` | `1` | Page courante (v-model) |
|
||||
| `perPage` | `number` | `10` | Lignes par page (v-model) |
|
||||
| `perPageOptions` | `number[]` | `[10, 25, 50]` | Options du sélecteur de lignes |
|
||||
| `rowClickable` | `boolean` | `true` | Lignes cliquables |
|
||||
| `tableClass` | `string` | `''` | Classes CSS sur le wrapper (twMerge) |
|
||||
| `emptyMessage` | `string` | `'Aucune donnée'` | Message si items vide |
|
||||
|
||||
## Slots
|
||||
|
||||
| Slot | Scope | Description |
|
||||
|------|-------|-------------|
|
||||
| `#header-{key}` | `{ column }` | Filtre dans le `<th>` (placeholder = label). Fallback : texte du label |
|
||||
| `#cell-{key}` | `{ item, column }` | Contenu du `<td>`. Fallback : `item[key]` |
|
||||
| `#empty` | — | Contenu état vide. Fallback : `emptyMessage` |
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Payload | Description |
|
||||
|-------|---------|-------------|
|
||||
| `update:page` | `number` | Changement de page |
|
||||
| `update:per-page` | `number` | Changement du nb de lignes (reset page à 1) |
|
||||
| `row-click` | `Record<string, any>` | Clic sur une ligne |
|
||||
|
||||
## Pagination
|
||||
|
||||
- ≤ 5 pages : toutes affichées
|
||||
- \> 5 pages : page 1 … [voisin] **[courante]** [voisin] … dernière
|
||||
- Boutons Prev/Next toujours visibles, désactivés aux extrêmes
|
||||
|
||||
## Accessibilité
|
||||
|
||||
- `<th scope="col">` sur chaque en-tête
|
||||
- `<nav aria-label="Pagination">` autour de la pagination
|
||||
- Page courante avec `aria-current="page"`
|
||||
- Lignes cliquables : `tabindex="0"` + Enter/Space
|
||||
</docs>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import MalioDataTable from '../../components/malio/datatable/DataTable.vue'
|
||||
|
||||
defineOptions({ name: 'DataTableStory' })
|
||||
|
||||
const columns = [
|
||||
{ key: 'nom', label: 'Nom' },
|
||||
{ key: 'prenom', label: 'Prénom' },
|
||||
{ key: 'ville', label: 'Ville' },
|
||||
{ key: 'montant', label: 'Montant' },
|
||||
]
|
||||
|
||||
const columnsSimple = [
|
||||
{ key: 'nom', label: 'Nom' },
|
||||
{ key: 'ville', label: 'Ville' },
|
||||
]
|
||||
|
||||
const allItems = [
|
||||
{ id: 1, nom: 'Dupont', prenom: 'Jean', ville: 'Paris', montant: 1200 },
|
||||
{ id: 2, nom: 'Martin', prenom: 'Marie', ville: 'Lyon', montant: 850 },
|
||||
{ id: 3, nom: 'Bernard', prenom: 'Pierre', ville: 'Marseille', montant: 2100 },
|
||||
{ id: 4, nom: 'Petit', prenom: 'Sophie', ville: 'Paris', montant: 950 },
|
||||
{ id: 5, nom: 'Robert', prenom: 'Paul', ville: 'Lyon', montant: 1800 },
|
||||
{ id: 6, nom: 'Richard', prenom: 'Claire', ville: 'Marseille', montant: 3200 },
|
||||
{ id: 7, nom: 'Durand', prenom: 'Luc', ville: 'Paris', montant: 750 },
|
||||
{ id: 8, nom: 'Moreau', prenom: 'Anne', ville: 'Lyon', montant: 1100 },
|
||||
{ id: 9, nom: 'Simon', prenom: 'Marc', ville: 'Marseille', montant: 2400 },
|
||||
{ id: 10, nom: 'Laurent', prenom: 'Julie', ville: 'Paris', montant: 1650 },
|
||||
{ id: 11, nom: 'Lefebvre', prenom: 'Thomas', ville: 'Lyon', montant: 900 },
|
||||
{ id: 12, nom: 'Leroy', prenom: 'Emma', ville: 'Marseille', montant: 1400 },
|
||||
]
|
||||
|
||||
const simpleItems = allItems.map(i => ({ nom: i.nom, ville: i.ville }))
|
||||
|
||||
const page = ref(1)
|
||||
const perPage = ref(5)
|
||||
const filtreNom = ref('')
|
||||
const filtreVille = ref<string | number | null>(null)
|
||||
|
||||
const pageSimple = ref(1)
|
||||
const perPageSimple = ref(10)
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
return allItems.filter((item) => {
|
||||
if (filtreNom.value && !item.nom.toLowerCase().includes(filtreNom.value.toLowerCase())) return false
|
||||
if (filtreVille.value && item.ville !== filtreVille.value) return false
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const paginatedItems = computed(() => {
|
||||
const start = (page.value - 1) * perPage.value
|
||||
return filteredItems.value.slice(start, start + perPage.value)
|
||||
})
|
||||
|
||||
function onRowClick(item: Record<string, unknown>) {
|
||||
alert(`Clic sur ${item.nom} ${item.prenom}`)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user