Merge pull request #122 from assasin0076/feat_pagination

feat: added basic pagination
This commit is contained in:
Ilya Artamonov
2023-01-25 13:27:18 +03:00
committed by GitHub
10 changed files with 308 additions and 29 deletions

View File

@@ -44,6 +44,7 @@ function getComponents() {
{ text: 'Card', link: 'components/card.md' },
{ text: 'Carousel', link: 'components/carousel' },
{ text: 'Dropdown', link: '/components/dropdown' },
{ text: 'Pagination', link: 'components/pagination' },
{ text: 'Progress', link: 'components/progress' },
{ text: 'Rating', link: 'components/rating' },
{ text: 'Spinner', link: '/components/spinner' },
@@ -57,7 +58,6 @@ function getComponents() {
{ text: 'Navbar', link: 'components/navbar' },
{ text: '- Footer', link: 'components/footer' },
{ text: '- Pagination', link: 'components/pagination' },
{ text: '- Sidebar', link: 'components/sidebar' },
]
}

View File

@@ -1,15 +1,119 @@
<script setup>
import PaginationExample from './pagination/examples/PaginationExample.vue'
import PaginationExample from './pagination/examples/PaginationExample.vue';
import PaginationNavigationExample from './pagination/examples/PaginationNavigationExample.vue';
import PaginationTableExample from './pagination/examples/PaginationTableExample.vue';
import PaginationWithIconsExample from './pagination/examples/PaginationWithIconsExample.vue';
import PaginationWithCustomTextExample from './pagination/examples/PaginationWithCustomTextExample.vue';
import PaginationWithCustomSlice from './pagination/examples/PaginationWithCustomSlice.vue';
</script>
# Vue Pagination Component - Flowbite
## Default pagination
```vue
<script setup>
import { Pagination } from 'flowbite-vue'
import { ref } from 'vue'
const currentPage = ref(1)
</script>
<template>
<Pagination></Pagination>
<Pagination v-model="currentPage" :total-pages="100"></Pagination>
</template>
```
<PaginationExample />
## Default with custom length
You can use your own pages count in the row by passing props: `slice-length`
This prop means left side and right side pages row slicing. In the example it has value `4`. So row length will be 4 + 1 + 4 pages - 9 pages.
```vue
<script setup>
import { Pagination } from 'flowbite-vue'
import { ref } from 'vue'
const currentPage = ref(1)
</script>
<template>
<Pagination v-model="currentPage" :total-pages="100" :slice-length="4"></Pagination>
</template>
```
<PaginationExample />
<PaginationWithCustomSlice />
## Pagination with navigation layout
```vue
<script setup>
import { Pagination } from 'flowbite-vue'
import { ref } from 'vue'
const currentPage = ref(1)
</script>
<template>
<div class="flex items-center justify-center text-center">
<Pagination v-model="currentPage" :total-pages="100" :layout="'navigation'"></Pagination>
</div>
</template>
```
<PaginationNavigationExample />
## Pagination with table layout
To use that layout you have to pass required props:
- `per-page`: it's items count displayed on each page.
- `total-items`: it's the total items count.
And there you don't need to use `total-pages` prop.
```vue
<script setup>
import { Pagination } from 'flowbite-vue'
import { ref } from 'vue'
const currentPage = ref(1)
</script>
<template>
<div class="flex items-center justify-center text-center">
<Pagination
v-model="currentPage"
:layout="'table'"
:per-page="10"
:total-items="998"
></Pagination>
</div>
</template>
```
<PaginationTableExample />
## Pagination with icons
```vue
<script setup>
import { Pagination } from 'flowbite-vue'
import { ref } from 'vue'
const currentPage = ref(1)
</script>
<template>
<Pagination v-model="currentPage" :total-pages="100" show-icons></Pagination>
</template>
```
<PaginationWithIconsExample />
## Pagination with custom labels
```vue
<script setup>
import { Pagination } from 'flowbite-vue'
import { ref } from 'vue'
const currentPage = ref(1)
</script>
<template>
<Pagination v-model="currentPage" :total-pages="100" previous-label="<<<" next-label=">>>"></Pagination>
</template>
```
<PaginationWithCustomTextExample />

