Skip to content

Commit

Permalink
feat(v-data-table): improved v-data-table
Browse files Browse the repository at this point in the history
- allow to hide footer
- new `totalItems` prop
- checkable table
- more
  • Loading branch information
gravitano committed Sep 27, 2021
1 parent 798c9ce commit d5994b9
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 36 deletions.
10 changes: 8 additions & 2 deletions src/components/VDataTable/VDataTable.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import type {PropType} from 'vue';

export interface VDataTableItem {
[key: string]: string;
selected?: boolean;
[key: string]: any;
}

export interface VDataTableHeader {
[key: string]: string;
[key: string]: any;
}

export interface VDataTableSortEvent {
sortBy: string;
direction: 'asc' | 'desc' | '';
}

export interface VDataTableProps {
Expand Down
10 changes: 5 additions & 5 deletions src/components/VDataTable/VDataTable.stories.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Meta, Story } from '@storybook/vue3';
import {Meta, Story} from '@storybook/vue3';
import MyDataTable from './VDataTable.vue';

const items = [...Array(30)].map((item, index) => ({
Expand Down Expand Up @@ -41,7 +41,7 @@ const Template: Story = (args) => ({
},
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
return {args};
},
// And then the `args` are bound to your component with `v-bind="args"`
template: `<my-component v-bind="args">{{ args.label }}</my-component>`,
Expand All @@ -52,15 +52,15 @@ Default.args = {};

export const Striped = Template.bind({});
Striped.args = {
striped: true
striped: true,
};

export const Hover = Template.bind({});
Hover.args = {
hover: true
hover: true,
};

export const Dense = Template.bind({});
Dense.args = {
dense: true
dense: true,
};
227 changes: 198 additions & 29 deletions src/components/VDataTable/VDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,26 @@ import type {
VDataTableProps,
} from './VDataTable';
import {SortAscendingIcon, SortDescendingIcon} from '@heroicons/vue/solid';
import VSpinner from '../VSpinner/VSpinner.vue';
import VCheckbox from '../VCheckbox/VCheckbox.vue';
// import get from 'lodash/get';
import {get} from '../../utils';
const props = defineProps({
modelValue: {
type: Array,
default: () => [],
},
value: {
type: Array,
default: () => [],
},
headers: {
type: Array as PropType<VDataTableHeader[]>,
default: () => [],
},
items: {
type: Array,
type: Array as PropType<VDataTableItem[]>,
default: () => [],
},
itemsPerPage: {
Expand Down Expand Up @@ -76,15 +88,45 @@ const props = defineProps({
type: Boolean,
default: false,
},
hideFooter: {
type: Boolean,
default: false,
},
totalItems: {
type: Number,
default: 0,
},
page: {
type: Number,
default: 1,
},
mustSort: {
type: Boolean,
default: false,
},
noShadow: {
type: Boolean,
default: false,
},
selectable: {
type: Boolean,
default: false,
},
});
const emit = defineEmits([
'update:search',
'sort',
'update:sortBy',
'update:sortDirection',
'update:page',
'update:itemsPerPage',
'update:pagination',
'page:change',
'itemsPerPage:change',
'pagination:change',
'update:modelValue',
'update:value',
]);
const {
Expand All @@ -102,9 +144,15 @@ const {
striped,
dense,
headers,
totalItems,
page: paginationPage,
mustSort,
selectable,
modelValue,
value,
} = toRefs(props);
const page = ref<number>(1);
const page = ref<number>(props.page);
const perPage = ref(itemsPerPage.value);
const offset = computed(() => (page.value - 1) * Number(perPage.value));
Expand Down Expand Up @@ -158,7 +206,9 @@ const computedHeaders = computed(() =>
const getThClass = (header: VDataTableHeader) => {
const isActive = header.sorting && sortBy.value === header.value;
return [
isActive ? 'text-primary' : 'text-gray-600',
isActive
? 'text-primary-500 hover:text-primary-600'
: 'text-gray-600 hover:text-primary-500',
{
[`text-${header.align}`]: !!header.align,
},
Expand All @@ -175,12 +225,20 @@ const handleSort = (header: VDataTableHeader) => {
if (!header) return;
let direction = '';
if (sortDirection.value === '') {
direction = 'asc';
} else if (sortDirection.value === 'asc') {
direction = 'desc';
} else if (sortDirection.value === 'desc') {
direction = '';
if (mustSort.value) {
if (sortDirection.value === 'asc') {
direction = 'desc';
} else {
direction = 'asc';
}
} else {
if (sortDirection.value === '') {
direction = 'asc';
} else if (sortDirection.value === 'asc') {
direction = 'desc';
} else if (sortDirection.value === 'desc') {
direction = '';
}
}
header.sorting = direction;
Expand All @@ -199,21 +257,117 @@ const onPerPageChange = (perPage: {text: string; value: number}) => {
const paddingClass = computed(() => (dense.value ? 'px-4 py-2' : 'px-6 py-3'));
const onPaginationChange = (params = {}) => {
emit('pagination:change', {
page: page.value,
itemsPerPage: perPage.value,
...params,
});
};
watch(page, (val) => {
emit('page:change', val);
onPaginationChange({page: val});
});
watch(perPage, (val) => {
emit('itemsPerPage:change', val);
onPaginationChange({itemsPerPage: val});
});
watch(paginationPage, (val) => {
page.value = val;
});
const selected = ref<any>([]);
const selectAll = computed({
// get: () =>
// items.value.every(function (item) {
// return item.selected;
// }),
// set: (val) => {
// items.value.forEach(function (item) {
// item.selected = val;
// });
// },
get() {
return items.value.length
? selected.value.length == items.value.length
: false;
},
set(value) {
const selectedItems: any = [];
if (value) {
items.value.forEach(function (item) {
selectedItems.push(item);
});
}
selected.value = selectedItems;
},
});
// watch(
// modelValue,
// (val) => {
// val.forEach(function (item: any) {
// const index = items.value.findIndex((s) => s.id === item.id);
// if (index > -1) {
// items.value[index].selected = true;
// }
// });
// console.log({val});
// },
// {deep: true},
// );
// watch(
// items,
// (val) => {
// emit('update:modelValue', val);
// },
// {deep: true},
// );
watch(
selected,
(val) => {
emit('update:modelValue', val);
},
{deep: true},
);
watch(
modelValue,
(val) => {
console.log(val);
selected.value = val;
},
{deep: true},
);
watch(
value,
(val) => {
console.log({val});
selected.value = val;
},
{deep: true},
);
</script>

<template>
<div
class="
w-full
flex flex-col
shadow
border-b border-gray-200
rounded-md
sm:rounded-lg
"
:class="[!noShadow ? 'shadow' : '']"
>
<div class="overflow-x-auto rounded-t-md">
<table class="w-full divide-y divide-gray-200">
Expand All @@ -223,16 +377,16 @@ watch(page, (val) => {
v-for="header in computedHeaders"
:key="header.value"
scope="col"
class="
text-left text-xs
font-medium
text-gray-600
uppercase
tracking-wider
"
class="text-left text-xs font-medium uppercase tracking-wider"
:class="[getThClass(header), paddingClass]"
>
<slot :name="`header.${header.value}`">
<slot
v-if="selectable && header.value === 'selected'"
name="header.selectable"
>
<v-checkbox v-model="selectAll" />
</slot>
<slot v-else :name="`header.${header.value}`">
<a
v-if="!disableSorting && header.sortable"
href="#"
Expand All @@ -243,11 +397,11 @@ watch(page, (val) => {
{{ header.text }}
<SortDescendingIcon
v-if="header.sorting === 'desc'"
class="h4 w-4"
class="ml-2 h4 w-4"
/>
<SortAscendingIcon
v-if="header.sorting === 'asc'"
class="h4 w-4"
class="ml-2 h4 w-4"
/>
</a>
<span v-else>
Expand Down Expand Up @@ -282,6 +436,8 @@ watch(page, (val) => {
class="text-center text-gray-600 text-sm whitespace-nowrap"
:class="[paddingClass]"
>
<v-spinner color="primary" class="mr-1" />

{{ loadingText }}
</td>
</tr>
Expand All @@ -302,22 +458,35 @@ watch(page, (val) => {
class="whitespace-nowrap text-sm text-gray-900"
:class="[getTdClass(header), paddingClass]"
>
<slot :name="`item.${header.value}`" :item="item">
{{ item[header.value] }}
<slot
v-if="selectable && header.value === 'selected'"
name="item.selected"
>
<v-checkbox v-model="selected" :value="item" />
</slot>
<slot
v-else
:name="`item.${header.value}`"
:item="item"
:index="index"
>
{{ get(item, header.value) }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>

<VDataTablePagination
v-model="page"
class="rounded-b-md"
:total-items="items.length"
v-model:itemsPerPage="perPage"
v-bind="pagination"
/>
<slot v-if="!hideFooter" name="footer">
<VDataTablePagination
v-model="page"
class="rounded-b-md"
:total-items="serverSide ? totalItems : items.length"
v-model:itemsPerPage="perPage"
v-bind="pagination"
/>
</slot>
</div>
</template>

Expand Down

0 comments on commit d5994b9

Please sign in to comment.