mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
@@ -29,6 +29,7 @@ const components = [
|
||||
'link',
|
||||
'modal',
|
||||
'navigation-menu',
|
||||
'pagination',
|
||||
'popover',
|
||||
'radio-group',
|
||||
'separator',
|
||||
|
||||
7
playground/pages/pagination.vue
Normal file
7
playground/pages/pagination.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const page = ref(5)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination v-model:page="page" :total="100" :sibling-count="1" show-edges />
|
||||
</template>
|
||||
121
src/runtime/components/Pagination.vue
Normal file
121
src/runtime/components/Pagination.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import { tv } from 'tailwind-variants'
|
||||
import type { PaginationRootProps, PaginationRootEmits } from 'radix-vue'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/pagination'
|
||||
import type { ButtonProps, IconProps } from '#ui/types'
|
||||
|
||||
const appConfig = _appConfig as AppConfig & { ui: { pagination: Partial<typeof theme> } }
|
||||
|
||||
const pagination = tv({ extend: tv(theme), ...(appConfig.ui?.pagination || {}) })
|
||||
|
||||
export interface PaginationProps extends Omit<PaginationRootProps, 'asChild'> {
|
||||
firstIcon?: IconProps['name']
|
||||
prevIcon?: IconProps['name']
|
||||
nextIcon?: IconProps['name']
|
||||
lastIcon?: IconProps['name']
|
||||
ellipsisIcon?: IconProps['name']
|
||||
color?: ButtonProps['color']
|
||||
variant?: ButtonProps['variant']
|
||||
activeColor?: ButtonProps['color']
|
||||
activeVariant?: ButtonProps['variant']
|
||||
showControls?: boolean
|
||||
size?: ButtonProps['size']
|
||||
class?: any
|
||||
ui?: Partial<typeof pagination.slots>
|
||||
}
|
||||
|
||||
export interface PaginationEmits extends PaginationRootEmits {}
|
||||
|
||||
export interface PaginationSlots {
|
||||
first(): any
|
||||
prev(): any
|
||||
next(): any
|
||||
last(): any
|
||||
ellipsis(): any
|
||||
item(props: {
|
||||
page: number
|
||||
pageCount: number
|
||||
item: {
|
||||
type: 'ellipsis'
|
||||
} | {
|
||||
type: 'page'
|
||||
value: number
|
||||
}
|
||||
index: number
|
||||
}): any
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { PaginationRoot, PaginationList, PaginationListItem, PaginationFirst, PaginationPrev, PaginationEllipsis, PaginationNext, PaginationLast, useForwardPropsEmits } from 'radix-vue'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
|
||||
const props = withDefaults(defineProps<PaginationProps>(), {
|
||||
color: 'white',
|
||||
activeColor: 'black',
|
||||
variant: 'solid',
|
||||
activeVariant: 'solid',
|
||||
showControls: true
|
||||
})
|
||||
const emits = defineEmits<PaginationEmits>()
|
||||
defineSlots<PaginationSlots>()
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emits)
|
||||
|
||||
const ui = computed(() => tv({ extend: pagination, slots: props.ui })())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationRoot v-slot="{ page, pageCount }" v-bind="rootProps" :class="ui.root({ class: props.class })">
|
||||
<PaginationList v-slot="{ items }" :class="ui.list()">
|
||||
<PaginationFirst v-if="showControls || $slots.first" as-child>
|
||||
<slot name="first">
|
||||
<UButton :color="color" :variant="variant" :size="size" :icon="firstIcon || appConfig.ui.icons.chevronDoubleLeft" />
|
||||
</slot>
|
||||
</PaginationFirst>
|
||||
<PaginationPrev v-if="showControls || $slots.prev" as-child>
|
||||
<slot name="prev">
|
||||
<UButton :color="color" :variant="variant" :size="size" :icon="prevIcon || appConfig.ui.icons.chevronLeft" />
|
||||
</slot>
|
||||
</PaginationPrev>
|
||||
|
||||
<template v-for="(item, index) in items">
|
||||
<PaginationListItem v-if="item.type === 'page'" :key="index" as-child :value="item.value">
|
||||
<slot name="item" v-bind="{ item, index, page, pageCount }">
|
||||
<UButton
|
||||
:color="page === item.value ? activeColor : color"
|
||||
:variant="page === item.value ? activeVariant : variant"
|
||||
:size="size"
|
||||
:label="String(item.value)"
|
||||
:ui="{ label: ui.label() }"
|
||||
square
|
||||
/>
|
||||
</slot>
|
||||
</PaginationListItem>
|
||||
|
||||
<PaginationEllipsis v-else :key="item.type" :index="index" as-child>
|
||||
<slot name="ellipsis">
|
||||
<UButton :color="color" :variant="variant" :size="size" :icon="ellipsisIcon || appConfig.ui.icons.ellipsis" :class="ui.ellipsis()" />
|
||||
</slot>
|
||||
</PaginationEllipsis>
|
||||
</template>
|
||||
|
||||
<PaginationNext v-if="showControls || $slots.next" as-child>
|
||||
<slot name="next">
|
||||
<UButton :color="color" :variant="variant" :size="size" :icon="nextIcon || appConfig.ui.icons.chevronRight" />
|
||||
</slot>
|
||||
</PaginationNext>
|
||||
<PaginationLast v-if="showControls || $slots.last" as-child>
|
||||
<slot name="last">
|
||||
<UButton :color="color" :variant="variant" :size="size" :icon="lastIcon || appConfig.ui.icons.chevronDoubleRight" />
|
||||
</slot>
|
||||
</PaginationLast>
|
||||
</PaginationList>
|
||||
</PaginationRoot>
|
||||
</template>
|
||||
1
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -20,6 +20,7 @@ export * from '../components/Kbd.vue'
|
||||
export * from '../components/Link.vue'
|
||||
export * from '../components/Modal.vue'
|
||||
export * from '../components/NavigationMenu.vue'
|
||||
export * from '../components/Pagination.vue'
|
||||
export * from '../components/Popover.vue'
|
||||
export * from '../components/RadioGroup.vue'
|
||||
export * from '../components/Separator.vue'
|
||||
|
||||
@@ -2,8 +2,11 @@ export default {
|
||||
chevronDown: 'i-heroicons-chevron-down-20-solid',
|
||||
chevronLeft: 'i-heroicons-chevron-left-20-solid',
|
||||
chevronRight: 'i-heroicons-chevron-right-20-solid',
|
||||
chevronDoubleLeft: 'i-heroicons-chevron-double-left-20-solid',
|
||||
chevronDoubleRight: 'i-heroicons-chevron-double-right-20-solid',
|
||||
check: 'i-heroicons-check-20-solid',
|
||||
close: 'i-heroicons-x-mark-20-solid',
|
||||
ellipsis: 'i-heroicons-ellipsis-horizontal-20-solid',
|
||||
empty: 'i-heroicons-circle-stack-20-solid',
|
||||
loading: 'i-heroicons-arrow-path-20-solid',
|
||||
minus: 'i-heroicons-minus-20-solid',
|
||||
|
||||
@@ -18,6 +18,7 @@ export { default as kbd } from './kbd'
|
||||
export { default as link } from './link'
|
||||
export { default as modal } from './modal'
|
||||
export { default as navigationMenu } from './navigation-menu'
|
||||
export { default as pagination } from './pagination'
|
||||
export { default as popover } from './popover'
|
||||
export { default as radioGroup } from './radio-group'
|
||||
export { default as separator } from './separator'
|
||||
|
||||
8
src/theme/pagination.ts
Normal file
8
src/theme/pagination.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
slots: {
|
||||
root: '',
|
||||
list: 'flex items-center gap-1',
|
||||
ellipsis: 'pointer-events-none',
|
||||
label: 'min-w-5'
|
||||
}
|
||||
}
|
||||
47
test/components/Pagination.spec.ts
Normal file
47
test/components/Pagination.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import Pagination, { type PaginationProps } from '../../src/runtime/components/Pagination.vue'
|
||||
import ComponentRender from '../component-render'
|
||||
import theme from '#build/ui/button'
|
||||
|
||||
describe('Pagination', () => {
|
||||
const sizes = Object.keys(theme.variants.size) as any
|
||||
const colors = Object.keys(theme.variants.color) as any
|
||||
const variants = Object.keys(theme.variants.variant) as any
|
||||
|
||||
const props = { total: 100 }
|
||||
|
||||
it.each([
|
||||
// Props
|
||||
['with total', { props }],
|
||||
['with as', { props: { ...props, as: 'div' } }],
|
||||
['with defaultPage', { props: { ...props, defaultPage: 2 } }],
|
||||
['with disabled', { props: { ...props, disabled: true } }],
|
||||
['with itemsPerPage', { props: { ...props, itemsPerPage: 5 } }],
|
||||
['with page', { props: { ...props, page: 2 } }],
|
||||
['with showEdges', { props: { ...props, showEdges: true } }],
|
||||
['with siblingCount', { props: { ...props, siblingCount: 1, showEdges: true, page: 5 } }],
|
||||
['without showControls', { props: { ...props, showControls: false } }],
|
||||
['with firstIcon', { props: { ...props, firstIcon: 'i-heroicons-arrow-left' } }],
|
||||
['with prevIcon', { props: { ...props, prevIcon: 'i-heroicons-arrow-small-left' } }],
|
||||
['with nextIcon', { props: { ...props, nextIcon: 'i-heroicons-arrow-small-right' } }],
|
||||
['with lastIcon', { props: { ...props, lastIcon: 'i-heroicons-arrow-right' } }],
|
||||
['with ellipsisIcon', { props: { ...props, ellipsisIcon: 'i-heroicons-ellipsis-vertical', siblingCount: 1, showEdges: true, page: 5 } }],
|
||||
...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]),
|
||||
...colors.map((color: string) => [`with color ${color}`, { props: { ...props, color } }]),
|
||||
...variants.map((variant: string) => [`with variant ${variant}`, { props: { ...props, color: 'primary', variant } }]),
|
||||
...colors.map((activeColor: string) => [`with active color ${activeColor}`, { props: { ...props, activeColor } }]),
|
||||
...variants.map((activeVariant: string) => [`with active variant ${activeVariant}`, { props: { ...props, color: 'primary', activeVariant } }]),
|
||||
['with class', { props: { ...props, class: 'relative' } }],
|
||||
['with ui', { props: { ...props, ui: { list: 'gap-3' } } }],
|
||||
// Slots
|
||||
['with first slot', { props, slots: { first: () => 'First slot' } }],
|
||||
['with prev slot', { props, slots: { prev: () => 'Prev slot' } }],
|
||||
['with next slot', { props, slots: { next: () => 'Next slot' } }],
|
||||
['with last slot', { props, slots: { last: () => 'Last slot' } }],
|
||||
['with ellipsis slot', { props: { ...props, siblingCount: 1, showEdges: true, page: 5 }, slots: { ellipsis: () => 'Ellipsis slot' } }],
|
||||
['with item slot', { props, slots: { item: () => 'Item slot' } }]
|
||||
])('renders %s correctly', async (nameOrHtml: string, options: { props?: PaginationProps, slots?: any }) => {
|
||||
const html = await ComponentRender(nameOrHtml, options, Pagination)
|
||||
expect(html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
2076
test/components/__snapshots__/Pagination.spec.ts.snap
Normal file
2076
test/components/__snapshots__/Pagination.spec.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user