View File

@@ -1,8 +1,11 @@
<template>
<div class="vp-raw flex flex-col">
<Pagination></Pagination>
<div class="vp-raw">
<Pagination v-model="currentPage" :total-pages="100"></Pagination>
</div>
</template>
<script setup>
<script lang="ts" setup>
import { Pagination } from '../../../../src/index'
import { ref } from 'vue'
const currentPage = ref<number>(1)
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="vp-raw flex items-center justify-center text-center">
<Pagination v-model="currentPage" :total-pages="100" :layout="'navigation'"></Pagination>
</div>
</template>
<script lang="ts" setup>
import { Pagination } from '../../../../src/index'
import { ref } from 'vue'
const currentPage = ref<number>(1)
</script>

View File

@@ -0,0 +1,16 @@
<template>
<div class="vp-raw flex items-center justify-center text-center">
<Pagination
v-model="currentPage"
:layout="'table'"
:per-page="10"
:total-items="998"
></Pagination>
</div>
</template>
<script lang="ts" setup>
import { Pagination } from '../../../../src/index'
import { ref } from 'vue'
const currentPage = ref<number>(1)
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="vp-raw">
<Pagination v-model="currentPage" :total-pages="100" :slice-length="4"></Pagination>
</div>
</template>
<script lang="ts" setup>
import { Pagination } from '../../../../src/index'
import { ref } from 'vue'
const currentPage = ref<number>(1)
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="vp-raw flex flex-col">
<Pagination v-model="currentPage" :total-pages="100" previous-label="<<<" next-label=">>>"></Pagination>
</div>
</template>
<script lang="ts" setup>
import { Pagination } from '../../../../src/index'
import { ref } from 'vue'
const currentPage = ref<number>(1)
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="vp-raw flex flex-col">
<Pagination v-model="currentPage" :total-pages="100" show-icons></Pagination>
</div>
</template>
<script lang="ts" setup>
import { Pagination } from '../../../../src/index'
import { ref } from 'vue'
const currentPage = ref<number>(1)
</script>

View File

