feat: add pagination component (#4522)

* feat: add pagination component

* chore: update
pull/48/MERGE
Vben 2024-09-26 23:09:17 +08:00 committed by GitHub
parent 26646d42f7
commit 639d2e1525
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 364 additions and 55 deletions

View File

@ -40,7 +40,6 @@
}
},
"dependencies": {
"@radix-icons/vue": "catalog:",
"@vben-core/composables": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shared": "workspace:*",

View File

@ -14,6 +14,7 @@ export * from './input-password';
export * from './link';
export * from './logo';
export * from './menu-badge';
export * from './pagination';
export * from './pin-input';
export * from './popover';
export * from './render-content';
@ -38,6 +39,7 @@ export * from './ui/hover-card';
export * from './ui/input';
export * from './ui/label';
export * from './ui/number-field';
export * from './ui/pagination';
export * from './ui/pin-input';
export * from './ui/popover';
export * from './ui/radio-group';

View File

@ -0,0 +1,2 @@
export type { PaginationProps as VbenPaginationProps } from './pagination';
export { default as VbenPagination } from './pagination.vue';

View File

@ -0,0 +1,41 @@
export interface PaginationProps {
/**
*
*/
disabled?: boolean;
/**
*
*/
pageSizeOptions?: number[];
/**
* true
*/
showEdges?: boolean;
/**
*
*/
showRowsPerPage?: boolean;
/**
*
*/
showTotalText?: boolean;
/**
*
*/
siblingCount?: number;
/**
*
*/
size?: 'default' | 'large' | 'small';
/**
*
*/
total?: number;
}
export const SIZE_CLASS_MAP = {
default: 'size-8',
large: 'size-9',
small: 'size-7',
};

View File

@ -0,0 +1,111 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { Button } from '../ui/button';
import {
Pagination,
PaginationEllipsis,
PaginationFirst,
PaginationLast,
PaginationList,
PaginationListItem,
PaginationNext,
PaginationPrev,
} from '../ui/pagination';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import { type PaginationProps, SIZE_CLASS_MAP } from './pagination';
interface Props extends PaginationProps {}
const {
disabled = false,
pageSizeOptions = [10, 20, 30, 50, 100, 200],
showEdges = true,
showRowsPerPage = true,
showTotalText = true,
siblingCount = 1,
size = 'default',
total = 500,
} = defineProps<Props>();
const currentPage = defineModel<number>('currentPage', { default: 1 });
const itemPerPage = defineModel<number>('itemPerPage', { default: 20 });
const itemSize = computed(() => {
return SIZE_CLASS_MAP[size];
});
const options = computed(() => {
return pageSizeOptions.map((item) => ({
label: `${item} 条/页`,
value: `${item}`,
}));
});
function handleUpdateModelValue(value: string) {
itemPerPage.value = Number(value);
}
</script>
<template>
<Pagination
v-model:page="currentPage"
:disabled="disabled"
:items-per-page="itemPerPage"
:show-edges="showEdges"
:sibling-count="siblingCount"
:total="total"
>
<PaginationList
v-slot="{ items }"
class="flex w-full items-center justify-end gap-1"
>
<span v-if="showTotalText" class="mr-2"> {{ total }} </span>
<Select
v-if="showRowsPerPage"
:model-value="`${itemPerPage}`"
@update:model-value="handleUpdateModelValue"
>
<SelectTrigger class="w-30 mr-auto h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<template v-for="item in options" :key="item.value">
<SelectItem :value="item.value"> {{ item.label }} </SelectItem>
</template>
</SelectContent>
</Select>
<PaginationFirst :class="cn('size-8', itemSize)" />
<PaginationPrev :class="cn('size-8', itemSize)" />
<template v-for="(item, index) in items">
<PaginationListItem
v-if="item.type === 'page'"
:key="index"
:value="item.value"
as-child
>
<Button
:class="cn('size-8 p-0 shadow-none', itemSize)"
:variant="item.value === currentPage ? 'default' : 'outline'"
>
{{ item.value }}
</Button>
</PaginationListItem>
<PaginationEllipsis v-else :key="item.type" :index="index" />
</template>
<PaginationNext :class="cn('size-8', itemSize)" />
<PaginationLast :class="cn('size-8', itemSize)" />
</PaginationList>
</Pagination>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronDownIcon } from '@radix-icons/vue';
import { ChevronDown } from 'lucide-vue-next';
import {
AccordionHeader,
AccordionTrigger,
@ -32,7 +32,7 @@ const delegatedProps = computed(() => {
>
<slot></slot>
<slot name="icon">
<ChevronDownIcon
<ChevronDown
class="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200"
/>
</slot>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { cn } from '@vben-core/shared/utils';
import { DotsHorizontalIcon } from '@radix-icons/vue';
import { MoreHorizontal } from 'lucide-vue-next';
const props = defineProps<{
class?: any;
@ -15,7 +15,7 @@ const props = defineProps<{
role="presentation"
>
<slot>
<DotsHorizontalIcon class="h-4 w-4" />
<MoreHorizontal class="h-4 w-4" />
</slot>
<span class="sr-only">More</span>
</span>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { cn } from '@vben-core/shared/utils';
import { ChevronRightIcon } from '@radix-icons/vue';
import { ChevronRight } from 'lucide-vue-next';
const props = defineProps<{
class?: any;
@ -15,7 +15,7 @@ const props = defineProps<{
role="presentation"
>
<slot>
<ChevronRightIcon />
<ChevronRight />
</slot>
</li>
</template>

View File

@ -10,7 +10,7 @@ import { buttonVariants } from './button';
interface Props extends PrimitiveProps {
class?: any;
size?: ButtonVariantSize;
variant?: 'heavy' & ButtonVariants;
variant?: ButtonVariants;
}
const props = withDefaults(defineProps<Props>(), {

View File

@ -5,7 +5,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
CheckboxIndicator,
CheckboxRoot,
@ -38,7 +38,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
class="flex h-full w-full items-center justify-center text-current"
>
<slot>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</slot>
</CheckboxIndicator>
</CheckboxRoot>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
ContextMenuCheckboxItem,
type ContextMenuCheckboxItemEmits,
@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</ContextMenuItemIndicator>
</span>
<slot></slot>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { DotFilledIcon } from '@radix-icons/vue';
import { Circle } from 'lucide-vue-next';
import {
ContextMenuItemIndicator,
ContextMenuRadioItem,
@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<DotFilledIcon class="h-4 w-4 fill-current" />
<Circle class="h-2 w-2 fill-current" />
</ContextMenuItemIndicator>
</span>
<slot></slot>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronRightIcon } from '@radix-icons/vue';
import { ChevronRight } from 'lucide-vue-next';
import {
ContextMenuSubTrigger,
type ContextMenuSubTriggerProps,
@ -38,6 +38,6 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<slot></slot>
<ChevronRightIcon class="ml-auto h-4 w-4" />
<ChevronRight class="ml-auto h-4 w-4" />
</ContextMenuSubTrigger>
</template>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { Cross2Icon } from '@radix-icons/vue';
import { X } from 'lucide-vue-next';
import {
DialogClose,
DialogContent,
@ -77,7 +77,7 @@ defineExpose({
"
@click="() => emits('close')"
>
<Cross2Icon class="h-4 w-4" />
<X class="h-4 w-4" />
</DialogClose>
</DialogContent>
</DialogPortal>

View File

@ -3,6 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { X } from 'lucide-vue-next';
import {
DialogClose,
DialogContent,
@ -56,7 +57,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<DialogClose
class="hover:bg-secondary absolute right-4 top-4 rounded-md p-0.5 transition-colors"
>
<Cross2Icon class="h-4 w-4" />
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
DropdownMenuCheckboxItem,
type DropdownMenuCheckboxItemEmits,
@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</DropdownMenuItemIndicator>
</span>
<slot></slot>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { DotFilledIcon } from '@radix-icons/vue';
import { Circle } from 'lucide-vue-next';
import {
DropdownMenuItemIndicator,
DropdownMenuRadioItem,
@ -37,7 +37,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<DotFilledIcon class="h-4 w-4 fill-current" />
<Circle class="h-2 w-2 fill-current" />
</DropdownMenuItemIndicator>
</span>
<slot></slot>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronRightIcon } from '@radix-icons/vue';
import { ChevronRight } from 'lucide-vue-next';
import {
DropdownMenuSubTrigger,
type DropdownMenuSubTriggerProps,
@ -32,6 +32,6 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<slot></slot>
<ChevronRightIcon class="ml-auto h-4 w-4" />
<ChevronRight class="ml-auto h-4 w-4" />
</DropdownMenuSubTrigger>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { MoreHorizontal } from 'lucide-vue-next';
import { PaginationEllipsis, type PaginationEllipsisProps } from 'radix-vue';
const props = defineProps<{ class?: any } & PaginationEllipsisProps>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationEllipsis
v-bind="delegatedProps"
:class="cn('flex size-8 items-center justify-center', props.class)"
>
<slot>
<MoreHorizontal class="size-4" />
</slot>
</PaginationEllipsis>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronsLeft } from 'lucide-vue-next';
import { PaginationFirst, type PaginationFirstProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationFirstProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationFirst v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronsLeft class="size-4" />
</slot>
</Button>
</PaginationFirst>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronsRight } from 'lucide-vue-next';
import { PaginationLast, type PaginationLastProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationLastProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationLast v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronsRight class="size-4" />
</slot>
</Button>
</PaginationLast>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronRight } from 'lucide-vue-next';
import { PaginationNext, type PaginationNextProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationNextProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationNext v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronRight class="size-4" />
</slot>
</Button>
</PaginationNext>
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronLeft } from 'lucide-vue-next';
import { PaginationPrev, type PaginationPrevProps } from 'radix-vue';
import { Button } from '../button';
const props = withDefaults(
defineProps<{ class?: any } & PaginationPrevProps>(),
{
asChild: true,
},
);
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<PaginationPrev v-bind="delegatedProps">
<Button :class="cn('size-8 p-0', props.class)" variant="outline">
<slot>
<ChevronLeft class="size-4" />
</slot>
</Button>
</PaginationPrev>
</template>

View File

@ -0,0 +1,10 @@
export { default as PaginationEllipsis } from './PaginationEllipsis.vue';
export { default as PaginationFirst } from './PaginationFirst.vue';
export { default as PaginationLast } from './PaginationLast.vue';
export { default as PaginationNext } from './PaginationNext.vue';
export { default as PaginationPrev } from './PaginationPrev.vue';
export {
PaginationList,
PaginationListItem,
PaginationRoot as Pagination,
} from 'radix-vue';

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { DashIcon } from '@radix-icons/vue';
import { Dot } from 'lucide-vue-next';
import { Primitive, type PrimitiveProps, useForwardProps } from 'radix-vue';
const props = defineProps<PrimitiveProps>();
@ -9,7 +9,7 @@ const forwardedProps = useForwardProps(props);
<template>
<Primitive v-bind="forwardedProps">
<slot>
<DashIcon />
<Dot />
</slot>
</Primitive>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Circle } from 'lucide-vue-next';
import {
RadioGroupIndicator,
RadioGroupItem,
@ -33,7 +33,7 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<RadioGroupIndicator class="flex items-center justify-center">
<CheckIcon class="fill-primary h-3.5 w-3.5" />
<Circle class="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupIndicator>
</RadioGroupItem>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CheckIcon } from '@radix-icons/vue';
import { Check } from 'lucide-vue-next';
import {
SelectItem,
SelectItemIndicator,
@ -35,7 +35,7 @@ const forwardedProps = useForwardProps(delegatedProps);
>
<span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectItemIndicator>
<CheckIcon class="h-4 w-4" />
<Check class="h-4 w-4" />
</SelectItemIndicator>
</span>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronDownIcon } from '@radix-icons/vue';
import { ChevronDown } from 'lucide-vue-next';
import {
SelectScrollDownButton,
type SelectScrollDownButtonProps,
@ -29,7 +29,7 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<slot>
<ChevronDownIcon />
<ChevronDown class="h-4 w-4" />
</slot>
</SelectScrollDownButton>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ChevronUpIcon } from '@radix-icons/vue';
import { ChevronUp } from 'lucide-vue-next';
import {
SelectScrollUpButton,
type SelectScrollUpButtonProps,
@ -29,7 +29,7 @@ const forwardedProps = useForwardProps(delegatedProps);
"
>
<slot>
<ChevronUpIcon />
<ChevronUp class="h-4 w-4" />
</slot>
</SelectScrollUpButton>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { CaretSortIcon } from '@radix-icons/vue';
import { ChevronDown } from 'lucide-vue-next';
import {
SelectIcon,
SelectTrigger,
@ -34,7 +34,7 @@ const forwardedProps = useForwardProps(delegatedProps);
>
<slot></slot>
<SelectIcon as-child>
<CaretSortIcon class="h-4 w-4 opacity-50" />
<ChevronDown class="h-4 w-4 opacity-50" />
</SelectIcon>
</SelectTrigger>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { Cross2Icon } from '@radix-icons/vue';
import { X } from 'lucide-vue-next';
import { ToastClose, type ToastCloseProps } from 'radix-vue';
const props = defineProps<
@ -29,6 +29,6 @@ const delegatedProps = computed(() => {
)
"
>
<Cross2Icon class="h-4 w-4" />
<X class="size-4" />
</ToastClose>
</template>

View File

@ -54,9 +54,6 @@ catalogs:
'@playwright/test':
specifier: ^1.47.2
version: 1.47.2
'@radix-icons/vue':
specifier: ^1.0.0
version: 1.0.0
'@stylistic/stylelint-plugin':
specifier: ^3.1.0
version: 3.1.0
@ -1373,9 +1370,6 @@ importers:
packages/@core/ui-kit/shadcn-ui:
dependencies:
'@radix-icons/vue':
specifier: 'catalog:'
version: 1.0.0(vue@3.5.8(typescript@5.6.2))
'@vben-core/composables':
specifier: workspace:*
version: link:../../composables
@ -4212,11 +4206,6 @@ packages:
'@polka/url@1.0.0-next.28':
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
'@radix-icons/vue@1.0.0':
resolution: {integrity: sha512-gKWWk9tTK/laDRRNe5KLLR8A0qUwx4q4+DN8Fq48hJ904u78R82ayAO3TrxbNLgyn2D0h6rRiGdLzQWj7rPcvA==}
peerDependencies:
vue: 3.5.8
'@rollup/plugin-alias@5.1.1':
resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==}
engines: {node: '>=14.0.0'}
@ -13050,10 +13039,6 @@ snapshots:
'@polka/url@1.0.0-next.28': {}
'@radix-icons/vue@1.0.0(vue@3.5.8(typescript@5.6.2))':
dependencies:
vue: 3.5.8(typescript@5.6.2)
'@rollup/plugin-alias@5.1.1(rollup@3.29.5)':
optionalDependencies:
rollup: 3.29.5

View File

@ -30,7 +30,6 @@ catalog:
'@manypkg/get-packages': ^2.2.2
'@nolebase/vitepress-plugin-git-changelog': ^2.5.0
'@playwright/test': ^1.47.2
'@radix-icons/vue': ^1.0.0
'@stylistic/stylelint-plugin': ^3.1.0
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.15