From a2ef175bdbb30b9ca8e77ebc3cbd0159d9d00b82 Mon Sep 17 00:00:00 2001 From: Eelco Wiersma Date: Sat, 14 Dec 2024 10:39:07 +0000 Subject: [PATCH] feat: add pagination component --- .changeset/fair-pants-grab.md | 5 + .../src/components/icons/icons.tsx | 11 ++ .../src/components/pagination/index.ts | 1 + .../src/components/pagination/pagination.tsx | 162 ++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 .changeset/fair-pants-grab.md create mode 100644 packages/saas-ui-react/src/components/pagination/index.ts create mode 100644 packages/saas-ui-react/src/components/pagination/pagination.tsx diff --git a/.changeset/fair-pants-grab.md b/.changeset/fair-pants-grab.md new file mode 100644 index 00000000..c81b85ef --- /dev/null +++ b/.changeset/fair-pants-grab.md @@ -0,0 +1,5 @@ +--- +'@saas-ui/react': minor +--- + +Added Pagination component diff --git a/packages/saas-ui-react/src/components/icons/icons.tsx b/packages/saas-ui-react/src/components/icons/icons.tsx index 97723f68..adbe5f39 100644 --- a/packages/saas-ui-react/src/components/icons/icons.tsx +++ b/packages/saas-ui-react/src/components/icons/icons.tsx @@ -117,3 +117,14 @@ export const CheckIcon = createIcon({ ), }) + +export const EllipsisIcon = createIcon({ + displayName: 'EllipsisIcon', + path: ( + + + + + + ), +}) diff --git a/packages/saas-ui-react/src/components/pagination/index.ts b/packages/saas-ui-react/src/components/pagination/index.ts new file mode 100644 index 00000000..f4693419 --- /dev/null +++ b/packages/saas-ui-react/src/components/pagination/index.ts @@ -0,0 +1 @@ +export * as Pagination from './pagination.tsx' diff --git a/packages/saas-ui-react/src/components/pagination/pagination.tsx b/packages/saas-ui-react/src/components/pagination/pagination.tsx new file mode 100644 index 00000000..c60e49f7 --- /dev/null +++ b/packages/saas-ui-react/src/components/pagination/pagination.tsx @@ -0,0 +1,162 @@ +'use client' + +import { forwardRef, useMemo } from 'react' + +import type { ButtonProps, TextProps } from '@chakra-ui/react' +import { + Button, + Pagination as ChakraPagination, + IconButton, + Text, + createContext, + usePaginationContext, +} from '@chakra-ui/react' + +import { + ChevronLeftIcon, + ChevronRightIcon, + EllipsisIcon, +} from '#components/icons/icons.ts' + +interface ButtonVariantMap { + current: ButtonProps['variant'] + default: ButtonProps['variant'] + ellipsis: ButtonProps['variant'] +} + +type PaginationVariant = 'outline' | 'solid' | 'subtle' + +interface ButtonVariantContext { + size: ButtonProps['size'] + variantMap: ButtonVariantMap +} + +const [RootPropsProvider, useRootProps] = createContext({ + name: 'RootPropsProvider', +}) + +export interface RootProps extends Omit { + size?: ButtonProps['size'] + variant?: PaginationVariant +} + +const variantMap: Record = { + outline: { default: 'ghost', ellipsis: 'plain', current: 'outline' }, + solid: { default: 'outline', ellipsis: 'outline', current: 'solid' }, + subtle: { default: 'ghost', ellipsis: 'plain', current: 'subtle' }, +} + +export const Root = forwardRef( + function PaginationRoot(props, ref) { + const { size = 'sm', variant = 'outline', ...rest } = props + return ( + + + + ) + }, +) + +export const Ellipsis = forwardRef< + HTMLDivElement, + ChakraPagination.EllipsisProps +>(function PaginationEllipsis(props, ref) { + const { size, variantMap } = useRootProps() + return ( + + + + ) +}) + +export const Item = forwardRef( + function PaginationItem(props, ref) { + const { page } = usePaginationContext() + const { size, variantMap } = useRootProps() + + const current = page === props.value + const variant = current ? variantMap.current : variantMap.default + + return ( + + + + ) + }, +) + +export const PrevButton = forwardRef< + HTMLButtonElement, + ChakraPagination.PrevTriggerProps +>(function PaginationPrevTrigger(props, ref) { + const { size, variantMap } = useRootProps() + + return ( + + + {props.children ?? } + + + ) +}) + +export const NextButton = forwardRef< + HTMLButtonElement, + ChakraPagination.NextTriggerProps +>(function PaginationNextTrigger(props, ref) { + const { size, variantMap } = useRootProps() + const { nextPage } = usePaginationContext() + + return ( + + + {props.children ?? } + + + ) +}) + +export const Items = (props: React.HTMLAttributes) => { + return ( + + {({ pages }) => + pages.map((page, index) => { + return page.type === 'ellipsis' ? ( + + ) : ( + + ) + }) + } + + ) +} + +interface PageTextProps extends TextProps { + format?: 'short' | 'compact' | 'long' +} + +export const PageText = forwardRef( + function PaginationPageText(props, ref) { + const { format = 'compact', ...rest } = props + const { page, pages, pageRange, count } = usePaginationContext() + const content = useMemo(() => { + if (format === 'short') return `${page} / ${pages.length}` + if (format === 'compact') return `${page} of ${pages.length}` + return `${pageRange.start + 1} - ${pageRange.end} of ${count}` + }, [format, page, pages.length, pageRange, count]) + + return ( + + {content} + + ) + }, +) + +export const PrevTrigger = ChakraPagination.PrevTrigger +export const NextTrigger = ChakraPagination.NextTrigger