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

@@ -0,0 +1,106 @@
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
}, {
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}])
const columns: TableColumn<Payment>[] = [{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
}, {
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
}, {
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = ({
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
})[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
}
}, {
accessorKey: 'email',
header: 'Email'
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
footer: ({ column }) => {
const total = column.getFacetedRowModel().rows.reduce((acc: number, row: TableRow<Payment>) => 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'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>

View File

@@ -77,6 +77,7 @@ Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/lat
- `accessorKey`: [The key of the row object to use when extracting the value for the column.]{class="text-muted"}
- `header`: [The header to display for the column. If a string is passed, it can be used as a default for the column ID. If a function is passed, it will be passed a props object for the header and should return the rendered header value (the exact type depends on the adapter being used).]{class="text-muted"}
- `footer`: [The footer to display for the column. Works exactly like header, but is displayed under the table.]{class="text-muted"}
- `cell`: [The cell to display each row for the column. If a function is passed, it will be passed a props object for the cell and should return the rendered cell value (the exact type depends on the adapter being used).]{class="text-muted"}
- `meta`: [Extra properties for the column.]{class="text-muted"}
- `class`:
@@ -161,7 +162,7 @@ props:
### Sticky
Use the `sticky` prop to make the header sticky.
Use the `sticky` prop to make the header or footer sticky.
::component-code
---
@@ -172,6 +173,10 @@ ignore:
- class
external:
- data
items:
sticky:
- true
- false
props:
sticky: true
data:
@@ -372,6 +377,22 @@ class: '!p-0'
This example is similar as the Popover [with following cursor example](/components/popover#with-following-cursor) and uses a [`refDebounced`](https://vueuse.org/shared/refDebounced/#refdebounced) to prevent the Popover from opening and closing too quickly when moving the cursor from one row to another.
::
### With column footer :badge{label="Soon" class="align-text-top"}
You can add a `footer` property to the column definition to render a footer for the column.
::component-example
---
prettier: true
collapse: true
name: 'table-column-footer-example'
highlights:
- 94
- 108
class: '!p-0'
---
::
### With column sorting
You can update a column `header` to render a [Button](/components/button) component inside the `header` to toggle the sorting state using the TanStack Table [Sorting APIs](https://tanstack.com/table/latest/docs/api/features/sorting).

View File

@@ -242,6 +242,16 @@ const columns: TableColumn<Payment>[] = [{
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
footer: ({ column }) => {
const total = column.getFacetedRowModel().rows.reduce((acc: number, row: TableRow<Payment>) => 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

@@ -83,10 +83,10 @@ export interface TableProps<T extends TableData = TableData> extends TableOption
*/
empty?: string
/**
* Whether the table should have a sticky header.
* Whether the table should have a sticky header or footer. True for both, 'header' for header only, 'footer' for footer only.
* @defaultValue false
*/
sticky?: boolean
sticky?: boolean | 'header' | 'footer'
/** Whether the table should be in loading state. */
loading?: boolean
/**
@@ -172,6 +172,7 @@ export interface TableProps<T extends TableData = TableData> extends TableOption
}
type DynamicHeaderSlots<T, K = keyof T> = Record<string, (props: HeaderContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-header`, (props: HeaderContext<T, unknown>) => any>
type DynamicFooterSlots<T, K = keyof T> = Record<string, (props: HeaderContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-footer`, (props: HeaderContext<T, unknown>) => any>
type DynamicCellSlots<T, K = keyof T> = Record<string, (props: CellContext<T, unknown>) => any> & Record<`${K extends string ? K : never}-cell`, (props: CellContext<T, unknown>) => any>
export type TableSlots<T extends TableData = TableData> = {
@@ -181,7 +182,7 @@ export type TableSlots<T extends TableData = TableData> = {
'caption': (props?: {}) => any
'body-top': (props?: {}) => any
'body-bottom': (props?: {}) => any
} & DynamicHeaderSlots<T> & DynamicCellSlots<T>
} & DynamicHeaderSlots<T> & DynamicFooterSlots<T> & DynamicCellSlots<T>
</script>
@@ -216,6 +217,22 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.table || {})
loadingAnimation: props.loadingAnimation
}))
const hasFooter = computed(() => {
function hasFooterRecursive(columns: TableColumn<T>[]): boolean {
for (const column of columns) {
if ('footer' in column) {
return true
}
if ('columns' in column && hasFooterRecursive(column.columns as TableColumn<T>[])) {
return true
}
}
return false
}
return hasFooterRecursive(columns.value)
})
const globalFilterState = defineModel<string>('globalFilter', { default: undefined })
const columnFiltersState = defineModel<ColumnFiltersState>('columnFilters', { default: [] })
const columnOrderState = defineModel<ColumnOrderState>('columnOrder', { default: [] })
@@ -461,6 +478,30 @@ defineExpose({
<slot name="body-bottom" />
</tbody>
<tfoot v-if="hasFooter" :class="ui.tfoot({ class: [props.ui?.tfoot] })">
<tr :class="ui.separator({ class: [props.ui?.separator] })" />
<tr v-for="footerGroup in tableApi.getFooterGroups()" :key="footerGroup.id" :class="ui.tr({ class: [props.ui?.tr] })">
<th
v-for="header in footerGroup.headers"
:key="header.id"
:data-pinned="header.column.getIsPinned()"
:colspan="header.colSpan > 1 ? header.colSpan : undefined"
:class="ui.th({
class: [
props.ui?.th,
typeof header.column.columnDef.meta?.class?.th === 'function' ? header.column.columnDef.meta.class.th(header) : header.column.columnDef.meta?.class?.th
],
pinned: !!header.column.getIsPinned()
})"
>
<slot :name="`${header.id}-footer`" v-bind="header.getContext()">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.footer" :props="header.getContext()" />
</slot>
</th>
</tr>
</tfoot>
</table>
</Primitive>
</template>

View File

@@ -7,6 +7,7 @@ export default (options: Required<ModuleOptions>) => ({
caption: 'sr-only',
thead: 'relative',
tbody: 'divide-y divide-default [&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:focus-visible:outline-primary',
tfoot: 'relative',
tr: 'data-[selected=true]:bg-elevated/50',
th: 'px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&:has([role=checkbox])]:pe-0',
td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
@@ -23,7 +24,14 @@ export default (options: Required<ModuleOptions>) => ({
},
sticky: {
true: {
thead: 'sticky top-0 inset-x-0 bg-default/75 z-[1] backdrop-blur',
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
},
header: {
thead: 'sticky top-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
},
footer: {
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
}
},
loading: {

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>"
`;