Skip to content

Commit

Permalink
Feat/36 pagination controls (#206)
Browse files Browse the repository at this point in the history
* chore: pagination component

* chore: added chevron svgs

* chore: chevron styling stroke color

* chore: added hover and active states

* chore: hover behavior fixed

* chore: fixed chevrons

* chore: dark mode styling

* chore: docs and barrel export

* chore: styling updates hover controls

* chore: dark mode styling

* chore: updated docs

* chore: updated chevron styling

* chore: added pagination props interface

* chore: updated docs

---------

Co-authored-by: Swastik Dilip <[email protected]>
  • Loading branch information
swdilip and swdilip committed Dec 19, 2023
1 parent 20862be commit a99559b
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/assets/build/chevron-left-outline.icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const ChevronLeftOutlineIcon = (props: any) => (
<svg
className="w-6 h-6"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
{...props}
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 1 1 5l4 4"
/>
</svg>
)
18 changes: 18 additions & 0 deletions src/assets/build/chevron-right-outline.icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const ChevronRightOutlineIcon = (props: any) => (
<svg
className="w-6 h-6"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
{...props}
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 9 4-4-4-4"
/>
</svg>
)
2 changes: 2 additions & 0 deletions src/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ export * from './build/folder.icon'
export * from './build/circle-outline.icon'
export * from './build/angle-down.icon'
export * from './build/calendar.icon'
export * from './build/chevron-left-outline.icon'
export * from './build/chevron-right-outline.icon'
export * from './build/arrow-left.icon'
export * from './build/arrow-right.icon'
183 changes: 183 additions & 0 deletions src/components/Pagination/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import React, { useState, useEffect } from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { ChevronLeftOutlineIcon, ChevronRightOutlineIcon } from '@/assets'

interface PaginationProps {
currentPage: number
setCurrentPage: React.Dispatch<React.SetStateAction<number>>
numberOfItemsPerPage: number
totalNumberOfFilteredItems: number
}

const PaginationVariants = cva([
'[&>*]:flex',
'[&>*]:items-center',
'[&>*]:justify-center',
'w-fit'
])

const PageNumberStyles = cva([
'min-w-40',
'max-w-40',
'h-10',
'px-4',
'py-2',
'rounded-sm',
'text-foreground-muted',
'active:bg-accent',
'active:text-primary-50',
'dark:hover:bg-background-dark',
'dark:active:bg-primary-300',
'dark:active:text-grey-900'
])

const PageNumberActiveStyles = cva([
'bg-accent',
'text-primary-50',
'hover:bg-accent',
'hover:text-primary-50',
'dark:bg-primary-300',
'dark:text-grey-900',
'dark:hover:bg-primary-300',
'dark:hover:text-grey-900'
])

interface PaginationProps
extends React.ComponentPropsWithoutRef<'div'>,
VariantProps<typeof PaginationVariants> {}

export const Pagination = React.forwardRef<HTMLDivElement, PaginationProps>(
(
{
className,
currentPage,
setCurrentPage,
numberOfItemsPerPage,
totalNumberOfFilteredItems,
...props
},
refs
) => {
const currentRowsLength = totalNumberOfFilteredItems ?? 0

const totalPages = React.useMemo(() => {
return Math.ceil(
currentRowsLength > 0 ? currentRowsLength / numberOfItemsPerPage : 0
)
}, [currentRowsLength, numberOfItemsPerPage])

const allPages = [...Array(totalPages + 1).keys()].slice(1)
const [pageNumbers, setPageNumbers] = useState(allPages)
const [showLeftDots, setShowLeftDots] = useState(false)
const [showRightDots, setShowRightDots] = useState(false)
const noOfSiblings = 1
const noOfPagesShown = noOfSiblings * 2 + 5

useEffect(() => {
if (totalPages <= noOfPagesShown) {
setPageNumbers(allPages)
setShowRightDots(false)
setShowLeftDots(false)
} else if (currentPage <= noOfPagesShown - 3) {
const pages = allPages.slice(0, noOfPagesShown - 2)
pages.push(totalPages)
setPageNumbers(pages)
setShowRightDots(true)
setShowLeftDots(false)
} else if (
noOfPagesShown - 3 < currentPage &&
currentPage < totalPages - 3
) {
const pages = [1]
const start = Math.max(2, currentPage - noOfSiblings)
const end = Math.min(totalPages - 1, currentPage + noOfSiblings)
for (let i = start; i <= end; i++) {
pages.push(i)
}
pages.push(totalPages)
setPageNumbers(pages)
setShowRightDots(true)
setShowLeftDots(true)
} else if (currentPage >= totalPages - 3) {
const pages = allPages.slice(-noOfPagesShown + 2)
pages.unshift(1)
setPageNumbers(pages)
setShowRightDots(false)
setShowLeftDots(true)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage, totalPages])

const goToNextPage = () => {
if (currentPage !== totalPages) setCurrentPage(currentPage + 1)
}

const goToPrevPage = () => {
if (currentPage !== 1) setCurrentPage(currentPage - 1)
}
return (
<nav className={cn(PaginationVariants(), className)} {...props}>
<ul>
<li className="hover:bg-background px-4 dark:hover:bg-background-dark rounded-sm">
<button
onClick={goToPrevPage}
disabled={currentPage === 1 || totalPages === 0}
>
<ChevronLeftOutlineIcon
className={
'w-3 h-9 pt-3 pb-3 -mb-1 text-foreground dark:text-foreground-dark'
}
/>
</button>
</li>

<div className="flex flex-row items-center px-0">
{pageNumbers.map(pgNumber => (
<React.Fragment key={pgNumber}>
{pgNumber === totalPages && showRightDots ? (
<div className="px-4 min-w-40 max-w-40 text-foreground-muted">
...
</div>
) : null}

<li>
<button
onClick={() => setCurrentPage(pgNumber)}
className={cn(
PageNumberStyles(),
currentPage === pgNumber && PageNumberActiveStyles()
)}
>
<div className="text-sm">{pgNumber}</div>
</button>
</li>

{pgNumber === 1 && showLeftDots ? (
<div>
<div className="px-4 max-w-40 min-w-40 text-foreground-muted">
...
</div>
</div>
) : null}
</React.Fragment>
))}
</div>

<li className="hover:bg-background px-4 dark:hover:bg-background-dark rounded-sm">
<button
onClick={goToNextPage}
disabled={currentPage === totalPages || totalPages === 0}
>
<ChevronRightOutlineIcon
className={
'w-3 h-9 py-3 -mb-1 text-foreground dark:text-foreground-dark'
}
/>
</button>
</li>
</ul>
</nav>
)
}
)
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './Tooltip'
export * from './Input'
export * from './Switch'
export * from './Chip'
export * from './Pagination'
export * from './Modal'
export * from './WebsiteFooter'
export * from './Select'
Expand Down
Loading

0 comments on commit a99559b

Please sign in to comment.