Skip to content

Commit

Permalink
feat(Table): add loading slot
Browse files Browse the repository at this point in the history
Resolves #3444
  • Loading branch information
benjamincanac committed Mar 4, 2025
1 parent a47c5ff commit 99e531d
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 80 deletions.
11 changes: 9 additions & 2 deletions src/runtime/components/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ type DynamicCellSlots<T, K = keyof T> = Record<string, (props: CellContext<T, un
export type TableSlots<T> = {
expanded: (props: { row: Row<T> }) => any
empty: (props?: {}) => any
loading: (props?: {}) => any
caption: (props?: {}) => any
} & DynamicHeaderSlots<T> & DynamicCellSlots<T>

Expand All @@ -175,7 +176,7 @@ import { reactiveOmit } from '@vueuse/core'
import { useLocale } from '../composables/useLocale'

const props = defineProps<TableProps<T>>()
defineSlots<TableSlots<T>>()
const slots = defineSlots<TableSlots<T>>()

const { t } = useLocale()

Expand Down Expand Up @@ -359,7 +360,13 @@ defineExpose({
</template>
</template>

<tr v-else :class="ui.tr({ class: [props.ui?.tr] })">
<tr v-else-if="loading && !!slots['loading']">
<td :colspan="columns?.length" :class="ui.loading({ class: props.ui?.loading })">
<slot name="loading" />
</td>
</tr>

<tr v-else>
<td :colspan="columns?.length" :class="ui.empty({ class: props.ui?.empty })">
<slot name="empty">
{{ t('table.noData') }}
Expand Down
3 changes: 2 additions & 1 deletion src/theme/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export default (options: Required<ModuleOptions>) => ({
tr: 'data-[selected=true]:bg-(--ui-bg-elevated)/50',
th: 'px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&:has([role=checkbox])]:pe-0',
td: 'p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&:has([role=checkbox])]:pe-0',
empty: 'py-6 text-center text-sm text-(--ui-text-muted)'
empty: 'py-6 text-center text-sm text-(--ui-text-muted)',
loading: 'py-6 text-center'
},
variants: {
pinned: {
Expand Down
3 changes: 2 additions & 1 deletion test/components/Table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ describe('Table', () => {
['with header slot', { props, slots: { 'id-header': () => 'ID Header slot' } }],
['with cell slot', { props, slots: { 'id-cell': () => 'ID Cell slot' } }],
['with expanded slot', { props, slots: { expanded: () => 'Expanded slot' } }],
['with empty slot', { props, slots: { empty: () => 'Empty slot' } }],
['with empty slot', { props: { ...props, data: [], columns }, slots: { empty: () => 'Empty slot' } }],
['with loading slot', { props: { ...props, data: [], columns, loading: true }, slots: { loading: () => 'Loading slot' } }],
['with caption slot', { props, slots: { caption: () => 'Caption slot' } }]
])('renders %s correctly', async (nameOrHtml: string, options: { props?: TableProps<typeof data[number]>, slots?: Partial<TableSlots<typeof data[number]>> }) => {
const html = await ComponentRender(nameOrHtml, options, Table)
Expand Down
99 changes: 61 additions & 38 deletions test/components/__snapshots__/Table-vue.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -514,48 +514,33 @@ exports[`Table > renders with empty slot correctly 1`] = `
<!--v-if-->
<thead class="relative [&amp;>tr]:after:absolute [&amp;>tr]:after:inset-x-0 [&amp;>tr]:after:bottom-0 [&amp;>tr]:after:h-px [&amp;>tr]:after:bg-(--ui-border-accented)">
<tr class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Id</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Amount</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="relative flex items-start">
<div class="flex items-center h-5"><button arialabel="Select all" class="shrink-0 flex items-center justify-center rounded-(--ui-radius) text-(--ui-bg) ring ring-inset ring-(--ui-border-accented) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-primary) size-4" id="v-0" role="checkbox" type="button" aria-checked="false" aria-required="false" data-state="unchecked">
<!---->
<!---->
</button></div>
<!--v-if-->
</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-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-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Date</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Status</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Email</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0"><button type="button" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-hidden disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors px-2.5 py-1.5 text-sm gap-1.5 text-(--ui-text) hover:bg-(--ui-bg-elevated) focus-visible:bg-(--ui-bg-elevated) hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent -mx-2.5"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Email</span>
<!--v-if-->
</button></th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right">Amount</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</thead>
<tbody class="divide-y divide-(--ui-border) [&amp;>tr]:data-[selectable=true]:hover:bg-(--ui-bg-elevated)/50 [&amp;>tr]:data-[selectable=true]:focus-visible:outline-(--ui-primary)">
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">m5gr84i9</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">316</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
</tr>
<!--v-if-->
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">3u1reuv4</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">242</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
<tr>
<td colspan="7" class="py-6 text-center text-sm text-(--ui-text-muted)">Empty slot</td>
</tr>
<!--v-if-->
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">derv1ws0</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">837</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">processing</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
</tr>
<!--v-if-->
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">5kma53ae</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">874</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">success</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
</tr>
<!--v-if-->
<tr data-selected="false" data-selectable="false" data-expanded="false" class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">bhqecj4p</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">721</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">failed</td>
<td data-pinned="false" class="p-4 text-sm text-(--ui-text-muted) whitespace-nowrap [&amp;:has([role=checkbox])]:pe-0">[email protected]</td>
</tr>
<!--v-if-->
</tbody>
</table>
</div>"
Expand Down Expand Up @@ -1303,6 +1288,44 @@ exports[`Table > renders with loading correctly 1`] = `
</div>"
`;

exports[`Table > renders with loading slot correctly 1`] = `
"<div class="relative overflow-auto">
<table class="min-w-full overflow-clip">
<!--v-if-->
<thead class="relative [&amp;>tr]:after:absolute [&amp;>tr]:after:inset-x-0 [&amp;>tr]:after:bottom-0 [&amp;>tr]:after:h-px [&amp;>tr]:after:bg-(--ui-border-accented) after:absolute after:bottom-0 after:inset-x-0 after:h-px after:bg-(--ui-primary) after:animate-[carousel_2s_ease-in-out_infinite] rtl:after:animate-[carousel-rtl_2s_ease-in-out_infinite]">
<tr class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="relative flex items-start">
<div class="flex items-center h-5"><button arialabel="Select all" class="shrink-0 flex items-center justify-center rounded-(--ui-radius) text-(--ui-bg) ring ring-inset ring-(--ui-border-accented) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-primary) size-4" id="v-0" role="checkbox" type="button" aria-checked="false" aria-required="false" data-state="unchecked">
<!---->
<!---->
</button></div>
<!--v-if-->
</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-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-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Date</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">Status</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0"><button type="button" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-hidden disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors px-2.5 py-1.5 text-sm gap-1.5 text-(--ui-text) hover:bg-(--ui-bg-elevated) focus-visible:bg-(--ui-bg-elevated) hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent -mx-2.5"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="shrink-0 size-5" width="1em" height="1em" viewBox="0 0 16 16"></svg><span class="truncate">Email</span>
<!--v-if-->
</button></th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<div class="text-right">Amount</div>
</th>
<th data-pinned="false" class="px-4 py-3.5 text-sm text-(--ui-text-highlighted) text-left rtl:text-right font-semibold [&amp;:has([role=checkbox])]:pe-0">
<!---->
</th>
</tr>
</thead>
<tbody class="divide-y divide-(--ui-border) [&amp;>tr]:data-[selectable=true]:hover:bg-(--ui-bg-elevated)/50 [&amp;>tr]:data-[selectable=true]:focus-visible:outline-(--ui-primary)">
<tr>
<td colspan="7" class="py-6 text-center">Loading slot</td>
</tr>
</tbody>
</table>
</div>"
`;

exports[`Table > renders with sticky correctly 1`] = `
"<div class="relative overflow-auto">
<table class="min-w-full overflow-clip">
Expand Down Expand Up @@ -1417,7 +1440,7 @@ exports[`Table > renders without data correctly 1`] = `
<tr class="data-[selected=true]:bg-(--ui-bg-elevated)/50"></tr>
</thead>
<tbody class="divide-y divide-(--ui-border) [&amp;>tr]:data-[selectable=true]:hover:bg-(--ui-bg-elevated)/50 [&amp;>tr]:data-[selectable=true]:focus-visible:outline-(--ui-primary)">
<tr class="data-[selected=true]:bg-(--ui-bg-elevated)/50">
<tr>
<td colspan="0" class="py-6 text-center text-sm text-(--ui-text-muted)">No data</td>
</tr>
</tbody>
Expand Down
Loading

0 comments on commit 99e531d

Please sign in to comment.