Skip to content

Commit 595fc64

Browse files
feat(Table): add body-top / body-bottom slots (#4354)
Co-authored-by: Benjamin Canac <[email protected]>
1 parent 8156971 commit 595fc64

File tree

4 files changed

+227
-5
lines changed

4 files changed

+227
-5
lines changed

src/runtime/components/Table.vue

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,12 @@ type DynamicHeaderSlots<T, K = keyof T> = Record<string, (props: HeaderContext<T
173173
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>
174174
175175
export type TableSlots<T extends TableData = TableData> = {
176-
expanded: (props: { row: Row<T> }) => any
177-
empty: (props?: {}) => any
178-
loading: (props?: {}) => any
179-
caption: (props?: {}) => any
176+
'expanded': (props: { row: Row<T> }) => any
177+
'empty': (props?: {}) => any
178+
'loading': (props?: {}) => any
179+
'caption': (props?: {}) => any
180+
'body-top': (props?: {}) => any
181+
'body-bottom': (props?: {}) => any
180182
} & DynamicHeaderSlots<T> & DynamicCellSlots<T>
181183
182184
</script>
@@ -371,6 +373,8 @@ defineExpose({
371373
</thead>
372374

373375
<tbody :class="ui.tbody({ class: [props.ui?.tbody] })">
376+
<slot name="body-top" />
377+
374378
<template v-if="tableApi.getRowModel().rows?.length">
375379
<template v-for="row in tableApi.getRowModel().rows" :key="row.id">
376380
<tr
@@ -425,6 +429,8 @@ defineExpose({
425429
</slot>
426430
</td>
427431
</tr>
432+
433+
<slot name="body-bottom" />
428434
</tbody>
429435
</table>
430436
</Primitive>

test/components/Table.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ describe('Table', () => {
161161
['with expanded slot', { props, slots: { expanded: () => 'Expanded slot' } }],
162162
['with empty slot', { props: { columns }, slots: { empty: () => 'Empty slot' } }],
163163
['with loading slot', { props: { columns, loading: true }, slots: { loading: () => 'Loading slot' } }],
164-
['with caption slot', { props, slots: { caption: () => 'Caption slot' } }]
164+
['with caption slot', { props, slots: { caption: () => 'Caption slot' } }],
165+
['with body-top slot', { props, slots: { 'body-top': () => 'Body top slot' } }],
166+
['with body-bottom slot', { props, slots: { 'body-bottom': () => 'Body bottom slot' } }]
165167
])('renders %s correctly', async (nameOrHtml: string, options: { props?: TableProps, slots?: Partial<TableSlots> }) => {
166168
const html = await ComponentRender(nameOrHtml, options, Table)
167169
expect(html).toMatchSnapshot()

test/components/__snapshots__/Table-vue.spec.ts.snap

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,113 @@ exports[`Table > renders with as correctly 1`] = `
5454
</section>"
5555
`;
5656

57+
exports[`Table > renders with body-bottom slot correctly 1`] = `
58+
"<div class="relative overflow-auto">
59+
<table class="min-w-full overflow-clip">
60+
<!--v-if-->
61+
<thead class="relative">
62+
<tr class="data-[selected=true]:bg-elevated/50">
63+
<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">Id</th>
64+
<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">Amount</th>
65+
<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">Status</th>
66+
<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">Email</th>
67+
</tr>
68+
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
69+
</thead>
70+
<tbody class="divide-y divide-default [&amp;>tr]:data-[selectable=true]:hover:bg-elevated/50 [&amp;>tr]:data-[selectable=true]:focus-visible:outline-primary">
71+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
72+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">m5gr84i9</td>
73+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">316</td>
74+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
75+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
76+
</tr>
77+
<!--v-if-->
78+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
79+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">3u1reuv4</td>
80+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">242</td>
81+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
82+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
83+
</tr>
84+
<!--v-if-->
85+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
86+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">derv1ws0</td>
87+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">837</td>
88+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">processing</td>
89+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
90+
</tr>
91+
<!--v-if-->
92+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
93+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">5kma53ae</td>
94+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">874</td>
95+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
96+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
97+
</tr>
98+
<!--v-if-->
99+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
100+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">bhqecj4p</td>
101+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">721</td>
102+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">failed</td>
103+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
104+
</tr>
105+
<!--v-if-->Body bottom slot
106+
</tbody>
107+
</table>
108+
</div>"
109+
`;
110+
111+
exports[`Table > renders with body-top slot correctly 1`] = `
112+
"<div class="relative overflow-auto">
113+
<table class="min-w-full overflow-clip">
114+
<!--v-if-->
115+
<thead class="relative">
116+
<tr class="data-[selected=true]:bg-elevated/50">
117+
<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">Id</th>
118+
<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">Amount</th>
119+
<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">Status</th>
120+
<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">Email</th>
121+
</tr>
122+
<tr class="absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)"></tr>
123+
</thead>
124+
<tbody class="divide-y divide-default [&amp;>tr]:data-[selectable=true]:hover:bg-elevated/50 [&amp;>tr]:data-[selectable=true]:focus-visible:outline-primary">Body top slot<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
125+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">m5gr84i9</td>
126+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">316</td>
127+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
128+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
129+
</tr>
130+
<!--v-if-->
131+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
132+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">3u1reuv4</td>
133+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">242</td>
134+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
135+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
136+
</tr>
137+
<!--v-if-->
138+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
139+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">derv1ws0</td>
140+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">837</td>
141+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">processing</td>
142+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
143+
</tr>
144+
<!--v-if-->
145+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
146+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">5kma53ae</td>
147+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">874</td>
148+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
149+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
150+
</tr>
151+
<!--v-if-->
152+
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-elevated/50">
153+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">bhqecj4p</td>
154+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">721</td>
155+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">failed</td>
156+
<td data-pinned="false" class="p-4 text-sm text-muted whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
157+
</tr>
158+
<!--v-if-->
159+
</tbody>
160+
</table>
161+
</div>"
162+
`;
163+
57164
exports[`Table > renders with caption correctly 1`] = `
58165
"<div class="relative overflow-auto">
59166
<table class="min-w-full overflow-clip">

0 commit comments

Comments
 (0)