-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Pagination): add basic feature and unit tests
- Loading branch information
1 parent
063d6b2
commit 0e2851a
Showing
13 changed files
with
441 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Unit from '../Unit'; | ||
import { Pagination } from '@/packages'; | ||
|
||
export function DemoPaginationDefault() { | ||
return ( | ||
<Unit layout="row"> | ||
<Pagination defaultPage={1} count={124} limit={10} /> | ||
</Unit> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import css from 'styled-jsx/css'; | ||
import { useTheme } from '../Theme'; | ||
|
||
export const usePaginationItemStyles = () => { | ||
const theme = useTheme(); | ||
|
||
return css.resolve` | ||
.raw-pagination-item { | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
white-space: nowrap; | ||
min-width: 40px; | ||
height: 40px; | ||
padding: 0 8px; | ||
border: 1px solid ${theme.palette.accents2}; | ||
border-radius: 6px; | ||
font-size: 14px; | ||
color: ${theme.palette.accents7}; | ||
background-color: ${theme.palette.background}; | ||
transition-property: border-color, background, color; | ||
transition-duration: 0.2s; | ||
transition-timing-function: ease; | ||
user-select: none; | ||
text-decoration: none; | ||
cursor: pointer; | ||
} | ||
.raw-pagination-item-disabled { | ||
background-color: ${theme.palette.accents2}; | ||
color: ${theme.palette.accents5}; | ||
cursor: not-allowed; | ||
} | ||
.raw-pagination-item.raw-pagination-item-active { | ||
background-color: ${theme.palette.foreground}; | ||
border-color: ${theme.palette.foreground}; | ||
color: ${theme.palette.background}; | ||
} | ||
.raw-pagination-item:not(.raw-pagination-item-active):not( | ||
.raw-pagination-item-disabled | ||
):hover { | ||
border-color: ${theme.palette.foreground}; | ||
color: ${theme.palette.foreground}; | ||
} | ||
.raw-pagination-item:not(.raw-pagination-item-active):not( | ||
.raw-pagination-item-disabled | ||
):active { | ||
background-color: ${theme.palette.accents2}; | ||
} | ||
`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,138 @@ | ||
import React, { FC, PropsWithChildren } from 'react'; | ||
import React, { FC, useMemo } from 'react'; | ||
import { ChevronLeft, ChevronRight } from 'react-feather'; | ||
import classNames from 'classnames'; | ||
import { PaginationProps } from './Pagination.types'; | ||
import { useControlled } from '../utils/hooks'; | ||
import PaginationPages from './PaginationPages'; | ||
import { usePaginationItemStyles } from './Pagination.styles'; | ||
|
||
const Pagination: FC<PropsWithChildren<PaginationProps>> = ({ | ||
const range = (start: number, end: number) => { | ||
const length = end - start + 1; | ||
return Array.from({ length }, (_, i) => start + i); | ||
}; | ||
|
||
const Pagination: FC<PaginationProps> = ({ | ||
page, | ||
defaultPage, | ||
count, | ||
limit, | ||
defaultPage = 1, | ||
count = 1, | ||
limit = 10, | ||
onChange, | ||
children, | ||
className = '', | ||
...restProps | ||
}) => { | ||
return <div {...restProps}></div>; | ||
const [internalPage, setInternalPage] = useControlled<number>({ | ||
defaultValue: defaultPage, | ||
value: page, | ||
}); | ||
const classes = classNames('raw-pagination', className); | ||
const { className: paginationItemClassName, styles: paginationItemStyles } = | ||
usePaginationItemStyles(); | ||
const pageCount = Math.ceil(count / limit); | ||
const boundaryCount = 5; | ||
const startPages = range(1, boundaryCount); | ||
const endPages = range(pageCount - boundaryCount + 1, pageCount); | ||
|
||
const pages: number[] = useMemo(() => { | ||
return [ | ||
...new Set( | ||
[ | ||
...(internalPage < boundaryCount ? startPages : [1]), | ||
internalPage, | ||
internalPage - 1, | ||
internalPage + 1, | ||
...(internalPage > pageCount - boundaryCount + 1 | ||
? endPages | ||
: [pageCount]), | ||
] | ||
.filter((value) => value >= 1 && value <= pageCount) | ||
.sort((a, b) => a - b) | ||
), | ||
].reduce<number[]>((previousValue, currentValue, currentIndex, array) => { | ||
previousValue.push(currentValue); | ||
if ( | ||
array[currentIndex + 1] && | ||
array[currentIndex + 1] - array[currentIndex] > 1 | ||
) { | ||
previousValue.push(0); | ||
} | ||
return previousValue; | ||
}, []); | ||
}, [internalPage, pageCount, startPages, endPages]); | ||
|
||
const clickPageNumberHandler = (pageNumber: number) => { | ||
setInternalPage(pageNumber); | ||
onChange?.(pageNumber); | ||
}; | ||
|
||
const clickPreviousPageHandler = () => { | ||
if (internalPage === 1) { | ||
return; | ||
} | ||
clickPageNumberHandler(internalPage - 1); | ||
}; | ||
|
||
const clickNextPageHandler = () => { | ||
if (internalPage === pageCount) { | ||
return; | ||
} | ||
clickPageNumberHandler(internalPage + 1); | ||
}; | ||
|
||
return ( | ||
<nav | ||
role="navigation" | ||
aria-label="pagination" | ||
className={classes} | ||
{...restProps} | ||
> | ||
<ul className="raw-pagination-list"> | ||
<li> | ||
<a | ||
className={classNames( | ||
'raw-pagination-item', | ||
internalPage === 1 && 'raw-pagination-item-disabled', | ||
paginationItemClassName | ||
)} | ||
aria-label="Go to previous page" | ||
onClick={clickPreviousPageHandler} | ||
> | ||
<ChevronLeft size={16} /> | ||
</a> | ||
</li> | ||
<PaginationPages | ||
pages={pages} | ||
internalPage={internalPage} | ||
clickPageNumberHandler={clickPageNumberHandler} | ||
/> | ||
<li> | ||
<a | ||
className={classNames( | ||
'raw-pagination-item', | ||
internalPage === pageCount && 'raw-pagination-item-disabled', | ||
paginationItemClassName | ||
)} | ||
aria-label="Go to next page" | ||
onClick={clickNextPageHandler} | ||
> | ||
<ChevronRight size={16} /> | ||
</a> | ||
</li> | ||
</ul> | ||
{paginationItemStyles} | ||
<style jsx> | ||
{` | ||
.raw-pagination-list { | ||
display: flex; | ||
align-items: center; | ||
gap: 8px; | ||
list-style: none; | ||
padding: 0; | ||
margin: 0; | ||
} | ||
`} | ||
</style> | ||
</nav> | ||
); | ||
}; | ||
|
||
export default Pagination; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React, { FC } from 'react'; | ||
import { MoreHorizontal } from 'react-feather'; | ||
import classNames from 'classnames'; | ||
import { PaginationPagesProps } from './Pagination.types'; | ||
import { useTheme } from '../Theme'; | ||
import { usePaginationItemStyles } from './Pagination.styles'; | ||
|
||
const PaginationPages: FC<PaginationPagesProps> = ({ | ||
pages, | ||
internalPage, | ||
clickPageNumberHandler, | ||
}) => { | ||
const theme = useTheme(); | ||
const { className: paginationItemClassName, styles: paginationItemStyles } = | ||
usePaginationItemStyles(); | ||
|
||
return ( | ||
<> | ||
{pages.map((pageItem, index) => { | ||
const isCurrentPage = pageItem === internalPage; | ||
return ( | ||
<li key={index}> | ||
{pageItem !== 0 ? ( | ||
<a | ||
className={classNames( | ||
'raw-pagination-item', | ||
isCurrentPage && 'raw-pagination-item-active', | ||
paginationItemClassName | ||
)} | ||
aria-current={isCurrentPage ? 'page' : undefined} | ||
onClick={() => clickPageNumberHandler(pageItem)} | ||
> | ||
{pageItem} | ||
</a> | ||
) : ( | ||
<span className="raw-pagination-ellipsis"> | ||
<MoreHorizontal size={16} color={theme.palette.accents7} /> | ||
</span> | ||
)} | ||
</li> | ||
); | ||
})} | ||
{paginationItemStyles} | ||
<style jsx> | ||
{` | ||
.raw-pagination-ellipsis { | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: 40px; | ||
height: 40px; | ||
} | ||
`} | ||
</style> | ||
</> | ||
); | ||
}; | ||
|
||
export default PaginationPages; |
Oops, something went wrong.