Pagination Component
This page Show an Example of a Reusable Pagination Component.
First Pattern
This is the wrapper for the Pagination component functionality.
Demo
Current Page:
Total Page:
...
Dependencies
{
"tailwindcss": "^3.3.2", // for styling
}
Code
vue
<script setup lang="ts">
import { computed } from "vue"
const emit = defineEmits<{
(event: "changePage", page: number): void
}>()
const props = withDefaults(
defineProps<{
currentPage?: number
pageSize?: number
totalPages?: number
alwaysShowNextAndPrevious: boolean // show back and next buttons even if disabled
}>(),
{
currentPage: 1,
pageSize: 50,
totalPages: 1,
alwaysShowNextAndPrevious: true
},
)
const changeCurrentPage = (page: number) => {
if (page === props.currentPage) return
emit("changePage", page)
}
const hasNextPage = computed(() => props.currentPage < props.totalPages)
const hasPrevPage = computed(() => !!(props.currentPage - 1))
const prevPage = computed(() => {
const prev = [props.currentPage - 1]
return prev.filter((page) => page > 0)
})
const nextPage = computed(() => {
const next = [props.currentPage + 1]
return next.filter((page) => page <= lastPage.value)
})
const prevTwoPages = computed(() => {
const prevTwo = [props.currentPage - 1, props.currentPage - 2]
return prevTwo.filter((page) => page > 0)
})
const nextTwoPages = computed(() => {
const nextTwo = [props.currentPage + 1, props.currentPage + 2]
return nextTwo.filter((page) => page <= lastPage.value)
})
const lastPage = computed(() => props.totalPages)
const pagesToDisplay = computed(() => [...prevPage.value, props.currentPage, ...nextPage.value])
</script>
<template>
<div class="flex w-fit items-center gap-x-2">
<!-- Displays the Back Button -->
<button
v-if="hasPrevPage || alwaysShowNextAndPrevious"
:disabled="currentPage === 1"
class="text-sm font-medium flex items-center justify-center gap-x-1 rounded px-3 py-2 text-red-400 ring-[1px] ring-red-400 disabled:cursor-not-allowed disabled:text-red-200 disabled:ring-red-200"
@click="() => changeCurrentPage(currentPage - 1)"
>
<svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M165.66 202.34a8 8 0 0 1-11.32 11.32l-80-80a8 8 0 0 1 0-11.32l80-80a8 8 0 0 1 11.32 11.32L91.31 128Z"/></svg>
<span class="inline-block">Back</span>
</button>
<!-- Displays the First Page before the ellipses (not on mobile) -->
<button
v-if="currentPage != 1 && currentPage - 1 >= 4"
class="hidden text-sm font-medium sm:flex items-center justify-center rounded px-3 py-2 text-red-400 ring-[1px] ring-red-400"
@click="() => changeCurrentPage(1)"
>
1
</button>
<!-- Displays the ellipses for the First Page (not on mobile) -->
<div
v-if="currentPage != 1 && currentPage - 1 >= 4"
class="hidden text-sm font-medium sm:flex items-center justify-center rounded px-3 py-2 text-red-400 ring-[1px] ring-red-400"
>
...
</div>
<!-- Displays theCurrent Page, previous page and Next Pages based on the pagesToDisplay variable -->
<button
v-for="number in pagesToDisplay"
:key="number"
:class="
currentPage == number ? 'bg-red-500 text-white ring-red-500' : 'text-red-400 ring-red-400'
"
class="text-sm font-medium flex items-center justify-center rounded px-3 py-2 ring-[1px]"
@click="() => changeCurrentPage(number)"
>
{{ number }}
</button>
<!-- Displays the ellipses for the Last Page (not on mobile) -->
<div
v-if="lastPage - currentPage >= 3"
class="hidden text-sm font-medium sm:flex items-center justify-center rounded px-3 py-2 text-red-400 ring-[1px] ring-red-400"
>
...
</div>
<!-- Displays the lastPage After Ellipses (not on mobile) -->
<button
v-if="lastPage - currentPage >= 3"
:class="
currentPage == lastPage ? 'bg-red-500 text-white ring-red-500' : 'text-red-400 ring-red-400'
"
class="hidden text-sm font-medium sm:flex items-center justify-center rounded px-3 py-2 ring-[1px]"
@click="() => changeCurrentPage(lastPage)"
>
{{ lastPage }}
</button>
<!-- Displays the Next Button -->
<button
v-if="hasNextPage || alwaysShowNextAndPrevious"
:disabled="currentPage >= totalPages"
class="text-sm font-medium flex items-center justify-center gap-x-1 rounded px-3 py-2 text-red-400 ring-[1px] ring-red-400 disabled:cursor-not-allowed disabled:text-red-200 disabled:ring-red-200"
@click="() => changeCurrentPage(currentPage + 1)"
>
<span class="inline-block">Next</span>
<svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="m181.66 133.66l-80 80a8 8 0 0 1-11.32-11.32L164.69 128L90.34 53.66a8 8 0 0 1 11.32-11.32l80 80a8 8 0 0 1 0 11.32"/></svg>
</button>
</div>
</template>
vue
<script setup lang="ts">
import { ref } from "vue";
import Pagination1 from "../components/Pagination/Pagination1.vue";
const currentPage = ref(1)
const totalPages = ref(30)
const pageSize = ref(50)
</script>
<template>
<div>
<div class="flex items-center gap-5 text-sm">
<p>Current Page: </p><input type="number" :min="1" :max="totalPages" v-model="currentPage"
class="w-[200px] ring-[1px] ring-green-500 rounded px-2 py-1" />
</div>
<div class="flex items-center gap-5 text-sm">
<p>Total Page: </p><input type="number" :min="1" v-model="totalPages"
class="w-[200px] ring-[1px] ring-green-500 rounded px-2 py-1" />
</div>
<div class="mx-auto w-fit mt-5">
<Pagination1 @changePage="(page) => currentPage = page" :currentPage="currentPage" :pageSize="pageSize"
:totalPages="totalPages" />
</div>
</div>
</template>
ts
import { ref, computed } from "vue";
import type { Ref } from "vue";
export interface UsePaginationOptions {
currentPage: Ref<number>;
pageSize: Ref<number>;
totalPages: Ref<number>;
}
export function usePagination(options: UsePaginationOptions) {
const hasNextPage = computed(
() => options.currentPage.value < options.totalPages.value
);
const hasPrevPage = computed(() => !!(options.currentPage.value - 1));
const prevPage = computed(() => {
const prev = [options.currentPage.value - 1];
return prev.filter((page) => page > 0);
});
const nextPage = computed(() => {
const next = [options.currentPage.value + 1];
return next.filter((page) => page <= lastPage.value);
});
const prevTwoPages = computed(() => {
const prevTwo = [
options.currentPage.value - 1,
options.currentPage.value - 2,
];
return prevTwo.filter((page) => page > 0);
});
const nextTwoPages = computed(() => {
const nextTwo = [
options.currentPage.value + 1,
options.currentPage.value + 2,
];
return nextTwo.filter((page) => page <= lastPage.value);
});
const lastPage = computed(() => options.totalPages.value);
const pagesToDisplay = computed(() => [
...prevPage.value,
options.currentPage,
...nextPage.value,
]);
return {
hasNextPage,
hasPrevPage,
prevPage,
nextPage,
prevTwoPages,
nextTwoPages,
lastPage,
pagesToDisplay,
};
}