DataTable Component
The DataTable
component is a flexible and customizable table component built with Vue 3. It supports a sort-by event, custom headers and custom items.
Basic Usage
Import and use DataTable
in your Vue components:
vue
<template>
<DataTable :headers="headers" :items="items" />
</template>
<script setup>
import { DataTable } from '@robuust-digital/vue-components/core';
const headers = [
{ id: 1, key: 'name', label: 'Name', sortable: true },
{ id: 2, key: 'age', label: 'Age', sortable: true, align: 'right' },
{ id: 3, key: 'email', label: 'Email', sortable: false, align: 'left' },
];
const items = [
{ id: 1, name: 'John Doe', age: 28, email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', age: 34, email: 'jane@example.com' },
{ id: 3, name: 'Sam Green', age: 45, email: 'sam@example.com' },
];
</script>
# | |||
---|---|---|---|
John Doe | john@example.com | ||
Jane Smith | jane@example.com | ||
Sam Green | sam@example.com |
Props
Prop | Type | Default | Description |
---|---|---|---|
headers | Array | [] | Array of header objects defining the table columns |
items | Array | [] | Array of item objects representing the table rows |
noResultsText | String | 'No results found.' | Text to display when no items are available |
loadingText | String | 'Loading content...' | Text to display when the table is in a loading state |
striped | Boolean | false | Apply striped rows style |
spinning | Boolean | false | Display a loading spinner |
wrapperClass | String | '' | Additional classes to apply to the table wrapper element |
Events
Event | Payload | Description |
---|---|---|
table:sortBy | { key: string, direction: 'asc' | 'desc' } | null | Emitted when sorting changes. The payload contains the column key and sort direction, or null when sorting is cleared. |
pagination:change | number | Emitted when the current page changes. Payload is the new page number. |
pagination:perPage | number | Emitted when items per page changes. Payload is the new per page value. |
Slots
Headers Slot
Customize the table headers.
vue
<template>
<DataTable :headers="headers" :items="items">
<template #headers="{ headers }">
<tr class="cursor-pointer">
<th class="text-left">
Custom header
</th>
<th class="text-left">
Another item
</th>
<th class="text-right">
Hello
</th>
</tr>
</template>
</DataTable>
</template>
Header Slot
Customize the table header.
vue
<template>
<DataTable :headers="headers" :items="items">
<template #header="{ header }">
<th
:class="{
'text-left': header.align === 'left' || !header.align,
'text-center': header.align === 'center',
'text-right': header.align === 'right',
}"
>
{{ header.label }}
</th>
</template>
</DataTable>
</template>
Header Label Slot
Customize the header labels.
vue
<template>
<DataTable :headers="headers" :items="items">
<template #header-label="{ header, sortBy }">
<span class="custom-class-styling">{{ header.label }}</span>
<button type="button" v-if="header.sortable" @click="sortBy(header.key)">
<ArrowUpIcon />
<ArrowDownIcon />
</button>
</template>
</DataTable>
</template>
Items Slot
Customize the table rows.
vue
<template>
<DataTable :headers="headers" :items="items">
<template #items="{ item }">
<tr class="cursor-pointer" @click="console.log('clicked on table row')">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.email }}</td>
</tr>
</template>
</DataTable>
</template>
Item Slot
Customize the table item columns.
vue
<template>
<DataTable :headers="headers" :items="items">
<template #item="{ item, index }">
<td v-for="(value, key) in item" :key="key">
{{ value }}
</td>
</template>
</DataTable>
</template>
Spinner Slot
Customize the loading spinner.
vue
<template>
<DataTable :headers="headers" :items="items" :spinning="true">
<template #spinner="{ spinning, label }">
<div v-if="spinning">
<CustomSpinnerIcon />
{{ label }}
</div>
</template>
</DataTable>
</template>
Pagination Slot
Customize the pagination component.
vue
<template>
<DataTable :headers="headers" :items="items" :pagination="paginationNode">
<template #pagination="{ pagination }">
<Pagination :pagination="pagination" />
</template>
</DataTable>
</template>
Examples
DataTable with Custom Headers
# | Name | Age | |
---|---|---|---|
John Doe | john@example.com | ||
Jane Smith | jane@example.com | ||
Sam Green | sam@example.com |
Show code
vue
<template>
<DataTable :headers="headers" :items="items">
<template #headers="{ headers }">
<tr>
<th v-for="header in headers" :key="header.id">
{{ header.label }}
</th>
</tr>
</template>
</DataTable>
</template>
DataTable with Custom Items
Member | Status | Actions | |||
---|---|---|---|---|---|
4-uurs arrangement | 01-01-2024 | € 10,15 | active | ||
8-uurs arrangement | 01-05-2024 | € 15,15 | pending | ||
12-uurs arrangement | 01-03-2024 | € 20,15 | active | ||
24-uurs arrangement | 01-02-2024 | € 25,15 | active |
Show code
vue
<template>
<DataTable
:headers="headers"
:items="items"
striped
@table:sort-by="(sortBy) => console.log(sortBy)"
>
<template #item="{ item }">
<td>
<a
href="#"
class="hover:underline block"
>
{{ item.package }}
</a>
</td>
<td>
{{ item.date }}
</td>
<td>
<a
href="#"
class="flex gap-x-2 items-center hover:underline"
>
<img
:src="item.member.avatar"
:alt="item.member.name"
width="28"
height="28"
class="size-7 rounded-full shrink-0"
loading="lazy"
/>
<span class="font-medium">{{ item.member.name }}</span>
</a>
</td>
<td class="text-right">
{{ item.price }}
</td>
<td>
<Badge
:color="item.status === 'active' ? 'green' : 'yellow'"
:label="item.status"
size="sm"
/>
</td>
<td class="text-right">
<ButtonBase
label="Edit"
type="button"
icon-only
color="clear"
:icon="PencilSquareIcon"
size="sm"
/>
<ButtonBase
label="Delete"
type="button"
icon-only
color="clear"
:icon="TrashIcon"
size="sm"
/>
</td>
</template>
</DataTable>
</template>
<script setup>
import { DataTable, Badge, ButtonBase } from '@robuust-digital/vue-components/core';
import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/16/solid';
const headers = [
{
id: 0, label: 'Package', key: 'package', sortable: true,
},
{
id: 1, label: 'Date', key: 'date', sortable: true,
},
{
id: 2, label: 'Member', key: 'member',
},
{
id: 3, label: 'Price', key: 'price', align: 'right', sortable: true,
},
{
id: 4, label: 'Status', key: 'status', align: 'left',
},
{
id: 5, label: 'Actions', key: 'actions', align: 'right',
},
];
const items = [
{
id: 1,
package: '4-uurs arrangement',
date: '01-01-2024',
status: 'active',
member: {
name: 'John Doe',
id: 0,
avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 10,15',
},
{
id: 2,
package: '8-uurs arrangement',
date: '01-05-2024',
status: 'pending',
member: {
name: 'Jane Doe',
id: 1,
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 15,15',
},
{
id: 3,
package: '12-uurs arrangement',
date: '01-03-2024',
status: 'active',
member: {
name: 'Sarah Doe',
id: 1,
avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 20,15',
},
{
id: 4,
package: '24-uurs arrangement',
date: '01-02-2024',
status: 'active',
member: {
name: 'Justin Doe',
id: 1,
avatar: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 25,15',
},
];
</script>
DataTable with Pagination
Member | Status | Actions | |||
---|---|---|---|---|---|
4-uurs arrangement | 01-01-2024 | € 10,15 | active | ||
8-uurs arrangement | 01-05-2024 | € 15,15 | pending | ||
12-uurs arrangement | 01-03-2024 | € 20,15 | active | ||
24-uurs arrangement | 01-02-2024 | € 25,15 | active |
1 - 10 of 100
Show code
vue
<template>
<DataTable
:headers="headers"
:items="items"
:pagination="paginationNode"
striped
@table:sort-by="(sortBy) => console.log(sortBy)"
@pagination:change="(e) => console.log(e)"
@pagination:per-page="(p) => console.log(p)"
>
<template #item="{ item }">
<td>
<a
href="#"
class="hover:underline block"
>
{{ item.package }}
</a>
</td>
<td>
{{ item.date }}
</td>
<td>
<a
href="#"
class="flex gap-x-2 items-center hover:underline"
>
<img
:src="item.member.avatar"
:alt="item.member.name"
width="28"
height="28"
class="size-7 rounded-full shrink-0"
loading="lazy"
/>
<span class="font-medium">{{ item.member.name }}</span>
</a>
</td>
<td class="text-right">
{{ item.price }}
</td>
<td>
<Badge
:color="item.status === 'active' ? 'green' : 'yellow'"
:label="item.status"
size="sm"
/>
</td>
<td class="text-right">
<ButtonBase
label="Edit"
type="button"
icon-only
color="clear"
:icon="PencilSquareIcon"
size="sm"
/>
<ButtonBase
label="Delete"
type="button"
icon-only
color="clear"
:icon="TrashIcon"
size="sm"
/>
</td>
</template>
</DataTable>
</template>
<script setup>
import { DataTable, Badge, ButtonBase } from '@robuust-digital/vue-components/core';
import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/16/solid';
const headers = [
{
id: 0, label: 'Package', key: 'package', sortable: true,
},
{
id: 1, label: 'Date', key: 'date', sortable: true,
},
{
id: 2, label: 'Member', key: 'member',
},
{
id: 3, label: 'Price', key: 'price', align: 'right', sortable: true,
},
{
id: 4, label: 'Status', key: 'status', align: 'left',
},
{
id: 5, label: 'Actions', key: 'actions', align: 'right',
},
];
const items = [
{
id: 1,
package: '4-uurs arrangement',
date: '01-01-2024',
status: 'active',
member: {
name: 'John Doe',
id: 0,
avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 10,15',
},
{
id: 2,
package: '8-uurs arrangement',
date: '01-05-2024',
status: 'pending',
member: {
name: 'Jane Doe',
id: 1,
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 15,15',
},
{
id: 3,
package: '12-uurs arrangement',
date: '01-03-2024',
status: 'active',
member: {
name: 'Sarah Doe',
id: 1,
avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 20,15',
},
{
id: 4,
package: '24-uurs arrangement',
date: '01-02-2024',
status: 'active',
member: {
name: 'Justin Doe',
id: 1,
avatar: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=200&h=200&auto=format&fit=facearea&facepad=3&q=80',
},
price: '€ 25,15',
},
];
const paginationNode = ref({
data: items,
total: 100,
from: 1,
to: 10,
links: [
{ url: null, label: 'Previous', active: false },
{ url: '/?page=1', label: '1', active: true },
{ url: '/?page=2', label: '2', active: false },
{ url: null, label: '...', active: false },
{ url: '/?page=11', label: '98', active: false },
{ url: '/?page=12', label: '99', active: false },
{ url: '/?page=2', label: 'Next', active: false },
],
});
</script>
CSS Customization ⚡️
To customize the dataTable
styles global
css
:root {
/* Available variables */
--rvc-table-wrapper-border-radius: 0;
--rvc-table-head-border-color: var(--rvc-base-border-dark-color);
--rvc-table-border-color: var(--rvc-base-border-color);
--rvc-table-border-style: var(--rvc-base-border-style);
--rvc-table-border-width: var(--rvc-base-border-width);
--rvc-table-head-bg-color: var(--color-slate-200);
--rvc-table-bg-color: var(--color-slate-50);
--rvc-table-font-size: var(--text-sm);
--rvc-table-head-font-size: var(--text-sm);
--rvc-table-head-font-weight: var(--font-weight-medium);
--rvc-table-font-weight: var(--font-weight-normal);
--rvc-table-padding-x: calc(var(--spacing) * 2);
--rvc-table-padding-y: calc(var(--spacing) * 3);
--rvc-table-white-space: nowrap;
--rvc-table-line-height: 1.1;
--rvc-table-spinner-size: calc(var(--spacing) * 5);
}