mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +00:00
feat: Table footer design updates (#9194)
* feat: table footer component cleanup * Update TableFooter.vue * feat: Update design * chore: remove RTL mixin * chore: Make component in composable format * chore: review fixes
This commit is contained in:
@@ -1,171 +1,48 @@
|
||||
<template>
|
||||
<footer
|
||||
v-if="isFooterVisible"
|
||||
class="bg-white dark:bg-slate-800 h-12 border-t border-solid border-slate-75 dark:border-slate-700/50 flex items-center justify-between px-6"
|
||||
class="bg-white dark:bg-slate-900 h-12 flex items-center justify-between px-6"
|
||||
>
|
||||
<div class="left-aligned-wrap">
|
||||
<div class="text-xs text-slate-600 dark:text-slate-200">
|
||||
<strong>{{ firstIndex }}</strong>
|
||||
- <strong>{{ lastIndex }}</strong> of
|
||||
<strong>{{ totalCount }}</strong> items
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-aligned-wrap">
|
||||
<div
|
||||
v-if="totalCount"
|
||||
class="primary button-group pagination-button-group"
|
||||
>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
class-names="goto-first"
|
||||
:is-disabled="hasFirstPage"
|
||||
@click="onFirstPage"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" size="18" />
|
||||
<fluent-icon
|
||||
icon="chevron-left"
|
||||
size="18"
|
||||
:class="pageFooterIconClass"
|
||||
/>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
:is-disabled="hasPrevPage"
|
||||
@click="onPrevPage"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" size="18" />
|
||||
</woot-button>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
@click.prevent
|
||||
>
|
||||
{{ currentPage }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
:is-disabled="hasNextPage"
|
||||
@click="onNextPage"
|
||||
>
|
||||
<fluent-icon icon="chevron-right" size="18" />
|
||||
</woot-button>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
class-names="goto-last"
|
||||
:is-disabled="hasLastPage"
|
||||
@click="onLastPage"
|
||||
>
|
||||
<fluent-icon icon="chevron-right" size="18" />
|
||||
<fluent-icon
|
||||
icon="chevron-right"
|
||||
size="18"
|
||||
:class="pageFooterIconClass"
|
||||
/>
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<table-footer-results
|
||||
:first-index="firstIndex"
|
||||
:last-index="lastIndex"
|
||||
:total-count="totalCount"
|
||||
/>
|
||||
<table-footer-pagination
|
||||
v-if="totalCount"
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
:total-count="totalCount"
|
||||
:page-size="pageSize"
|
||||
@page-change="$emit('page-change', $event)"
|
||||
/>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
mixins: [rtlMixin],
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 25,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import TableFooterResults from './TableFooterResults.vue';
|
||||
import TableFooterPagination from './TableFooterPagination.vue';
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
computed: {
|
||||
pageFooterIconClass() {
|
||||
return this.isRTLView ? '-mr-3' : '-ml-3';
|
||||
},
|
||||
isFooterVisible() {
|
||||
return this.totalCount && !(this.firstIndex > this.totalCount);
|
||||
},
|
||||
firstIndex() {
|
||||
return this.pageSize * (this.currentPage - 1) + 1;
|
||||
},
|
||||
lastIndex() {
|
||||
return Math.min(this.totalCount, this.pageSize * this.currentPage);
|
||||
},
|
||||
searchButtonClass() {
|
||||
return this.searchQuery !== '' ? 'show' : '';
|
||||
},
|
||||
hasLastPage() {
|
||||
return !!Math.ceil(this.totalCount / this.pageSize);
|
||||
},
|
||||
hasFirstPage() {
|
||||
return this.currentPage === 1;
|
||||
},
|
||||
hasNextPage() {
|
||||
return this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
||||
},
|
||||
hasPrevPage() {
|
||||
return this.currentPage === 1;
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 25,
|
||||
},
|
||||
methods: {
|
||||
onNextPage() {
|
||||
if (this.hasNextPage) {
|
||||
return;
|
||||
}
|
||||
const newPage = this.currentPage + 1;
|
||||
this.onPageChange(newPage);
|
||||
},
|
||||
onPrevPage() {
|
||||
if (this.hasPrevPage) {
|
||||
return;
|
||||
}
|
||||
const newPage = this.currentPage - 1;
|
||||
this.onPageChange(newPage);
|
||||
},
|
||||
onFirstPage() {
|
||||
if (this.hasFirstPage) {
|
||||
return;
|
||||
}
|
||||
const newPage = 1;
|
||||
this.onPageChange(newPage);
|
||||
},
|
||||
onLastPage() {
|
||||
if (this.hasLastPage) {
|
||||
return;
|
||||
}
|
||||
const newPage = Math.ceil(this.totalCount / this.pageSize);
|
||||
this.onPageChange(newPage);
|
||||
},
|
||||
onPageChange(page) {
|
||||
this.$emit('page-change', page);
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
const totalPages = computed(() => Math.ceil(props.totalCount / props.pageSize));
|
||||
const firstIndex = computed(() => props.pageSize * (props.currentPage - 1) + 1);
|
||||
const lastIndex = computed(() =>
|
||||
Math.min(props.totalCount, props.pageSize * props.currentPage)
|
||||
);
|
||||
const isFooterVisible = computed(
|
||||
() => props.totalCount && !(firstIndex.value > props.totalCount)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goto-first,
|
||||
.goto-last {
|
||||
i:last-child {
|
||||
@apply -ml-1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="flex items-center bg-slate-50 dark:bg-slate-800 h-8 rounded-lg">
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
:is-disabled="hasFirstPage"
|
||||
class-names="dark:!bg-slate-800 !opacity-100 ltr:rounded-l-lg ltr:rounded-r-none rtl:rounded-r-lg rtl:rounded-l-none"
|
||||
:class="buttonClass(hasFirstPage)"
|
||||
@click="onFirstPage"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="chevrons-left"
|
||||
size="20"
|
||||
icon-lib="lucide"
|
||||
:class="hasFirstPage && 'opacity-40'"
|
||||
/>
|
||||
</woot-button>
|
||||
<div class="bg-slate-75 dark:bg-slate-700/50 w-px rounded-sm h-4" />
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
:is-disabled="hasPrevPage"
|
||||
class-names="dark:!bg-slate-800 !opacity-100 rounded-none"
|
||||
:class="buttonClass(hasPrevPage)"
|
||||
@click="onPrevPage"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="chevron-left-single"
|
||||
size="20"
|
||||
icon-lib="lucide"
|
||||
:class="hasPrevPage && 'opacity-40'"
|
||||
/>
|
||||
</woot-button>
|
||||
|
||||
<div
|
||||
class="flex px-3 items-center gap-3 tabular-nums bg-slate-50 dark:bg-slate-800 text-slate-700 dark:text-slate-100"
|
||||
>
|
||||
<span class="text-sm text-slate-800 dark:text-slate-75">
|
||||
{{ currentPage }}
|
||||
</span>
|
||||
<span class="text-slate-600 dark:text-slate-500">/</span>
|
||||
<span class="text-sm text-slate-600 dark:text-slate-500">
|
||||
{{ totalPages }}
|
||||
</span>
|
||||
</div>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
:is-disabled="hasNextPage"
|
||||
class-names="dark:!bg-slate-800 !opacity-100 rounded-none"
|
||||
:class="buttonClass(hasNextPage)"
|
||||
@click="onNextPage"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="chevron-right-single"
|
||||
size="20"
|
||||
icon-lib="lucide"
|
||||
:class="hasNextPage && 'opacity-40'"
|
||||
/>
|
||||
</woot-button>
|
||||
<div class="bg-slate-75 dark:bg-slate-700/50 w-px rounded-sm h-4" />
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
class-names="dark:!bg-slate-800 !opacity-100 ltr:rounded-r-lg ltr:rounded-l-none rtl:rounded-l-lg rtl:rounded-r-none"
|
||||
:class="buttonClass(hasLastPage)"
|
||||
:is-disabled="hasLastPage"
|
||||
@click="onLastPage"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="chevrons-right"
|
||||
size="20"
|
||||
icon-lib="lucide"
|
||||
:class="hasLastPage && 'opacity-40'"
|
||||
/>
|
||||
</woot-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineEmits } from 'vue';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
totalPages: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const hasLastPage = computed(
|
||||
() => props.currentPage === props.totalPages || props.totalPages === 1
|
||||
);
|
||||
const hasFirstPage = computed(() => props.currentPage === 1);
|
||||
const hasNextPage = computed(() => props.currentPage === props.totalPages);
|
||||
const hasPrevPage = computed(() => props.currentPage === 1);
|
||||
|
||||
const emit = defineEmits(['page-change']);
|
||||
|
||||
function buttonClass(hasPage) {
|
||||
if (hasPage) {
|
||||
return 'hover:!bg-slate-50 dark:hover:!bg-slate-800';
|
||||
}
|
||||
return 'dark:hover:!bg-slate-700/50';
|
||||
}
|
||||
|
||||
function onPageChange(newPage) {
|
||||
emit('page-change', newPage);
|
||||
}
|
||||
|
||||
const onNextPage = () => {
|
||||
if (!onNextPage.value) {
|
||||
onPageChange(props.currentPage + 1);
|
||||
}
|
||||
};
|
||||
const onPrevPage = () => {
|
||||
if (!hasPrevPage.value) {
|
||||
onPageChange(props.currentPage - 1);
|
||||
}
|
||||
};
|
||||
const onFirstPage = () => {
|
||||
if (!hasFirstPage.value) {
|
||||
onPageChange(1);
|
||||
}
|
||||
};
|
||||
const onLastPage = () => {
|
||||
if (!hasLastPage.value) {
|
||||
onPageChange(props.totalPages);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 font-medium">
|
||||
{{
|
||||
$t('GENERAL.SHOWING_RESULTS', {
|
||||
firstIndex,
|
||||
lastIndex,
|
||||
totalCount,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
firstIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
lastIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user