@@ -1,51 +1,162 @@
<template>
<nav aria-label="Page navigation example">
<div class="text-sm text-gray-700 dark:text-gray-400 mb-2" v-if="layout === 'table'">
Showing
<span class="font-semibold text-gray-900 dark:text-white">{{ startItemsCount }}</span>
to
<span class="font-semibold text-gray-900 dark:text-white">{{ endItemsCount }}</span>
of
<span class="font-semibold text-gray-900 dark:text-white">{{ computedTotalItems }}</span>
</div>
<ul class="inline-flex -space-x-px">
<li>
<a href="#" class="py-2 px-3 ml-0 leading-tight text-gray-500 bg-white rounded-l-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Previous</a>
<button
:disabled="isDecreaseDisabled"
@click="decreasePage"
class="flex items-center py-2 px-3 ml-0 leading-tight text-gray-500 bg-white rounded-l-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
>
<svg v-if="showIcons" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
{{ previousLabel }}
</button>
</li>
<li
v-for="index in pagesToDisplay"
:key="index"
>
<button
:disabled="isSetPageDisabled(index)"
@click="setPage(index)"
class="w-12 py-2 px-3 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
:class="{'text-blue-600 dark:text-white bg-blue-50 dark:bg-gray-700': index === modelValue}"
>
{{ index }}
</button>
</li>
<li>
<a href="#" class="py-2 px-3 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">1</a>
</li>
<li>
<a href="#" class="py-2 px-3 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">2</a>
</li>
<li>
<a href="#" aria-current="page" class="py-2 px-3 text-blue-600 bg-blue-50 border border-gray-300 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white">3</a>
</li>
<li>
<a href="#" class="py-2 px-3 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">4</a>
</li>
<li>
<a href="#" class="py-2 px-3 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">5</a>
</li>
<li>
<a href="#" class="py-2 px-3 leading-tight text-gray-500 bg-white rounded-r-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Next</a>
<button
:disabled="isIncreaseDisabled"
@click="increasePage"
class="flex items-center py-2 px-3 leading-tight text-gray-500 bg-white rounded-r-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
>
{{ nextLabel }}
<svg v-if="showIcons" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
</button>
</li>
</ul>
</nav>
</template>
<script lang="ts" setup>
import { computed, toRefs } from 'vue'
import { computed } from 'vue'
import type { PropType } from 'vue'
import type { PaginationLayout } from '@/components/Pagination/types'
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
currentPage: {
modelValue: {
type: Number,
default: 1,
},
totalPages: {
type: Number,
default: 1,
},
perPage: {
type: Number,
default: 10,
},
totalItems: {
type: Number,
required: false,
},
layout: {
type: String, // 'navigation' | 'pagination' | 'table'
type: String as PropType<PaginationLayout>, // 'navigation' | 'pagination' | 'table'
default: 'pagination',
},
showIcons: {
type: Boolean,
default: false,
},
totalPages: {
sliceLength: {
type: Number,
default: 1,
default: 2,
},
previousLabel: {
type: String,
default: 'Previous',
},
nextLabel: {
type: String,
default: 'Next',
},
})
const setPage = (index: number) => {
emit('update:modelValue', index)
}
const decreasePage = () => {
emit('update:modelValue', props.modelValue - 1)
}
const increasePage = () => {
emit('update:modelValue', props.modelValue + 1)
}
const computedTotalPages = computed(() => {
if (!props.totalItems) return props.totalPages
if (!props.perPage) return props.totalPages
return Math.ceil(props.totalItems / props.perPage)
})
const isDecreaseDisabled = computed(() => props.modelValue <= 1)
const isIncreaseDisabled = computed(() => props.modelValue >= computedTotalPages.value)
const isSetPageDisabled = (index: number) => index === props.modelValue
const pagesToDisplay = computed(() => {
if (props.layout === 'navigation') return []
if (props.layout === 'table') return []
if (computedTotalPages.value <= props.sliceLength * 2 + 1) {
const pages = []
for (let page = 1; page <= computedTotalPages.value; page++) {
pages.push(page)
}
return pages
}
if (props.modelValue <= props.sliceLength) {
const pages = []
const slicedLength = Math.abs(props.modelValue - props.sliceLength) + props.modelValue + props.sliceLength + 1
for (let page = 1; page <= slicedLength; page++) {
pages.push(page)
}
return pages
}
if (props.modelValue >= computedTotalPages.value - props.sliceLength) {
const pages = []
for (let page = Math.abs(computedTotalPages.value - props.sliceLength * 2); page <= computedTotalPages.value; page++) {
pages.push(page)
}
return pages
}
const pages = []
let startedPage = props.modelValue - props.sliceLength > 0 ? props.modelValue - props.sliceLength : 1
for (let page = startedPage; page < props.modelValue + props.sliceLength + 1; page++) {
if (page >= computedTotalPages.value) break
pages.push(page)
}
return pages
})
const startItemsCount = computed(() => props.modelValue * props.perPage - props.perPage + 1)
const endItemsCount = computed(() => {
const count = props.modelValue * props.perPage + 1
if (!props.totalItems) return count
if (count > props.totalItems) return props.totalItems
return count
})
const computedTotalItems = computed(() => {
if (props.totalItems) return props.totalItems
return computedTotalPages.value * props.perPage
})
</script>

View File

@@ -0,0 +1 @@
export type PaginationLayout = 'navigation' | 'pagination' | 'table'