feat(Table): add footer support to display column summary (#4194)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Igor G
2025-07-02 16:57:21 +02:00
committed by GitHub
parent a0e71d9e29
commit c355cacd43
8 changed files with 409 additions and 5 deletions

View File

@@ -4,7 +4,7 @@ import { flushPromises } from '@vue/test-utils'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { UCheckbox, UButton, UBadge, UDropdownMenu } from '#components'
import Table from '../../src/runtime/components/Table.vue'
import type { TableProps, TableSlots, TableColumn } from '../../src/runtime/components/Table.vue'
import type { TableProps, TableSlots, TableColumn, TableRow } from '../../src/runtime/components/Table.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/table'
@@ -99,6 +99,16 @@ describe('Table', () => {
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
footer: ({ column }) => {
const total = column.getFacetedRowModel().rows.reduce((acc: number, row: TableRow<typeof data[number]>) => acc + Number.parseFloat(row.getValue('amount')), 0)
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(total)
return h('div', { class: 'text-right font-medium' }, `Total: ${formatted}`)
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))

View File

@@ -50,6 +50,7 @@ exports[`Table > renders with as correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</section>"
`;
@@ -104,6 +105,7 @@ exports[`Table > renders with body-bottom slot correctly 1`] = `
</tr>
<!--v-if-->Body bottom slot
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -157,6 +159,7 @@ exports[`Table > renders with body-top slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -211,6 +214,7 @@ exports[`Table > renders with caption correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -265,6 +269,7 @@ exports[`Table > renders with caption slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -319,6 +324,7 @@ exports[`Table > renders with cell slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -373,6 +379,7 @@ exports[`Table > renders with class correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -564,6 +571,32 @@ exports[`Table > renders with columns correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<tfoot class="relative">
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
<tr class="data-[selected=true]:bg-elevated/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right font-medium">Total: €2,990.00</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</tfoot>
</table>
</div>"
`;
@@ -618,6 +651,7 @@ exports[`Table > renders with data correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -635,6 +669,7 @@ exports[`Table > renders with empty correctly 1`] = `
<td colspan="0" class="py-6 text-center text-sm text-muted">There is no data</td>
</tr>
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -674,6 +709,32 @@ exports[`Table > renders with empty slot correctly 1`] = `
<td colspan="7" class="py-6 text-center text-sm text-muted">Empty slot</td>
</tr>
</tbody>
<tfoot class="relative">
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
<tr class="data-[selected=true]:bg-elevated/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right font-medium">Total: €0.00</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</tfoot>
</table>
</div>"
`;
@@ -728,6 +789,7 @@ exports[`Table > renders with expanded slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -782,6 +844,7 @@ exports[`Table > renders with header slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -836,6 +899,7 @@ exports[`Table > renders with loading animation carousel correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -890,6 +954,7 @@ exports[`Table > renders with loading animation carousel-inverse correctly 1`] =
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -944,6 +1009,7 @@ exports[`Table > renders with loading animation elastic correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -998,6 +1064,7 @@ exports[`Table > renders with loading animation swing correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1052,6 +1119,7 @@ exports[`Table > renders with loading color error correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1106,6 +1174,7 @@ exports[`Table > renders with loading color info correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1160,6 +1229,7 @@ exports[`Table > renders with loading color neutral correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1214,6 +1284,7 @@ exports[`Table > renders with loading color primary correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1268,6 +1339,7 @@ exports[`Table > renders with loading color secondary correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1322,6 +1394,7 @@ exports[`Table > renders with loading color success correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1376,6 +1449,7 @@ exports[`Table > renders with loading color warning correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1430,6 +1504,7 @@ exports[`Table > renders with loading correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1469,6 +1544,32 @@ exports[`Table > renders with loading slot correctly 1`] = `
<td colspan="7" class="py-6 text-center">Loading slot</td>
</tr>
</tbody>
<tfoot class="relative">
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
<tr class="data-[selected=true]:bg-elevated/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right font-medium">Total: €0.00</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</tfoot>
</table>
</div>"
`;
@@ -1523,6 +1624,7 @@ exports[`Table > renders with sticky correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1577,6 +1679,7 @@ exports[`Table > renders with ui correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1594,6 +1697,7 @@ exports[`Table > renders without data correctly 1`] = `
<td colspan="0" class="py-6 text-center text-sm text-muted">No data</td>
</tr>
</tbody>
<!--v-if-->
</table>
</div>"
`;

View File

@@ -50,6 +50,7 @@ exports[`Table > renders with as correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</section>"
`;
@@ -104,6 +105,7 @@ exports[`Table > renders with body-bottom slot correctly 1`] = `
</tr>
<!--v-if-->Body bottom slot
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -157,6 +159,7 @@ exports[`Table > renders with body-top slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -211,6 +214,7 @@ exports[`Table > renders with caption correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -265,6 +269,7 @@ exports[`Table > renders with caption slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -319,6 +324,7 @@ exports[`Table > renders with cell slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -373,6 +379,7 @@ exports[`Table > renders with class correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -564,6 +571,32 @@ exports[`Table > renders with columns correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<tfoot class="relative">
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
<tr class="data-[selected=true]:bg-elevated/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right font-medium">Total: €2,990.00</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</tfoot>
</table>
</div>"
`;
@@ -618,6 +651,7 @@ exports[`Table > renders with data correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -635,6 +669,7 @@ exports[`Table > renders with empty correctly 1`] = `
<td colspan="0" class="py-6 text-center text-sm text-muted">There is no data</td>
</tr>
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -674,6 +709,32 @@ exports[`Table > renders with empty slot correctly 1`] = `
<td colspan="7" class="py-6 text-center text-sm text-muted">Empty slot</td>
</tr>
</tbody>
<tfoot class="relative">
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
<tr class="data-[selected=true]:bg-elevated/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right font-medium">Total: €0.00</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</tfoot>
</table>
</div>"
`;
@@ -728,6 +789,7 @@ exports[`Table > renders with expanded slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -782,6 +844,7 @@ exports[`Table > renders with header slot correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -836,6 +899,7 @@ exports[`Table > renders with loading animation carousel correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -890,6 +954,7 @@ exports[`Table > renders with loading animation carousel-inverse correctly 1`] =
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -944,6 +1009,7 @@ exports[`Table > renders with loading animation elastic correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -998,6 +1064,7 @@ exports[`Table > renders with loading animation swing correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1052,6 +1119,7 @@ exports[`Table > renders with loading color error correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1106,6 +1174,7 @@ exports[`Table > renders with loading color info correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1160,6 +1229,7 @@ exports[`Table > renders with loading color neutral correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1214,6 +1284,7 @@ exports[`Table > renders with loading color primary correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1268,6 +1339,7 @@ exports[`Table > renders with loading color secondary correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1322,6 +1394,7 @@ exports[`Table > renders with loading color success correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1376,6 +1449,7 @@ exports[`Table > renders with loading color warning correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1430,6 +1504,7 @@ exports[`Table > renders with loading correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1469,6 +1544,32 @@ exports[`Table > renders with loading slot correctly 1`] = `
<td colspan="7" class="py-6 text-center">Loading slot</td>
</tr>
</tbody>
<tfoot class="relative">
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
<tr class="data-[selected=true]:bg-elevated/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right font-medium">Total: €0.00</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</tfoot>
</table>
</div>"
`;
@@ -1523,6 +1624,7 @@ exports[`Table > renders with sticky correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1577,6 +1679,7 @@ exports[`Table > renders with ui correctly 1`] = `
</tr>
<!--v-if-->
</tbody>
<!--v-if-->
</table>
</div>"
`;
@@ -1594,6 +1697,7 @@ exports[`Table > renders without data correctly 1`] = `
<td colspan="0" class="py-6 text-center text-sm text-muted">No data</td>
</tr>
</tbody>
<!--v-if-->
</table>
</div>"
`;